[{"data":1,"prerenderedAt":2859},["ShallowReactive",2],{"navigation":3,"search":181,"/blog/choosing-between-s3-standard-vs-glacier-copy":2314,"/blog/choosing-between-s3-standard-vs-glacier-copy-surround":2855},[4],{"title":5,"path":6,"stem":7,"children":8,"page":180},"Blog","/blog","blog",[9,13,17,21,25,29,33,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176],{"title":10,"path":11,"stem":12},"REST API design tips","/blog/rest-api-design-tips","blog/REST-api-design-tips",{"title":14,"path":15,"stem":16},"AI Engineering - My Findings","/blog/ai-engineering-my-findings","blog/ai-engineering-my-findings",{"title":18,"path":19,"stem":20},"Alembic - My Findings","/blog/alembic-my-findings","blog/alembic-my-findings",{"title":22,"path":23,"stem":24},"AWS - My Findings","/blog/aws-my-findings","blog/aws-my-findings",{"title":26,"path":27,"stem":28},"Bash - My Findings","/blog/bash-my-findings","blog/bash-my-findings",{"title":30,"path":31,"stem":32},"Choosing Between S3 Standard vs Glacier","/blog/choosing-between-s3-standard-vs-glacier","blog/choosing-between-s3-standard-vs-glacier",{"title":30,"path":34,"stem":35},"/blog/choosing-between-s3-standard-vs-glacier-copy","blog/choosing-between-s3-standard-vs-glacier copy",{"title":37,"path":38,"stem":39},"Clever way to show two banners at the same time","/blog/clever-way-to-show-two-banners","blog/clever-way-to-show-two-banners",{"title":41,"path":42,"stem":43},"Collection of FastAPI Interview Questions","/blog/collection-of-fastapi-interview-questions","blog/collection-of-fastapi-interview-questions",{"title":45,"path":46,"stem":47},"Collection of Python Interview Questions","/blog/collection-of-python-interview-questions","blog/collection-of-python-interview-questions",{"title":49,"path":50,"stem":51},"Database connection in Python","/blog/database-connection-in-python","blog/database-connection-in-python",{"title":53,"path":54,"stem":55},"Database Interview Questions","/blog/database-interview-questions","blog/database-interview-questions",{"title":57,"path":58,"stem":59},"Database - My Findings","/blog/database-my-findings","blog/database-my-findings",{"title":61,"path":62,"stem":63},"Docker - My Findings","/blog/docker-my-findings","blog/docker-my-findings",{"title":65,"path":66,"stem":67},"FastAPI Best Practices","/blog/fastapi-best-practices","blog/fastapi-best-practices",{"title":69,"path":70,"stem":71},"Git - My Findings","/blog/git-my-findings","blog/git-my-findings",{"title":73,"path":74,"stem":75},"How to handle file uploads in FastAPI","/blog/how-to-handle-file-uploads-in-fastapi","blog/how-to-handle-file-uploads-in-fastapi",{"title":77,"path":78,"stem":79},"How To Improve Your Skills In Tech World","/blog/how-to-improve-skills-in-tech-world","blog/how-to-improve-skills-in-tech-world",{"title":81,"path":82,"stem":83},"How To Setup Alembic With SQLAlchemy","/blog/how-to-setup-alembic","blog/how-to-setup-alembic",{"title":85,"path":86,"stem":87},"How to Use Work & Personal GitHub Accounts on Ubuntu (SSH + gh CLI)","/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu","blog/how-to-use-work-and-personal-github-accounts-on-ubuntu",{"title":89,"path":90,"stem":91},"How to write good articles","/blog/how-to-write-good-articles","blog/how-to-write-good-articles",{"title":93,"path":94,"stem":95},"Linux - My Findings","/blog/linux-my-findings","blog/linux-my-findings",{"title":97,"path":98,"stem":99},"Loading Relationship Data via Pydantic Schema in FastAPI","/blog/loading-relationship-data-via-pydantic-schema-in-fastapi","blog/loading-relationship-data-via-pydantic-schema-in-fastapi",{"title":101,"path":102,"stem":103},"Neo4j - My Findings","/blog/neo4j-my-findings","blog/neo4j-my-findings",{"title":105,"path":106,"stem":107},"Pandas - My Findings","/blog/pandas-my-findings","blog/pandas-my-findings",{"title":109,"path":110,"stem":111},"Programming - My Findings","/blog/programming-my-findings","blog/programming-my-findings",{"title":113,"path":114,"stem":115},"Python and Managing Date and Time","/blog/python-and-managing-date-and-time","blog/python-and-managing-date-and-time",{"title":117,"path":118,"stem":119},"Python Interview Code Challenges","/blog/python-interview-code-challenges","blog/python-interview-code-challenges",{"title":121,"path":122,"stem":123},"Python Logging","/blog/python-logging","blog/python-logging",{"title":125,"path":126,"stem":127},"Python - My Findings","/blog/python-my-findings","blog/python-my-findings",{"title":129,"path":130,"stem":131},"Python OOP Concepts","/blog/python-oop-concepts","blog/python-oop-concepts",{"title":133,"path":134,"stem":135},"Secure File Uploads: A No-Nonsense Guide","/blog/secure-file-uploads-a-no-nonsense-guide","blog/secure-file-uploads-a-no-nonsense-guide",{"title":137,"path":138,"stem":139},"SEO - My Findings","/blog/seo-my-findings","blog/seo-my-findings",{"title":141,"path":142,"stem":143},"SQL - My Findings","/blog/sql-my-findings","blog/sql-my-findings",{"title":145,"path":146,"stem":147},"SQLAlchemy for Beginners","/blog/sqlalchemy-for-beginners","blog/sqlalchemy-for-beginners",{"title":149,"path":150,"stem":151},"SQLAlchemy - My Findings","/blog/sqlalchemy-my-findings","blog/sqlalchemy-my-findings",{"title":153,"path":154,"stem":155},"SQLModel - My Findings","/blog/sqlmodel-my-findings","blog/sqlmodel-my-findings",{"title":157,"path":158,"stem":159},"Taking `pushd` & `popd` to next level","/blog/taking-pushd-and-popd-to-next-level","blog/taking-pushd-and-popd-to-next-level",{"title":161,"path":162,"stem":163},"The Ultimate Guide To Naming Conventions For Pydantic Schemas In FastAPI","/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi","blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi",{"title":165,"path":166,"stem":167},"Final guide to create an AI-Optimized Editor Setup","/blog/ultimate-guide-to-prompting-editors","blog/ultimate-guide-to-prompting-editors",{"title":169,"path":170,"stem":171},"Underscore Use Cases in Python","/blog/underscore-use-cases-in-python","blog/underscore-use-cases-in-python",{"title":173,"path":174,"stem":175},"Ways To Approve Private Memberships","/blog/ways-to-approve-private-memberships","blog/ways-to-approve-private-memberships",{"title":177,"path":178,"stem":179},"Why Python Type System Sucks","/blog/why-python-type-system-sucks","blog/why-python-type-system-sucks",false,[182,186,192,197,202,208,213,219,224,229,234,239,244,249,254,259,264,269,274,279,284,287,292,297,302,307,312,317,322,327,332,335,339,343,348,352,357,362,367,372,377,380,385,388,393,398,403,407,412,416,421,426,431,436,440,443,446,450,455,460,465,470,475,480,485,490,494,499,504,507,511,516,521,525,530,535,540,545,550,555,560,565,568,571,574,577,580,583,586,589,592,595,598,601,604,607,610,615,618,623,628,633,638,643,648,651,656,661,666,671,676,681,686,691,696,701,706,711,716,721,726,731,736,741,746,751,756,761,764,769,774,779,782,787,792,797,802,807,812,817,820,823,827,831,836,841,846,851,856,861,866,871,876,881,884,889,893,898,903,908,913,918,922,925,928,933,938,943,948,953,958,963,966,971,976,979,983,988,993,998,1002,1007,1012,1016,1021,1026,1031,1035,1040,1044,1049,1054,1059,1064,1069,1072,1075,1078,1083,1088,1091,1096,1101,1106,1111,1116,1121,1126,1131,1136,1141,1146,1151,1156,1159,1163,1168,1173,1178,1183,1188,1193,1198,1203,1208,1213,1218,1223,1226,1230,1235,1240,1245,1249,1254,1258,1262,1267,1272,1276,1280,1283,1288,1293,1298,1302,1307,1310,1314,1317,1321,1324,1329,1334,1337,1342,1347,1352,1357,1362,1367,1372,1377,1382,1387,1390,1395,1400,1405,1410,1413,1418,1423,1428,1431,1434,1438,1443,1447,1452,1456,1461,1465,1470,1475,1479,1484,1489,1494,1498,1503,1507,1512,1517,1521,1526,1532,1537,1541,1546,1551,1554,1557,1562,1567,1571,1576,1580,1584,1589,1593,1598,1601,1606,1611,1616,1620,1625,1629,1632,1637,1641,1646,1650,1655,1659,1664,1668,1673,1678,1683,1686,1691,1696,1701,1706,1709,1714,1719,1724,1729,1734,1739,1742,1747,1752,1757,1760,1763,1768,1773,1778,1783,1788,1793,1797,1802,1807,1812,1817,1822,1827,1832,1837,1842,1847,1852,1857,1862,1867,1871,1874,1879,1883,1888,1893,1898,1903,1906,1909,1912,1917,1922,1925,1930,1935,1940,1945,1950,1955,1959,1964,1969,1974,1977,1982,1987,1990,1994,1999,2003,2008,2013,2018,2023,2028,2033,2037,2042,2046,2050,2055,2060,2064,2069,2072,2077,2082,2087,2090,2095,2100,2105,2110,2115,2120,2125,2130,2135,2140,2145,2150,2155,2159,2164,2169,2174,2179,2184,2187,2191,2196,2201,2206,2211,2214,2219,2224,2229,2234,2239,2242,2247,2252,2257,2262,2267,2271,2275,2279,2284,2288,2292,2296,2299,2304,2309],{"id":11,"title":10,"titles":183,"content":184,"level":185},[],"Best practices and tips for designing RESTful APIs, including naming conventions, versioning, and endpoint structuring. Deep Dive into REST API Design and Implementation Best Practices",1,{"id":187,"title":188,"titles":189,"content":190,"level":191},"/blog/rest-api-design-tips#tips","Tips",[10],"Always have separate delete endpoint to delete a resource even for soft delete where you just set is_deleted flag to true.Never allow PATCH to delete resource.Send 204 to already deleted resource without raising exception of resource already deleted ( Idempotency principles).",2,{"id":193,"title":194,"titles":195,"content":196,"level":191},"/blog/rest-api-design-tips#naming-conventions","Naming Conventions",[10],"Use kebab-case for URL paths and snake_case or camelCase for query parameters.When using hyphens in query params, it can clash with minus‑sign semantics in some contexts. most libraries won't parse them as identifiers. Hence, avoid hyphens in query keys.POST /sign-in\nPOST /sign-in?redirectUrl=https://example.com/dashboard # camelCase query params when JS frameworks\nPOST /sign_in?redirect_url=https://example.com/dashboard # snake_case query params when Python, Ruby, etc.",{"id":198,"title":199,"titles":200,"content":201,"level":191},"/blog/rest-api-design-tips#_2-query-parameters","2. Query Parameters",[10],"You have two main, equally valid options—pick one and be consistent:",{"id":203,"title":204,"titles":205,"content":206,"level":207},"/blog/rest-api-design-tips#a-snake_case","A. snake_case",[10,199],"Lowercase + underscoresCommon in many REST APIs and frameworksExample:GET /products?category=electronics&price_min=100&sort_by=popularity",3,{"id":209,"title":210,"titles":211,"content":212,"level":207},"/blog/rest-api-design-tips#b-camelcase","B. camelCase",[10,199],"Lowercase first word, then capital letters for subsequent wordsAligns with many JavaScript front‑end conventionsExample:GET /products?category=electronics&priceMin=100&sortBy=popularity",{"id":214,"title":215,"titles":216,"content":217,"level":218},"/blog/rest-api-design-tips#a-note-on-hyphens-in-query-strings","A Note on Hyphens in Query Strings",[10,199,210],"Hyphens can clash with minus‑sign semantics in some contexts; most libraries won't parse them as identifiers.For that reason, avoid hyphens in query keys.",4,{"id":220,"title":221,"titles":222,"content":223,"level":191},"/blog/rest-api-design-tips#_3-general-tips","3. General Tips",[10],"Keep names semanticUse real words: sortBy, filter, pageSize, langDocument your choice in your API style guide so every team follows the same format.Versioning: if you version in the URL, do it as its own segment:/v1/products",{"id":225,"title":226,"titles":227,"content":228,"level":207},"/blog/rest-api-design-tips#api-endpoint-should-be-resource-not-action","API endpoint should be resource not action",[10,221],"https://api.example.com/tasks // [!code ++]\n\nhttps://api.example.com/get-tasks // [!code --]",{"id":230,"title":231,"titles":232,"content":233,"level":207},"/blog/rest-api-design-tips#collection-name-should-be-plural","Collection name should be plural",[10,221],"https://api.example.com/tasks // [!code ++]\nhttps://api.example.com/tasks/{task_id} // [!code ++]\n\nhttps://api.example.com/task // [!code --]\nhttps://api.example.com/task/{task_id} // [!code --]",{"id":235,"title":236,"titles":237,"content":238,"level":207},"/blog/rest-api-design-tips#group-auth-related-endpoints-under-auth-prefix","Group auth related endpoints under /auth prefix",[10,221],"POST /auth/register\nPOST /auth/login\nPOST /auth/verification-emails\nGET /auth/verification-emails/{token}\nPOST /auth/forgot-password\nPOST /auth/reset-password",{"id":240,"title":241,"titles":242,"content":243,"level":207},"/blog/rest-api-design-tips#entity-crud-endpoints-conventions","Entity CRUD endpoints conventions",[10,221],"GET    /tasks           # Get all tasks\nPOST   /tasks           # Create new task\nGET    /tasks/{task_id} # Get task by id\nPUT    /tasks/{task_id} # Update task by id (Replace)\nPATCH  /tasks/{task_id} # Partial update task by id\nDELETE /tasks/{task_id} # Delete task by id\n\nPOST   /tasks/batch     # Create multiple tasks\n\nGET    /tasks/{task_id}/comments  # Get all comments for task\nPOST   /tasks/{task_id}/comments  # Create new comment for task\nGET    /comments/{comment_id}     # Get comment by id for task\nPUT    /comments/{comment_id}     # Update comment by id for task\nPATCH  /comments/{comment_id}     # Partial update comment by id for task\nDELETE /comments/{comment_id}     # Delete comment by id for task",{"id":245,"title":246,"titles":247,"content":248,"level":191},"/blog/rest-api-design-tips#dont-go-deeper-than-3-levels-collectionresourcecollection","Don't go deeper than 3 levels: collection/resource/collection",[10],"https://api.example.com/tasks/1/comments // [!code ++]\nhttps://api.example.com/tasks/1/comments/1/replies // [!code --]",{"id":250,"title":251,"titles":252,"content":253,"level":191},"/blog/rest-api-design-tips#filter-sorting-pagination","Filter, Sorting & Pagination",[10],"\u003C!-- Filter: Use key-value pair -->\nhttps://api.example.com/tasks?completed=true\nhttps://api.example.com/users?last_name=john&age=36\n\n\u003C!-- Filter: Provide filtering on specific property/column/field with global search -->\nhttps://api.example.com/users?q=john\nhttps://api.example.com/tasks?q=john&fields=username,email\n\n\u003C!--\n    Pagination: Prefer using limit-offset instead of page-size\n\n    ?page=1&size=10 => fine for simple pagination\n    🤔 => How to get 10 items starting from 4th item with page-size?\n-->\nhttps://api.example.com/tasks?limit=10&offset=0\nhttps://api.example.com/tasks?limit=10&offset=3 \u003C- You can get 10 items starting from 4th item with limit-offset\n\n\u003C!-- Sorting: sorting key with order -->\nhttps://api.example.com/tasks?sort=-created_at,title",{"id":255,"title":256,"titles":257,"content":258,"level":191},"/blog/rest-api-design-tips#instead-of-returning-list-return-object-with-name-for-making-your-response-future-proof","Instead of returning list return object with name for making your response future proof",[10],"# --- Before\n[\n    { \"id\": 1, \"title\": \"Buy Tomato\", \"is_completed\": False },\n    { \"id\": 2, \"title\": \"Learn Sanskrit\", \"is_completed\": False },\n]\n\n# --- After\n{\n    \"tasks\": [\n        { \"id\": 1, \"title\": \"Buy Tomato\", \"is_completed\": False },\n        { \"id\": 2, \"title\": \"Learn Sanskrit\", \"is_completed\": False },\n    ],\n    \"total\": 2,\n}",{"id":260,"title":261,"titles":262,"content":263,"level":191},"/blog/rest-api-design-tips#dont-return-map-structure-in-response-instead-return-list-of-objects","Don't return map structure in response, instead return list of objects",[10],"# --- Before\n{\n    \"john\": [\n        { \"id\": 1, \"title\": \"Buy Tomato\", \"is_completed\": False },\n        { \"id\": 2, \"title\": \"Learn Sanskrit\", \"is_completed\": False },\n    ],\n    \"jane\": [\n        { \"id\": 3, \"title\": \"Buy Apple\", \"is_completed\": False },\n        { \"id\": 4, \"title\": \"Finish reading book\", \"is_completed\": False },\n    ]\n}\n\n# --- After\n{\n    \"tasks\": [\n        { \"id\": 1, \"title\": \"Buy Tomato\", \"is_completed\": False, \"user\": \"john\" },\n        { \"id\": 2, \"title\": \"Learn Sanskrit\", \"is_completed\": False, \"user\": \"john\" },\n        { \"id\": 3, \"title\": \"Buy Apple\", \"is_completed\": False, \"user\": \"jane\" },\n        { \"id\": 4, \"title\": \"Finish reading book\", \"is_completed\": False, \"user\": \"jane\" },\n    ],\n    \"total\": 4\n}",{"id":265,"title":266,"titles":267,"content":268,"level":191},"/blog/rest-api-design-tips#asynclonger-operations","Async/Longer Operations",[10],"For longer processes (POST { processId: 99 } => /long-process/create) Send 202 status code to indicate that request has been accepted for processing\nAdd status endpoint URL in response header Location to check the status of the operationExpose GET (/long-process/status/99) endpoint to check the status of the operation\nReturn 200 status code if operation isn't completed with any of the below response:Provide status of the operation{ status: \"In Progress\" }\nProvide estimated time to complete{ \"time_to_complete_in_minutes\": \"15\" }\nYou can also provide link to cancel/stop the operation{\n    \"rel\": \"cancel\",\n    \"method\": \"delete\",\n    \"href\": \"/long-process/cancel/99\"\n}\nReturn 303 status code with Location header with URL to created resource once operation is completed{ \"status\": \"completed\" }",{"id":270,"title":271,"titles":272,"content":273,"level":207},"/blog/rest-api-design-tips#api-response-should-be-generic-and-agnostic","API response should be generic and agnostic",[10,266],"Even if you are building API for a specific client, make sure your API response is generic and agnosticDon't return client specific data in response. Doing so will make your API tightly coupled with the client and requires changing both client and server code whenever requirement changes.E.g. For chat page, instead of single endpoint for chat details, chat messages, meta data, etc. create separate endpoints for each of them.",{"id":275,"title":276,"titles":277,"content":278,"level":191},"/blog/rest-api-design-tips#seo","SEO",[10],"Hyphens improve keyword recognition in URL (excluding query parameters). E.g. /product-details",{"id":280,"title":281,"titles":282,"content":283,"level":191},"/blog/rest-api-design-tips#webhooks","Webhooks",[10],"Unique Endpoints: Avoid using a single, generic endpoint for all services (e.g., /webhooks). Instead, create specific paths for each service.Example: /webhooks/stripe, /webhooks/github, /webhooks/shopify.Descriptive Naming: The URLs should clearly indicate which service they belong to.Security & Validation: Each endpoint should implement specific security measures required by the corresponding service (e.g., secret keys, signature verification, IP whitelisting).Version Control: If your API evolves, include versioning in the URL to prevent breaking existing integrations.Example: /api/v1/webhooks/github.Example Endpoints:ServiceRecommended URL StructureGitHubapi.yourdomain.comStripeapi.yourdomain.comTwilioapi.yourdomain.comGeneric/Catch-allapi.yourdomain.comservice_nameBest Practices:HTTPS Only: Always use HTTPS for all webhook endpoints to ensure encrypted communication 1.Acknowledge Immediately: Your endpoint should process the request quickly and return a 200 OK status to the sender as soon as possible, even if you queue the actual processing for later 1.Idempotency: Implement a mechanism to handle duplicate requests gracefully (e.g., using a unique identifier in the request headers) to prevent processing the same event multiple times. Storing Raw Webhook Events in Database:Error Handling and Reprocessing: If your initial processing logic fails due to a bug, network issue, or dependency outage, having the raw event data stored allows you to re-queue and reprocess the event once the underlying problem is resolved.Debugging and Auditing: Stored raw events provide a complete, immutable log of what you received. This is invaluable for debugging when something goes wrong and for auditing system behavior.Data Integrity: It ensures you capture the complete payload exactly as sent by the source, preventing potential data loss if your real-time processing fails to extract all necessary fields immediately.Decoupling Ingestion from Processing: By storing the event first and then processing it asynchronously (e.g., using a queue worker), you can respond quickly to the webhook source with a 200 OK status, improving reliability and preventing the source from retrying repeatedly.Best Practices for StorageAsynchronous Processing: Implement a \"store first, process later\" pattern. A dedicated background job or message queue should pick up the stored events for actual processing.Data Structure: Store the full JSON or raw text payload in a dedicated column (e.g., a  or  type, depending on your database).Metadata: Include relevant metadata such as the time received, the source/provider (e.g., Stripe, GitHub), and a processing status (e.g., , , ).Retention Policy: Implement a data retention policy to clean up old events after a certain period (e.g., 30-90 days) to prevent the database from growing indefinitely.Security: Ensure sensitive data within webhooks is handled securely, possibly with encryption at rest, and adhere to relevant privacy regulations. You may prefer reading Database findings for Webhooks. html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}",{"id":15,"title":14,"titles":285,"content":286,"level":185},[],"Learn useful tips and best practices in AI engineering. Anthropic - Prompting 101Matt Pocock - How I use Claude for real engineeringMatt Pocock - Context Engineering",{"id":288,"title":289,"titles":290,"content":291,"level":191},"/blog/ai-engineering-my-findings#tips","✨ Tips",[14],"Keep your context window lean and avoid roating your context window with unnecessary information. Leaner the context window more accurate AI becomes.When implementing new feature tell AI to break it down in multiple phases and work on each phase one by one. Let it drop TODO comments for future phases in your codebase for reference. It has couple of benefits when you push these next phase plan to GitHub issue:\nKeep the context window lean by only focusing on single phase. Clear context window on new phase.Spawn background agents to work on it(Not likely but for people who are in hurry) Implementing them later if it can't be done nowMCP servers eat up your context window so keep as minimum as possible MCP servers attached to your session/chat.Couple of small files > One big file. This helps AI to focus on specific task and also keeps the context clear and your code organized.",{"id":293,"title":294,"titles":295,"content":296,"level":191},"/blog/ai-engineering-my-findings#prompts","Prompts",[14],"",{"id":298,"title":299,"titles":300,"content":301,"level":207},"/blog/ai-engineering-my-findings#agentsmd","AGENTS.md",[14,294],"- In all interactions and commit messages, be extremely concise and sacrifice grammar for the sake of concision.\n\n## GitHub\n\n- Your primary method for interactive with GitHub should be GitHub CLI\n\n## Plans\n\n- At the end of each plan, give me list of unresolved questions to answer, if any. Make the question extremely concise. Sacrifice grammar for the sake of the concision.",{"id":303,"title":304,"titles":305,"content":306,"level":207},"/blog/ai-engineering-my-findings#general","General",[14,294],"\u003C!-- Much easier to scan, less reading overhead, cheaper to output. (@mattpocockuk) -->\nBe extremely concise. Sacrifice grammar for the sake of concision.\n\n\u003C!-- Push your multiple phase plan to GitHub issues -->\n\u003C!-- Ensure it uses GitHub CLI for this. You might already have it in your AGENTS.md -->\nMake a GtiHub issue containing the current plan, Including all of the the items you have checked off the plan list.",{"id":308,"title":309,"titles":310,"content":311,"level":207},"/blog/ai-engineering-my-findings#generating-prompts-via-ai","Generating prompts via AI",[14,294],"Add system prompt You're an expert social media manager having decades of experience in building valuable & high quality content. Your job is to post content that helps community. Sumit user prompt You're training intern on how to write good social media post for LinkedIn. Explain him how to write engaging valuable and high-quality LinkedIn post for any given topic. They should include tips rules, tactics you follow and discovered in your entire career. This will give you high quality prompt which you can use for generating content. Update this according to your needs.",{"id":313,"title":314,"titles":315,"content":316,"level":207},"/blog/ai-engineering-my-findings#marketing","Marketing",[14,294],"Marketing action where you must grab your audience’s attention and convince them to take action\nCraft a persuasive and succinct messaging strategy that highlights the key selling points of {product/service} and encourages customers to buy. Keep the content persuasive and to the point.",{"id":318,"title":319,"titles":320,"content":321,"level":207},"/blog/ai-engineering-my-findings#linkedin-prompt","LinkedIn Prompt",[14,294],"### A Guide to Crafting High-Impact LinkedIn Posts\n\nThink of LinkedIn as a professional networking event, not a sales pitch. Our goal is to build a community by providing value, sharing expertise, and fostering meaningful conversations. This guide will walk you through the core principles, tactics, and rules I've honed over my career to create engaging, valuable, and high-quality LinkedIn posts.\n\n### The Core Principles: Your Content Compass\n\nBefore you write a single word, understand these foundational principles. They are the \"why\" behind every successful post.\n\n* **Value First, Always:** Every post must offer something useful to your audience. This could be in the form of advice, industry insights, or practical tips that can be applied to their careers. Before you hit \"post,\" ask yourself: \"How will this benefit my connections?\"\n* **Authenticity Builds Trust:** People connect with people. Share genuine experiences, personal anecdotes, and even lessons from failures. This humanizes our brand and makes you more relatable and approachable. Avoid overly polished or perfect-sounding content; focus on being real.\n* **Engagement is a Two-Way Street:** Don't just broadcast information. The goal is to spark conversations. Ask questions, invite opinions, and always, always respond to comments. This shows you're listening and values your audience's input.\n\n### Actionable Tips & Tactics: The \"How-To\"\n\nHere's a breakdown of the tactics you'll use daily to bring our content strategy to life.\n\n#### Content Creation\n\n* **The Power of Storytelling:** Weave narratives into your posts. Share personal journeys, challenges you've overcome, and successes. Stories are memorable and create a stronger connection than dry facts.\n* **Write a Killer Hook:** The first one to three lines of your post are critical to stop the scroll. Start with a bold statement, a thought-provoking question, or a surprising statistic to grab attention.\n* **Ask Engaging Questions:** Prompting your audience with questions is a direct way to encourage interaction. Instead of just stating facts, ask for their experiences or opinions on the topic.\n* **Vary Your Content Formats:** Experiment with different types of posts to see what resonates. This can include text-only posts, images, infographics, videos, and carousels. LinkedIn's algorithm doesn't inherently favor one format over another, so focus on quality and what best suits your message.\n* **Leverage Visuals:** Posts with strong visuals, like high-quality images, infographics, or short videos, are more likely to capture attention. Ensure any visuals are optimized for mobile viewing.\n* **Use Hashtags Wisely:** Add relevant hashtags to increase the discoverability of your posts. Aim for a mix of broad and niche hashtags to reach a wider, yet relevant, audience.\n\n#### Engagement Strategy\n\n* **The \"Golden Hour\":** The first hour after posting is crucial. The LinkedIn algorithm tests your post with a small segment of your audience. High initial engagement signals that your content is valuable, leading to wider distribution.\n* **Tag Strategically:** Mention relevant individuals or companies in your posts when appropriate. This can expand your reach to their networks.\n* **Respond to Every Comment:** When someone takes the time to comment, acknowledge it. This fosters a sense of community and encourages future interaction.\n\n### Content Formatting Rules: The \"Look and Feel\"\n\nHow your post is formatted is just as important as the content itself.\n\n* **Break Up Text:** Avoid large blocks of text. Use short paragraphs, ideally single sentences, to make your content easy to digest, especially on mobile devices.\n* **Use Emojis Sparingly and Strategically:** Emojis can add personality and visual breaks to your text, but use them professionally and in a way that aligns with our brand voice.\n* **Keep it Clear and Concise:** Use straightforward language and avoid jargon. Your message should be easy to understand for a broad professional audience.\n\n### Understanding the LinkedIn Algorithm\n\nThe LinkedIn algorithm's primary goal is to show users the most relevant content to keep them engaged on the platform. It prioritizes posts that spark meaningful conversations and provide value to the professional community. Here's what it looks for:\n\n* **Relevance:** The algorithm considers your connections, past interactions, and the topics you engage with to determine what's relevant to you.\n* **Engagement:** Posts that receive a high number of likes, comments, and shares, especially within the first hour, are deemed high-quality and are shown to a wider audience.\n* **Credibility:** The algorithm assesses the professional background of the person posting to gauge the credibility of the content.\n\nBy consistently applying these principles and tactics, you'll not only create high-performing posts but also contribute to building a strong and engaged community around our brand.",{"id":323,"title":324,"titles":325,"content":326,"level":207},"/blog/ai-engineering-my-findings#x-twitter-prompt","X (Twitter) Prompt",[14,294],"Forget everything you think you know about just \"tweeting.\" We're not here to shout into the void. We're here to build, engage, and provide value.\n\n### The Philosophy: Be the Community You Want to See\n\nBefore we get into tactics, remember this: social media is about being *social*. Our goal isn't just to rack up followers; it's to create a space where people feel seen, heard, and helped. Every post should aim to do one of four things for our audience:\n*   **Educate:** Teach them something useful.\n*   **Entertain:** Make them laugh, smile, or feel something.\n*   **Inspire:** Motivate them with stories or powerful ideas.\n*   **Help:** Solve a problem or answer a question.\n\nIf a post doesn't do at least one of these, we should question why we're posting it.\n\n---\n\n### Part 1: The Anatomy of a Perfect Post - The Core Rules\n\nThink of every post as a mini-package of value. Here are the non-negotiable components.\n\n#### **Rule #1: Start with a Powerful Hook**\nYou have less than a second to stop someone from scrolling. The first line of your tweet is the most critical part.\n*   **Ask a provocative question:** \"Why do most marketing strategies fail within 6 months?\"\n*   **Make a bold statement:** \"Most people are using X all wrong. Here's how to fix it.\"\n*   **Share a surprising fact:** \"Did you know that 80% of New Year's resolutions are abandoned by February?\"\n\nA strong hook makes a promise that the rest of the post (or thread) will fulfill.\n\n#### **Rule #2: Provide Clear, Concise Value**\nTwitter's beauty is its brevity. Get straight to the point.\n*   Use simple language. Avoid jargon that your audience might not understand.\n*   Break up text with spacing and bullet points for readability.\n*   If you need more space, use a thread. Threads are fantastic for step-by-step guides, storytelling, or breaking down complex topics.\n\n#### **Rule #3: Visuals are a Must**\nTweets with images, GIFs, or videos consistently get more engagement. They make your content stand out in a crowded feed.\n*   **Images & Infographics:** Use high-quality, vibrant visuals. Infographics are perfect for breaking down data.\n*   **Videos:** Keep them short and engaging. The first few seconds are crucial. Videos up to 45 seconds tend to hold attention best. Ensure your video is high-resolution (up to 1920x1200 is supported).\n*   **GIFs:** Use them in replies to add personality and humor.\n\n#### **Rule #4: Encourage Interaction**\nDon't just talk *at* your audience; talk *with* them.\n*   **Ask Questions:** Invite your followers to share their opinions and experiences. This turns your post into a conversation.\n*   **Run Polls:** Polls are a simple, low-effort way for your audience to engage and for you to gather valuable feedback.\n*   **Use a Call-to-Action (CTA):** Tell people exactly what you want them to do next. \"Retweet if you agree,\" \"Share your thoughts below,\" or \"Click the link for the full guide\" are simple but effective CTAs.\n\n---\n\n### Part 2: Advanced Tactics - From Good to Great\n\nOnce you've mastered the fundamentals, it's time to elevate your strategy.\n\n#### **Tactic #1: Master the Art of the Thread**\nThreads are your secret weapon for delivering immense value.\n*   **Structure is Key:** Your first tweet is the hook. Each subsequent tweet should build on the last. The final tweet should summarize the key takeaway or include a strong CTA.\n*   **Signal It:** Add \"(Thread)\" or a \"👇\" emoji to your first tweet so people know there's more to come.\n*   **Add Value in Every Tweet:** Each part of the thread should be useful on its own. Avoid filler content.\n\n#### **Tactic #2: Use Hashtags Strategically, Not Desperately**\nForget stuffing your posts with a dozen hashtags. That looks spammy.\n*   **The Rule of 1-3:** Use one to three highly relevant hashtags per post. This is crucial for discoverability in specific conversations.\n*   **Niche & Trending:** Mix broad hashtags with niche ones specific to your community. Participate in relevant trending topics, but only if you can add genuine value.\n\n#### **Tactic #3: It's a Two-Way Street: Proactive Engagement**\nBuilding a community means you can't just post and ghost.\n*   **Respond Quickly:** Make time to reply to comments and DMs. This shows you're listening and turns casual followers into loyal fans.\n*   **Engage with Others:** Follow and interact with leaders and peers in your niche. Retweet their valuable content with your own insights (use the \"Quote Tweet\" feature). This builds relationships and puts you in front of new audiences.\n\n#### **Tactic #4: Consistency and Timing are Everything**\n*   **Post Regularly:** It's better to post 1-3 high-quality tweets per day than ten low-effort ones. Consistency keeps your audience engaged and tells the algorithm you're an active presence.\n*   **Find Your Optimal Times:** Use X Analytics to see when your audience is most active and schedule your posts for those peak times.\n\n---\n\n### Your Pre-Post Checklist:\n\nBefore you hit that \"Post\" button, run through this quick list:\n\n*   **[ ] Is there a strong hook?** (Does the first line make me want to read more?)\n*   **[ ] Is the value clear?** (What will someone learn or feel from this?)\n*   **[ ] Is it easy to read?** (Is it concise? Is it formatted well?)\n*   **[ ] Is there a visual element?** (Image, GIF, or video?)\n*   **[ ] Is there an interactive element?** (A question, poll, or CTA?)\n*   **[ ] Are the hashtags relevant and minimal?** (1-3 max)\n\nDon't be afraid to experiment, track what works, and most importantly, be human. This is about connection, not perfection. Now, go create something valuable",{"id":328,"title":329,"titles":330,"content":331,"level":207},"/blog/ai-engineering-my-findings#writing","Writing",[14,294],"Writing about technical concepts for a technical & non-technical audiences\nI need to explain {technical_concept} to a developer who has a basic understanding of programming but may be unfamiliar with the specific concept I'm addressing. The explanation should be:\n\n1. Clear, concise, and easy to understand, avoiding technical jargon or complex language that might confuse someone who isn't an expert in the field.\nAccompanied by two examples:\na. A simple and minimal example that demonstrates the concept clearly without any extra content or complexities.\nb. A real-world example that is still minimalistic but illustrates how the concept can be applied in practical scenarios. Ensure that this example is relevant to common development tasks and avoids unnecessary details.\n3. Emphasize the practical application of the concept in everyday programming tasks, helping the developer to see the direct relevance and utility of the concept.\n4. Where applicable, offer brief comparisons with similar concepts to provide context and deepen understanding, while keeping these comparisons concise and to the point. html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sMMmS, html code.shiki .sMMmS{--shiki-default:#BD93F9;--shiki-default-font-weight:bold}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sVPtC, html code.shiki .sVPtC{--shiki-default:#FFB86C;--shiki-default-font-weight:bold}html pre.shiki code .sZsTv, html code.shiki .sZsTv{--shiki-default:#F1FA8C;--shiki-default-font-style:italic}",{"id":19,"title":18,"titles":333,"content":334,"level":185},[],"Learn useful tips and best practices for using Alembic in your database migrations. I use postgresql (most of the time async) as my database and sqlalchemy as my ORM. How to Add a Non-Nullable Field to a Populated Table",{"id":336,"title":337,"titles":338,"content":296,"level":191},"/blog/alembic-my-findings#cheatsheet","📚 Cheatsheet",[18],{"id":340,"title":341,"titles":342,"content":296,"level":207},"/blog/alembic-my-findings#gotchas","🚨 Gotchas",[18,337],{"id":344,"title":345,"titles":346,"content":347,"level":207},"/blog/alembic-my-findings#alembic-cant-detect-string-length-changes","Alembic can't detect String length changes",[18,337],"Assume you have mobile number col having String(15) and you decide to remove the length restriction. - mobile_number: Mapped[str] = mapped_column(String(15))\n+ mobile_number: Mapped[str] = mapped_column(String) Now, if you generate the migration, alembic won't detect the change and won't generate any migration. To fix this issue, you need to manually add the migration. def upgrade() -> None:\n    op.alter_column('table_name', 'mobile_number',\n        existing_type=sa.String(length=15),\n        type_=sa.String(),\n        existing_nullable=True\n    )\n\ndef downgrade() -> None:\n    op.alter_column('table_name', 'mobile_number',\n        existing_type=sa.String(),\n        type_=sa.String(length=15),\n        existing_nullable=True\n    )",{"id":349,"title":289,"titles":350,"content":351,"level":191},"/blog/alembic-my-findings#tips",[18],"If you're using postgresql & enum, install this library: alembic-postgresql-enum. Check why.",{"id":353,"title":354,"titles":355,"content":356,"level":207},"/blog/alembic-my-findings#dont-generate-empty-migration","Don't generate empty migration",[18,289],"Source # File: alembic/env.py\nfrom alembic.operations import MigrationScript\nfrom collections.abc import Iterable\n\n# Don't generate empty migrations\n# Docs: https://alembic.sqlalchemy.org/en/latest/cookbook.html#cookbook-no-empty-migrations\ndef process_revision_directives(\n    context: MigrationContext,\n    revision: str | Iterable[str | None] | Iterable[str],\n    directives: list[MigrationScript],\n):\n    assert config.cmd_opts is not None\n    if getattr(config.cmd_opts, \"autogenerate\", False):\n        script = directives[0]\n        assert script.upgrade_ops is not None\n        if script.upgrade_ops.is_empty():\n            directives[:] = []\n\n# Other code\n\ndef do_run_migrations(connection: Connection) -> None:\n    context.configure(\n        connection=connection,\n        target_metadata=target_metadata,\n        compare_server_default=custom_compare_server_default,\n        process_revision_directives=process_revision_directives, // [!code ++]\n    )\n\n    # Rest of the func code Now, whenever you run alembic revision --autogenerate -m \"\u003Cmsg>\" and alembic can't detect any changes, it won't create a new migration script. However, in some known cases you might want to generate script to manually add some changes. In that case, you can use alembic revision -m \"\u003Cmsg>\". # Alembic auto-generate migration script\n# It won't create a new migration script if there are no changes detected\nalembic revision --autogenerate -m \"\u003Cmsg>\"\n\n# Alembic create a new empty migration script\nalembic revision -m \"\u003Cmsg>\"",{"id":358,"title":359,"titles":360,"content":361,"level":207},"/blog/alembic-my-findings#enable-date-time-in-alembic-revision-file-name","Enable date & time in alembic revision file name",[18,289],"Uncomment file_template in alembic.ini file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s",{"id":363,"title":364,"titles":365,"content":366,"level":207},"/blog/alembic-my-findings#enable-comparing-server-default-values-in-envpy","Enable comparing server default values in env.py",[18,289],"# Enable comparing server default\ncontext.configure(\n    compare_server_default=True,  # Allow alembic to compare server default)\n\n# I don't know exact place but I add it before `config = context.config`",{"id":368,"title":369,"titles":370,"content":371,"level":207},"/blog/alembic-my-findings#enable-comparing-jsonb-columns-server-default-value","Enable comparing JSONB column's server default value",[18,289],"from alembic.migration import MigrationContext\nfrom sqlalchemy import Column, FetchedValue\nfrom sqlalchemy.dialects.postgresql import JSONB\nfrom typing import Any\n\ndef custom_compare_server_default(\n    context: MigrationContext,\n    inspected_column: Column[Any],\n    metadata_column: Column[Any],\n    inspected_default: str | None,\n    metadata_default: FetchedValue | None,\n    rendered_metadata_default: str | None,\n):\n    # Check if the column type is JSONB\n    if isinstance(metadata_column.type, JSONB):\n        # Compare the rendered metadata default with the inspected default\n        if rendered_metadata_default != inspected_default:\n            return True  # Indicate that the defaults differ and should be updated\n        return False  # Indicate no difference\n    return None  # Default behavior for other types Afterwards, use above callable in compare_server_default param in context.configure. # * Add this to either `do_run_migrations`.\ncontext.configure(\n    compare_server_default=custom_compare_server_default, // [!code ++]\n)",{"id":373,"title":374,"titles":375,"content":376,"level":207},"/blog/alembic-my-findings#auto-import-models-in-envpy-file-for-auto-generation-of-migrations","Auto import models in env.py file for auto generation of migrations",[18,289],"# File: repo_root/src/utils/imports.py\n\nimport os\nfrom importlib import import_module\nfrom pathlib import Path\n\n# 🚨 Adjust the paths according to your project structure\ncurr_dir = Path(__file__).parent.resolve()\nsrc_dir = curr_dir.parent\n\ndef import_models_for_alembic():\n    # Consider modules.py & all files in models directory as models\n    models_file_glob = src_dir.glob(\"**/models.py\")\n    models_dir_glob = src_dir.glob(\"**/models/*.py\")\n\n    # Combine the two generators\n    models_file_glob = [*models_file_glob, *models_dir_glob]\n\n    for file in models_file_glob:\n        # Skip __init__.py\n        if file.name == \"__init__.py\":\n            continue\n\n        relative_path = file.relative_to(src_dir)\n        module = str(relative_path).replace(os.sep, \".\").replace(\".py\", \"\")\n        import_module(module) html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}",{"id":23,"title":22,"titles":378,"content":379,"level":185},[],"Learn useful tips and best practices for using various AWS services effectively.",{"id":381,"title":382,"titles":383,"content":384,"level":191},"/blog/aws-my-findings#recommendeda-directory-structure","Recommendeda Directory Structure",[22],"I prefer using SAM CLI for working with AWS to version control your AWS infra and CI/CD. import logging\nimport sys\nfrom typing import Any\n\n# # Env variables\n# MY_ENV = os.environ[\"MY_ENV\"]\n\n# Logging Setup\nlogger = logging.getLogger()\nlogger.handlers = []  # remove pre-configured handlers\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter(\"[%(levelname)s] %(message)s\"))\nlogger.addHandler(handler)\nlogger.setLevel(logging.INFO)\n\n# # Boto3 Clients\n# s3_client = boto3.client(\"s3\")\n\ndef lambda_handler(event: dict[str, Any], context: Any):\n  logger.info(\"Event: %s\", event)\n\n  # ...\nimport logging\nimport sys\nfrom typing import Any\n\n# # Env variables\n# MY_ENV = os.environ[\"MY_ENV\"]\n\n# Logging Setup\nlogger = logging.getLogger()\nlogger.handlers = []  # remove pre-configured handlers\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter(\"[%(levelname)s] %(message)s\"))\nlogger.addHandler(handler)\nlogger.setLevel(logging.INFO)\n\n# # Boto3 Clients\n# s3_client = boto3.client(\"s3\")\n\ndef lambda_handler(event: dict[str, Any], context: Any):\n  logger.info(\"Event: %s\", event)\n\n  # ...\n'''\nNote: Create dir same as your layer name so that you can import this via:\n`from common_layer.utils import some_common_util`\n\nThis will give you visibility on from which layer specific code comes\n'''\ndef some_common_util(): ...\npandas\nhttpx\n'''\nNote: Create dir same as your layer name so that you can import this via:\n`from integrations_layer.hubspot import Hubspot`\n\nThis will give you visibility on from which layer specific code comes\n'''\nclass Hubspot:\n  ...\nhubspot-api-client\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: Base processor\n\nResources:\n  # Your resources\n{}\n# Root stack to deploy\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: Your Stack\n\nResources:\n  # Your resources With this structure you can deploy your stack with SAM CLI. This imposes best practices like: Avoid ambiguity when importing code from layers by namespacing them with directory same as layer name. This also avoid name collision and overriding of same module in multiple layers.\n# You don't know which layer has this module\nfrom utils import my_utils\n\n# Clear visibility on from which layer you're importing\nfrom common_layer.utils import my_util\nSAM CLI compatible layers directory structure which handles installation of requirements.txtKeep .venv, README.md, .gitignore, ruff.toml, etc at root level.",{"id":386,"title":337,"titles":387,"content":296,"level":191},"/blog/aws-my-findings#cheatsheet",[22],{"id":389,"title":390,"titles":391,"content":392,"level":207},"/blog/aws-my-findings#services","Services",[22,337],"ServiceFull FormDescriptionIAMIdentity and Access ManagementUsed to manage users, groups, and rolesS3Simple Storage ServiceUsed to store filesEC2Elastic Compute CloudProvide remote/cloud serversSESSimple Email ServiceUsed to send emailsDynamoDBAmazon Dynamo DatabaseNoSQL database serviceRDSRelational Database ServiceManaged relational database serviceLambdaAWS LambdaServerless computing serviceAPI GatewayAmazon API GatewayCreate, publish, maintain, monitor, and secure APIsCloudFrontAmazon CloudFrontContent delivery network (CDN) serviceRoute 53Amazon Route 53Domain Name System (DNS) web serviceCloudWatchAmazon CloudWatchMonitor AWS resources and applicationsSNSSimple Notification ServicePub/sub messaging and mobile notificationsSQSSimple Queue ServiceFully managed message queuing serviceAppSyncAWS AppSyncManaged GraphQL serviceCognitoAmazon CognitoUser authentication and authorization serviceAmazon Auto ScalingAmazon Auto ScalingAutomatically adjust the number of EC2 instancesAmazon InspectorAmazon InspectorAutomated security assessment serviceAWS Firewall ManagerAWS Firewall ManagerCentral management of AWS WAF and VPC security groupsAWS Key Management Service (KMS)AWS Key Management ServiceManaged service to create and control encryption keysAmazon SageMakerAmazon SageMakerManaged service to build, train, and deploy machine learning modelsKinesisAmazon KinesisReal-time data streaming serviceElastic BeanstalkAWS Elastic BeanstalkPlatform as a service (PaaS) for deploying and scaling web applications and servicesElastic Load BalancingElastic Load BalancingAutomatically distribute incoming application traffic across multiple targetsVPCVirtual Private CloudVirtual network dedicated to your AWS accountEBSElastic Block StorePersistent block storage volumes for EC2 instancesEFSElastic File SystemScalable file storage for use with EC2 instancesGlacierAmazon GlacierLow-cost storage service for data archiving and backupAthenaAmazon AthenaQuery data in S3 using SQLRedshiftAmazon RedshiftData warehousing serviceElastiCacheAmazon ElastiCacheIn-memory data store and cache serviceCloudFormationAWS CloudFormationInfrastructure as code serviceAWS BackupAWS BackupFully managed backup service for AWS resources",{"id":394,"title":395,"titles":396,"content":397,"level":207},"/blog/aws-my-findings#ec2-elastic-compute-cloud","EC2 (Elastic Compute Cloud)",[22,337],"Provide remote/cloud serversYou can enable SSH login from \"Security Group\". There is source IP field where you can specify your IP address, Generally it's default to 0.0.0.0/0 which means anyone can access your server. If you want to only allow your IP address to access the server, you can specify your IP address in this field.Default username for Amazon Linux is ec2-user",{"id":399,"title":400,"titles":401,"content":402,"level":207},"/blog/aws-my-findings#ses-simple-email-service","SES (Simple Email Service)",[22,337],"Used to send emails. You can also send emails in bulk.You can verify your domain and email address in SES. You can only send emails from verified domain and email address.",{"id":404,"title":405,"titles":406,"content":296,"level":207},"/blog/aws-my-findings#cloudwatch","CloudWatch",[22,337],{"id":408,"title":409,"titles":410,"content":411,"level":218},"/blog/aws-my-findings#alarms","Alarms",[22,337,405],"If you want to set email alerts for lambda fails. Here's best config I'm aware of: Period: 15 minutes or 1 minute (select based on your requirement)Statistic: SumMetric: ErrorsConditions: Greater than 0Notification: Select SNS topic to send email alerts",{"id":413,"title":414,"titles":415,"content":296,"level":207},"/blog/aws-my-findings#dynamodb","DynamoDB",[22,337],{"id":417,"title":418,"titles":419,"content":420,"level":218},"/blog/aws-my-findings#querying","Querying",[22,337,414],"At most you can query 2 cols, partition key and sort key. If you want to query more than 2 cols, you have to use scan which is not recommended for large datasets. Instead prefer RDS if you have more tables like these.",{"id":422,"title":423,"titles":424,"content":425,"level":207},"/blog/aws-my-findings#s3","S3",[22,337],"When you upload file with same key in S3, it will overwrite the existing file with new file.",{"id":427,"title":428,"titles":429,"content":430,"level":207},"/blog/aws-my-findings#lambda","Lambda",[22,337],"Use below boilerplate code for: Noise free logging (avoid duplicate timestamp in CloudWatch)Keep env variables on top to identify which env vars are needed for this lambdaAlways log event for easier debugging import logging\nimport sys\nfrom typing import Any\n\n# # Env variables\n# MY_ENV = os.environ[\"MY_ENV\"]\n\n# Logging Setup\nlogger = logging.getLogger()\nlogger.handlers = []  # remove pre-configured handlers\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter(\"[%(levelname)s] %(message)s\"))\nlogger.addHandler(handler)\nlogger.setLevel(logging.INFO)\n\n# # Boto3 Clients\n# s3_client = boto3.client(\"s3\")\n\n\ndef lambda_handler(event: dict[str, Any], context: Any):\n    logger.info(\"Event: %s\", event)\n    \n    # ...",{"id":432,"title":433,"titles":434,"content":435,"level":207},"/blog/aws-my-findings#aws-backup","AWS Backup",[22,337],"It's two part job: Create a backup planAssign resources to the backup plan For s3 buckets, you have to enable bucket versioning to use AWS Backup. You can enable versioning from S3 bucket properties. Ref: YouTube Video",{"id":437,"title":289,"titles":438,"content":439,"level":191},"/blog/aws-my-findings#tips",[22],"Use CloudFormation to deploy your AWS resources. It allows you to define your infrastructure as code, making it easier to manage and version control.Prefer layer_\u003Clayer_name> for Lambda layers. It helps in identifying from which layer the function is using the code.Always use cloudformation with versioning to setup your AWS resources. It allows you to track changes and roll back if needed.\nUse cloudformation designer & related tool to visualize your AWS infra.Enable AWS Cost Anomaly Detection to monitor your spending and receive alerts for unusual spending patterns. Generally, I prefer 10% threshold on AWS account.Always tag your s3 objects. Examples can be by resource owner, by department, etc. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"id":27,"title":26,"titles":441,"content":442,"level":185},[],"Lean useful tips and best practices for writing effective Bash scripts.",{"id":444,"title":337,"titles":445,"content":296,"level":191},"/blog/bash-my-findings#cheatsheet",[26],{"id":447,"title":448,"titles":449,"content":296,"level":207},"/blog/bash-my-findings#scripting","Scripting",[26,337],{"id":451,"title":452,"titles":453,"content":454,"level":218},"/blog/bash-my-findings#variables-user-input","Variables & User Input",[26,337,448],"#!/bin/bash\n\n# Variables\n# Variable names are in uppercase, e.g. `NAME`, `FILE_PATH`.\n# Variables don't have spaces around `=`\nNAME=\"john\"\necho \"Hello, $NAME\" # Hello, john\n\n# Concatenation\nSPORT=\"Foot\"\necho \"I like ${SPORT}ball\"\n\n# User Input\nread -p \"Enter your name: \" USERNAME\necho \"Your name is $USERNAME\"\n\n# Reading multiple inputs\necho \"Enter your name and age:\"\nread NAME AGE\necho \"Your name is $NAME and you are $AGE years old\"\n# > John 25 => Your name is John and you are 25 years old\n# > John => Your name is John and you are years old\n# Hence, missing input won't throw an error\n\n# Array\nNAMES=(John Jane Doe)\necho \"First Name: ${NAMES[0]}\"\necho \"All Names: ${NAMES[@]}\"",{"id":456,"title":457,"titles":458,"content":459,"level":218},"/blog/bash-my-findings#conditional-statements","Conditional Statements",[26,337,448],"#!/bin/bash\n\n# Ensure spaces around `[` and `]`\n# Ensure quotes around variable names\n# You can use `==` or `=` for string comparison\nNAME=\"john\"\n\nif [ \"$NAME\" == \"john\" ]; then\n  echo \"Hello, John\"\nfi\n\n# One liner if statement\n[ \"$NAME\" == \"john\" ] && echo \"Hello, John\"\n\n# If Else\nif [ \"$NAME\" == \"john\" ]; then\n  echo \"Hello, John\"\nelse\n  echo \"Hello, Stranger\"\nfi\n\n# On liner if else\n[ \"$NAME\" == \"john\" ] && echo \"Hello, John\" || echo \"Hello, Stranger\"\n\n# If Elif Else\nif [ \"$NAME\" == \"john\" ]; then\n  echo \"Hello, John\"\nelif [ \"$NAME\" == \"jane\" ]; then\n  echo \"Hello, Jane\"\nelse\n  echo \"Hello, Stranger\"\nfi\n\n# On liner if elif else (🚨 Not Recommended due to readability issues)\n[ \"$NAME\" == \"john\" ] && echo \"Hello, John\" || [ \"$NAME\" == \"jane\" ] && echo \"Hello, Jane\" || echo \"Hello, Stranger\"\n\n# Arithmetic Comparison\nAGE=25\n\nif [ \"$AGE\" -gt 18 ]; then\n  echo \"You are an adult\"\nfi\n# Other operators: -eq, -ne, -lt, -le, -ge",{"id":461,"title":462,"titles":463,"content":464,"level":218},"/blog/bash-my-findings#loops","Loops",[26,337,448],"#!/bin/bash\n\n# For loop\nNAMES=\"John Jane Doe\"\nfor NAME in $NAMES; do\n  echo \"Hello, $NAME\"\ndone\n\n# For loop on array/list\nNAMES=(John Jane Doe)\nfor NAME in ${NAMES[@]}; do\n  echo \"Hello, $NAME\"\ndone\n\n# For loop on array/list without variable\nfor NAME in John Jane Doe; do\n  echo \"Hello, $NAME\"\ndone\n\n# For Loop on Range\nfor INDEX in {1..5}; do\n  echo \"Number: $INDEX\"\ndone",{"id":466,"title":467,"titles":468,"content":469,"level":218},"/blog/bash-my-findings#functions","Functions",[26,337,448],"#!/bin/bash\n\n# Function Declaration\nfunction greet() {\n  echo \"Hello, John\"\n}\n\n# Function Call\ngreet\n\n# Function with Parameters\nfunction greet() {\n  echo \"Hello, $1\"\n}\n\ngreet John # Hello, John\n# In function scope, $1, $2, ... are used to access parameters and not the positional parameters\n\n# Function with default value & naming parameters\nfunction greet() {\n    local name=$1 # Assign the first parameter to `name`\n    local greeting=${2:-\"Hello\"} # Assign the second parameter to `greeting` with default value \"Hello\"\n    echo \"$greeting $name\"\n}\n\ngreet john # Hello john\ngreet john Hi # Hi john",{"id":471,"title":472,"titles":473,"content":474,"level":218},"/blog/bash-my-findings#positional-parameters","Positional Parameters",[26,337,448],"#!/bin/bash\n\n# $0 - Script Name\necho \"Script Name: $0\"\n\n# $1, $2, $3, ... - Positional Parameters\necho \"First Argument: $1\"\necho \"Second Argument: $2\"",{"id":476,"title":477,"titles":478,"content":479,"level":218},"/blog/bash-my-findings#piping-redirection","Piping & Redirection",[26,337,448],"#!/bin/bash\n\n# Piping\nls | grep \".txt\"\n\n# Redirection\n# > - Overwrite\n# >> - Append\necho \"Hello, World\" > file.txt\necho \"Hello, Again\" >> file.txt\n\n# Reading from file\n# `wc` - Word Count\nwc -w \u003C file.txt\n\n# Reading from file and writing to another file\ncat file.txt > file2.txt\n\n# Reading from file and appending to another file\ncat file.txt >> file2.txt\n\n# Read input until EOF (End of File)\n# You can also use any other delimiter instead of EOF\n# wc -w \u003C\u003C done\nwc -w \u003C\u003C EOF",{"id":481,"title":482,"titles":483,"content":484,"level":218},"/blog/bash-my-findings#string-manipulation","String Manipulation",[26,337,448],"#!/bin/bash\n\nstr=\"Hello, World\"\n\n# Length of the string\necho ${#str} # 12\n\n# Convert first character to lowercase\necho ${string,} # hello, World\n\n# Convert all to lowercase\necho ${string,,} # hello, world\n\n# Update str variable for further examples\nstr=\"hello, World\"\n\n# Convert first character to uppercase\necho ${str^} # Hello, World\n\n# Convert all to uppercase\necho ${str^^} # HELLO, WORLD\n\nstr=\"Hello, World\"\n\n# Indexing\n\n# Get substring from index 1 to end\necho ${str:1} # ello, World\n\n# Get substring from index 1 to 3 characters\necho ${str:7:3} # Wor\n\n# Get substring from end\necho ${str:-1} # Hello, World\n\n# Get substring from end of length 1\necho ${str: -1} # d\n\n# Get substring from end of length 5\necho ${str: -5} # World\n\n# From start, Replace shortest match\necho ${str#*l} # lo, World\n\n# From start, Replace longest match\necho ${str##*l} # d\n\n# From end, Replace shortest match\necho ${str%l*} # Hello, Wor\n\n# From end, Replace longest match\necho ${str%%l*} # He\n\n# Find and Replace\necho ${str/World/John} # Hello, John\necho ${str/l/L} # HeLlo, World\n\n# Multiple Find and Replace\necho ${str//l/L} # HeLLo, WorLd\n\n# Remove match single\necho ${str/l} # Helo, World\n\n# Remove match all\necho ${str//l} # Heo, Word",{"id":486,"title":487,"titles":488,"content":489,"level":218},"/blog/bash-my-findings#testing-file-conditions","Testing File Conditions",[26,337,448],"#!/bin/bash\n\n# Check if directory exist\nif [ -d ~/Downloads ]; then\n    echo \"Directory exists\"\nelse\n    echo \"Directory doesn't exist\"\nfi\n\n# Check if file exists\nif [ -f ~/Downloads/file.txt ]; then\n    echo \"File exists\"\nelse\n    echo \"File doesn't exist\"\nfi",{"id":491,"title":492,"titles":493,"content":296,"level":191},"/blog/bash-my-findings#snippets","📝 Snippets",[26],{"id":495,"title":496,"titles":497,"content":498,"level":207},"/blog/bash-my-findings#utility-functions","Utility Functions",[26,492],"function start_spinner() {\n    # Create a spinner graphic\n    SPINNER=\"-\\|/\"\n\n    i=0\n    while : ; do\n        printf \"\\b%s\" \"${SPINNER:i++%${#SPINNER}:1}\"\n        sleep 0.1\n    done &\n\n    # Save spinner process ID to kill it later\n    SPINNER_PID=$!\n\n    trap 'stop_spinner; exit;' SIGINT # ℹ️ Stop the spinner when the script is interrupted\n}\n\nfunction stop_spinner() {\n    # Kill the spinner\n    kill $SPINNER_PID\n    \n    # Clear the spinner characters\n    printf \"\\b \\n\"\n}\n\n# Usage: redirect_output_to_file \"path/to/log/file.log\"\nfunction redirect_output_to_file() {\n    local LOG_FILE_PATH=$1\n\n    exec 3>&1 4>&2 # Save the original file descriptors\n    exec > \"$LOG_FILE_PATH\" 2>&1 # Redirect stdout and stderr to the log file\n}\n\nfunction reset_output_redirection() {\n    exec 1>&3 2>&4\n}\n\nfunction blank_lines() {\n    local COUNT=$1\n    for ((i=0; i\u003CCOUNT; i++)); do\n        echo \"\"\n    done\n}\n\nfunction with_blank_lines() {\n    local VARIANT=$1\n    local MSG=$2\n    local BLANK_LINES_COUNT=${3:-1} # Default to 1 blank line\n\n    [ \"$VARIANT\" == \"t\" ] && blank_lines $BLANK_LINES_COUNT && echo $MSG\n    [ \"$VARIANT\" == \"b\" ] && echo $MSG && blank_lines $BLANK_LINES_COUNT\n    [ \"$VARIANT\" == \"tb\" ] && blank_lines $BLANK_LINES_COUNT && echo $MSG && blank_lines $BLANK_LINES_COUNT\n}\n\nfunction banner() {\n    local LABEL=$1\n    local LEVEL=$2\n\n    if [ \"$LEVEL\" -eq 1 ]; then\n        msg=\"# $LABEL #\"\n        edge=$(echo \"$msg\" | sed 's/./#/g')\n\n        blank_lines 2\n        echo \"####################################\"\n        echo \"# --- $LABEL\"\n        echo \"####################################\"\n        echo \"\"\n    elif [ \"$LEVEL\" -eq 2 ]; then\n        echo \"\"\n        echo \"#\"\n        echo \"# --- $LABEL\"\n        echo \"#\"\n        echo \"\"\n    elif [ \"$LEVEL\" -eq 3 ]; then\n        echo \"\"\n        echo \"# --- $LABEL\"\n        echo \"\"\n    else\n        echo \"🚨 ERROR: Invalid level\"\n        exit 1\n    fi\n}",{"id":500,"title":501,"titles":502,"content":503,"level":207},"/blog/bash-my-findings#redirect-output-to-file-and-resetting-back-with-spinner","Redirect output to file and resetting back with spinner",[26,492],"#!/bin/bash\n\n# 🚨 We'll be using utility function from the snippets section\n\nLOG_FILE_PATH=$(pwd)/exec-$(date +%H_%M_%S).log\n\nstart_spinner\n\nredirect_output_to_file $LOG_FILE_PATH\n\necho \"Going for sleep\" # This will be written to the log file\nsleep 2\necho \"Done sleeping\" # This will be written to the log file\n\n# ❗ Order of the following two commands is important\nreset_output_redirection # From now on, all output will be printed to the terminal\n\n# Once we reset the stdout and stderr, allow stop_spinner to remove the spinner graphic from the terminal\nstop_spinner\n\necho \"This'll be printed to the terminal\" html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}",{"id":31,"title":30,"titles":505,"content":506,"level":185},[],"A comprehensive guide to choose between Amazon S3 Standard and Glacier storage classes based on your data access patterns and cost considerations.",{"id":508,"title":509,"titles":510,"content":296,"level":191},"/blog/choosing-between-s3-standard-vs-glacier#overview-s3-standard-vs-glacier-storage-classes","🔍 Overview: S3 Standard vs Glacier storage classes",[30],{"id":512,"title":513,"titles":514,"content":515,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#s3-standard-and-standardia","S3 Standard (and Standard‑IA)",[30,509],"Purpose: Frequent or predictable access.Access latency: Milliseconds.Durability: 99.999999999% (“11 nines”) (AWS Documentation, cloudwithdj.com)Availability: ~99.99% (Standard), ~99.9% (Standard‑IA) (AWS Documentation)Cost: Higher per‑GB; minimal or no retrieval fees. Standard‑IA cheaper storage but charges per retrieval.Minimum durations: None for Standard; 30 days minimum for Standard‑IA/One‑Zone‑IA (AWS Documentation)",{"id":517,"title":518,"titles":519,"content":520,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#glacier-storage-classes-within-s3","Glacier Storage Classes (within S3)",[30,509],"Three archival tiers, each offering the same 11‑nines durability as S3 Standard (AWS Documentation). TierMin Storage DurationTypical Access FrequencyRetrieval TimeReal‑time Access?Glacier Instant Retrieval90 daysQuarterly or lessMilliseconds✓ (AWS Documentation)Glacier Flexible Retrieval (formerly Glacier)90 days1–2 times/yearMinutes to hours✗ (must restore) (AWS Documentation, Digital Cloud)Glacier Deep Archive180 days≤ once/year12–48 hours✗ (restore required) (AWS Documentation, Amazon Web Services, Inc.) Storage cost: up to ~90% lower vs Standard (e.g. ~$0.004 /GB/mo) (Medium)Retrieval cost: charged per GB and request; faster tiers cost more (Wikipedia)Deletion charges: deleting objects before minimum duration still bills for full minimum term (AWS Documentation)Object size note: Glacier Instant requires ≥ 128 KB minimum (billed as 128 KB if smaller) (AWS Documentation)",{"id":522,"title":523,"titles":524,"content":296,"level":191},"/blog/choosing-between-s3-standard-vs-glacier#when-to-use-which","✅ When to Use Which",[30],{"id":526,"title":527,"titles":528,"content":529,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#use-s3-standard-or-standardia-when","Use S3 Standard or Standard‑IA when",[30,523],"Data is accessed frequently or predictably (Standard: > monthly; IA: ~monthly).Immediate, high‑performance access is required.You want no additional retrieval latency or fees.",{"id":531,"title":532,"titles":533,"content":534,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#use-glacier-instant-retrieval-when","Use Glacier Instant Retrieval when",[30,523],"Data is rarely accessed (e.g. quarterly or less), but must be available instantly if needed.Example: medical images, satellite imagery, media archives that might be served dynamically (AWS Documentation, Amazon Web Services, Inc.)Cost‑sensitive vs Standard‑IA for low access frequency.",{"id":536,"title":537,"titles":538,"content":539,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#use-glacier-flexible-retrieval-when","Use Glacier Flexible Retrieval when",[30,523],"Data access is very infrequent (once or twice a year).Retrieval delays of minutes to hours are acceptable (Expedited: 1–5 min; Standard: 3–5 h; Bulk: 5–12 h) (Amazon Web Services, Inc., Reddit, AWS Documentation)Use case: backups, disaster recovery, audit logs.",{"id":541,"title":542,"titles":543,"content":544,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#use-glacier-deep-archive-when","Use Glacier Deep Archive when",[30,523],"Data is archived for long-term compliance (e.g. 7–10 years) or rarely needed (\u003C once/year).Lowest possible storage cost outweighs retrieval latency (makes sense if you can wait 12‑48 hours) (Amazon Web Services, Inc., AWS for Engineers, cloudwithdj.com)Ideal replacement for tape libraries, regulatory archives, digital preservation.",{"id":546,"title":547,"titles":548,"content":549,"level":191},"/blog/choosing-between-s3-standard-vs-glacier#️-lifecycle-cost-considerations","⚙️ Lifecycle & Cost Considerations",[30],"Automatically transition objects from Standard to Glacier via S3 Lifecycle policies based on age, tags, etc.Transition into Glacier has no minimum wait time, but billing still honors the 90/180‑day minimum (Medium, Reddit).Lifecycle transitions cost per request, especially when transitioning many small objects. Sometimes batching small files (e.g. zipping) helps (Reddit).",{"id":551,"title":552,"titles":553,"content":554,"level":191},"/blog/choosing-between-s3-standard-vs-glacier#quick-usecase-scenarios","🧠 Quick Use‑Case Scenarios",[30],"Website assets (images/videos): S3 Standard (low latency, frequent use).Monthly backup archives: Standard‑IA or Glacier Instant if access needs are rare but quick.Yearly audit logs or compliance data: Glacier Flexible Retrieval – cost effective with some delay.Multi-year retention archives (e.g. legal, financial): Glacier Deep Archive — ultra‑low cost, slow access is acceptable.",{"id":556,"title":557,"titles":558,"content":559,"level":191},"/blog/choosing-between-s3-standard-vs-glacier#summary-comparison","🧩 Summary Comparison",[30],"Access frequency & latency: Standard (frequent/millisecond) → Instant Glacier (rare/millisecond) → Flexible (rare/minutes–hours) → Deep Archive (very rare/hours).Storage cost: Standard > Standard‑IA > Instant Retrieval > Flexible Retrieval > Deep Archive.Retrieval cost & delay: Standard has none; Glacier tiers trade cost for delay.",{"id":561,"title":562,"titles":563,"content":564,"level":207},"/blog/choosing-between-s3-standard-vs-glacier#️-recommendation-tips","✔️ Recommendation Tips",[30,557],"Define your data by access pattern, latency requirement, frequency of retrieval, and retention policy.Use S3 Lifecycle rules to automatically tier data over time.Monitor retrieval volumes and sizes to avoid surprising retrieval costs (especially for large restores).Batch small files to minimize request count and extra overhead.",{"id":34,"title":30,"titles":566,"content":567,"level":185},[],"A detailed comparison of AWS S3 Standard and Glacier storage classes, including use cases, cost considerations, and lifecycle management tips.",{"id":569,"title":296,"titles":570,"content":296,"level":185},"/blog/choosing-between-s3-standard-vs-glacier-copy#",[],{"id":572,"title":509,"titles":573,"content":296,"level":191},"/blog/choosing-between-s3-standard-vs-glacier-copy#overview-s3-standard-vs-glacier-storage-classes",[296],{"id":575,"title":513,"titles":576,"content":515,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#s3-standard-and-standardia",[296,509],{"id":578,"title":518,"titles":579,"content":520,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#glacier-storage-classes-within-s3",[296,509],{"id":581,"title":523,"titles":582,"content":296,"level":191},"/blog/choosing-between-s3-standard-vs-glacier-copy#when-to-use-which",[296],{"id":584,"title":527,"titles":585,"content":529,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#use-s3-standard-or-standardia-when",[296,523],{"id":587,"title":532,"titles":588,"content":534,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#use-glacier-instant-retrieval-when",[296,523],{"id":590,"title":537,"titles":591,"content":539,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#use-glacier-flexible-retrieval-when",[296,523],{"id":593,"title":542,"titles":594,"content":544,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#use-glacier-deep-archive-when",[296,523],{"id":596,"title":547,"titles":597,"content":549,"level":191},"/blog/choosing-between-s3-standard-vs-glacier-copy#️-lifecycle-cost-considerations",[296],{"id":599,"title":552,"titles":600,"content":554,"level":191},"/blog/choosing-between-s3-standard-vs-glacier-copy#quick-usecase-scenarios",[296],{"id":602,"title":557,"titles":603,"content":559,"level":191},"/blog/choosing-between-s3-standard-vs-glacier-copy#summary-comparison",[296],{"id":605,"title":562,"titles":606,"content":564,"level":207},"/blog/choosing-between-s3-standard-vs-glacier-copy#️-recommendation-tips",[296,557],{"id":38,"title":37,"titles":608,"content":609,"level":185},[],"Learn how to display announcement & temporary information banner simultaneously on your website You'll see some Vue code but this can be used with any Javascript framework or plain HTML/CSS/JS",{"id":611,"title":612,"titles":613,"content":614,"level":191},"/blog/clever-way-to-show-two-banners#requirement","Requirement",[37],"In production app, We can have announcement/promotional banners like 50% off. Apart from this we may want to show temporary information banner like admin is impersonating a user. Issue arise when we already have announcement/promotion banner & we want to show high priority app related banner. It won't be ideal to show both banner one after another as it can take up too many space and doesn't look good either. First think that come to your mind is preserving previous banner's state in browser's cookie or localStorage and perform some magic calculation to render content accordingly. But this can get complex quickly. Instead, I come up with simple solution, Render both but stack banners and show banner with higher priority on top. This is how I did in my Nuxt Boilerplate: \u003Cscript lang=\"ts\" setup>\n// Hold state if admin is impersonating user\nconst userStore = useUserStore()\n\n// Announcement/Promotional global banner store\nconst bannerStore = useBannerStore()\n\u003C/script>\n\n\u003Ctemplate>\n  \u003C!--\n      Clever way to show existing banner and impersonating banner without adding complexity\n      Instead of adding complexity create two separate banner and make impersonating banner overlap existing one\n\n      This will hide main banner behind impersonating when admin is impersonating user.\n      When admin stop impersonating underlying banner will appear without managing any dynamic state & storing main banner state it in cookie.\n\n      Also as we added v-if for userSession to impersonating banner, it automatically gets removed when admin sign out while impersonating.\n    -->\n  \u003CUserImpersonatingBanner\n    v-if=\"userStore.userSession?.impersonatedBy\"\n    class=\"sticky top-0\"\n  />\n  \u003CUBanner\n    v-else-if=\"bannerStore.props.title\"\n    v-bind=\"bannerStore.props\"\n    class=\"sticky top-0\"\n  />\n\u003C/template> In a post snippet, if you notice, both are stacked on top of each other. Due to order of elements impersonating banner will be on top of existing banner when admin is impersonating user. When admin stop impersonating, underlying banner will appear automatically. Excellence is, It'll auto handle the case where if admin sign out while impersonating user, impersonating banner will be removed from DOM automatically due to v-if condition and underlying banner will appear without any additional logic. Here's demo of how it looks in action: html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sY_PY, html code.shiki .sY_PY{--shiki-default:#50FA7B;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":42,"title":41,"titles":616,"content":617,"level":185},[],"A curated list of common FastAPI interview questions and answers.",{"id":619,"title":620,"titles":621,"content":622,"level":191},"/blog/collection-of-fastapi-interview-questions#q-what-is-fastapi-and-how-does-it-differ-from-other-web-frameworks-in-python","Q: What is FastAPI, and how does it differ from other web frameworks in Python?",[41],"FastAPI is a modern, high-performance web framework for building APIs with Python 3.7+ based on standard Python type hints. It has quickly gained popularity due to its speed, simplicity, and developer-friendly features. Some of the key features and differences from other web frameworks in FastAPI are: Automatic Documentation: FastAPI generates interactive API documentation automatically using the OpenAPI standard1.Python Type Hints: FastAPI uses Python-type hints for automatic data validation, improving code readability and enabling automatic validation of incoming data1.Data Validation: FastAPI uses Pydantic models for data validation1.Asynchronous Support: FastAPI fully embraces asynchronous operations, allowing you to use Python’s async and await keywords to write asynchronous endpoints1.Dependency Injection: FastAPI supports dependency injection, allowing you to declare dependencies for your endpoints1.Security Features: FastAPI includes various security features out of the box, such as support for OAuth2, JWT (JSON Web Tokens), and automatic validation of request data1.",{"id":624,"title":625,"titles":626,"content":627,"level":191},"/blog/collection-of-fastapi-interview-questions#q-explain-dependency-injection-in-fastapi-and-its-benefits","Q: Explain dependency injection in FastAPI and its benefits",[41],"As the name suggest, dependency injection allow FastAPI route to declare things that is needed to run or enhance the route functionality. Common uses of dependencies include sharing connections to databases, implementing authorization and authentication, debugging and monitoring, and injecting configuration settings. A dependency provider is a function or class responsible for supplying dependencies to FastAPI route. FastAPI also support nested or sub dependencies. Here are some benefits of using Dependency Injection in FastAPI: Isolation of Code: Dependencies enable the isolation of code by passing them to route, rather than instantiating them directly. This isolation enhances code reusability, maintainability, and testability.Improved Code Readability: Dependencies make code more readable by encapsulating complex logic and clearly specifying the requirements of dependent functions and classes.Reduced Code Duplication: Dependencies minimize code duplication by facilitating the sharing of dependencies among different routes.Facilitated Testing: Dependencies simplify the testing of code by allowing the simulation of dependencies in tests, making it easier to verify the functionality of the code.",{"id":629,"title":630,"titles":631,"content":632,"level":191},"/blog/collection-of-fastapi-interview-questions#q-how-does-fastapi-handle-asynchronous-programming-and-what-are-the-advantages","Q: How does FastAPI handle asynchronous programming, and what are the advantages?",[41],"FastAPI is designed with built-in support for asynchronous programming. It allows you to use Python's async and await keywords to write asynchronous endpoints. This is particularly useful for handling I/O-bound tasks and improving the overall responsiveness of your application. Here are some advantages of asynchronous programming in FastAPI: Efficient Use of Server Resources: Using async in FastAPI leads to more efficient use of server resources, as the server can handle other tasks while waiting for I/O operations to complete.Faster Response Times: This efficiency translates into faster response times for applications.Handling Multiple Requests: By using asynchronous programming, FastAPI is able to handle multiple requests at the same time without blocking. This means the application can continue doing other work while it’s waiting for the response from an external API, leading to more efficient use of resources and faster response times.Well-suited for Real-time Applications: This asynchronous support is particularly valuable for real-time applications, such as chat applications, IoT devices, or systems that demand rapid data processing. Overall, the asynchronous capabilities of FastAPI allow developers to create low-latency and scalable web applications that can handle concurrent requests efficiently5.",{"id":634,"title":635,"titles":636,"content":637,"level":191},"/blog/collection-of-fastapi-interview-questions#q-explain-the-purpose-of-pydantic-in-a-fastapi-project","Q: Explain the purpose of Pydantic in a FastAPI project",[41],"Pydantic is a Python library designed for data validation and parsing. It offers a straightforward way to define data models and ensures that incoming data adheres to those models. This validation and parsing process simplifies the task of guaranteeing that your application receives data in the correct format, promoting robustness and data integrity. FastAPI seamlessly incorporates Pydantic models to carry out request and response validation, a critical aspect of any API development. This integration equips you with the means to ensure the safety and reliability of API.",{"id":639,"title":640,"titles":641,"content":642,"level":191},"/blog/collection-of-fastapi-interview-questions#q-how-can-you-handle-authentication-and-authorization-in-a-fastapi-application","Q: How can you handle authentication and authorization in a FastAPI application",[41],"FastAPI provides built-in support for handling user authentication and authorization, using JSON Web Tokens (JWT). This support is implemented through the OAuth2 specification, which is an open standard for authorization. FastAPI also supports OAuth2 scopes, which allow you to limit what each token can do. Moreover, FastAPI provides built in helper dependencies like OAuth2PasswordRequestForm to handle authentication seamlessly. Here are the general steps involved in setting up authentication and authorization in FastAPI: Password Hashing: Before storing a user’s password in your database, you should hash the password. This adds a layer of security and ensures that user passwords remain confidential.Token Creation: When a user logs in with their username and password, you should verify their credentials and then create a JWT token that can be used for subsequent requests.Token Verification: With each request, the user should send their token (usually in the Authorization header). You should verify the token and check if the user is allowed to perform the requested operation.Dependency Injection: FastAPI uses dependency injection to handle authentication. You can declare dependencies in your path operation functions to enforce that the user must be authenticated to access certain routes.OAuth2 Scopes: FastAPI allows you to declare scopes in your routes, like Depends(get_current_user, scopes=\"items:read\"). Scopes let you protect data and limit what each token can do, providing a way to authorize the user.",{"id":644,"title":645,"titles":646,"content":647,"level":191},"/blog/collection-of-fastapi-interview-questions#q-explain-how-fastapi-handles-background-tasks","Q: Explain how FastAPI handles background tasks",[41],"FastAPI provides a way to run time-consuming tasks asynchronously in the background of a FastAPI web application. These tasks are defined as functions that run after being triggered by the main application. Here’s how FastAPI handles background tasks: Define a Task Function: Create a function to be run as the background task. It can be a regular def function or an async def function.Add the Background Task: Inside your path operation function, pass your task function to the BackgroundTasks object with the method .add_task(). This schedules the task to be run in the background after the response is sent.Return a Response: The path operation function can then return a response to the client. The background task will continue to run in the background. Here’s a simple example: from fastapi import BackgroundTasks, FastAPI\n\napp = FastAPI()\n\ndef write_notification(email: str, message=\"\"):\n    with open(\"log.txt\", mode=\"w\") as email_file:\n        content = f\"notification for {email}: {message}\"\n        email_file.write(content)\n\n@app.post(\"/send-notification/{email}\")\nasync def send_notification(email: str, background_tasks: BackgroundTasks):\n    background_tasks.add_task(write_notification, email, message=\"some notification\") // [!code hl]\n    return {\"message\": \"Notification sent in the background\"} In this example, the write_notification function is a task that writes a notification to a file. This task is added to the background_tasks in the send_notification path operation function. The send_notification function then returns a response, and the write_notification task continues to run in the background. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":46,"title":45,"titles":649,"content":650,"level":185},[],"A curated list of common Python interview questions and answers.",{"id":652,"title":653,"titles":654,"content":655,"level":191},"/blog/collection-of-python-interview-questions#q-static-vs-class-methods","Q: Static vs Class methods?",[45],"Static methods and class methods are both methods that are associated with a class rather than an instance of the class. Static Method:A static method is a method that belongs to the class rather than an instance of the class.It is defined using the @staticmethod decorator.It does not have access to the instance or its attributes.It is called on the class, not on an instance of the class.class MyClass:\n    @staticmethod\n    def my_static_method():\n        # Code for static method\nClass Method:A class method is a method that takes the class itself as its first argument.It is defined using the @classmethod decorator.It has access to the class and its attributes, but not to the instance.It is called on the class, not on an instance of the class.class MyClass:\n    @classmethod\n    def my_class_method(cls):\n        # Code for class method In summary, static methods are independent of class instances and class methods have access to the class itself.",{"id":657,"title":658,"titles":659,"content":660,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-mro","Q: What is MRO?",[45],"MRO stands for Method Resolution Order in Python. It defines the order in which classes are searched when looking for a method in the inheritance hierarchy. The MRO plays a crucial role in multiple inheritance scenarios. In Python, the C3 linearization algorithm is used to determine the MRO. The MRO is calculated based on the following principles: Depth-First Search:The MRO starts with the derived class and then follows the chain of base classes in a depth-first manner.Left-to-Right:In case of multiple inheritance (a class inheriting from more than one class), the MRO follows a left-to-right order as specified in the class definition. For example: class A:\n    pass\n\nclass B(A):\n    pass\n\nclass C(A):\n    pass\n\nclass D(B, C):\n    pass\n\n# MRO for class D: D -> B -> C -> A\nprint(D.mro()) In the example above, the MRO for class D is [D, B, C, A]. This means that when looking for a method in class D, it will first check in D, then in B, then in C, and finally in A. Understanding the MRO is essential for resolving method and attribute lookup in complex class hierarchies.",{"id":662,"title":663,"titles":664,"content":665,"level":191},"/blog/collection-of-python-interview-questions#q-python-module-vs-package","Q: Python module vs package?",[45],"In Python, both modules and packages are organizational units for code, but they serve different purposes. Module:A module is a single Python file that contains code, functions, and variables.It is a way to organize related code into a file to make it reusable and maintainable.You can create a module by saving a Python script with a .py extension.Example of a module (my_module.py):# my_module.py\ndef my_function():\n    print(\"Hello from my function in my_module\")\nYou can then use this module in another script:# main_script.py\nimport my_module\n\nmy_module.my_function()\nPackage:A package is a way of organizing related modules into a directory hierarchy.It contains a special file called __init__.py to indicate that the directory should be treated as a package.Packages help in organizing larger codebases and avoid naming conflicts.Example of a package:my_package/\n├── __init__.py\n├── module1.py\n└── module2.py\nContents of module1.py:# module1.py\ndef function1():\n    print(\"Function 1 from module1\")\nContents of module2.py:# module2.py\ndef function2():\n    print(\"Function 2 from module2\")\nYou can then use these modules within the package:# main_script.py\nfrom my_package import module1, module2\n\nmodule1.function1()\nmodule2.function2() In summary, a module is a single file containing Python code, while a package is a collection of related modules organized in a directory hierarchy. The __init__.py file distinguishes a directory as a package.",{"id":667,"title":668,"titles":669,"content":670,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-the-purpose-of-a-single-underscore-variable-in-python","Q: What is the purpose of a single underscore variable in Python?",[45],"In Python, a single underscore (_) has a specific purpose and meaning, but it can be used in different contexts. Here are some common use cases: Unused Variable:A single underscore is often used as a variable name when the variable is intentionally not going to be used. This convention is a way to indicate to other programmers (and to tools like linters) that the variable is intentionally ignored.# Unused variable\n_ = my_function_that_returns_a_value()\nLast Result in the Interpreter:In an interactive Python session (like the Python REPL or IPython), the single underscore _ is automatically assigned to the result of the last expression.>>> 2 + 3\n5\n>>> _  # Represents the result of the last expression (5)\n5\nInternationalization (gettext):In the context of internationalization and localization (using the gettext module), the single underscore is often used as a shorthand for marking strings for translation.from gettext import gettext as _\n\nmessage = _(\"This is a translatable string\")\nHowever, in practice, double underscores are more commonly used for this purpose. It's important to note that using a single underscore as a variable name is a convention and not a strict rule enforced by the Python interpreter. Programmers use it to convey specific meanings in their code.",{"id":672,"title":673,"titles":674,"content":675,"level":191},"/blog/collection-of-python-interview-questions#q-__init__-vs-__new__-methods-in-python","Q: __init__ vs __new__ methods in Python?",[45],"In Python, both __init__ and __new__ are special methods, but they serve different purposes in the object creation process. __new__ Method:The __new__ method is responsible for creating a new instance of a class.It is a static method (a method bound to the class and not the instance) and is called before the __init__ method.The primary purpose of __new__ is to create and return a new instance of the class. It takes the class as its first argument, followed by any additional arguments that were passed to the class constructor.If __new__ is not defined in a class, it defaults to the object.__new__ method, which creates a new instance of the class.Example:class MyClass:\n    def __new__(cls, *args, **kwargs):\n        # Custom logic for creating a new instance\n        instance = super().__new__(cls)\n        # Additional initialization can be done here if needed\n        return instance\n__init__ Method:The __init__ method is responsible for initializing the attributes of an instance after it has been created by __new__.It is an instance method and takes the newly created instance (self) along with any additional arguments that were passed to the class constructor.The primary purpose of __init__ is to set up the initial state of the object, assigning values to attributes or performing other initialization tasks.Example:class MyClass:\n    def __init__(self, arg1, arg2):\n        # Initialization logic\n        self.arg1 = arg1\n        self.arg2 = arg2 In summary, __new__ is responsible for creating a new instance of the class, and __init__ is responsible for initializing the attributes of that instance. In most cases, you'll only need to define the __init__ method unless you have specific requirements for customizing the object creation process.",{"id":677,"title":678,"titles":679,"content":680,"level":191},"/blog/collection-of-python-interview-questions#q-why-are-full-values-shared-between-two-objects","Q: Why are full values shared between two objects?",[45],"In Python, when two objects share the same values, it is usually because they are referencing the same object in memory, not because the values themselves are shared. This behavior is a result of how Python handles certain types of objects, especially immutable objects. Let's distinguish between mutable and immutable objects: Mutable Objects:Objects whose state can be changed after creation are mutable.Examples include lists, dictionaries, and custom objects.list1 = [1, 2, 3]\nlist2 = list1  # Both list1 and list2 reference the same list object\nIn this case, if you modify list1, list2 will reflect the changes because they point to the same list object.Immutable Objects:Objects whose state cannot be changed after creation are immutable.Examples include strings, tuples, and numeric types.string1 = \"hello\"\nstring2 = string1  # Both string1 and string2 reference the same string object\nAlthough strings are immutable, the reference (string2) is shared, and both variables point to the same string object. This behavior is more evident with immutable objects because modifying their values actually creates new objects. It's important to understand that this sharing of values is specific to certain types of objects and does not apply universally across all types in Python. If you want to create a new object with the same values but independent of the original object, you can use techniques like slicing (for sequences) or the copy module. # Creating a new list with the same values\nnew_list = list(original_list) In summary, the sharing of values between two objects in Python typically occurs when they reference the same mutable or immutable object in memory. Understanding the mutability or immutability of objects helps in grasping this behavior.",{"id":682,"title":683,"titles":684,"content":685,"level":191},"/blog/collection-of-python-interview-questions#q-explain-pythons-garbage-collection-mechanism","Q: Explain Python's garbage collection mechanism",[45],"Python's garbage collection manages memory by using reference counting and a cyclic garbage collector for handling circular references. The gc module provides manual control over garbage collection, with gc.collect() triggering the process. CPython, the main Python implementation, employs a generational garbage collector. While automatic garbage collection is efficient, programmers should be aware of potential memory issues.",{"id":687,"title":688,"titles":689,"content":690,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-the-global-interpreter-lock-and-why-is-it-an-issue-with-an-example","Q: What is the global interpreter lock and why is it an issue with an example?",[45],"The Global Interpreter Lock (GIL) is a mutex (or lock) that protects access to Python objects, preventing multiple native threads from executing Python bytecodes at once. In other words, it allows only one thread to execute Python bytecode in the interpreter at any given time, even on multi-core systems. Why the GIL is an Issue: Concurrency Limitation:The GIL limits the execution of multiple threads simultaneously, impacting the ability to take full advantage of multi-core processors.It becomes a bottleneck for CPU-bound and multithreaded applications as it restricts parallel execution.Performance Implications:Although Python supports threading, due to the GIL, threads are not as effective for parallelizing CPU-bound tasks.The GIL doesn't hinder performance for I/O-bound tasks (tasks waiting for external resources) as much because the lock is released during I/O operations. Example: Consider a simple CPU-bound task that calculates the square of each element in a list using multiple threads: import threading\n\ndef square_numbers(numbers):\n    global result\n    for number in numbers:\n        result.append(number * number)\n\nresult = []\nnumbers = [1, 2, 3, 4, 5]\n\n# Create two threads to square the numbers concurrently\nthread1 = threading.Thread(target=square_numbers, args=(numbers,))\nthread2 = threading.Thread(target=square_numbers, args=(numbers,))\n\n# Start the threads\nthread1.start()\nthread2.start()\n\n# Wait for both threads to finish\nthread1.join()\nthread2.join()\n\nprint(result) Due to the GIL, the threads will not execute concurrently when performing the CPU-bound task. As a result, the performance improvement typically associated with multithreading in other languages may not be realized in this Python example. It's important to note that the GIL is specific to the CPython interpreter, and other Python implementations like Jython or IronPython do not have a GIL. Additionally, for I/O-bound tasks, asynchronous programming using asyncio and async/await can be a more effective approach than traditional multithreading.",{"id":692,"title":693,"titles":694,"content":695,"level":191},"/blog/collection-of-python-interview-questions#q-what-are-iterators","Q: What are iterators?",[45],"In Python, an iterator is an object that implements the iterator protocol, which consists of the methods __iter__() and __next__() (or __iter__() and __getitem__() for older-style iterators). Iterators are used to represent a stream of data and facilitate iteration over elements in a sequence, container, or collection. They allow you to loop over a set of values, one at a time, without having to know the underlying details of the data structure. Here are the key components of iterators: __iter__() Method:The __iter__() method returns the iterator object itself.It is called when you use the iter() function on an object.__next__() Method:The __next__() method returns the next element from the iterator.It is called when you use the next() function on an iterator.StopIteration Exception:When there are no more elements to return, the __next__() method should raise the StopIteration exception to signal the end of the iteration.Iterable:An iterable is an object that can be iterated over, and it typically implements the __iter__() method.Iterables may or may not be iterators themselves. Example of a simple iterator: class MyIterator:\n    def __init__(self, data):\n        self.data = data\n        self.index = 0\n\n    def __iter__(self):\n        return self\n\n    def __next__(self):\n        if self.index \u003C len(self.data):\n            result = self.data[self.index]\n            self.index += 1\n            return result\n        else:\n            raise StopIteration\n\n# Using the iterator\nmy_list = [1, 2, 3, 4, 5]\nmy_iterator = MyIterator(my_list)\n\nfor item in my_iterator:\n    print(item) In this example, MyIterator is an iterator for a list. The __iter__ method returns the iterator object (self), and the __next__ method returns the next element from the list until there are no more elements to return. In practice, many Python objects, such as lists, tuples, dictionaries, and strings, are iterable and can be used directly in for loops. Iterators provide a way to customize the iteration behavior for your own objects.",{"id":697,"title":698,"titles":699,"content":700,"level":191},"/blog/collection-of-python-interview-questions#q-what-do-you-understand-about-generators-in-python","Q: What do you understand about generators in Python?",[45],"Generators in Python are a way to create iterators using a special kind of function. They allow you to iterate over a potentially large sequence of data without creating the entire sequence in memory, which is particularly useful for working with large datasets or infinite sequences. Key characteristics of generators: Function with yield:Generators are created using functions that contain the yield keyword.When a generator function is called, it returns an iterator but does not start executing immediately. The function is paused at the yield statement.Lazy Evaluation:Values are generated one at a time and are only computed when requested.This is in contrast to normal functions that compute and return the entire result at once.Stateful Execution:The generator function retains its local state between successive calls.When the generator is resumed after a call to yield, it continues execution from where it was paused.Example:def simple_generator():\n    yield 1\n    yield 2\n    yield 3\n\n# Using the generator\nmy_generator = simple_generator()\nprint(next(my_generator))  # Output: 1\nprint(next(my_generator))  # Output: 2\nprint(next(my_generator))  # Output: 3\nInfinite Sequences:Generators can represent infinite sequences, such as counting numbers or a stream of data, without consuming infinite memory.def infinite_counter():\n    count = 0\n    while True:\n        yield count\n        count += 1\n\n# Using the infinite counter\ncounter = infinite_counter()\nprint(next(counter))  # Output: 0\nprint(next(counter))  # Output: 1\n# ... Generators are particularly useful when dealing with large datasets, streaming data, or when the entire sequence is not needed at once. They provide a memory-efficient and elegant way to work with sequences in Python. Additionally, the yield keyword allows generators to maintain their state between calls, making them suitable for scenarios where maintaining state is important.",{"id":702,"title":703,"titles":704,"content":705,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-a-monkey-patching-in-python","Q: What is a monkey patching in Python?",[45],"Monkey patching in Python refers to the dynamic modification of a module or class during runtime. It involves altering or extending the behavior of a module or class, typically for the purpose of fixing bugs, adding new features, or modifying existing functionality. Monkey patching is powerful but should be used judiciously, as it can lead to code that is harder to understand and maintain. Key points about monkey patching: Dynamic Modification:Monkey patching involves making changes to code at runtime, often by directly modifying the attributes or methods of classes or objects.Common Use Cases:Fixing Bugs: Monkey patching can be used to fix bugs in third-party libraries or modules without modifying their source code.Adding Functionality: It allows developers to add new functionality to existing classes or modules without subclassing.Testing: Monkey patching is sometimes used in testing to replace or mock certain behaviors temporarily.Example:# Original class\nclass MyClass:\n    def original_method(self):\n        return \"Original method\"\n\n# Monkey patching: Adding a new method\ndef new_method(self):\n    return \"Patched method\"\n\nMyClass.patched_method = new_method\n\n# Using the modified class\nobj = MyClass()\nprint(obj.original_method())  # Output: Original method\nprint(obj.patched_method())   # Output: Patched method\nConsiderations:Monkey patching can lead to code that is harder to understand and maintain, as it introduces changes outside the regular development process.It can cause compatibility issues with future versions of the patched code or with other modules that interact with it.It's important to document and communicate the use of monkey patching in a codebase to ensure that other developers are aware of the modifications. While monkey patching can be a powerful tool, it's generally recommended to use it with caution and explore other alternatives such as subclassing, decorators, or more structured ways of extending or modifying functionality, especially when working on larger projects or collaborating with other developers.",{"id":707,"title":708,"titles":709,"content":710,"level":191},"/blog/collection-of-python-interview-questions#q-whats-the-difference-between-deep-and-shallow-copy-in-python","Q: What’s the difference between deep and shallow copy in Python?",[45],"In Python, the concepts of shallow copy and deep copy refer to creating copies of objects, particularly complex objects like lists or dictionaries. The distinction lies in how nested objects within the original are handled. Shallow Copy:A shallow copy creates a new object but does not create copies of nested objects. Instead, it copies references to the nested objects.Changes made to the nested objects will be reflected in both the original and the shallow copy.In Python, you can use the copy module's copy() function or the object's own copy() method to create a shallow copy.import copy\n\noriginal_list = [1, [2, 3], 4]\n\n# Using copy() method for shallow copy\nshallow_copy_list = original_list.copy()\n\n# or using copy() function\nshallow_copy_list = copy.copy(original_list)\nDeep Copy:A deep copy creates a new object and recursively creates copies of all nested objects, ensuring that changes in nested objects do not affect the original or other copies.In Python, you can use the copy module's deepcopy() function to create a deep copy.import copy\n\noriginal_list = [1, [2, 3], 4]\n\n# Using deepcopy() function for deep copy\ndeep_copy_list = copy.deepcopy(original_list) Example: import copy\n\noriginal_list = [1, [2, 3], 4]\n\n# Shallow copy\nshallow_copy_list = copy.copy(original_list)\n\n# Deep copy\ndeep_copy_list = copy.deepcopy(original_list)\n\n# Modify the nested list\noriginal_list[1][0] = 99\n\n# Changes are reflected in shallow copy but not in deep copy\nprint(original_list)      # Output: [1, [99, 3], 4]\nprint(shallow_copy_list)  # Output: [1, [99, 3], 4]\nprint(deep_copy_list)     # Output: [1, [2, 3], 4] In the example, modifying the nested list in the original list affects the shallow copy, but the deep copy remains unchanged. In summary, the key difference is how nested objects are treated. Shallow copy creates new objects but copies references to nested objects, while deep copy creates new objects and recursively copies all nested objects, ensuring complete independence between the original and the copy.",{"id":712,"title":713,"titles":714,"content":715,"level":191},"/blog/collection-of-python-interview-questions#q-how-will-you-define-polymorphism-in-python","Q: How will you define polymorphism in Python?",[45],"Polymorphism in Python refers to the ability of different objects to be treated as instances of a common type. It allows objects of different classes to be used interchangeably based on their common interface, methods, or attributes. There are two main types of polymorphism in Python: Compile-Time Polymorphism (Static Binding):Also known as method overloading.It involves defining multiple methods in a class with the same name but different parameter types or a different number of parameters.The correct method is selected during compilation based on the method signature.class MathOperations:\n    def add(self, x, y):\n        return x + y\n\n    def add(self, x, y, z):\n        return x + y + z\n\nmath_obj = MathOperations()\nresult1 = math_obj.add(2, 3)      # Calls the first add method\nresult2 = math_obj.add(2, 3, 4)   # Calls the second add method\nRun-Time Polymorphism (Dynamic Binding):Also known as method overriding.It involves defining a method in the subclass that already exists in the superclass.The correct method is selected during runtime based on the actual type of the object.class Animal:\n    def sound(self):\n        pass\n\nclass Dog(Animal):\n    def sound(self):\n        return \"Woof!\"\n\nclass Cat(Animal):\n    def sound(self):\n        return \"Meow!\"\n\n# Polymorphic behavior\ndef make_sound(animal):\n    return animal.sound()\n\ndog = Dog()\ncat = Cat()\n\nprint(make_sound(dog))  # Output: Woof!\nprint(make_sound(cat))  # Output: Meow! In the example, the make_sound function takes any object of type Animal and calls its sound method. The correct sound method is determined at runtime based on the actual type of the object passed. Polymorphism enhances code flexibility and readability by allowing objects of different types to be treated uniformly through a common interface. It is a fundamental concept in object-oriented programming that supports code reuse and extensibility.",{"id":717,"title":718,"titles":719,"content":720,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-a-closure-in-python","Q: What is a closure in Python?",[45],"In Python, a closure is a function object that has access to variables in its lexical scope, even when the function is called outside that scope. This means that a closure can \"close over\" variables from its outer function, retaining access to those variables even after the outer function has finished execution. Key characteristics of closures: Nested Function:A closure involves a nested function (a function defined within another function).Access to Outer Function's Variables:The inner function has access to the variables of its outer (enclosing) function, even after the outer function has completed execution.Immutable Closure:Closures capture variables by reference, not by value. This means if the enclosed variables are mutable, changes to them will be reflected in the closure. However, reassignment of the variable within the outer function does not affect the closure. Example of a Closure: def outer_function(x):\n    def inner_function(y):\n        return x + y\n    return inner_function\n\nclosure_instance = outer_function(10)\nresult = closure_instance(5)\nprint(result)  # Output: 15 In this example, inner_function is a closure because it has access to the x variable from its outer function, outer_function, even though outer_function has already finished execution. When closure_instance is called with 5, it adds 5 to the captured value of x (10), resulting in 15. Closures are often used to create functions with behavior dependent on some initial setup or configuration. They provide a way to achieve data encapsulation and help manage the scope of variables in a clean and modular way.",{"id":722,"title":723,"titles":724,"content":725,"level":191},"/blog/collection-of-python-interview-questions#q-explain-multithreading-in-python","Q: Explain multithreading in Python",[45],"Multithreading in Python involves the concurrent execution of multiple threads within a single process. Threads are lightweight sub-processes that share the same memory space, allowing for parallel execution of tasks. Python provides a built-in threading module for working with threads. However, it's important to note that due to the Global Interpreter Lock (GIL) in the standard CPython implementation, true parallel execution of threads is limited. The GIL allows only one thread to execute Python bytecode at a time, impacting the parallelism benefits of multiple threads, especially in CPU-bound tasks. For I/O-bound tasks, multithreading can still provide advantages. Here's an overview of multithreading in Python using the threading module: Creating Threads:Threads are created by instantiating the Thread class from the threading module.You can define a target function that the thread will execute.import threading\n\ndef my_function():\n    # Code to be executed by the thread\n\nmy_thread = threading.Thread(target=my_function)\nStarting Threads:Threads are started by calling the start() method on the thread object.The start() method initiates the execution of the target function in a separate thread.my_thread.start()\nJoining Threads:The join() method is used to wait for the thread to complete its execution before proceeding further in the main thread.my_thread.join()\nThread Safety:Thread safety is crucial when working with shared resources. It involves using locks or other synchronization mechanisms to prevent race conditions and ensure data consistency.Thread Pools:Python provides the concurrent.futures module for working with thread pools, allowing you to submit tasks to a pool of threads.from concurrent.futures import ThreadPoolExecutor\n\nwith ThreadPoolExecutor() as executor:\n    results = executor.map(my_function, iterable_of_arguments) Multithreading in Python is particularly beneficial for I/O-bound tasks where threads can execute independently, waiting for external resources, such as network or file I/O. For CPU-bound tasks, alternative approaches like multiprocessing or asynchronous programming may be more suitable due to the limitations imposed by the GIL.",{"id":727,"title":728,"titles":729,"content":730,"level":191},"/blog/collection-of-python-interview-questions#q-asyncio-vs-multithreading","Q: asyncio vs multithreading?",[45],"Concurrency Model:Asyncio is a single-threaded, single-process design. It uses coroutines and an event loop to manage tasks.Multithreading involves multiple threads of execution within a single process. Each thread runs independently and can execute different parts of your program concurrently.Use Cases:Asyncio is ideal for I/O-bound tasks, especially when you're dealing with many connections and each connection doesn't need to do much work.Multithreading is beneficial for CPU-bound tasks and can also be used for I/O-bound tasks if the number of connections is limited.Performance:Asyncio can handle many open connections concurrently, making it suitable for building high-performance network servers.Multithreading can become less efficient with a large number of threads due to the overhead of context switching.Ease of Use:Asyncio can be more complex to understand and implement due to its asynchronous nature.Multithreading can be easier to understand and implement, but it can be prone to synchronization issues. Here's a simple example to illustrate the difference: # Multithreading Example\nimport threading\nimport time\n\ndef print_nums():\n    for i in range(5):\n        time.sleep(1)\n        print(i)\n\ndef print_hello():\n    for _ in range(5):\n        time.sleep(1)\n        print(\"Hello\")\n\nt1 = threading.Thread(target=print_nums)\nt2 = threading.Thread(target=print_hello)\n\nt1.start()\nt2.start() # Asyncio Example\nimport asyncio\n\nasync def print_nums():\n    for i in range(5):\n        await asyncio.sleep(1)\n        print(i)\n\nasync def print_hello():\n    for _ in range(5):\n        await asyncio.sleep(1)\n        print(\"Hello\")\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(asyncio.gather(print_nums(), print_hello()))\nloop.close() In both examples, print_nums and print_hello run concurrently. However, the multithreading example uses threads, while the asyncio example uses coroutines¹².",{"id":732,"title":733,"titles":734,"content":735,"level":191},"/blog/collection-of-python-interview-questions#q-explain-with-statement-in-python","Q: Explain with statement in python",[45],"The with statement in Python is used to simplify resource management by providing a convenient way to acquire and release resources, such as files, sockets, or locks. It ensures that certain operations are properly set up and cleaned up, even if an exception occurs during the execution of the block of code. The general syntax of the with statement is as follows: with expression [as variable]:\n    # Code block Here's how the with statement works: Acquiring Resources:The expression following the with keyword is expected to return a context manager object. A context manager is an object that defines the methods __enter__() and __exit__().with open(\"example.txt\", \"r\") as file:\n    # Code to read from the file\nIn this example, the open() function returns a file object, which acts as a context manager. The file is automatically opened when entering the with block.Code Execution:The indented code block following the with statement is executed. This block represents the body of the with statement and is where you work with the acquired resources.with open(\"example.txt\", \"r\") as file:\n    content = file.read()\n    # Code to process the file content\nAutomatic Cleanup:After the code block is executed, the __exit__() method of the context manager is called. This method is responsible for releasing or cleaning up any resources acquired in the __enter__() method.with open(\"example.txt\", \"r\") as file:\n    content = file.read()\n    # Code to process the file content\n\n# The file is automatically closed at this point, regardless of whether an exception occurred.\nHandling Exceptions:The with statement also handles exceptions that may occur within the code block. If an exception occurs, the __exit__() method is still called, allowing for proper cleanup.try:\n    with open(\"example.txt\", \"r\") as file:\n        content = file.read()\n        # Code that may raise an exception\nexcept SomeException as e:\n    # Exception handling The with statement is particularly useful when working with resources that require explicit setup and cleanup procedures. It enhances code readability and reduces the likelihood of resource leaks by ensuring that cleanup operations are consistently performed, even in the presence of exceptions.",{"id":737,"title":738,"titles":739,"content":740,"level":191},"/blog/collection-of-python-interview-questions#q-explain-decorators-in-python","Q: Explain decorators in python",[45],"In Python, decorators are a powerful and flexible way to modify or extend the behavior of functions or methods. Decorators allow you to wrap a function with additional functionality without changing its source code directly. They are often used for tasks such as logging, memoization, access control, and more. The syntax for using a decorator involves placing the decorator symbol (@decorator_name) above the function definition. The decorator can be a function or a class. Here's a basic overview of how decorators work: Decorator Function:A decorator is a function that takes another function as its argument and returns a new function that usually extends or modifies the behavior of the original function.def my_decorator(func):\n    def wrapper():\n        print(\"Something is happening before the function is called.\")\n        func()\n        print(\"Something is happening after the function is called.\")\n    return wrapper\nApplying the Decorator:Use the @decorator_name syntax to apply a decorator to a function.@my_decorator\ndef say_hello():\n    print(\"Hello!\")\n\nsay_hello()\nThis is equivalent to say_hello = my_decorator(say_hello).Chaining Decorators:You can apply multiple decorators to a single function, and they will be applied in the order they appear.@decorator1\n@decorator2\n@decorator3\ndef my_function():\n    # Function code\nPassing Arguments to Decorators:Decorators can accept arguments, allowing for more flexibility.def parametrized_decorator(param):\n    def decorator(func):\n        def wrapper():\n            print(f\"Decorator parameter: {param}\")\n            func()\n        return wrapper\n    return decorator\n\n@parametrized_decorator(\"some_value\")\ndef my_function():\n    print(\"Hello from my_function!\")\n\nmy_function()\nIn this example, parametrized_decorator is a decorator factory that returns a decorator based on the provided parameter. Decorators are widely used in Python for various purposes, including code organization, code reuse, and aspect-oriented programming. Common use cases include logging, timing, access control, and memoization. Understanding decorators is essential for writing clean and modular code.",{"id":742,"title":743,"titles":744,"content":745,"level":191},"/blog/collection-of-python-interview-questions#q-map-filter-and-reduce-functions","Q: map, filter, and reduce functions",[45],"In functional programming, Python's map, filter, and reduce functions are powerful tools that allow for concise and expressive manipulation of data. Here's a brief overview of each: map Function:Purpose: map applies a given function to all items in an iterable (e.g., a list) and returns a new iterable with the results.Example:numbers = [1, 2, 3, 4, 5]\nsquared_numbers = map(lambda x: x**2, numbers)\nThis will result in squared_numbers containing [1, 4, 9, 16, 25].filter Function:Purpose: filter constructs a list from those elements of the iterable for which a function returns true.Example:numbers = [1, 2, 3, 4, 5]\neven_numbers = filter(lambda x: x % 2 == 0, numbers)\nThis will result in even_numbers containing [2, 4].reduce Function:Purpose: reduce is not a built-in function in Python 3, but it can be imported from the functools module. It successively applies a binary function to the items of an iterable, reducing it to a single accumulated result.Example:from functools import reduce\nnumbers = [1, 2, 3, 4, 5]\nsum_all = reduce(lambda x, y: x + y, numbers)\nThis will result in sum_all containing 15 (the sum of all elements). When discussing these functions in an interview, it's essential to demonstrate not only the syntax but also an understanding of how they fit into functional programming paradigms. Emphasize the immutability of data, the avoidance of side effects, and the benefits of writing more declarative and concise code.",{"id":747,"title":748,"titles":749,"content":750,"level":191},"/blog/collection-of-python-interview-questions#q-monolithic-vs-microservice-architecture","Q: Monolithic vs Microservice architecture",[45],"Monolithic and microservice architectures are two different approaches to structuring applications. Here's a detailed comparison: Monolithic Architecture: A monolithic application is built as a single, unified unit.All functionalities of a project exist in a single codebase.It's often easier to develop and deploy due to its simplicity.It's ideal for smaller, less complex applications or for businesses with limited resources.However, it becomes too large and difficult to manage over time.Any change requires updating the entire stack, making updates restrictive and time-consuming.A single bug in any module can bring down the entire application. Microservice Architecture: A microservices architecture is a collection of smaller, independently deployable services.Each service handles a small portion of the functionality and data.It's more appropriate for larger and more intricate applications that demand greater scalability and flexibility.It allows for the use of different technologies and languages across services.It's failure-resistant and fault-tolerant.However, it's more complex to develop and requires managing inter-service communication.It can also introduce challenges related to data consistency and managing distributed systems. In summary, the choice between monolithic and microservices architectures depends on the specific requirements of your project.",{"id":752,"title":753,"titles":754,"content":755,"level":191},"/blog/collection-of-python-interview-questions#q-mean-median-and-mode-in-python","Q: Mean, Median, and Mode in Python",[45],"Please refer to this.",{"id":757,"title":758,"titles":759,"content":760,"level":191},"/blog/collection-of-python-interview-questions#q-what-is-the-purpose-of-asterisk-forward-slash-in-function-arguments","Q: What is the purpose of asterisk (*) & forward slash (/) in function arguments?",[45],"asterisk (*) and forward slash (/) controls how you pass values to the function. Arguments before / are positional-only.Arguments between / and * can be positional or keyword.Arguments after * are keyword-only. def my_func(position_only, /, positional_or_keyword, *, keyword_only):\n    print(position_only, positional_or_keyword, keyword_only)\n\nmy_func(1, 2, keyword_only=3) # ✅ Valid\nmy_func(1, positional_or_keyword=2, keyword_only=3) # ✅ Valid\n\nmy_func(position_only=1, positional_or_keyword=2, keyword_only=3) # ❌ Invalid\nmy_func(1, 2, 3) # ❌ Invalid You can even use * or / alone in function arguments. def my_func(*, keyword_only):\n    print(keyword_only)\n\ndef my_func(position_only, /):\n    print(position_only) html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}",{"id":50,"title":49,"titles":762,"content":763,"level":185},[],"Learn how to connect to a database in Python using different methods including sqlite3 and SQLAlchemy with context managers.",{"id":765,"title":766,"titles":767,"content":768,"level":191},"/blog/database-connection-in-python#simple-connection","Simple connection",[49],"import sqlite3\n\n# Connect to database\nconn = sqlite3.connect(\"example.db\")\ncursor = conn.cursor()\n\ncursor.execute(\"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)\")\ncursor.execute(\"INSERT INTO users (name) VALUES ('Alice')\")\ncursor.execute(\"SELECT * FROM users\")\nprint(cursor.fetchall())\n\nconn.commit()\nconn.close()",{"id":770,"title":771,"titles":772,"content":773,"level":191},"/blog/database-connection-in-python#using-context-manager","Using context manager",[49],"import sqlite3\nfrom contextlib import contextmanager\n\n# `open_db` context manager\n@contextmanager\ndef open_db(db_name: str):\n    conn = sqlite3.connect(db_name)\n    try:\n        cursor = conn.cursor()\n        yield cursor\n    finally:\n        conn.commit()\n        conn.close()\n\n# Connect to database\nwith open_db(\"example.db\") as cursor:\n    cursor.execute(\"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)\")\n    cursor.execute(\"INSERT INTO users (name) VALUES ('Alice')\")\n    cursor.execute(\"SELECT * FROM users\")\n    print(cursor.fetchall()) class OpenDB:\n    def __init__(self, file_name: str):\n        self.file_name = file_name\n        self.conn = sqlite3.connect(self.file_name)\n\n    def __enter__(self):\n        return self.conn.cursor()\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.conn.commit()\n        self.conn.close()",{"id":775,"title":776,"titles":777,"content":778,"level":191},"/blog/database-connection-in-python#sqlalchemy-context-manager","SQLAlchemy & Context Manager",[49],"from sqlalchemy import create_engine, String\nfrom sqlalchemy.orm import sessionmaker, MappedAsDataclass, DeclarativeBase, Mapped, mapped_column\n\nclass Base(MappedAsDataclass, DeclarativeBase):\n    pass\n\nclass User(Base):\n    __tablename__ = \"users\"\n    id: Mapped[int] = mapped_column(primary_key=True, init=False)\n    name: Mapped[str] = mapped_column(String(30))\n\n# Connect to database\nengine = create_engine(\"sqlite:///example.db\")\nBase.metadata.create_all(engine)\nSession = sessionmaker(bind=engine)\n\n# Execute queries using context manager\nwith Session() as session:\n    session.add(User(name=\"Alice\"))\n    session.commit()\n    users = session.query(User).all()\n    print(users) html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}",{"id":54,"title":53,"titles":780,"content":781,"level":185},[],"A curated list of common database interview questions and answers.",{"id":783,"title":784,"titles":785,"content":786,"level":191},"/blog/database-interview-questions#q-what-is-stored-procedure","Q: What is stored procedure?",[53],"A stored procedure is a prepared SQL code that you can save, so the code can be reused over and over again. So if you have an SQL query that you write over and over again, save it as a stored procedure, and then just call it to execute it.",{"id":788,"title":789,"titles":790,"content":791,"level":191},"/blog/database-interview-questions#q-what-is-a-trigger","Q: What is a trigger?",[53],"A trigger is executed automatically when an event associated with a table occurs. For example, a trigger can be invoked when a row is inserted into a specified table or when certain table columns are being updated. Real life example: You can create default categories in the category table when a new user is added to the user table.",{"id":793,"title":794,"titles":795,"content":796,"level":191},"/blog/database-interview-questions#q-what-is-a-view","Q: What is a view?",[53],"A view is a virtual table based on the result-set of an SQL statement. A view contains rows and columns, just like a real table. Database views are not physically stored in the database, but are generated dynamically based on underlying data tables. They are widely used in query optimization, data access control, and data abstraction.",{"id":798,"title":799,"titles":800,"content":801,"level":191},"/blog/database-interview-questions#q-types-of-keys-in-a-relational-database","Q: Types of keys in a relational database?",[53],"Primary Key: A primary key is a column or a group of columns used to identify a row uniquely in a table. A primary key enforces the entity integrity of the table.Foreign Key: A foreign key is a column or a group of columns in a relational database table that provides a link between data in two tables. It acts as a cross-reference between tables because it references the primary key of another table.Unique Key: A unique key is a set of one or more than one fields/columns of a table that uniquely identify a record in a database table. A unique key is similar to the primary key, and it has the same constraints, but it can have null values.Composite Key: A composite key is a combination of two or more columns in a table that can be used to uniquely identify each row in the table when the columns are combined uniqueness is guaranteed.",{"id":803,"title":804,"titles":805,"content":806,"level":191},"/blog/database-interview-questions#q-what-is-normalization","Q: What is normalization?",[53],"Normalization is a database design technique that reduces data redundancy and eliminates undesirable characteristics like Insertion, Update and Deletion Anomalies. Normalization rules divide larger tables into smaller tables and define relationships between them.",{"id":808,"title":809,"titles":810,"content":811,"level":191},"/blog/database-interview-questions#q-what-is-cte","Q: What is CTE?",[53],"CTE is Common Table Expression which is used to create a temporary result set that can be referenced within a SELECT, INSERT, UPDATE, or DELETE statement. This is now deprecated. You can use the WITH clause to define a CTE.",{"id":813,"title":814,"titles":815,"content":816,"level":191},"/blog/database-interview-questions#q-how-to-optimize-a-sql-query","Q: How to optimize a SQL query?",[53],"Check the blog post on Sql Query Optimization",{"id":58,"title":57,"titles":818,"content":819,"level":185},[],"Learn useful tips and best practices for database design and management.",{"id":821,"title":289,"titles":822,"content":296,"level":191},"/blog/database-my-findings#tips",[57],{"id":824,"title":304,"titles":825,"content":826,"level":207},"/blog/database-my-findings#general",[57,289],"Store datetime in UTC timezone & ISO format and let the client/frontend handle the conversion to the user's timezone.Instead of using is_deleted or is_active boolean columns, use deleted_at or deactivated_at timestamp columns to indicate soft deletion or deactivation. This allows for better tracking of when the record was deleted or deactivated.",{"id":828,"title":829,"titles":830,"content":296,"level":207},"/blog/database-my-findings#sql-query-optimization","SQL Query Optimization",[57,289],{"id":832,"title":833,"titles":834,"content":835,"level":218},"/blog/database-my-findings#_1-query-rewrite","1. Query Rewrite",[57,289,829],"Check if your current query can be rewritten to be more efficient. For example, you can use JOIN instead of subqueries, or use EXISTS instead of IN.",{"id":837,"title":838,"titles":839,"content":840,"level":218},"/blog/database-my-findings#_2-indexing","2. Indexing",[57,289,829],"Make sure you have proper indexes on the columns used in the WHERE clause. Indexes can significantly speed up the query performance.",{"id":842,"title":843,"titles":844,"content":845,"level":218},"/blog/database-my-findings#_3-avoid-select","3. Avoid SELECT *",[57,289,829],"Avoid using SELECT * in your queries. Instead, explicitly list the columns you need. This can reduce the amount of data that needs to be fetched from the database.",{"id":847,"title":848,"titles":849,"content":850,"level":218},"/blog/database-my-findings#_4-limit-the-result-set","4. Limit the Result Set",[57,289,829],"If you only need a subset of the data, use the LIMIT clause to restrict the number of rows returned by the query.",{"id":852,"title":853,"titles":854,"content":855,"level":218},"/blog/database-my-findings#_5-query-caching","5. Query Caching",[57,289,829],"Enable query caching in your database server to cache the results of frequently executed queries. This can reduce the query execution time for subsequent requests.",{"id":857,"title":858,"titles":859,"content":860,"level":207},"/blog/database-my-findings#use-select-1-limit-n-over-count-for-checking-existence","Use SELECT 1 + LIMIT N over COUNT(*) for checking existence",[57,289],"When we only want to check if a record exists and don't want to get the count of records, using SELECT 1 + LIMIT N is more efficient than COUNT(*). Thi is because COUNT(*) has to count all the records, while SELECT 1 + LIMIT N will stop as soon as it finds the first record.",{"id":862,"title":863,"titles":864,"content":865,"level":207},"/blog/database-my-findings#database-design","Database Design",[57,289],"Prefer using deactivated_at or deleted_at timestamp columns instead of is_deleted or is_active for soft deletion. With this you'll be able to track when the record was deleted or deactivated. Only downside is when user wants to restore the record, you have to update the timestamp column to null which seems erasing the history of when it was deleted or deactivated.Add deactivated_at and banned_at timestamp columns to user table instead of account table. This will restrict directly on user where adding to account table won't mak much sense.Prefer database look ups over enums for flexibility. [Reference]",{"id":867,"title":868,"titles":869,"content":870,"level":191},"/blog/database-my-findings#table-definition","Table Definition",[57],"Use singular names for table name (e.g. user, post)Use underscore to separate words for table & column names (e.g. user_id, first_name)Email column should be of length 254Use timestamp for created_at and updated_at columns",{"id":872,"title":873,"titles":874,"content":875,"level":191},"/blog/database-my-findings#recommended-type-length-of-columns","Recommended type & length of columns",[57],"Column NameTypeLengthusernamestring50emailstring254passwordstring128namestring50first_namestring50last_namestring50contact_numberstring15addressstring255zip_codestring10titlestring255",{"id":877,"title":878,"titles":879,"content":880,"level":191},"/blog/database-my-findings#webhook-table-design","Webhook Table Design",[57],"Learn based practices to design tables that sync data from remote data to local database via webhooks or any other mechanism Avoid adding constraints on database columns. Let your data layer have raw data and your application layer handles the validation. For example, instead of using enums for status in your database, prefer text field and at application layer use schema validation libraries like Zod or Pydantic to validate the data. This way you won't miss any webhook data and you will always have data synced to your database and while developing or in production if there's any mismatch between your expectations and the actual received data you can throw error and get alert.You may prefer reading API findings for Webhooks.",{"id":62,"title":61,"titles":882,"content":883,"level":185},[],"Learn useful tips and best practices for using Docker effectively. Net Ninja Playlist for Docker",{"id":885,"title":886,"titles":887,"content":888,"level":191},"/blog/docker-my-findings#commands","🎮 Commands",[61],"# Build an image from a Dockerfile\n# It assumes the Dockerfile is in the current directory (.)\n# `-t` flag is used to tag the image with a name\ndocker build -t \u003Cimage-name> .\n\n# Run a container from an image\n# `--name` flag is used to give a name to the container\n# `-p` flag is used to map a port from the host to the container\n# `-d` flag is used to run the container in detached mode (in the background)\n# docker run -p \u003Chost-port>:\u003Ccontainer-port> -d --name \u003Ccontainer-name> \u003Cimage-name>\ndocker run -p 3000:3000 -d --name my-app my-image\n\ndocker images                             # List all images\ndocker image rm \u003Cimage-id/image-name>     # Remove an image\ndocker rmi \u003Cimage-id/image-name>          # Remove an image\ndocker rmi $(docker images -q)            # Remove all images\n\ndocker ps                                 # List of running containers\ndocker ps -a                              # List all containers\ndocker ps -aq                             # List all container IDs\ndocker start \u003Ccontainer-id/container-name> # Start a container\ndocker start $(docker ps -aq)             # Start all containers\ndocker stop \u003Ccontainer-id/container-name> # Stop a container\ndocker rm \u003Ccontainer-id/container-name>   # Remove a container\ndocker rm $(docker ps -aq)              # Remove all containers\n\ndocker volume ls                          # List all volumes\ndocker volume rm \u003Cvolume-name>            # Remove a volume\ndocker volume rm $(docker volume ls -q)   # Remove all volumes\n\n# Restart the container\n# No need to do port mapping and other configuration we did earlier because it's already done when we ran \"docker run\" command\ndocker start \u003Ccontainer-id/container-name>\n\ndocker compose up     # Run containers defined in `docker-compose.yml` file\ndocker compose down   # Stop and remove containers defined in `docker-compose.yml` file\n\ndocker exec -it \u003Ccontainer-id/container-name> sh # Access the container's shell\ndocker exec -it \u003Ccontainer-id/container-name> -c \"npm install\"  # Run command inside the container",{"id":890,"title":891,"titles":892,"content":296,"level":191},"/blog/docker-my-findings#glossary","Glossary",[61],{"id":894,"title":895,"titles":896,"content":897,"level":207},"/blog/docker-my-findings#images","Images",[61,891],"Like blueprints for containersIt has configuration for creating a container\nWhen we run an image, it creates a containerImages are built using a file called Dockerfile Below is example of a Dockerfile for a Node.js application: # Parent image\nFROM node:20\n\n# Working directory\nWORKDIR /app\n\n# Copy files from host to container\nCOPY ./ ./\n\n# Install dependencies in container\nRUN npm install\n\n# Expose a port (Allows us to access the app from outside the container)\nEXPOSE 5173\n\n# Default command to run when container starts\nCMD [\"npm\", \"run\", \"dev\"] When we copy files from host to container COPY ./ ./, it copies the files from the current directory of the host machine. We can ignore files by adding them to a .dockerignore file just like .gitignore.",{"id":899,"title":900,"titles":901,"content":902,"level":207},"/blog/docker-my-findings#containers","Containers",[61,891],"Runnable instances of images",{"id":904,"title":905,"titles":906,"content":907,"level":207},"/blog/docker-my-findings#layer-caching","Layer Caching",[61,891],"When we build an image, Docker caches each step as a layer\nFor example, It'll cache parent image step, working directory step, copy files step, etc so that it doesn't have to rebuild them again.If we change a step in the Dockerfile, Docker will only rebuild the steps after the changed step # Parent image\nFROM node:20\n\n# Working directory\nWORKDIR /app\n\n# 🚨 As we copy source code in this layer and if source code changes, Docker will use cached layers for above steps and rebuild the steps after this step\n# Copy files from host to container\nCOPY ./ ./\n\n# Install dependencies in container\nRUN npm install\n\n# Expose a port (Allows us to access the app from outside the container)\nEXPOSE 5173\n\n# Default command to run when container starts\nCMD [\"npm\", \"run\", \"dev\"] If you notice, We've to perform npm install step again if we change the source code and not the dependencies. To avoid this, we can improve the Dockerfile like below: # Parent image\nFROM node:20\n\n# Working directory\nWORKDIR /app\n\n# Copy package.json file from host to container\nCOPY package.json ./\n\n# Install dependencies in container\nRUN npm install\n\n# Copy source code from host to container\n# 🚨 Now when source code changes, our node_modules will be retrieved from cache without reinstalling all the deps because it's above this layer\nCOPY ./ ./\n\n# Expose a port (Allows us to access the app from outside the container)\nEXPOSE 5173\n\n# Default command to run when container starts\nCMD [\"npm\", \"run\", \"dev\"] Now, If we change the source code, Docker will use cached layers for npm install step and rebuild the steps after COPY ./ ./ step saving us some time.",{"id":909,"title":910,"titles":911,"content":912,"level":207},"/blog/docker-my-findings#volumes","Volumes",[61,891],"Used to share files between container and host machine.\nNice example can be source code, It's useful for development because we can see changes in the container without rebuilding the image.It's two way sync, changes in container will reflect in host machine and vice versa.You can map host directory to container directory using -v flag in docker run command. docker run -p \u003Chost-port>:\u003Ccontainer-port> -v \u003Chost-directory>:\u003Ccontainer-directory> -d --name \u003Ccontainer-name> \u003Cimage-name> You can also map container to host directory using -v flag in docker run command. # docker run -p \u003Chost-port>:\u003Ccontainer-port> -v \u003Ccontainer-directory> -d --name \u003Ccontainer-name> \u003Cimage-name>\n\ndocker run -p 3000:3000 -v /app/node_modules -v /app/node_modules -d --name my-app my-image",{"id":914,"title":915,"titles":916,"content":917,"level":207},"/blog/docker-my-findings#docker-compose","Docker Compose",[61,891],"Used to run multiple containers at onceInstead of writing long docker run command even for single container, we can define all the configurations in a docker-compose.yml file version: '1'\nservices:\n  my-app:\n    build: .\n    ports:\n      - '3000:3000'\n    volumes:\n      - /app/node_modules\n      - .:/app You can run the containers using below command: docker-compose up",{"id":919,"title":289,"titles":920,"content":921,"level":191},"/blog/docker-my-findings#tips",[61],"While working on multiple projects use single container for specific service/image. E.g. Postgres container.\nFor development, You use single container across multiple projects to save spaceFor Production, Have separate container even for same version for each project html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"id":66,"title":65,"titles":923,"content":924,"level":185},[],"A guide on best practices for tips, naming conventions, and more when working with FastAPI. FastAPI Best PracticesFastAPI Error Handling",{"id":926,"title":194,"titles":927,"content":296,"level":191},"/blog/fastapi-best-practices#naming-conventions",[65],{"id":929,"title":930,"titles":931,"content":932,"level":207},"/blog/fastapi-best-practices#prefer-singular-over-plural-when-possible","Prefer Singular Over Plural When Possible",[65,194],"Instead of ending up like \"categories\" or \"items\", prefer singular names like \"category\" or \"item\". Not all aware of plural form of words, so it's better to stick with singular. # API Router\nuser_router = APIRouter()\n\n# CRUD\nuser_crud = UserCRUD[...]()\n\n# Schema\nclass User(BaseModel):\n    name: str\n    email: EmailStr\n\n# Database Model\nclass User(Base):\n    __tablename__ = \"user\"\n\n# This is fine as plural is best practice for endpoints\n@app.get(\"/items\")\nasync def get_items(): ...",{"id":934,"title":935,"titles":936,"content":937,"level":207},"/blog/fastapi-best-practices#resource-name-in-fastapi-operations","Resource Name in FastAPI Operations",[65,194],"Use below prefixes to resource name schema for different operations. new_ for post operationsupdated_ for put operationspatched_ for patch operations Don't use suffixes like _in or _out. @app.post(\n    \"/items\",\n    response_model=schemas.ItemDetails,\n)\nasync def create_item(\n    item_in: schemas.ItemCreate, // [!code --]\n    new_item: schemas.ItemCreate, // [!code ++]\n    db: AsyncSession = Depends(get_db),\n):\n    return await item_crud.create(db, new_item)\n\n@app.patch(\n    \"/items/{item_id}\",\n    response_model=schemas.ItemDetails,\n)\nasync def patch_item(\n    item_id: PositiveInt,\n    item_in: schemas.ItemPatch, // [!code --]\n    patched_item: schemas.ItemPatch, // [!code ++]\n    db: AsyncSession = Depends(get_db),\n):\n    db_item = await item_crud.get_or_404(db, item_id)\n    return await item_crud.patch(db, db_item, patched_item)",{"id":939,"title":940,"titles":941,"content":942,"level":207},"/blog/fastapi-best-practices#naming-db-records-in-fastapi-operations","Naming DB records in FastAPI Operations",[65,194],"Use db_ prefix for retrieved record from DB @app.patch(\n    \"/items/{item_id}\",\n    response_model=schemas.ItemDetails,\n)\nasync def patch_item(\n    item_id: PositiveInt,\n    patched_item: schemas.ItemPatch,\n    db: AsyncSession = Depends(get_db),\n):\n    db_item = await item_crud.get_or_404(db, item_id) // [!code hl]\n    return await item_crud.patch(db, db_item, patched_item)",{"id":944,"title":945,"titles":946,"content":947,"level":207},"/blog/fastapi-best-practices#provide-valid-types-for-id-parameters","Provide valid types for id parameters",[65,194],"Use PositiveInt for integer based id parameters instead of int @app.get(\n    \"/items/{item_id}\",\n    response_model=schemas.ItemDetails,\n)\nasync def get_item(\n    item_id: int, // [!code --]\n    item_id: PositiveInt, // [!code ++]\n    db: AsyncSession = Depends(get_db),\n):\n    return await item_crud.get_or_404(db, item_id)",{"id":949,"title":950,"titles":951,"content":952,"level":207},"/blog/fastapi-best-practices#pydantic-schema-naming-conventions-for-fastapi-operations","Pydantic schema naming conventions for FastAPI operations",[65,194],"Checkout existing guide on \"Pydantic schema naming conventions for FastAPI operations\"",{"id":954,"title":955,"titles":956,"content":957,"level":191},"/blog/fastapi-best-practices#order-of-operations-schemas","Order of Operations & Schemas",[65],"Prefer using \"CRUD\" as order for writing your operations and schemas. # File: router.py\n\n@item_router.post(\"/items\")                # Create\nasync def create_item(): ...\n\n@item_router.get(\"/items\")                 # Read All\nasync def get_items(): ...\n\n@item_router.get(\"/items/{item_id}\")       # Read One\nasync def get_item(): ...\n\n@item_router.put(\"/items/{item_id}\")       # Update\nasync def update_item(): ...\n\n@item_router.patch(\"/items/{item_id}\")     # Partial Update\nasync def patch_item(): ...\n\n@item_router.delete(\"/items/{item_id}\")    # Delete\nasync def delete_item(): ... # File: schemas.py\n\nclass ItemCreate(BaseModel): ...                # Create\nclass ItemCreateDB(ItemCreate): ...              # Create DB\n\nclass ItemListItem(BaseModel): ...             # Read All\nItemList = RootModel[Sequence[ItemListItem]]   # Read All (RootModel)\n\nclass ItemDetails(BaseModel): ...              # Read One\n\nclass ItemUpdate(BaseModel): ...              # Update\nclass ItemUpdateDB(ItemUpdate): ...            # Update DB\n\nclass ItemPatch(BaseModel): ...               # Partial Update\nclass ItemPatchDB(ItemPatch): ...             # Partial Update DB If you've other Non-CRUD operations besides regular resource CRUD, Following existing order of HTTP methods is recommended. For example, if you want to add POST operation to trigger a workflow, you can add it after POST operation for creating a workflow. This will help you with two things: Your CRUD operations will be in order according to \"CRUD\"Your non-CRUD operations will be in order according to HTTP methods # File: router.py\n\n@app.post( \"/workflows\")                          # Create\nasync def create_workflow(): ...\n\n@app.post(\"/workflows/{workflow_id}/trigger\")    # Create workflow Review\nasync def trigger_workflow(): ...\n\n@app.get(\"/workflows\")                          # Read All\nasync def get_workflows(): ...\n\n@app.get(\"/workflows/{workflow_id}\")           # Read One\nasync def get_workflow(): ...\n\n@app.put(\"/workflows/{workflow_id}\")           # Update\nasync def update_workflow(): ...\n\n@app.patch(\"/workflows/{workflow_id}\")        # Partial Update\nasync def patch_workflow(): ...\n\n@app.delete(\"/workflows/{workflow_id}\")       # Delete\nasync def delete_workflow(): ...",{"id":959,"title":960,"titles":961,"content":962,"level":207},"/blog/fastapi-best-practices#writing-prefix-while-including-router","Writing prefix while including router",[65,955],"Prefer no prefix for including router in the main app.Instead of prefix use tags if needed to group the endpoints. router =  APIRouter()\n\n# Prefer this (No prefix defined)\nrouter.include_router(products_router) Above will gives you more clarity when you read the product router router = APIRouter()\n\n@router.get(\"/products\")\nasync def get_products(): ...\n\n@router.get(\"/products/{id}\")\nasync def get_product(id: PositiveInt): ... router =  APIRouter()\n\n# Avoid this\nrouter.include_router(products_router, prefix=\"/products\")\nNow, When going through the product router, you might get confused about the endpoint you wish to interact withrouter = APIRouter()\n\n@router.get(\"/\")\nasync def get_products(): ...\n\n@router.get(\"/{id}\")\nasync def get_product(id: int): ...",{"id":964,"title":289,"titles":965,"content":296,"level":191},"/blog/fastapi-best-practices#tips",[65],{"id":967,"title":968,"titles":969,"content":970,"level":207},"/blog/fastapi-best-practices#instead-of-responding-using-integer-status-code-use-fastapistatus","Instead of responding using integer status code, use fastapi.status",[65,289],"from fastapi import status // [!code ++]\n\n@app.get(\"/\")\nasync def root():\n    raise HTTPException(\n        status_code=404, // [!code --]\n        status_code=status.HTTP_404_NOT_FOUND, // [!code ++]\n        detail=\"Item doesn't exist\"\n    )",{"id":972,"title":973,"titles":974,"content":975,"level":207},"/blog/fastapi-best-practices#use-async-client-instead-of-requests-library-to-make-api-call-in-fastapi","Use async client instead of requests library to make API call in FastAPI",[65,289],"Check more details on it in this video import requests // [!code --]\nfrom httpx import AsyncClient // [!code ++]\n\ndef get_data(): // [!code --]\n    response = requests.get(\"https://api.example.com/data\") // [!code --]\n    return response.json() // [!code --]\n\nasync def get_data(): // [!code ++]\n    async with AsyncClient() as client: // [!code ++]\n        response = await client.get(\"https://api.example.com/data\") // [!code ++]\n        return response.json() // [!code ++] html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}",{"id":70,"title":69,"titles":977,"content":978,"level":185},[],"Learn useful tips and best practices in Git version control system.",{"id":980,"title":981,"titles":982,"content":296,"level":191},"/blog/git-my-findings#commit","👩🏻‍💻 Commit",[69],{"id":984,"title":985,"titles":986,"content":987,"level":207},"/blog/git-my-findings#squash-all-git-commits-into-one","Squash all git commits into one?",[69,981],"git reset $(git commit-tree HEAD^{tree} -m \"Initial commit\")",{"id":989,"title":990,"titles":991,"content":992,"level":207},"/blog/git-my-findings#change-commit-message-of-already-pushed-commit","Change commit message of already pushed commit",[69,981],"# prompt you to enter new commit message (of latest commit only)\ngit commit --amend -m \"an updated commit message\"\n\n# force push\ngit push -f",{"id":994,"title":995,"titles":996,"content":997,"level":207},"/blog/git-my-findings#add-more-changes-to-the-already-pushed-commit","Add more changes to the already pushed commit",[69,981],"# 1. Perform the changes (I know you pushed the commit)\n\ngit add .                        # 2. Stag changes\n\ngit commit --amend --no-edit     # 3. Amend the commit\n\ngit push -f                      # 4. Force push",{"id":999,"title":1000,"titles":1001,"content":296,"level":191},"/blog/git-my-findings#branches","🎋 Branches",[69],{"id":1003,"title":1004,"titles":1005,"content":1006,"level":207},"/blog/git-my-findings#orphan-branche","Orphan Branche",[69,1000],"There's might be case where you want to start from scratch without any files and commit history. In this case, You can create orphan branch in the repo: git checkout --orphan \u003Cnewbranch>\ngit rm -rf .",{"id":1008,"title":1009,"titles":1010,"content":1011,"level":207},"/blog/git-my-findings#move-uncommited-changes-to-new-branch","Move uncommited changes to new branch",[69,1000],"Sometimes we accidentally make changes in the main branch and we want to move those changes to a new branch. In this case, we can create a new branch and move the changes to the new branch. git checkout -b \u003Cnew-branch>",{"id":1013,"title":1014,"titles":1015,"content":296,"level":191},"/blog/git-my-findings#tags","🔖 Tags",[69],{"id":1017,"title":1018,"titles":1019,"content":1020,"level":207},"/blog/git-my-findings#remove-tag","Remove tag",[69,1014],"# Remove local tag\ngit tag --delete tagname\n\n# Remove remote tag\ngit push --delete origin \u003Ctag-name>",{"id":1022,"title":1023,"titles":1024,"content":1025,"level":207},"/blog/git-my-findings#create-push-tag-to-remote","Create & Push tag to remote",[69,1014],"git tag -a v1.0.0 -m \"v1.0.0\"\ngit push origin v1.0.0",{"id":1027,"title":1028,"titles":1029,"content":1030,"level":207},"/blog/git-my-findings#change-tag-commit","Change tag commit",[69,1014],":::details Why?\nCheckout this stackoverflow question.\n::: # Reassign the same tag to different commit\ngit tag --force v1.0 \u003Ccommit-sha>\n\n# Force push the tag\ngit push --force --tags",{"id":1032,"title":1033,"titles":1034,"content":296,"level":191},"/blog/git-my-findings#remotes","🎯 Remotes",[69],{"id":1036,"title":1037,"titles":1038,"content":1039,"level":207},"/blog/git-my-findings#push-to-multiple-repositories-with-single-codebase","Push to multiple repositories with single codebase",[69,1033],"This can be useful when you want to push the same codebase to multiple repositories. For example, you have a main repo and you want to push the same codebase to another repo in different organization. You can also sync Github & GitLab repositories using this method 🤯 # 1. Clone main repo\ngit clone https://github.com/firstorg/myrepo.git\n\n# 2. Add another remote\ngit remote set-url --add origin https://github.com/secondorg/myrepo.git\n\n# 3. Verify the remotes\ngit remote -v # Output should have two push urls\n\n# 4. Push to both remotes with regular push command\ngit push",{"id":1041,"title":1042,"titles":1043,"content":296,"level":191},"/blog/git-my-findings#misc","🧮 Misc",[69],{"id":1045,"title":1046,"titles":1047,"content":1048,"level":207},"/blog/git-my-findings#temporarily-ignore-folderfile-in-git","Temporarily ignore folder/file in git",[69,1042],"You can use the git update-index command with the --skip-worktree option to temporarily ignore changes to a specific folder or file in a Git repository. git update-index --skip-worktree /temp To stop ignoring changes to the folder/file (undo above), use --no-skip-worktree option. git update-index --no-skip-worktree /temp",{"id":1050,"title":1051,"titles":1052,"content":1053,"level":207},"/blog/git-my-findings#login-store-git-credentials","Login & Store git credentials",[69,1042],"When you setup a new system you might need to enter your git credentials again and again. To avoid this, you can store your git credentials in the system's credential store. git config --global user.name \"John Doe\"\ngit config --global user.email johndoe@example.com\n\ngit config --global credential.helper store From next whenever you enter your credentials, it will be stored in the system's credential store.",{"id":1055,"title":1056,"titles":1057,"content":1058,"level":207},"/blog/git-my-findings#remove-git-credentials","Remove git credentials",[69,1042],"git config --global --unset credential.helper",{"id":1060,"title":1061,"titles":1062,"content":1063,"level":207},"/blog/git-my-findings#rename-git-tracked-files","Rename git tracked files",[69,1042],"Never rename files using mv command or manually. Instead use git mv command to let git know about the rename. git mv Readme.md README.md",{"id":1065,"title":1066,"titles":1067,"content":1068,"level":207},"/blog/git-my-findings#view-git-ignored-untracked-files","View git ignored & untracked files",[69,1042],"# Shows all gitignored files that exist in your working directory\ngit ls-files --others --ignored --exclude-standard\n\n# Shows only top-level gitignored items\ngit ls-files --others --ignored --exclude-standard | cut -d'/' -f1 | sort -u\n\n# Shows only top-level files (excludes directories)\ngit ls-files --others --ignored --exclude-standard | grep -v '/' html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"id":74,"title":73,"titles":1070,"content":1071,"level":185},[],"A guide on handling file uploads in FastAPI with validation and saving to disk. First of all, Start with utility function to save file to disk. import os\nfrom secrets import token_hex\nfrom pathlib import Path\nfrom fastapi import UploadFile\n\nasync def save_file_to_disk(file: UploadFile, path: str | Path) -> Path:\n    # convert received `path` to `Path` object\n    _path = Path(path)\n\n    # create directory if not exists\n    os.makedirs(_path, exist_ok=True)\n\n    # generate unique file name\n    new_file_name = token_hex(16) + Path(file.filename).suffix\n\n    # Create file path for new file\n    file_path = _path / new_file_name\n\n    # Save file in chunks asynchronously\n    with open(file_path, \"wb\") as buffer:\n        async for chunk in file:\n            buffer.write(chunk)\n    return file_path Next, Let's create a dependency that validates the file type. This dependency will be accept a list of allowed file types and raise an exception if the file type is not in the list. from fastapi import HTTPException, UploadFile, Depends\n\nclass FileValidator:\n    def __init__(self, allowed_types: list[str]):\n        self.allowed_types = allowed_types\n\n    async def __call__(self, file: UploadFile = File(...)):\n        if file.content_type not in self.allowed_types:\n            raise HTTPException(status_code=400, detail=\"Invalid file type\")\n        return file\n\n# Dependency instances for different file types\nimg_validator = FileValidator([\"image/jpeg\", \"image/png\"])\npdf_validator = FileValidator([\"application/pdf\"]) Finally, Let's use this validator in our path operation: from fastapi import FastAPI, UploadFile, Depends\nfrom .deps import img_validator\nfrom .utils import save_file_to_disk\n\napp = FastAPI()\n\n@app.post(\"/upload/images/\")\nasync def upload_images(files: list[UploadFile] = Depends(img_validator)):\n    # Create a list of tasks to save files to disk asynchronously\n    upload_tasks = [save_file_to_disk(f) for f in files]\n\n    # Wait for all tasks to complete\n    file_paths = await asyncio.gather(*upload_tasks)\n\n    # Return file paths\n    return {\"file_paths\": file_paths} In addition to this you can also add file size validation, file name validation, etc as per your requirements using dependencies. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}",{"id":78,"title":77,"titles":1073,"content":1074,"level":185},[],"Tips and strategies to enhance your skills and stay updated in the ever-evolving tech industry. When starting out your tech career you might don't know where to start. There are so many things to learn and so many technologies to choose from. It's easy to get lost in all of this. Today I explained my colleague how to improve skills & where to look for them. I shared long list of links to him but that was particular to Vue technology so I'll be generic here. I thought it might be useful for others as well so here's my tips on improving skills in tech world: Find high quality YouTube channels for your technology. This might be hard at first so just ask your colleagues or scan the internet for recommendations. Additionally, I also recommend checking related YouTube channels as well. For example, If you are a Vue or Python developing using VS Code, You can subscribe to VSCode's official YouTube channel as well.Try to find sites that posts high quality blog post. Avoid blog posts written by agency or company, They just do that for SEO and don't have technical knowledge. Instead, Find active people from your community and check if they maintain a blog.To learn advanced & latest stuff, Watch conference talks (You can find them on YouTube)Subscribing to newsletter of your technology is also a good idea. You can find latest news, blog posts, tutorials, etc. in your inbox.Stay up to date on what's hot by checking Trending Repos in GitHub. You can narrow it down to your specific language or technology.Follow active people of your community or Organizations on GitHubStart watching repo releases on GitHub for your favorite repos to get latest updatesFinally, Follow active & creative people of your community on Twitter. You can also follow twitter handles of tool or package you are using. To sum up, You need to find high quality content creators and follow them on YouTube, Twitter, GitHub, etc. to improve your skills. Always remember, \"If I had eight hours to chop down a tree, I'd spend six hours sharpening my axe. - Abraham Lincoln\"",{"id":82,"title":81,"titles":1076,"content":1077,"level":185},[],"A step-by-step guide to setting up Alembic for database migrations in a Python project using SQLAlchemy.",{"id":1079,"title":1080,"titles":1081,"content":1082,"level":191},"/blog/how-to-setup-alembic#introduction","Introduction",[81],"Alembic is a lightweight database migration tool for SQLAlchemy. It is used to generate migrations for a database schema, and apply migrations to a database.",{"id":1084,"title":1085,"titles":1086,"content":1087,"level":191},"/blog/how-to-setup-alembic#setup","Setup",[81],"First install Alembic in your virtual environment. uv add alembic Initialize Alembic via the init command. alembic init -t async alembic It will crate a alembic.ini file and a alembic directory in your project. Now let's set our database URL. For our case we want to get it from environment variable and don't want to hardcode it in the alembic.ini file. Hence, we will comment line similar to below in alembic.ini file. sqlalchemy.url = driver://user:pass@localhost/dbname // [!code --]\n# sqlalchemy.url = driver://user:pass@localhost/dbname It's convenient to create above get_db_url function in Settings class and use it in alembic. from pydantic_settings import BaseSettings\nfrom sqlalchemy.engine.url import URL\n\nclass Settings(BaseSettings):\n    # Your settings\n\n    # Database\n    DB_ECHO: bool = False\n    DB_DRIVER_NAME: str\n    DB_HOST: str\n    DB_USER: str\n    DB_PASSWORD: str\n    DB_NAME: str\n\n    def get_db_url(self):\n        url_object = URL.create(\n            drivername=self.DB_DRIVER_NAME,\n            host=self.DB_HOST,\n            username=self.DB_USER,\n            password=self.DB_PASSWORD,\n            database=self.DB_NAME,\n        )\n        return url_object.render_as_string(hide_password=False) Now, instead of creating get_url function inside alembic/env.py file, you can import Settings class and use get_db_url method. from core.settings import settings\n\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n\nconfig.set_main_option(\"sqlalchemy.url\", settings.get_db_url()) You can use above get_db_url in sqlalchemy as well like below:from core.settings import settings\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\nengine = create_async_engine(settings.get_db_url(), echo=settings.DB_ECHO) With this, we have successfully update the database URL in alembic however there's still important piece is missing. We need to update the target_metadata variable. It is used to generate migrations for a database schema. We will update it in alembic/env.py file. In addition to this, We also have to import all the models that we want to include in the migration so alembic can generate schema automatically. from app.db.base import Base # 🚨 Update import path according to your `Base`\nfrom app.models.user import * # Allow auto generating schema# import other models if you have any\ntarget_metadata = None // [!code --]\ntarget_metadata = Base.metadata // [!code ++] :::details Auto import models instead of manually importing them\nI also created utility function that auto imports all the models from the project. You can find it here. With this utility function you can update the alembic/env.py file as below. from app.utils.imports import import_models_for_alembic // [!code ++]\n\nfrom app.models.user import * # Allow auto generating schema# import other models if you have anyimport_models_for_alembic() # Check snippet to know what it does ::: Now finally, Let's create a new model to run migration. # File: app/models/user.py\n\nfrom sqlalchemy import String\nfrom sqlalchemy.orm import Mapped, mapped_column\n\nfrom app.db.base import Base\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    username: Mapped[str] = mapped_column(String(30), nullable=False, index=True) Now, let's generate the migration. alembic revision --autogenerate -m \"create_user_table\" Hurray! We have successfully generated the migration. Now, let's apply it to the database. alembic upgrade head Also checkout my other findings on alembic here. html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}",{"id":86,"title":85,"titles":1089,"content":1090,"level":185},[],"A complete, ordered guide to running multiple GitHub accounts on a fresh Ubuntu machine — SSH keys, per-directory git identity, and automatic gh CLI account switching.",{"id":1092,"title":1093,"titles":1094,"content":1095,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#prerequisites","Prerequisites",[85],"You'll need these on your Ubuntu machine before starting: Git — sudo apt install -y gitOpenSSH client — sudo apt install -y openssh-clientGitHub CLI (gh) — Install via the official guide: GitHub CLI → Installation",{"id":1097,"title":1098,"titles":1099,"content":1100,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-1-create-one-ssh-key-per-account","Step 1 — Create one SSH key per account",[85],"Generate a separate key for each account. Keeping them separate is what lets SSH pick the right identity per host. # Work key\nssh-keygen -t ed25519 -C \"work@example.com\" -f ~/.ssh/id_work\n\n# Personal key\nssh-keygen -t ed25519 -C \"personal@example.com\" -f ~/.ssh/id_personal When prompted you can set a passphrase (recommended) or press Enter to skip. This creates four files: ~/.ssh/id_work + ~/.ssh/id_work.pub (work)~/.ssh/id_personal + ~/.ssh/id_personal.pub (personal)",{"id":1102,"title":1103,"titles":1104,"content":1105,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-2-add-the-public-keys-to-github","Step 2 — Add the public keys to GitHub",[85],"Copy each public key and add it to the matching account at GitHub → Settings → SSH and GPG keys → New SSH key. # Work — paste this into the work-username account\ncat ~/.ssh/id_work.pub\n\n# Personal — paste this into the personal-username account\ncat ~/.ssh/id_personal.pub The SSH keys page is at github.com/settings/keys — make sure you're logged into the correct account before adding each key.",{"id":1107,"title":1108,"titles":1109,"content":1110,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-3-the-sshconfig-the-heart-of-it","Step 3 — The ~/.ssh/config (the heart of it)",[85],"This is the file that gives you the github-work and github-personal experience. Create ~/.ssh/config: nano ~/.ssh/config Paste: Host github-personal\n   HostName github.com\n   IdentityFile ~/.ssh/id_personal\n   IdentitiesOnly yes\n\nHost github-work\n   HostName github.com\n   IdentityFile ~/.ssh/id_work\n   IdentitiesOnly yes Lock down the permissions (SSH is picky about this): chmod 600 ~/.ssh/config\nchmod 600 ~/.ssh/id_work ~/.ssh/id_personal\nchmod 644 ~/.ssh/id_work.pub ~/.ssh/id_personal.pub How it works: both hosts really connect to github.com, but each presents a different key. IdentitiesOnly yes forces SSH to use only the listed key (otherwise it may offer the wrong one and get rejected). You now address the two accounts by alias instead of github.com: git@github-work:ORG/REPO.git → authenticates as workgit@github-personal:USER/REPO.git → authenticates as personal Test both: ssh -T git@github-work       # Hi work-username! You've successfully authenticated...\nssh -T git@github-personal   # Hi personal-username! You've successfully authenticated... The first connection asks you to trust GitHub's host key — type yes. Seeing the right username in each greeting confirms the key→account mapping is correct.",{"id":1112,"title":1113,"titles":1114,"content":1115,"level":207},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#using-the-aliases-when-cloning","Using the aliases when cloning",[85,1108],"Always clone with the alias that matches the account that owns the repo: # Work repo\ngit clone git@github-work:your-org/work-repo.git\n\n# Personal repo\ngit clone git@github-personal:personal-username/personal-repo.git For an existing repo, point its remote at the right alias: git remote set-url origin git@github-work:your-org/work-repo.git",{"id":1117,"title":1118,"titles":1119,"content":1120,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-4-per-directory-git-identity","Step 4 — Per-directory git identity",[85],"SSH now picks the right key, but your commits still need the right name/email. Instead of setting this per repo by hand, let the directory decide using Git's includeIf. The idea: pick a folder layout where personal and work projects live in different trees, e.g.: ~/Projects/\n├── work/\n└── personal/ Set your default (work) identity in ~/.gitconfig: [user]\n    name = work-username\n    email = work@example.com\n\n[init]\n    defaultBranch = main Now add a conditional include at the bottom of ~/.gitconfig that overrides the identity for anything under your personal folder: [includeIf \"gitdir:~/Projects/personal/\"]\n    path = ~/.gitconfig-personal And create ~/.gitconfig-personal: [user]\n    name = personal-username\n    email = personal@example.com The trailing slash in gitdir:~/Projects/personal/ matters — it means \"any repo inside this directory\". Git evaluates this per repository, so every repo under ~/Projects/personal/ automatically uses your personal name/email, and everything else falls back to work. Verify from inside each tree: cd ~/Projects/work/work-repo         && git config user.name   # work-username\ncd ~/Projects/personal/personal-repo && git config user.name   # personal-username This user.name value is the linchpin of the next step — we'll make the gh CLI follow it.",{"id":1122,"title":1123,"titles":1124,"content":1125,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-5-authenticate-gh-for-both-accounts","Step 5 — Authenticate gh for both accounts",[85],"Log in to each account once. gh stores them side by side and lets you switch between them. gh auth login   # run this once per account Choose: GitHub.com → your preferred protocol → Login with a web browser, and complete it for your work account. Then run gh auth login again and complete it for your personal account. Confirm both are present: gh auth status github.com\n  ✓ Logged in to github.com account personal-username (keyring)\n  - Active account: true\n  ✓ Logged in to github.com account work-username (keyring)\n  - Active account: false You can switch manually with gh auth switch -u \u003Cusername> — but doing that by hand every time you change projects is exactly what we want to avoid. Onward.",{"id":1127,"title":1128,"titles":1129,"content":1130,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-6-make-gh-auto-select-the-right-account-per-repo","Step 6 — Make gh auto-select the right account per repo",[85],"gh has no built-in \"use account X in directory Y\" feature (cli/cli#326). The clean workaround is a tiny wrapper script placed ahead of the real gh on your PATH. Before every gh call it reads the repo's git config user.name and, if needed, switches the active gh account to the one whose login matches — then hands off to the real gh. Because it's a script on PATH (not a shell function), it works everywhere: interactive shells, scripts, and CI alike. Create ~/.local/bin/gh: mkdir -p ~/.local/bin\nnano ~/.local/bin/gh Paste: #!/usr/bin/env bash\n# gh wrapper: auto-switch the active gh account to match the current repo's\n# `git config user.name` before delegating to the real gh.\n#\n# Sits ahead of /usr/bin/gh on PATH so it applies to interactive shells,\n# scripts, and CI alike. The real binary is always called by absolute path\n# so this wrapper can never recurse into itself.\n#\n# See https://github.com/cli/cli/issues/326 (idea from @uncenter).\n\nREAL_GH=\"/usr/bin/gh\"\n\n# Robustness: if the hard-coded path ever moves, fall back to the next gh on\n# PATH that isn't this very script.\nif [ ! -x \"$REAL_GH\" ]; then\n    self=\"$(readlink -f \"$0\" 2>/dev/null || echo \"$0\")\"\n    REAL_GH=\"\"\n    while IFS= read -r cand; do\n        [ -x \"$cand\" ] || continue\n        [ \"$(readlink -f \"$cand\" 2>/dev/null || echo \"$cand\")\" = \"$self\" ] && continue\n        REAL_GH=\"$cand\"\n        break\n    done \u003C \u003C(type -aP gh 2>/dev/null)\n    if [ -z \"$REAL_GH\" ]; then\n        echo \"gh-wrapper: could not locate the real gh binary\" >&2\n        exit 127\n    fi\nfi\n\nwant=\"$(git config user.name 2>/dev/null)\"\nif [ -n \"$want\" ]; then\n    active=\"$(\"$REAL_GH\" auth status --json hosts \\\n        --jq '.hosts[\"github.com\"][] | select(.active).login' 2>/dev/null)\"\n    if [ -n \"$active\" ] && [ \"$want\" != \"$active\" ]; then\n        if \"$REAL_GH\" auth status --json hosts \\\n            --jq '.hosts[\"github.com\"][].login' 2>/dev/null | grep -Fqx \"$want\"; then\n            \"$REAL_GH\" auth switch -u \"$want\" >/dev/null 2>&1\n        fi\n    fi\nfi\n\nexec \"$REAL_GH\" \"$@\" Make it executable: chmod +x ~/.local/bin/gh",{"id":1132,"title":1133,"titles":1134,"content":1135,"level":207},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#make-sure-the-wrapper-wins-on-path","Make sure the wrapper wins on PATH",[85,1128],"The wrapper only works if ~/.local/bin comes before /usr/bin on your PATH. On Ubuntu, ~/.local/bin is usually added automatically, but make it explicit in ~/.bashrc: # ~/.bashrc\nexport PATH=\"$HOME/.local/bin:$PATH\" Reload and confirm gh now resolves to the wrapper: source ~/.bashrc\nhash -r                 # clear any cached path to the old gh\ncommand -v gh           # -> /home/\u003Cyou>/.local/bin/gh",{"id":1137,"title":1138,"titles":1139,"content":1140,"level":207},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#how-the-wrapper-works","How the wrapper works",[85,1128],"It reads git config user.name for the current repo (set automatically by your includeIf rules).It asks the real gh which account is currently active.If they differ and a matching account exists, it runs gh auth switch — otherwise it does nothing (so the common case is a single cheap status check).exec \"$REAL_GH\" \"$@\" replaces the process, so the exit code, stdin/stdout/stderr, and signals all pass through transparently.The real binary is always invoked by its absolute path, so the wrapper can never accidentally call itself in a loop.",{"id":1142,"title":1143,"titles":1144,"content":1145,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#step-7-verify-the-whole-thing","Step 7 — Verify the whole thing",[85],"# Work repo -> should report the work account\ncd ~/Projects/work/work-repo\ngh api user --jq '.login'        # work-username\n\n# Personal repo -> should report the personal account\ncd ~/Projects/personal/personal-repo\ngh api user --jq '.login'        # personal-username If both lines print the expected username without you switching anything, you're done. SSH, commit identity, and gh now all follow the directory automatically. 🎉",{"id":1147,"title":1148,"titles":1149,"content":1150,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#troubleshooting","Troubleshooting",[85],"Confirm the public key (~/.ssh/id_work.pub) is added to the correct GitHub account.Check ~/.ssh/config has IdentitiesOnly yes so SSH doesn't offer the wrong key.Run ssh -vT git@github-work and look at the Offering public key: line to see which key is being tried.command -v gh must print ~/.local/bin/gh. If it prints /usr/bin/gh, your PATH order is wrong or your shell cached the old path — run hash -r and open a new terminal.Make sure the repo's git config user.name exactly matches a gh login (case-sensitive). gh auth status lists the valid logins.git config user.email inside the repo tells you what's active.Remember includeIf matches on the trailing-slash directory — gitdir:~/Projects/personal/ (with the slash) covers everything inside it.Already committed with the wrong identity? Fix the config, then git commit --amend --reset-author for the latest commit.This is exactly why we used a PATH wrapper instead of a shell function — as long as ~/.local/bin is on the PATH that the script inherits, the wrapper applies. If a cron/CI shell has a minimal PATH, add ~/.local/bin to it explicitly.",{"id":1152,"title":1153,"titles":1154,"content":1155,"level":191},"/blog/how-to-use-work-and-personal-github-accounts-on-ubuntu#wrapping-up","Wrapping up",[85],"The whole setup comes down to letting the directory decide who you are: ~/.ssh/config maps github-work/github-personal aliases to separate keys.includeIf swaps your commit identity based on where the repo lives.A small gh wrapper makes the GitHub CLI follow that same identity. Set it up once on each new machine and you can forget it exists — which is exactly what you want from account juggling. cli/cli#326 — Allow multiple account credentialsGitHub Docs — Connecting with SSHGit Docs — Conditional includes (includeIf)GitHub CLI — Manual html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}",{"id":90,"title":89,"titles":1157,"content":1158,"level":185},[],"Tips and best practices for writing engaging and effective articles.",{"id":1160,"title":1161,"titles":1162,"content":296,"level":191},"/blog/how-to-write-good-articles#_1-tips-for-writing-great-headlines","1. Tips for Writing Great Headlines",[89],{"id":1164,"title":1165,"titles":1166,"content":1167,"level":207},"/blog/how-to-write-good-articles#headlines-should-promise-a-payoff","Headlines should promise a payoff",[89,1161],"At glance, your headline should highlight why someone should spend their valuable time reading. In short, what do readers get out of your post? E.g.\nReclaim Your Time with These 12 Time Management TipsWrite That Down – Take the Best Meeting Minutes with This TemplateLaunch Your New Product in No Time with Agile Project Management",{"id":1169,"title":1170,"titles":1171,"content":1172,"level":207},"/blog/how-to-write-good-articles#ask-a-question","Ask a question",[89,1161],"We always search with question in mind so why not have same in headline?Below is some question headline prefixes:How Can I...How Often Should...When Does...Where Does...Why Do...How to...Below are example of some technical blogs:How Can I Improve My Website’s Performance?How Often Should I Update My Blog?When Does Google Update Its Search Algorithm?Where Does WordPress Store Images?Why Do I Need a CDN?How to Optimize Your Website for Mobile",{"id":1174,"title":1175,"titles":1176,"content":1177,"level":207},"/blog/how-to-write-good-articles#use-i-you-we-me-my-in-headlines","Use I, You, We, Me, My in Headlines",[89,1161],"Using personal pronouns in headlines can make them more relatable and engaging. E.g.\nI Tried the Pomodoro Technique for a Week. Here’s What HappenedYou Can Improve Your Writing Skills in Just 5 Minutes a DayWe Tested 5 Popular Email Marketing Tools. Here’s What We FoundMe vs. Myself: How to Know When to Use Each PronounMy Favorite Productivity Hacks for Remote Work",{"id":1179,"title":1180,"titles":1181,"content":1182,"level":207},"/blog/how-to-write-good-articles#use-numbers","Use Numbers",[89,1161],"It's physiologically proven that numbers in headlines attract more clicks. E.g.\n10 Ways to Improve Your Writing Skills5 Tips for Better Time Management3 Simple Steps to a More Productive Morning7 Tools Every Blogger Needs to Know About12 Ways to Get More Done in Less Time",{"id":1184,"title":1185,"titles":1186,"content":1187,"level":207},"/blog/how-to-write-good-articles#use-power-words","Use Power Words",[89,1161],"Power words standout and attract more clicks. Below are few power words:\nDon't/Can't\n10 Things You Can't Miss at the Conference5 Reasons You Don't Want to Miss This Webinar3 Things You Can't Forget to Pack for Your Next TripNeed\n7 Things You Need to Know Before You Start a Blog5 Tools Every Marketer Needs to Know About3 Reasons You Need to Start Using a Password ManagerSave\nSave Time and Money with These 5 Productivity ToolsSave Your Sanity with These 3 Email Management TipsSave Your Skin with These 7 Sunscreen TipsPrevent/Prepare\n10 Ways to Prevent Burnout5 Tips to Prepare for Your Next Job Interview3 Things You Need to Prepare for Your Next PresentationWhy You\nWhy You Need to Start Using a Password ManagerWhy You Should Care About Your Website’s PerformanceWhy You Can’t Afford to Miss This WebinarImportant\n7 Important Things to Know Before You Start a Blog5 Important Tips for Better Time Management3 Important Steps to a More Productive MorningBest/Worst\nThe Best Tools for Managing Your Social Media AccountsThe Worst Mistakes You Can Make in Your Email MarketingThe Best Way to Get More Done in Less Time",{"id":1189,"title":1190,"titles":1191,"content":1192,"level":207},"/blog/how-to-write-good-articles#write-5-headlines-and-choose-the-best","Write 5 Headlines and Choose the Best",[89,1161],"Write 5 headlines using different techniques and choose the best one.Here's 5 different headlines for a blog post about time management, Decide which one you think is the most compelling:\n10 Time Management Tips for Busy ProfessionalsHow to Get More Done in Less Time10 Ways to Reclaim Your TimeThe Best Time Management Tips for Busy ProfessionalsWhy You Need to Start Using These Time Management Tips",{"id":1194,"title":1195,"titles":1196,"content":1197,"level":207},"/blog/how-to-write-good-articles#dont-use-same-headline-technique-every-time","Don't use same headline technique every time",[89,1161],"This is common senseIf you use same technique every time, your headlines will start to sound the same and readers will get bored. 36% of people preferred titles with numbers21% of readers liked reader-addressing headlines17% wanted a headline to include \"How to\"",{"id":1199,"title":1200,"titles":1201,"content":1202,"level":191},"/blog/how-to-write-good-articles#_2-good-cover-image","2. Good Cover Image",[89],"Cover image is first thing that reader sees",{"id":1204,"title":1205,"titles":1206,"content":1207,"level":191},"/blog/how-to-write-good-articles#_3-brief-introduction","3. Brief Introduction",[89],"Think it like a Table of Content for your articleIt should be brief and to the pointIt can include below:\nWhat is the article about?Why should reader read this article?What will reader learn from this article?What inspire you to write this article?Ideal length is 3-5 sentences",{"id":1209,"title":1210,"titles":1211,"content":1212,"level":191},"/blog/how-to-write-good-articles#_4-how-to-write-great-body-content","4. How to write great body content",[89],"Use short paragraphs\nShort paragraphs are easier to read on screenIt's easier to read and understandIt's easier to scanUse images where required\nImages make your content more engagingImages help to illustrate your pointsUse bullet points where required\nBullet points make your content easier to readBullet points help to break up your contentBullet points help to highlight key pointsDon't explain different points/topic instead share link to dedicated resource\nIt will help to keep your article short and to the pointIt will help to keep your article focusedIf it's technical blog consider below points:\nUse code snippetsStep-by-step instructionsShow screenshotsUse diagramsUse tablesShow examplesShare links to documentation",{"id":1214,"title":1215,"titles":1216,"content":1217,"level":191},"/blog/how-to-write-good-articles#_5-write-conclusion-if-required","5. Write conclusion if required",[89],"Conclusion is not always requiredIf you have a lot of information in your article, a conclusion can help to summarize the key pointsWhen writing a conclusion, you should:\nSummarize the key pointsRestate the main argumentEnd with a call to actionIdeal length is 3-5 sentencesFollowing types of articles require conclusion:\nReviewsComparison articles",{"id":1219,"title":1220,"titles":1221,"content":1222,"level":191},"/blog/how-to-write-good-articles#_6-read-your-article-once-before-publishing","6. Read your article once before publishing",[89],"This will help you to find any grammatical mistakesAllows you to check if article is well structured",{"id":94,"title":93,"titles":1224,"content":1225,"level":185},[],"Learn useful tips and best practices in Linux operating system.",{"id":1227,"title":1228,"titles":1229,"content":296,"level":191},"/blog/linux-my-findings#commands","Commands",[93],{"id":1231,"title":1232,"titles":1233,"content":1234,"level":207},"/blog/linux-my-findings#users-groups","Users & Groups",[93,1228],"Users having UID 0 are superusers (root)Users having UID >= 1000 are normal users & \u003C 1000 are system usersSystem users are helpful when we want to run something/cron in backgroundView users in /etc/passwd file & groups in /etc/group fileIf someone has permission like rw- for directory, it means user can view the content of the directory and add files to it but can't navigate to it. sudo useradd myuser # create a user\n\nsudo userdel myuser # delete a user\n\nsudo useradd -m myuser # create a user with home directory\n\nsudo userdel -r myuser # delete a user along with home directory\n\nsudo useradd -r mybot # create a system user using `-r` flag\n\npasswrd # change password of currently logged in user\n\nsudo passwd myuser # change password of a user \"myuser\"\n\ncat /etc/passwd # list all users\n# username:password:UID:GID:comment:home:shell\n\n# --- Group ---\n\ngroups # list groups of current user\n\ngroups myuser # list groups of user \"myuser\"\n\nsudo groupadd mygroup # create a group\n\nsudo groupdel mygroup # delete a group\n\nsudo usermod -aG mygroup myuser # add user \"myuser\" to group \"mygroup\" (logout & login required)\n# sudo gpasswd -a myuser mygroup # Same thing as above but using `gpasswd`\n\nsudo gpasswd -d myuser mygroup # Remove user \"myuser\" from the group \"mygroup\"\n\ncat /etc/group # list all groups\n# groupname:password:GID:users",{"id":1236,"title":1237,"titles":1238,"content":1239,"level":207},"/blog/linux-my-findings#permissions","Permissions",[93,1228],"type#read4write2execute1 rwx => 7rw- => 6r-x => 5 chmod +w file.txt # add execute permission to file.txt for all\n\nchmod -w file.txt # remove execute permission from file.txt for all\n\nchmod u+w file.txt # add execute permission to file.txt for user\n\nchmod g+w file.txt # add execute permission to file.txt for group\n\nchmod o+w file.txt # add execute permission to file.txt for others\n\nchmod g+rw file.txt # add read & write permission to file.txt for group\n\nchmod 770 file.txt # add read, write & execute permission to file.txt. User & group have full permission & others have no permission\n\nchmod -R 755 mydir # Recursively update permission\n\nchown -R myuser:mygroup mydir # Change owner of directory \"mydir\" to \"myuser\" & group to \"mygroup\"\n\nchown -R $USER:$USER mydir # Change owner of directory \"mydir\" to current user & group to current user",{"id":1241,"title":1242,"titles":1243,"content":1244,"level":207},"/blog/linux-my-findings#utility","Utility",[93,1228],"mktemp # create a temporary directory\n\nMYTEMPDIR=$(mktemp) # create a temporary directory and store the path in a variable\necho $MYTEMPDIR # print the path to the temporary directory\n\n# ---\n\necho $RANDOM # print a random number\n\n# Read latest data of constantly updating file\ntail -f /var/log/syslog",{"id":1246,"title":1247,"titles":1248,"content":296,"level":191},"/blog/linux-my-findings#security","Security",[93],{"id":1250,"title":1251,"titles":1252,"content":1253,"level":207},"/blog/linux-my-findings#ufw-uncomplicated-firewall","ufw - Uncomplicated Firewall",[93,1247],"# Enable firewall\nsudo ufw enable\n\n# Disable firewall\nsudo ufw disable\n\n# Check status\nsudo ufw status\nsudo ufw status verbose # for verbose output\n\n# Allow port\nsudo ufw allow 3000\n\n# Close/Block port\nsudo ufw deny 3000\n\n# Delete rule\nsudo ufw delete allow 3000",{"id":1255,"title":1256,"titles":1257,"content":296,"level":191},"/blog/linux-my-findings#hosting","Hosting",[93],{"id":1259,"title":1260,"titles":1261,"content":296,"level":207},"/blog/linux-my-findings#nginx","Nginx",[93,1256],{"id":1263,"title":1264,"titles":1265,"content":1266,"level":218},"/blog/linux-my-findings#useful-commands","Useful Commands",[93,1256,1260],"# Start Nginx\nsudo systemctl start nginx\n\n# Stop Nginx\nsudo systemctl stop nginx\n\n# Restart Nginx\nsudo systemctl restart nginx\n\n# Reload Nginx (without dropping connections):\nsudo systemctl reload nginx\n\n# Test Nginx Configuration for Syntax Errors\nsudo nginx -t\n\n# Create Symbolic Link to Enable a Site\nsudo ln -s /etc/nginx/sites-available/your_site_name /etc/nginx/sites-enabled/\n\n# View Nginx Error Logs\nsudo tail -f /var/log/nginx/error.log\n\n# View Nginx Access Logs\nsudo tail -f /var/log/nginx/access.log",{"id":1268,"title":1269,"titles":1270,"content":1271,"level":218},"/blog/linux-my-findings#useful-links","Useful links",[93,1256,1260],"root vs aliaslocation block priority",{"id":1273,"title":1274,"titles":1275,"content":296,"level":191},"/blog/linux-my-findings#systemd","Systemd",[93],{"id":1277,"title":1264,"titles":1278,"content":1279,"level":207},"/blog/linux-my-findings#useful-commands-1",[93,1274],"# --- Working with systemd service ---\n\n# Start service/Unit\nsudo systemctl start nginx\n\n# Stop service/Unit\nsudo systemctl stop nginx\n\n# Restart service/Unit\nsudo systemctl restart nginx\n\n# Reload service/Unit\nsudo systemctl reload nginx\n\n# Autostart service/Unit on boot\nsudo systemctl enable nginx\n\n# Disable autostart service/Unit on boot\nsudo systemctl disable nginx\n\n# --- Debugging ---\n\n# Check status of a service/Unit\njournalctl -u nginx\n\n# Get recent logs\njournalctl -e -u nginx\n\n# Check status of a service/Unit and follow the logs (continuous)\njournalctl -u nginx -f html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"id":98,"title":97,"titles":1281,"content":1282,"level":185},[],"Best practices for loading related data in FastAPI using Pydantic schemas and SQLAlchemy relationships. Instead of select(...).options() or db.refresh(item, attribute_names=[]) explicitly everywhere, Instead:One-to-One / Many-to-One => use lazy=\"joined\" in relationship colMany-to-One / Many-to-Many => use write_only in relationship col and later fetch data by pagination or filter# When list if used in relationship => lazy=\"write_only\"\nsome_col: Mapped[list[\"SomeModel\"]] = relationship(init=False, back_populates=\"some_col\", lazy=\"write_only\")\n\n# When single item is used in relationship => lazy=\"joined\"\nsome_col: Mapped[\"SomeModel\"] = relationship(init=False, back_populates=\"some_col\", lazy=\"joined\") When you have a relationship between two tables, you might want to send the additional data related to another table. For example, let's say we've two tables user and address and user has one-to-one relationship with address. And we want to send address data along with user data when user is fetched. {\n    \"id\": 1,\n    \"name\": \"John\",\n    \"address\": {\n        \"id\": 1,\n        \"city\": \"New York\"\n    }\n} Let's go over best practices to fetching related data in FastAPI using Pydantic schema.",{"id":1284,"title":1285,"titles":1286,"content":1287,"level":191},"/blog/loading-relationship-data-via-pydantic-schema-in-fastapi#fetching-related-data-explicitly","Fetching Related Data Explicitly",[97],"Assume, we have one-to-one relationship on user and address table and have address relationship col like below on user table and schema like below: class User(Base):\n    # ...\n\n    address: Mapped[\"Address\"] = relationship(init=False, back_populates=\"user\")\n\nclass UserRead(BaseModel):\n    id: int\n    name: str\n    address: AddressRead In this case, to get the desired result as shown in first snippet we've to write endpoint like this: @app.get(\"/users/{user_id}\", response_model=UserRead)\nasync def get_user(user_id: int, db: AsyncSession = Depends(get_db),):\n    # When using select => select(User).where(User.id == user_id).options(joinedload(User.address))\n    return await db.get(User, user_id, options=[joinedload(User.address)]) This works fine but it can be verbose and you might forget to add options in some cases. Also, We also reuse UserRead schema in multiple places like user list endpoint (response_model=list[UserRead]) or user create endpoint. Hence, you've to write options multiple times and for cases like create endpoint, it gets more tricky. @app.post(\"/users\", response_model=UserRead)\nasync def create_user(user: UserCreate, db: AsyncSession = Depends(get_db),):\n    user = User(**user.model_dump())\n    db.add(user)\n    db.commit()\n    db.refresh(user)\n    return user # 🚨 Serialization error When we create user and return with response_model=UserRead we get serialization error because address_id we passed in UserCreate schema will not magically convert to AddressRead in UserRead schema. In above case, we've to load address explicitly like below: db.refresh(user) // [!code --]\ndb.refresh(user, attribute_names=[\"address\"]) // [!code ++] Now, we'll get address data along with user data even when we create user. To summarize, we've to be careful with relationships & pydantic schemas. However, there's a better approach to handle this based on relationship type.",{"id":1289,"title":1290,"titles":1291,"content":1292,"level":191},"/blog/loading-relationship-data-via-pydantic-schema-in-fastapi#one-to-one-many-to-one-relationship","One-to-One & Many-to-One Relationship",[97],"Let's take a same example of user and address table with one-to-one relationship. In this case, instead of doing all the hassle of options with select statement and magically handling while creating user, we can use lazy=\"joined\" in relationship col like below: class User(Base):\n    # ...\n\n    address: Mapped[\"Address\"] = relationship(init=False, back_populates=\"user\", lazy=\"joined\") This will tell SQLAlchemy to load address data along with user data whenever user is fetched. And, you can use UserRead schema as it is without any changes. @app.get(\"/users/{user_id}\", response_model=UserRead)\nasync def get_user(user_id: int, db: AsyncSession = Depends(get_db),):\n    return await db.get(User, user_id)",{"id":1294,"title":1295,"titles":1296,"content":1297,"level":207},"/blog/loading-relationship-data-via-pydantic-schema-in-fastapi#why-not-use-it-with-other-relationships","Why not use it with other relationships?",[97,1290],"When you use lazy=\"joined\", SQLAlchemy will fetch data in single query. Now, assume there's social media app where user has many posts via One-to-Many. If you use lazy=\"joined\" with posts relationship, it will fetch all posts of user in single query which can be huge data and slow down the response time. E.g. When you fetch user details it'll fetch thousands of posts along with it. You can think of related example yourself for Many-to-Many relationship. Hence, never use lazy=\"joined\" with One-to-Many or Many-to-Many relationship. At simplest, avoid using lazy=\"joined\" with Mapped[list[...]]",{"id":1299,"title":1300,"titles":1301,"content":296,"level":191},"/blog/loading-relationship-data-via-pydantic-schema-in-fastapi#many-to-one-many-to-many-relationship","Many-to-One & Many-to-Many Relationship",[97],{"id":1303,"title":1304,"titles":1305,"content":1306,"level":207},"/blog/loading-relationship-data-via-pydantic-schema-in-fastapi#its-wip","It's WIP 🚧",[97,1300],"When you've Many-to-One or Many-to-Many relationship, you can use lazy=\"write_only\" in relationship col like below: class User(Base):\n    # ...\n\n    # Notice here we used \"write_only\" because it's list\n    posts: Mapped[list[\"Post\"]] = relationship(init=False, back_populates=\"user\", lazy=\"write_only\")\n    # posts: WriteOnlyMapped[list[\"Post\"]] = relationship(init=False, back_populates=\"user\")\n\nclass Post(Base):\n    # ...\n\n    # Notice here we used \"joined\" because it's single item\n    user: Mapped[User] = relationship(init=False, back_populates=\"addresses\", lazy=\"joined\")\n\nclass UserRead(BaseModel):\n    id: int\n    name: str\n    posts: list[PostRead] This will tell SQLAlchemy to not load address data along with user data whenever user is fetched. Instead, we'll manually fetch posts data by pagination or filter. user_db = db.get(User, user_id)\nuser_posts = user_db.posts.limit(10).offset(0).all() html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}",{"id":102,"title":101,"titles":1308,"content":1309,"level":185},[],"Learn useful tips and best practices in Neo4j graph database. YouTube Video - Neo4j Crash Course",{"id":1311,"title":337,"titles":1312,"content":1313,"level":191},"/blog/neo4j-my-findings#cheatsheet",[101],"As we have SQL for querying relational data, neo4j has \"Cypher\" for querying graph data. // ----- Simple Queries ---\n\n// MATCH is similar to FROM in SQL\n// Just like Columns in SQL, we have Nodes and Relationships in Cypher\n// We refer nodes with parenthesis `()` and relationships with square brackets `[]`\n// 🚨 Below two lines are just for explanation & aren't valid cypher\nMATCH () // This means match a node\nMATCH (node) // This means match a node and assign it to a variable `node`. This like aliasing in SQL.\n\n// MATCH = FROM, RETURN = SELECT\nMATCH (n) RETURN n // This means match a node and return it. This is similar to SELECT * FROM table in SQL.\n\n// Assuming you're using DB given in the crash course video in resources.\n\n// Each node will have labels attached to it to identify the type of node. E.g. Person, Movie, etc.\nMATCH (player:PLAYER) RETURN player // This means match a node with label Player and return it.\n\n// Apart from label, Each node will also have properties attached to it. E.g. name, age, etc.\n// Retrieved properties will be returned as rows & columns in the result.\nMATCH (player:PLAYER) RETURN player.name // Get all nodes with label \"PLAYER\" and return its `name` property.\nMATCH (player:PLAYER) RETURN player.name, player.height // You can retrieve multiple properties as well.\n// When we query like above, name column will be named as `player.name` and height column will be named as `player.height`.\nMATCH (player:PLAYER) RETURN player.name AS name, player.height AS height // You can also change the name of columns. Now, We'll get `name` and `height` columns.\n\n// ----- Filtering Queries ---\nMATCH (player:PLAYER) WHERE player.name = 'Keanu Reeves' RETURN player // Get all nodes with label \"PLAYER\" and name \"Keanu Reeves\"\nMATCH (player:PLAYER {name: 'Keanu Reeves'}) RETURN player // This is a shorthand for above query.\nMATCH (player:PLAYER {name: 'Keanu Reeves', height: 2.7}) RETURN player // Query with multiple properties.\nMATCH (player:PLAYER) WHERE player.name \u003C> 'Keanu Reeves' RETURN player // Get all nodes with label \"PLAYER\" and name is not \"Keanu Reeves\"\nMATCH (player:PLAYER) WHERE player.height >= 2 RETURN player // Get players who are taller than 2 meter.\nMATCH (player:PLAYER) WHERE (player.weight / (player.height * player.height)) > 25 RETURN player // Get players who have BMI greater than 25.\nMATCH (player:PLAYER) WHERE player.weight >= 100 AND player.height \u003C= 2 RETURN player // Get players who are heavier than 100 and shorter than 2 meter.\nMATCH (player:PLAYER) WHERE player.weight >= 120 OR player.height  >= 2.1 RETURN player // Get players who are heavier than 120 or taller than 2.1 meter.\nMATCH (player:PLAYER) WHERE NOT player.weight >= 120 OR player.height  >= 2.1 RETURN player // Get players who are not heavier than 120 or taller than 2.1 meter.\nMATCH (player:PLAYER) WHERE player.name STARTS WITH 'K' RETURN player // Get players whose name starts with 'K'.\n\n// ----- LIMIT & SKIP ---\n// This is similar to LIMIT & OFFSET in SQL\nMATCH (player:PLAYER) RETURN player LIMIT 5 // Get first 5 players.\nMATCH (player:PLAYER) RETURN player SKIP 5 LIMIT 5 // Skip first 5 players and get next 5 players.\n\n// ----- ORDER BY ---\nMATCH (player:PLAYER) RETURN player ORDER BY player.height DESC // Get all players and order them by name.\n\n// ----- Query Multiple Entities ---\nMATCH (player:PLAYER), (coach:COACH) RETURN player, coach // Get all players and coaches.\nMATCH (player:PLAYER), (coach:COACH) WHERE player.height >= 2 RETURN player, coach // Get all players and coaches where player height is greater than 2.\n\n// ----- Query Relationships ---\nMATCH (player:PLAYER) -[:PLAYS_FOR]-> (team:TEAM) WHERE team.name = 'LA Lakers' RETURN players html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":106,"title":105,"titles":1315,"content":1316,"level":185},[],"Learn useful tips and best practices in Pandas data manipulation library.",{"id":1318,"title":337,"titles":1319,"content":1320,"level":191},"/blog/pandas-my-findings#cheatsheet",[105],"import pandas as pd\n\n# Create a DataFrame\n# df = pd.DataFrame([[1,2,3], [4,5,6], [7,8,9]], columns=['A', 'B', 'C'])\ndf = pd.DataFrame({\n    'A': [1, 4, 7],\n    'B': [2, 5, 8],\n    'C': [3, 6, 9],\n})\n\ndf.head() # Get the first 5 rows\ndf.head(2) # Get the first 2 rows\n\ndf.tail() # Get the last 5 rows\ndf.tail(2) # Get the last 2 rows\n\ndf['A'].head() # Get the first 5 rows of a column\ndf['A'].tail() # Get the last 5 rows of a column\n\ndf[['A', 'B']].head() # Get the first 5 rows of multiple columns\ndf[['A', 'B']].tail() # Get the last 5 rows of multiple columns\n\ndf.sample() # Get a random row\ndf.sample(10) # Get 10 random rows\n\ndf[\"A\"] # Get a column\ndf.A # Get a column\ndf[[\"A\", \"B\"]] # Get multiple columns\n\n# df.loc[row_index, column_name]\n# df.iloc[row_index, column_index]\ndf.loc[0] # Get the first row\ndf.loc[0:2] # Get the first 3 rows\ndf.loc[[0, 1, 2]] # You can also pass a list of indexes\ndf.loc[0:2, 'A'] # Get the first 3 rows of a column\ndf.loc[0:2, ['A', 'B']] # Get the first 3 rows of multiple columns\ndf.loc[[0, 2], ['A', 'B']] # Get the first and third rows of multiple columns\ndf.iloc[:, [0, 1]] # Get all rows of the first 2 columns\n\ndf.loc[0, 'A'] = 10 # Set the \"A\" col value of the first row to 10\ndf.loc[0, ['A', 'B']] = [10, 20] # Set the \"A\" and \"B\" col values of the first row to 10 and 20\ndf.loc[0:2, 'A'] = 10 # Set the \"A\" col values of the first 3 rows to 10\n\n# * at and iat are faster than loc and iloc but they only work with a single value\ndf.at[0, 'A'] # Get the value of the first row of the \"A\" column\ndf.iat[0, 0] # Get the value of the first row of the first column\ndf.at[0, 'A'] = 10 # Set the value of the first row of the \"A\" column to 10\ndf.iat[0, 0] = 10 # Set the value of the first row of the first column to 10\ndf.at[0:3, 'A'] # 🚨 ERROR\n\ndf.shape   # Get the shape of the DataFrame\ndf.columns # Get the columns of the DataFrame\ndf.index   # Get the index of the DataFrame\ndf.values  # Get the values of the DataFrame\ndf.dtypes  # Get the data types of the DataFrame\ndf.info()  # Get the info of the DataFrame\ndf.describe() # Get the statistics of the DataFrame\n\n# Load files\npd.read_csv('path/to/file.csv')\npd.read_csv('remote_url.csv')\npd.read_json('path/to/file.json')\n\n# Dump data in file\ndf.to_csv(\"file_name.csv\")\ndf.to_json(\"file_name.json\") html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":110,"title":109,"titles":1322,"content":1323,"level":185},[],"Learn useful tips and best practices in programming.",{"id":1325,"title":1326,"titles":1327,"content":1328,"level":207},"/blog/programming-my-findings#multiprocessing-vs-multithreading-vs-asyncio","Multiprocessing vs Multithreading vs Asyncio",[109],"if io_bound:\n    if io_very_slow:\n        print(\"Use Asyncio\")\n    else:\n        print(\"Use Threads\")\nelse:\n    print(\"Multi Processing\") CPU Bound => Multi ProcessingI/O Bound, Fast I/O, Limited Number of Connections => Multi ThreadingI/O Bound, Slow I/O, Many connections => Asyncio Source: StackOverflow Question",{"id":1330,"title":1331,"titles":1332,"content":1333,"level":207},"/blog/programming-my-findings#processes-vs-threads","Processes vs Threads",[109],"Watch video html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":114,"title":113,"titles":1335,"content":1336,"level":185},[],"A comprehensive guide to handling date and time in Python using the datetime module. Naive datetime doesn't have any timezone information. Aware datetime has timezone information.",{"id":1338,"title":1339,"titles":1340,"content":1341,"level":191},"/blog/python-and-managing-date-and-time#datetimedate","datetime.date",[113],"import datetime\n\nd = datetime.date(2023, 9, 20) # (year, month, date) 20th September 2023\n# ❗ Leading zero may give error: datetime.date(2023, 09, 20) => Error\n\n# get today\ntoday = datetime.date.today()\nprint(today) # 2023-09-20\nprint(today.year) # 2023\nprint(today.month) # 9\nprint(today.day) # 20\n\n'''\nWeek day\n\n(on 2023-09-20 it's Wednesday)\n\n`weekday` => Monday = 0, Sunday = 6\n`isoweekday` =>  Monday = 1, Sunday = 7\n'''\nprint(today.weekday()) # 2\nprint(today.isoweekday()) # 3",{"id":1343,"title":1344,"titles":1345,"content":1346,"level":191},"/blog/python-and-managing-date-and-time#datetimetimedelta","datetime.timedelta",[113],"import datetime\n\ntoday = datetime.date.today()\ndelta_1_day = datetime.timedelta(days=1)\n\nprint(today + delta_1_day) # 2023-09-21\n\n'''\ndate - date = timedelta\ndate + timedelta = date\ndate - timedelta = date\ntimedelta + timedelta = timedelta\n'''\n\nmy_birthday_next_year = datetime.date(2024, 1, 28)\ndelta_till_birthday = my_birthday_next_year - today\nprint(delta_till_birthday) # 130 days, 0:00:00 (130 days left in my birthday)\nprint(delta_till_birthday.days) # 130",{"id":1348,"title":1349,"titles":1350,"content":1351,"level":191},"/blog/python-and-managing-date-and-time#datetimetime","datetime.time",[113],"Less used compared to others import datetime\n\nt = datetime.time(9, 30, 45, 100000)\nprint(t.hour) # 9",{"id":1353,"title":1354,"titles":1355,"content":1356,"level":191},"/blog/python-and-managing-date-and-time#datetimedatetime","datetime.datetime",[113],"import datetime\n\ndt = datetime.datetime(2023, 9, 20, 9, 30, 45, 100000)\n\nprint(dt) # 2023-09-20 09:30:45.100000\nprint(dt.date()) # 2023-09-20\nprint(dt.time()) # 09:30:45.100000\nprint(dt.year) # 2023\n\ndelta_1_day = datetime.timedelta(days=1)\nprint(dt + delta_1_day) # 2023-09-21 09:30:45.100000\n\ndelta_12_hours = datetime.timedelta(hours=12)\nprint(dt + delta_12_hours) # 2023-09-20 21:30:45.100000",{"id":1358,"title":1359,"titles":1360,"content":1361,"level":218},"/blog/python-and-managing-date-and-time#today-vs-now-vs-utcnow","today vs now vs utcnow",[113,1354],"import datetime\n\ndt_today = datetime.datetime.today()\ndt_now = datetime.datetime.now()\ndt_utcnow = datetime.datetime.utcnow()\n\nprint(dt_today)  # 2023-09-20 09:04:31.564997\nprint(dt_now)    # 2023-09-20 09:04:31.565011\nprint(dt_utcnow) # 2023-09-20 03:34:31.565013\n\n'''\n`dt_today` & `dt_now` are almost same because execute at same time.\nThe difference: `.today()` returns current local datetime with timezone set to `None` whereas `.now()` allows us to pass timezone.\nHence, if you leave timezone empty for both they will be same.\n'''\n\n# `dt_utcnow` is still naive datetime. It doesn't have timezone information.",{"id":1363,"title":1364,"titles":1365,"content":1366,"level":218},"/blog/python-and-managing-date-and-time#pytz-and-timezone-aware-datetime","pytz and timezone aware datetime",[113,1354],"pytz is third-party module that is recommend by python itself in their docs. import datetime\n\nimport pytz\n\ndt = datetime.datetime(2023, 9, 20, 9, 30, 45, 100000, tzinfo=pytz.UTC)\nprint(dt) # 2023-09-20 09:30:45.100000+00:00\n\n# Current UTC time that is timezone aware\ndt_now = datetime.datetime.now(tz=pytz.UTC) # ℹ️ Recommended\nprint(dt_now) # 2023-09-20 08:49:21.452949+00:00\n\ndt_uctnow = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)\nprint(dt_uctnow) # 2023-09-20 08:49:21.453095+00:00\n\n'''\n`dt_now` & `dt-utcnow` is identical because both are timezone aware.\n`dt_now` is recommended though.\n'''\n\ndt_india = dt_now.astimezone(pytz.timezone('Asia/Kolkata'))\nprint(dt_india) # 2023-09-20 14:22:36.130913+05:30 for tz in pytz.all_timezones:\n    print(tz)",{"id":1368,"title":1369,"titles":1370,"content":1371,"level":218},"/blog/python-and-managing-date-and-time#how-to-make-naive-datetime-timezone-aware","How to make naive datetime, timezone aware",[113,1354],"Refer to this snippet. Use iso format to pass datetime around or for saving. Refer to this tip.",{"id":1373,"title":1374,"titles":1375,"content":1376,"level":191},"/blog/python-and-managing-date-and-time#printing-datetime-converting-back","Printing datetime & Converting back",[113],"strftime => datetime to stringstrptime => string to datetime",{"id":1378,"title":1379,"titles":1380,"content":1381,"level":207},"/blog/python-and-managing-date-and-time#printing-or-converting-datetime-to-string","Printing or converting datetime to string",[113,1374],"You can use strftime method to print datetime in any format you want. Check out all format codes with example in official python docs. import datetime\n\nimport pytz\n\n# local time without timezone info\ndt_india = datetime.datetime.now(tz=pytz.timezone('Asia/Kolkata'))\nprint(dt_india.isoformat())\n\nprint(dt_india.strftime('%B %d, %Y')) # September 20, 2023",{"id":1383,"title":1384,"titles":1385,"content":1386,"level":207},"/blog/python-and-managing-date-and-time#convert-string-datetime-to-datetime-object","Convert string datetime to datetime object",[113,1374],"import datetime\n\ndt_str = 'September 20, 2023'\n\n# Use `strptime` to convert string datetime to datetime object\n# Second argument is what format that string is in\ndt = datetime.datetime.strptime(dt_str, '%B %d, %Y')\nprint(dt) # 2023-09-20 00:00:00 Corey Schafer - Datetime Module - How to work with Dates, Times, Timedeltas, and Timezones html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}",{"id":118,"title":117,"titles":1388,"content":1389,"level":185},[],"A collection of common Python interview code challenges with solutions.",{"id":1391,"title":1392,"titles":1393,"content":1394,"level":191},"/blog/python-interview-code-challenges#prime-numbers","Prime Numbers",[117],"Prime numbers are numbers that are divisible by only 1 and themselves. For example, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, etc. are prime numbers. def is_prime(n: int):\n    if n \u003C 2:\n        return False\n\n    for i in range(2, n):\n        if n % i == 0:\n            return False\n\n    return True\n\ndef get_nth_prime_number(n: int):\n    i = 2\n    count = 0\n\n    while count \u003C n:\n        if is_prime(i):\n            count += 1\n        i += 1\n\n    return i - 1\n\ndef get_n_prime_numbers(n: int):\n    primes = []\n    i = 2\n\n    while len(primes) \u003C n:\n        if is_prime(i):\n            primes.append(i)\n        i += 1\n\n    return primes",{"id":1396,"title":1397,"titles":1398,"content":1399,"level":191},"/blog/python-interview-code-challenges#fibonacci-numbers","Fibonacci Numbers",[117],"Fibonacci numbers are numbers in the sequence 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, etc. where each number is the sum of the two preceding ones. def get_nth_fibonacci_numbers(n: int):\n    prev, next = 0, 1\n\n    if n == 0:\n        return prev\n    elif n == 1:\n        return next\n\n    for _ in range(3, n + 1):\n        prev, next = next, prev + next\n\n    return next\n\ndef get_nth_fibonacci_numbers_using_recursion(n: int):\n    if n == 0:\n        return 0\n    elif n == 1:\n        return 1\n    else:\n        return fibonacci(n - 1) + fibonacci(n - 2)\n\ndef get_fibonacci_numbers_up_to_n(n: int):\n    prev, next = 0, 1\n    fibonacci_numbers = [prev, next]\n\n    while next \u003C n:\n        prev, next = next, prev + next\n        fibonacci_numbers.append(next)\n\n    return fibonacci_numbers\n\ndef get_n_fibonacci_numbers(n: int):\n    nums = [0, 1]\n\n    for i in range(3, n + 1):\n        nums.append(nums[-1] + nums[-2])\n\n    return nums",{"id":1401,"title":1402,"titles":1403,"content":1404,"level":191},"/blog/python-interview-code-challenges#mean-median-and-mode","Mean, Median, and Mode",[117],"Mean: The mean is the average of a set of numbers. It is calculated by summing all the numbers in the set and dividing by the total count of numbers.  def mean(data: list[int]):\n      return sum(data) / len(data)\nMedian: The median is the middle value of a set of numbers when they are ordered. If the set has an odd number of elements, the median is the middle value. If the set has an even number of elements, the median is the average of the two middle values.  def median(data: list[int]):\n      sorted_data = sorted(data)\n      length = len(sorted_data)\n      middle_index = length // 2\n\n      if length % 2 == 0:\n          return (sorted_data[middle_index - 1] + sorted_data[middle_index]) / 2\n      else:\n          return sorted_data[middle_index]\nMode: The mode is the value that appears most frequently in a set of numbers.  from collections import defaultdict\n\n  def mode(data: list[int]):\n      freq = defaultdict(int)\n      for i in data:\n          freq[i] += 1\n\n      max_freq = max(freq.values())\n\n      for i in data:\n          if freq[i] == 2:\n              return i Python's statistics module also provides functions for calculating the mean, median, and mode: import statistics\n\ndata = [1, 2, 3, 4, 5, 5, 6, 7, 8, 9]\n\nmean = statistics.mean(data)\nmedian = statistics.median(data)\nmode = statistics.mode(data)",{"id":1406,"title":1407,"titles":1408,"content":1409,"level":191},"/blog/python-interview-code-challenges#pagination","Pagination",[117],"from typing import Any\n\ndef paginate(data: list[Any], page: int = 1, items_per_page: int = 10):\n    start = (page - 1) * items_per_page\n    end = page * items_per_page\n\n    return data[start:end] html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":122,"title":121,"titles":1411,"content":1412,"level":185},[],"A comprehensive guide to logging in Python using the built-in logging module.",{"id":1414,"title":1415,"titles":1416,"content":1417,"level":191},"/blog/python-logging#basic-logging","Basic Logging",[121],"Logging demonstrated below is fine for single module/file logging. But, for larger projects, it's recommended that you create a logger object like in next section. YouTube Video - Python Tutorial: Logging Basics - Logging to Files, Setting Levels, and Formatting 5 levels of logging in Python:\nDEBUG: Detailed information, typically of interest only when diagnosing problems.INFO: Confirmation that things are working as expected.WARNING: An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.ERROR: Due to a more serious problem, the software has not been able to perform some function.CRITICAL: A serious error, indicating that the program itself may be unable to continue running.Default logging level is WARNING. So, levels having less severity than WARNING won't show up. import logging\n\nlogging.debug('This is a debug message') # This won't show up because default level is WARNING\nlogging.info('This is an info message') # This won't show up because default level is WARNING\nlogging.warning('This is a warning message') # This will show up\nlogging.error('This is an error message') # This will show up\nlogging.critical('This is a critical message') # This will show up\n\nlogging.basicConfig(level=logging.DEBUG) # This will set the logging level to DEBUG\nlogging.debug('This is a debug message') # This will show up now because level is set to DEBUG By default, logs are written to your console. You can also write logs to a file. logging.basicConfig(filename='logs/app.log', level=logging.DEBUG)\nlogging.debug('This is a debug message') # This will be written to logs/app\n# Content of log file => DEBUG:root:This is a debug message\n# Default format of logging => LEVEL:LOGGER_NAME:MESSAGE\n\nlogging.basicConfig(filename='logs/app.log', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # Use format param to change the format of log message\n\nlogging.debug('This is a debug message')\n# Content of log file => 2024-08-06 12:00:00,000 - DEBUG - This is a debug message",{"id":1419,"title":1420,"titles":1421,"content":1422,"level":191},"/blog/python-logging#advanced-logging","Advanced Logging",[121],"When we log using logging it uses root logger and that's the reason why we get root in the log message. When using multiple modules/files, if we don't create seperate logger objects, we might get some unwanted behavior. For example, When you import a module and that module configures logging, it will affect the logging configuration of your main program. import logging\n\n# First get the logger object\n# If you're running script then __name__ will be __main__.\n# If you're importing this module then __name__ will be the name of the module/file. E.g. If it's in app.py then __name__ will be \"app\"\nlogger = logging.getLogger(__name__)\n\n# Now, you can configure & use this logger object\nlogger.setLevel(logging.DEBUG)\n\n# Let's save logs to file\n# First create a file handler\nfile_handler = logging.FileHandler('logs/app.log')\nfile_handler.setLevel(logging.ERROR) # This will only log messages with level ERROR or higher\n\n# Let's customize the format of log message for file handler\nformatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')\nfile_handler.setFormatter(formatter) # Here, we're setting the format of log message for file handler\n\n# Finally, add the file handler to logger object\nlogger.addHandler(file_handler)\n\nlogger.debug('This is a debug message') # This won't show up in file because level is set to ERROR\nlogger.error('This is an error message') # This will show up in file\n\n# Now, Assume you want to log to console as well along with file\nstream_handler = logging.StreamHandler()\nstream_handler.setFormatter(formatter) # We can reuse our formatter\n\n# Notice, We haven't set level for stream handler. So, it will fallback to main logger's level which is DEBUG\n\n# Finally, add the stream handler to logger object\nlogger.addHandler(stream_handler)\n\nlogger.debug('This is a debug message') # This will show up in console but not in file\nlogger.error('This is an error message') # This will show up in both console and file",{"id":1424,"title":1425,"titles":1426,"content":1427,"level":191},"/blog/python-logging#how-i-setup-logger-in-large-projects","How I setup logger in large projects",[121],"Here's how I setup my logger in large projects. I also create multiple loggers like app logger that just logs execution of application and mail logger that logs all the emails related stuff. I prefer creating separate directory for all loggers. |- src/\n  |- loggers/\n    |- app.py\n    |- mail.py In production you will preserve logs for 60-90 days and delete old logs. Python logging module provides RotatingFileHandler which can be used to rotate logs based on size or time. # app.py\nfrom paths import project_root_path\nfrom core.settings import settings # I use pydantic settings\n\napp_logger = logging.getLogger(__name__) # Logger name will be \"app\" because we're using __name__\n\n# Generate log file name\nfile_name = Path(__file__).stem\nlogger_file_name = (\n    project_root_path / \"logs\" / file_name / f\"{file_name}.log\"\n) # This means our logs will be saved in \u003Croot>/logs/app/app.log\n\n# create handler\nhandler = TimedRotatingFileHandler(\n    filename=logger_file_name,\n    when=\"D\",\n    interval=1,\n    backupCount=90 if settings.ENVIRONMENT == \"production\" else 1, # Keep logs for 90 days in production and 1 day in development\n    encoding=\"utf-8\",\n    delay=False,\n)\n\n# create formatter and add to handler\nformatter = logging.Formatter(\n    fmt=\"%(asctime)s - %(levelname)s - %(message)s\"\n)\nhandler.setFormatter(formatter)\n\n# add the handler to logger\napp_logger.addHandler(handler)\n\n# set the logging level\napp_logger.setLevel(logging.INFO) Now, you can use this logger in your application. from loggers.app import app_logger\n\napp_logger.info(\"Application started\") Cheers 🥂, We have logger that logs to file and most importantly, it preserves logs for 90 days in production and delete old logs. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":126,"title":125,"titles":1429,"content":1430,"level":185},[],"Learn useful tips and best practices in Python programming language. ResourcesPython CheatsheetEffective Python Async like a PRODeveloping an Asynchronous Task Queue in Python",{"id":1432,"title":337,"titles":1433,"content":296,"level":191},"/blog/python-my-findings#cheatsheet",[125],{"id":1435,"title":1436,"titles":1437,"content":296,"level":207},"/blog/python-my-findings#pathlib","Pathlib",[125,337],{"id":1439,"title":1440,"titles":1441,"content":1442,"level":218},"/blog/python-my-findings#get-current-file-direction","Get current file direction",[125,337,1436],"import pathlib\n\n# Get current file directory\ncurr_dir = pathlib.Path(__file__).parent.resolve()\n\n# Get current working directory\ncwd = pathlib.Path().resolve()\n\n# Join paths using `/`\nemoji_file_path = curr_dir / 'emoji-test.txt'\n\n# Read file content\nemoji_data = emoji_file_path.read_text()",{"id":1444,"title":1445,"titles":1446,"content":296,"level":207},"/blog/python-my-findings#list","List",[125,337],{"id":1448,"title":1449,"titles":1450,"content":1451,"level":218},"/blog/python-my-findings#get-index-while-iterating-over-list","Get index while iterating over list",[125,337,1445],"names = [\"Tony\", \"Steve\", \"Thor\", \"Bruce\"]\n\nfor index, name in enumerate(names):\n    print(f\"{index}: {name}\")",{"id":1453,"title":1454,"titles":1455,"content":296,"level":207},"/blog/python-my-findings#dict","Dict",[125,337],{"id":1457,"title":1458,"titles":1459,"content":1460,"level":218},"/blog/python-my-findings#various-ways-to-iterate-over-dict","Various ways to iterate over dict",[125,337,1454],"names = {\"Tony\": \"Stark\", \"Steve\": \"Rogers\", \"Thor\": \"Odinson\", \"Bruce\": \"Banner\"}\n\n# Iterate over keys\nfor key in names:\n    print(key)\n\n# Iterate over values\nfor value in names.values():\n    print(value)\n\n# Iterate over keys and values\nfor key, value in names.items():\n    print(f\"{key}: {value}\")",{"id":1462,"title":1463,"titles":1464,"content":296,"level":207},"/blog/python-my-findings#set","Set",[125,337],{"id":1466,"title":1467,"titles":1468,"content":1469,"level":218},"/blog/python-my-findings#basics","Basics",[125,337,1463],"s = {'a', 'b', 'c', 'd', 'e'}\n\ns = {} # 🚨 This will create an empty dict, not a set\ns = set() # This will create an empty set\n\ns.add('a') # {'a'}\n\ns.update(['a', 'b', 'c']) # {'a', 'b', 'c'}\nanother_set = {'f', 'g'}\ns.update(['d', 'e'], another_set) # {'a', 'b', 'c', 'd', 'e', 'f', 'g'}\n\ns.remove('c') # {'a', 'b', 'd', 'e', 'f', 'g'}\ns.remove('h') # 🚨 KeyError: 'h'\ns.discard('h') # No error, Set value: {'a', 'b', 'd', 'e', 'f', 'g'}\n\ns1 = {\"a\", \"b\", \"c\"}\ns2 = {\"b\", \"c\", \"d\"}\ns3 = {\"c\", \"d\", \"e\"}\ns1.intersection(s2) # {'b', 'c'}\ns1.intersection(s2, s3) # {'c'}\ns1.difference(s2) # {'a'}\ns2.difference(s1) # {'d'}\ns1.symmetric_difference(s2) # {'a', 'd'}\ns2.symmetric_difference(s1) # {'a', 'd'}\ns2.difference(s1, s3) # set()\ns3.difference(s2, s1) # {'e'}",{"id":1471,"title":1472,"titles":1473,"content":1474,"level":218},"/blog/python-my-findings#difference-between-two-sets","Difference between two sets",[125,337,1463],"Credits: Tweet names = {\"Mike\", \"Pinky\", \"Brain\", \"Dot\"}\nother_names = {\"Brain\", \"Yakko\", \"Wacko\", \"Rita\"}\nprint(names - other_names) # {'Pinky', 'Dot', 'Mike'}",{"id":1476,"title":1477,"titles":1478,"content":296,"level":207},"/blog/python-my-findings#enum","Enum",[125,337],{"id":1480,"title":1481,"titles":1482,"content":1483,"level":218},"/blog/python-my-findings#string-enum","String Enum",[125,337,1477],"from enum import StrEnum # since python3.11\n\nclass Fruits(StrEnum):\n    APPLE = 'Apple'\n    BANANA = 'Banana'",{"id":1485,"title":1486,"titles":1487,"content":1488,"level":218},"/blog/python-my-findings#numberedinteger-enum","Numbered/Integer Enum",[125,337,1477],"from enum import IntEnum\n\nclass Fruits(IntEnum):\n    VALID = 1\n    INVALID = 0",{"id":1490,"title":1491,"titles":1492,"content":1493,"level":218},"/blog/python-my-findings#base-enum","Base Enum",[125,337,1477],"from enum import Enum\n\nclass Fruits(Enum):\n    APPLE = 'Apple'\n    BANANA = 'Banana'\n    VALID = 1\n    INVALID = 0",{"id":1495,"title":1496,"titles":1497,"content":296,"level":207},"/blog/python-my-findings#packing-unpacking","Packing & Unpacking",[125,337],{"id":1499,"title":1500,"titles":1501,"content":1502,"level":218},"/blog/python-my-findings#packing-variable","Packing Variable",[125,337,1496],"Credits: Tweet a, *b, c = [1, 2, 3, 4, 5]\nprint(b) # [2, 3, 4]",{"id":1504,"title":1505,"titles":1506,"content":296,"level":207},"/blog/python-my-findings#decorators","Decorators",[125,337],{"id":1508,"title":1509,"titles":1510,"content":1511,"level":218},"/blog/python-my-findings#simple-decorator","Simple Decorator",[125,337,1505],"from collections.abc import Callable\nfrom functools import wraps\n\ndef log[T, **P](func: Callable[P, T]) -> Callable[P, T]:\n    @wraps(func)\n    def wrapper(*args: P.args, **kwargs: P.kwargs):\n        print(\"before\")\n        func(*args, **kwargs)\n        print(\"after\")\n\n    return wrapper\n\n@log\ndef greet():\n    print(\"Hello\")\n\ngreet()\n'''\nbefore\nHello\nafter\n'''",{"id":1513,"title":1514,"titles":1515,"content":1516,"level":218},"/blog/python-my-findings#decorator-with-parameters","Decorator with Parameters",[125,337,1505],"import random\nfrom contextlib import suppress\nfrom functools import wraps\nfrom collections.abc import Callable\nfrom functools import wraps\n\ndef retry(max_retries: int):\n    def decorator[T, **P](func: Callable[P, T]) -> Callable[P, T]:\n        @wraps(func)\n        def wrapper(*args: P.args, **kwargs: P.kwargs):\n            for _ in range(max_retries):\n                with suppress(Exception):\n                    return func(*args, **kwargs)\n            else:\n                raise Exception(\"Max retries limit reached!\")\n        return wrapper\n    return decorator\n\n@retry(3)\ndef only_roll_highs():\n    number = random.randint(1,6)\n    if number \u003C 5:\n        raise ValueError(number)\n    return number\n\nprint(only_roll_highs()) # 5/6/Exception",{"id":1518,"title":1519,"titles":1520,"content":296,"level":207},"/blog/python-my-findings#testing","Testing",[125,337],{"id":1522,"title":1523,"titles":1524,"content":1525,"level":218},"/blog/python-my-findings#patching-env-variables","Patching env variables",[125,337,1519],"Source code: def greet():\n    username = os.environ['USER']\n    return f\"Hello, {username}\"",{"id":1527,"title":1528,"titles":1529,"content":1530,"level":1531},"/blog/python-my-findings#_1-using-unittests-mock","1. Using unittest's mock",[125,337,1519,1523],"You can use this method even in pytest tests. from unittest import mock\n\ndef test_greet(capsys):\n    with mock.patch.dict(os.environ, {\"USER\": \"Tony\"}):\n        greet()\n\n    out, = capsys.readouterr()\n    assert out == \"Hello, Tony\"",5,{"id":1533,"title":1534,"titles":1535,"content":1536,"level":1531},"/blog/python-my-findings#_2-using-pytests-monkeypatch","2. Using pytest's monkeypatch",[125,337,1519,1523],"def test_greet(capsys, monkeypatch):\n    monkeypatch.setenv(\"USER\", \"Tony\")\n\n    greet()\n\n    out, = capsys.readouterr()\n    assert out == \"Hello, Tony\"",{"id":1538,"title":1539,"titles":1540,"content":296,"level":207},"/blog/python-my-findings#networking","Networking",[125,337],{"id":1542,"title":1543,"titles":1544,"content":1545,"level":218},"/blog/python-my-findings#validate-ip-address","Validate IP Address",[125,337,1539],"Credits: Python Papers Newsletter by Mike Driscoll import ipaddress\n\ndef is_valid_ip(ip):\n    try:\n        ipaddress.ip_address(ip)\n        return True\n    except ValueError:\n        return False\n\nprint(is_valid_ip(\"192.168.5.1\")) # False",{"id":1547,"title":1548,"titles":1549,"content":1550,"level":207},"/blog/python-my-findings#weird-python","Weird Python",[125,337],"# Booleans are subclass of integers in Python. True => 1, False => 0\n0 == False # True\n1 == True # True\n[\"hello\", \"world\"][False] # \"hello\" (Explanation => [\"hello\", \"world\"][0])\n[\"hello\", \"world\"][True] # \"world\" (Explanation => [\"hello\", \"world\"][1])\nisinstace(True, int) # True",{"id":1552,"title":492,"titles":1553,"content":296,"level":191},"/blog/python-my-findings#snippets",[125],{"id":1555,"title":467,"titles":1556,"content":296,"level":207},"/blog/python-my-findings#functions",[125,492],{"id":1558,"title":1559,"titles":1560,"content":1561,"level":218},"/blog/python-my-findings#execute-function-immediately-in-python-iife","Execute function immediately in python (IIFE)",[125,492,467],"@lambda f:f()\ndef say():\n    print(f\"hello!\")",{"id":1563,"title":1564,"titles":1565,"content":1566,"level":218},"/blog/python-my-findings#throttle-function","Throttle function",[125,492,467],"import time\nimport functools\n\ndef basic_throttle(calls_per_second):\n    def decorator(func):\n\n        last_called = 0.0\n        count = 0\n\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            nonlocal last_called, count\n            current_time = time.time()\n\n            # Reset counter if new second\n            if current_time - last_called >= 1:\n                last_called = current_time\n                count = 0\n\n            # Enforce the limit\n            if count \u003C calls_per_second:\n                count += 1\n                return func(*args, **kwargs)\n\n            return None\n\n        return wrapper\n    return decorator\n\n@basic_throttle(5)\ndef send_alert():\n    print(f\"Alert !\")\n\nfor i in range(10):\n    send_alert()\n    time.sleep(0.1)\n\n'''\nAlert !\nAlert !\nAlert !\nAlert !\nAlert !\n'''",{"id":1568,"title":1569,"titles":1570,"content":296,"level":207},"/blog/python-my-findings#os-io","OS & I/O",[125,492],{"id":1572,"title":1573,"titles":1574,"content":1575,"level":218},"/blog/python-my-findings#prepending-text-to-file","Prepending text to file",[125,492,1569],"def prepend_text(filename: Union[str, Path], text: str):\n    with fileinput.input(filename, inplace=True) as file:\n        for line in file:\n            if file.isfirstline():\n                print(text)\n            print(line, end=\"\")",{"id":1577,"title":1578,"titles":1579,"content":296,"level":207},"/blog/python-my-findings#date-time-timezones","Date, Time & Timezones",[125,492],{"id":1581,"title":1369,"titles":1582,"content":1583,"level":218},"/blog/python-my-findings#how-to-make-naive-datetime-timezone-aware",[125,492,1578],"import datetime\nimport pytz\n\n# local time without timezone info\ndt_india = datetime.datetime.now()\n\n# If you try to convert this naive datetime in different timezone using `astimezone` it will give error\n\n# 1. Create timezone\ntz_india = pytz.timezone('Asia/Kolkata')\n\n# 2. Make naive datetime timezone aware using `localize`\ndt_india = tz_india.localize(dt_india)\nprint(dt_india) # 2023-09-20 14:31:03.941181+05:30\n\n# as `dt_india` is now timezone aware, you can convert it to any other timezone using `astimezone`",{"id":1585,"title":1586,"titles":1587,"content":1588,"level":218},"/blog/python-my-findings#walk-directory-recursively","Walk directory recursively",[125,492,1578],"from pathlib import Path\n\npath = Path(\"docs\")\nfor p in path.rglob(\"*\"):\n     print(p.name)",{"id":1590,"title":1591,"titles":1592,"content":296,"level":207},"/blog/python-my-findings#testing-snippets-testing","Testing {#snippets-testing}",[125,492],{"id":1594,"title":1595,"titles":1596,"content":1597,"level":218},"/blog/python-my-findings#use-capsys-fixture-with-types-in-pytest","Use capsys fixture with types in pytest",[125,492,1591],"from pytest import CaptureFixture // [!code hl]\nfrom src.main import app\nfrom typer.testing import CliRunner\n\nrunner = CliRunner()\n\ndef test_app(capsys: CaptureFixture[str]): // [!code hl]\n    result = runner.invoke(app, ['master', 'fill-code-snippets'])\n    assert result.exit_code == 0\n    with capsys.disabled():\n        print(f\"result.stdout: {result.stdout}\")",{"id":1599,"title":1505,"titles":1600,"content":296,"level":207},"/blog/python-my-findings#decorators-1",[125,492],{"id":1602,"title":1603,"titles":1604,"content":1605,"level":218},"/blog/python-my-findings#timerperformance-decorator","Timer/Performance decorator",[125,492,1505],"from time import perf_counter\nfrom functools import wraps\nfrom typing import Callable\n\ndef measure_exec_time[T, **P](func: Callable[P, T]) -> Callable[P, T]:\n    @wraps(func)\n    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:\n        start_time = perf_counter()\n        result = func(*args, **kwargs)\n        end_time = perf_counter()\n        print(f\"Execution time: {end_time - start_time}\")\n        return result\n\n    return wrapper\n\n@measure_exec_time\ndef slow_function():\n    time.sleep(2)\n    print(\"Done!\")\n\nslow_function()",{"id":1607,"title":1608,"titles":1609,"content":1610,"level":218},"/blog/python-my-findings#retry-decorator","Retry decorator",[125,492,1505],"Credits: ArjanCodes Repo import time\nimport math\nfrom functools import wraps\n\ndef retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):\n    \"\"\"Retry calling the decorated function using an exponential backoff.\n\n    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/\n    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry\n\n    :param ExceptionToCheck: the exception to check. may be a tuple of\n        exceptions to check\n    :type ExceptionToCheck: Exception or tuple\n    :param tries: number of times to try (not retry) before giving up\n    :type tries: int\n    :param delay: initial delay between retries in seconds\n    :type delay: int\n    :param backoff: backoff multiplier e.g. value of 2 will double the delay\n        each retry\n    :type backoff: int\n    :param logger: logger to use. If None, print\n    :type logger: logging.Logger instance\n    \"\"\"\n    def deco_retry(f):\n\n        @wraps(f)\n        def f_retry(*args, **kwargs):\n            mtries, mdelay = tries, delay\n            while mtries > 1:\n                try:\n                    return f(*args, **kwargs)\n                except ExceptionToCheck as e:\n                    msg = \"%s, Retrying in %d seconds...\" % (str(e), mdelay)\n                    if logger:\n                        logger.warning(msg)\n                    else:\n                        print(msg)\n                    time.sleep(mdelay)\n                    mtries -= 1\n                    mdelay *= backoff\n            return f(*args, **kwargs)\n\n        return f_retry  # true decorator\n\n    return deco_retry\n\n@retry(Exception, tries=4)\ndef test_fail(text):\n    raise Exception(\"Fail\")\n\ntest_fail(\"it works!\")",{"id":1612,"title":1613,"titles":1614,"content":1615,"level":218},"/blog/python-my-findings#exception-logging-decorator","Exception Logging Decorator",[125,492,1505],"Credits: ArjanCodes Repo import logging\nfrom functools import wraps\n\n# Example from: https://www.geeksforgeeks.org/create-an-exception-logging-decorator-in-python/\n\ndef create_logger():\n\n # create a logger object\n logger = logging.getLogger('exc_logger')\n logger.setLevel(logging.INFO)\n\n # create a file to store all the\n # logged exceptions\n logfile = logging.FileHandler('exc_logger.log')\n\n fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\n formatter = logging.Formatter(fmt)\n\n logfile.setFormatter(formatter)\n logger.addHandler(logfile)\n\n return logger\n\nlogger = create_logger()\n\n# you will find a log file\n# created in a given path\nprint(logger)\n\ndef exception(logger):\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            try:\n                return func(*args, **kwargs)\n            except:\n                issue = \"exception in \"+func.__name__+\"\\n\"\n                issue = issue+\"=============\\n\"\n                logger.exception(issue)\n                raise\n        return wrapper\n    return decorator\n\n@exception(logger)\ndef divideByZero():\n return 12/0\n\n# Driver Code\nif __name__ == '__main__':\n divideByZero()",{"id":1617,"title":1618,"titles":1619,"content":296,"level":207},"/blog/python-my-findings#design-patterns","Design Patterns",[125,492],{"id":1621,"title":1622,"titles":1623,"content":1624,"level":218},"/blog/python-my-findings#singleton","Singleton",[125,492,1618],"class Singleton(type):\n    def __init__(cls, *args, **kwargs):\n        cls.__instance = None\n        super().__init__(*args, **kwargs)\n\n    def __call__(cls, *args, **kwargs):\n        if cls.__instance is None:\n            cls.__instance = super().__call__(*args, **kwargs)\n            return cls.__instance\n        else:\n            return cls.__instance\n\nclass Logger(metaclass=Singleton):\n    def __init__(self):\n        print(\"Creating global Logger instance\")",{"id":1626,"title":1627,"titles":1628,"content":296,"level":191},"/blog/python-my-findings#tips","🪄 Tips",[125],{"id":1630,"title":304,"titles":1631,"content":296,"level":207},"/blog/python-my-findings#general",[125,1627],{"id":1633,"title":1634,"titles":1635,"content":1636,"level":218},"/blog/python-my-findings#always-install-packages-in-virtual-environment-forcefully","Always install packages in virtual environment forcefully",[125,1627,304],"# ~/.zshrc\nexport PIP_REQUIRE_VIRTUALENV=true Now if you try to install packages without activating virtual environment, it'll throw error. pip install requests\n# ERROR: Could not find an activated virtualenv (required).",{"id":1638,"title":1639,"titles":1640,"content":296,"level":207},"/blog/python-my-findings#performance","Performance",[125,1627],{"id":1642,"title":1643,"titles":1644,"content":1645,"level":218},"/blog/python-my-findings#caching-with-lru_cache-decorator","Caching with @lru_cache decorator",[125,1627,1639],"Use func_tools.lru_cache to cache the result of a expensive function call. from functools import lru_cache\nfrom time import sleep\n\n@lru_cachedef slow_func():\n    sleep(3)\n    print('Done!') You can also pass maxsize param. It specifies the maximum number of calls that can be cached. Beware of using @lru_cache on class methods as it can cause memory leaks. Refer to this video for more details. Get uncached version of function You can use slow_func.__wrapped__() to get uncached version of function. This is useful when writing test cases where you don't want to test cached output.",{"id":1647,"title":1648,"titles":1649,"content":296,"level":207},"/blog/python-my-findings#typing","Typing",[125,1627],{"id":1651,"title":1652,"titles":1653,"content":1654,"level":218},"/blog/python-my-findings#use-protocol-instead-of-callable-for-function-type","Use Protocol instead of Callable for function type",[125,1627,1648],"Instead of creating function type based on callable use Protocol. Protocol will allow you to write param names. from typing import Callable\n\nEmailSender = Callable[[str, str, str], None] Use below 👇 from typing import Protocol\n\nclass EmailSender(Protocol):\n    def __call__(self, to: str, subject: str, body: str) -> None: ...",{"id":1656,"title":1657,"titles":1658,"content":296,"level":207},"/blog/python-my-findings#list-tips-list","List {#tips-list}",[125,1627],{"id":1660,"title":1661,"titles":1662,"content":1663,"level":218},"/blog/python-my-findings#list-comprehension-vs-filter-vs-for-loop","List Comprehension vs filter vs for loop",[125,1627,1657],"List Comprehension (fastest): When you need a listFilter: When you need an iteratorFor loop: for complex conditions",{"id":1665,"title":1666,"titles":1667,"content":296,"level":207},"/blog/python-my-findings#error-handling","Error Handling",[125,1627],{"id":1669,"title":1670,"titles":1671,"content":1672,"level":218},"/blog/python-my-findings#exceptions-tips-error-handling-exceptions","Exceptions {#tips-error-handling-exceptions}",[125,1627,1666],"Write as minimum code as possible in try block to avoid catching unrelated exceptionsOnly catch exceptions that you are expecting. Avoid catching Exception or BaseExceptionYou can execute custom code when an exception is raised by first catching the exception and then re-raising it like below:try:\n    print(3/0)\nexcept ZeroDivisionError:\n    print(\"Oops! You can't divide by zero.\") # Your custom code\n    raise # re-raise the same exception",{"id":1674,"title":1675,"titles":1676,"content":1677,"level":207},"/blog/python-my-findings#classes","Classes",[125,1627],"Excellent video on guide to writing classes Use module instead of class if you are not creating multiple instances of class.Keep your classes small. Mostly probably, You can split large classes into multiple smaller classes. You can split them based on either \"Data Focused\" or \"Behavior Focused\". Refer to mentioned video for more details.Instead of lots of instance properties and related methods, use a dataclasses.Try to make your classes as flexible & dependency as possible. For example, If you want to send mail in method use Protocol or accept a function in method param instead of hardcoding low level SMTP or other third-party package in your class.If methods looks like a property, use @property decorator. It'll make your code more readable. Credits: ArjanCodes YouTube channelanthonywritescode YouTube channel",{"id":1679,"title":1680,"titles":1681,"content":1682,"level":207},"/blog/python-my-findings#date-time-timezones-tips-date-time-timezones","Date, Time & Timezones {#tips-date-time-&-timezones}",[125,1627],"To pass date, time, etc around your application or save them, use iso format. import datetime\nimport pytz\n\n# local time without timezone info\ndt_india = datetime.datetime.now(tz=pytz.timezone('Asia/Kolkata'))\nprint(dt_india.isoformat()) # 2023-09-20T14:43:49.865596+05:30 html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}",{"id":130,"title":129,"titles":1684,"content":1685,"level":185},[],"An introduction to Object-Oriented Programming (OOP) concepts in Python, including Inheritance, Encapsulation, and Polymorphism.",{"id":1687,"title":1688,"titles":1689,"content":1690,"level":191},"/blog/python-oop-concepts#inheritance","👨‍👦 Inheritance",[129],"Inheritance is a way of creating a new class by extending a existing class without modifying it. The newly formed class is a derived class (or child class). Similarly, the existing class is a base class (or parent class). class Person:\n    def read(self):\n        print(\"I can read\")\n\nclass Student(Person): // [!code hl]\n    pass\n\ns = Student()\n\n# Student class inherited the read() method from Person class\ns.read()\n\n# Output: I can read",{"id":1692,"title":1693,"titles":1694,"content":1695,"level":191},"/blog/python-oop-concepts#encapsulation","📦 Encapsulation",[129],"Encapsulation is used to hide the internal state of an object from the outside, which is known as information hiding. This is achieved by restricting access to certain components of an object, which is typically done using private and public access modifiers. Consider a class Employee in a Human Resources system. The class contains private attributes like __salary and __position. These are sensitive details that should not be exposed directly. class Employee:\n    def __init__(self, name, position, salary):\n        self.__name = name\n        self.__position = position\n        self.__salary = salary\n\n    def get_salary(self):\n        return self.__salary\n\n    def promote(self, new_position, increase):\n        self.__position = new_position\n        self.__salary += increase Here, encapsulation prevents direct modification of an employee's salary and position. Instead, methods like get_salary and promote are used to interact with these attributes. This ensures that salary changes are controlled and can include additional logic, like validation or notification.",{"id":1697,"title":1698,"titles":1699,"content":1700,"level":191},"/blog/python-oop-concepts#polymorphism","🎭 Polymorphism",[129],"Polymorphism lets us define methods in the child class with the same name as defined in their parent class. This concept allows for flexibility and the ability to use different objects interchangeably, even though they may be of different classes. Imagine a scenario where we have a parent class Animal with a method speak(). Different child classes (Dog, Cat) will have their own implementation of speak(). class Animal:\n    def speak(self):\n        pass\n\nclass Dog(Animal):\n    def speak(self):\n        return \"Woof!\"\n\nclass Cat(Animal):\n    def speak(self):\n        return \"Meow!\"\n\n# Usage\nanimals = [Dog(), Cat()]\nfor animal in animals:\n    print(animal.speak()) Here, when we call speak() on a Dog or Cat object, Python automatically calls the correct method depending on the type of the object. This is polymorphism, the same method name (speak) works differently depending on the object it's called on.",{"id":1702,"title":1703,"titles":1704,"content":1705,"level":207},"/blog/python-oop-concepts#polymorphism-vs-method-overriding-vs-method-overloading","Polymorphism vs Method Overriding vs Method Overloading",[129,1698],"Polymorphism allows objects of different classes to respond to the same method call in unique ways, typically implemented through method overriding, where a subclass provides a specific version of a method from its superclass. Method overloading, creating methods in the same class with the same name but different parameters, isn't supported in Python. Polymorphism and overriding focus on class interactions and hierarchy, while overloading deals with method variations within a class. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}",{"id":134,"title":133,"titles":1707,"content":1708,"level":185},[],"Learn how to securely upload files to your servers with best practices & recommendations File uploads are a common feature, but a frequent source of security holes. Here's a concise guide to doing them right.",{"id":1710,"title":1711,"titles":1712,"content":1713,"level":191},"/blog/secure-file-uploads-a-no-nonsense-guide#endpoints-specific-not-generic","Endpoints: Specific, Not Generic",[133],"Don't use one /api/upload endpoint for everything. Create separate endpoints for each feature. /api/users/avatar/api/products/image Why? Specific Validation: Avatars need different size/type rules than product images.Security: Apply different permissions. Users upload their own avatar; admins upload product images.Clarity: Simpler code, easier maintenance.",{"id":1715,"title":1716,"titles":1717,"content":1718,"level":191},"/blog/secure-file-uploads-a-no-nonsense-guide#serving-files-specific-routes-are-critical","Serving Files: Specific Routes are Critical",[133],"Like endpoints, use specific routes to serve files. A generic /files/:filename route is a security risk. Why? Access Control: This is non-negotiable. A user's invoice is private. A profile picture is public. You can't enforce this with one generic route. Specific routes let you apply middleware for auth checks.",{"id":1720,"title":1721,"titles":1722,"content":1723,"level":191},"/blog/secure-file-uploads-a-no-nonsense-guide#the-validation-checklist","The Validation Checklist",[133],"Validation must happen on both client and server. 1. Client-Side (Good UX) Check file size and type (file.size, file.type) in the browser.Give immediate feedback. Prevents pointless uploads.Never trust this. It's easily bypassed. 2. Server-Side (Mandatory Security) Size: Always re-check the file size. Prevents simple DoS attacks.Type: This is the most critical step. Never trust the file extension or the client-provided MIME type.Use Magic Numbers: Read the first few bytes of the file to determine its true type. In the Node.js/Nuxt.js ecosystem, the best tool is the file-type library.",{"id":1725,"title":1726,"titles":1727,"content":1728,"level":218},"/blog/secure-file-uploads-a-no-nonsense-guide#core-security-practices","Core Security Practices",[133,1721],"Safe Storage: Don't save uploads in your public web directory. Use a non-web-accessible folder or, better, a cloud storage service like S3 or Google Cloud Storage.Randomize Filenames: Never use the original filename. It can be manipulated (../../etc/passwd.jpg). Generate a unique, random name.Authentication: Ensure the user is logged in and has permission to upload before processing the file.",{"id":1730,"title":1731,"titles":1732,"content":1733,"level":207},"/blog/secure-file-uploads-a-no-nonsense-guide#tldr","TL;DR",[133,1721],"Use specific routes for uploading and serving.Validate on client for UX, on server for security.On server, check size and use a library like file-type to verify content.Generate random filenames.Store files outside the web root or in the cloud.",{"id":1735,"title":1736,"titles":1737,"content":1738,"level":207},"/blog/secure-file-uploads-a-no-nonsense-guide#tips-common-pitfalls","Tips & Common Pitfalls",[133,1721],"Using user id as file name to store user avatars cause caching issue when user uploads new imageYour images shouldn't be guessable and keep them random",{"id":138,"title":137,"titles":1740,"content":1741,"level":185},[],"A summary of my findings and best practices for SEO.",{"id":1743,"title":1744,"titles":1745,"content":1746,"level":191},"/blog/seo-my-findings#best-practices","Best Practices",[137],"Do note include your admin & sign in protected pages in your sitemap.xml & disallow them in robots.txtHandle canonical URLs properly to avoid duplicate content issuesUse descriptive and keyword-rich title tags and meta descriptionsOptimize images with alt text and appropriate file namesEnsure your website is mobile-friendly and responsiveImprove page load speed by optimizing images, leveraging browser caching, and minimizing codeUse header tags (H1, H2, H3) to structure your content effectivelyDon't include URLs which are redirected to your sitemap.xmlAdd noindex (meta robots or X-Robots-Tag) on auth-related pagesDo not rely on robots.txt alone for de-indexing (it can block crawling but not guaranteed indexing)Optionally use nofollow on internal auth links if you want lower bot priority",{"id":1748,"title":1749,"titles":1750,"content":1751,"level":191},"/blog/seo-my-findings#auth-pages-indexing","Auth Pages & Indexing",[137],"Use noindex for sign-in, sign-up, and password-recovery pages in most cases. These pages are usually thin/duplicate and can hurt search quality if indexed.They can hijack branded queries (showing a login page instead of useful content).Keep crawl focus on valuable public pages. Exception: If your product is login-first (web app), indexing the main login page can be acceptable, but keep sign-up/reset pages as noindex. Reference: https://developers.google.com/search/docs/crawling-indexing/block-indexing",{"id":1753,"title":1754,"titles":1755,"content":1756,"level":191},"/blog/seo-my-findings#checklist","Checklist",[137],"Title & Description Meta Tags Favicon/Icon /sitemap.xml /robots.txt OGImage OG Tags Schema.org (if applicable) Submit your site to search engines\n Google Search Console Yandex Bing",{"id":142,"title":141,"titles":1758,"content":1759,"level":185},[],"Learn useful tips and best practices in SQL for managing and querying relational databases. This guide uses mysql syntax. Please refer to the documentation of the database you are using for any differences.",{"id":1761,"title":337,"titles":1762,"content":296,"level":191},"/blog/sql-my-findings#cheatsheet",[141],{"id":1764,"title":1765,"titles":1766,"content":1767,"level":207},"/blog/sql-my-findings#common-sql-commands","Common SQL Commands",[141,337],"SELECT - extracts data from a databaseUPDATE - updates data in a databaseDELETE - deletes data from a databaseINSERT INTO - inserts new data into a databaseCREATE DATABASE - creates a new databaseALTER DATABASE - modifies a databaseCREATE TABLE - creates a new tableALTER TABLE - modifies a tableDROP TABLE - deletes a tableCREATE INDEX - creates an index (search key)DROP INDEX - deletes an index",{"id":1769,"title":1770,"titles":1771,"content":1772,"level":207},"/blog/sql-my-findings#common-data-types","Common Data Types",[141,337],"INT - integerDECIMAL(M, N) - Decimal number with M total digits and N digits after the decimal pointVARCHAR(N) - String of text with a maximum length of N charactersBLOB - Binary Large Object for storing binary dataDATE - Date format (YYYY-MM-DD)TIMESTAMP - Date and time format (YYYY-MM-DD HH:MM:SS)",{"id":1774,"title":1775,"titles":1776,"content":1777,"level":207},"/blog/sql-my-findings#table-related-commands","Table Related Commands",[141,337],"CREATE TABLE students (\n    id INT PRIMARY KEY AUTO_INCREMENT,\n    name VARCHAR(20) NOT NULL,\n    major VARCHAR(20) UNIQUE,\n    city VARCHAR(20) DEFAULT 'Ahmedabad'\n    -- PRIMARY KEY (id) - Another way to define primary key (🚨 You've to add comma in above line if you uncomment this)\n);\n\nDESCRIBE students; -- To see the structure of the table\n\nALTER TABLE students ADD gpa DECIMAL(3, 2); -- To add a new column\n\nALTER TABLE students DROP COLUMN gpa; -- To delete a column\n\nALTER TABLE students MODIFY COLUMN name VARCHAR(30); -- To modify a column\n\nDROP TABLE students; -- To delete the table\nDROP TABLE students, teachers; -- Drop multiple tables\nDROP TABLE IF EXISTS students; -- Drop table if exists",{"id":1779,"title":1780,"titles":1781,"content":1782,"level":207},"/blog/sql-my-findings#select","SELECT",[141,337],"-- Syntax: SELECT column1, column2, ... FROM table_name;\n\n-- Select all columns from a table \"customers\"\nSELECT * FROM customers;\n\n-- Select only the \"city\" column from the \"customers\" table\nSELECT city FROM customers;\n\n-- Select only the \"city\" and \"country\" columns from the \"customers\" table\nSELECT city, country FROM customers;\n\n-- Select distinct values from the \"city\" column in the \"customers\" table\n-- This is useful when you want to list all unique values in a column (without duplicates)\nSELECT DISTINCT city FROM customers;",{"id":1784,"title":1785,"titles":1786,"content":1787,"level":207},"/blog/sql-my-findings#where","WHERE",[141,337],"-- Syntax: SELECT column1, column2, ... FROM table_name WHERE condition;\n\n-- Select all customers from the country \"India\"\nSELECT * FROM customers WHERE country='India';\n\n-- Select all customers where country id is 1\nSELECT * FROM customers WHERE country_id=1;\n\n-- Select all customers where country id is not 1\nSELECT * FROM customers WHERE country_id != 1;\n\n-- Select all customers where country id is not 1\nSELECT * FROM customers WHERE NOT country_id=1;\n\n-- Select all customers where country id is less than 10\nSELECT * FROM customers WHERE counter_id \u003C 10;\n\n-- Select all customers where country id is between 5 and 10\nSELECT * FROM customers WHERE counter_id BETWEEN 5 AND 10;\n\n-- Select all customers where country id is not between 11 and 14\nSELECT * FROM customers WHERE counter_id NOT BETWEEN 11 AND 14;\n\n-- Select all customers where customer_name is between \"John\" and \"Peter\"\nSELECT * FROM customers WHERE customer_name BETWEEN \"John\" AND \"Peter\" ORDER BY customer_name;\n\n-- Select all customers who have created their account in 2019\nSELECT * FROM customers WHERE create_date BETWEEN '2019-01-01' AND '2019-12-31';\n\n-- Select all customers where country name starts with \"In\". Percent (%) is used to match any number of characters including zero.\nSELECT * FROM customers WHERE country LIKE 'In%';\n\n-- Select all customers where country name has 5 characters and first two are \"In\" and forth is \"i\". Underscore (_) is used to match a single character\nSELECT * FROM customers WHERE country LIKE 'In_i_';\n\n-- Select all customers where country name ends with \"ia\"\nSELECT * FROM customers WHERE country LIKE '%ia';\n\n-- Select all customers where country name contains \"nd\"\nSELECT * FROM customers WHERE country LIKE '%nd%';\n\n-- Select all customers where country name does not start with \"In\"\nSELECT * FROM customers WHERE country NOT LIKE 'In%';\n\n-- Select all customers where country name is either \"India\" or \"USA\"\nSELECT * FROM customers WHERE country IN ('India', 'USA');\n\n-- Select all customers where country name is not \"India\" or \"USA\"\nSELECT * FROM customers WHERE country NOT IN ('India', 'USA');\n\n-- Select all customers that have an order in the \"orders\" table\nSELECT * FROM customers WHERE customer_id IN (SELECT customer_id FROM orders);\n\n-- Select all customers from the country \"India\" and the city \"Ahmedabad\"\nSELECT * FROM customers WHERE country='India' AND city='Ahmedabad';\n\n-- Select all customers from the country \"India\" or country name starts with \"In\"\nSELECT * FROM customers WHERE country='India' OR country LIKE 'In%';\n\n-- Select all customers from the country \"India\" and customer name starts with either \"G\" or \"R\" (Parenthesis are important here)\nSELECT * FROM customers WHERE country='India' AND (customer_name LIKE 'G%' OR customer_name='R%');\n\n-- Select all the customers from the country \"India\" and customer name starts with \"G\" plus all customers that have a customer name starting with \"R\"\nSELECT * FROM customers WHERE country='India' AND customer_name LIKE 'G%' OR customer_name='R%';",{"id":1789,"title":1790,"titles":1791,"content":1792,"level":1531},"/blog/sql-my-findings#operator-reference","Operator Reference",[141,337,1785],"OperatorDescription=Equal>Greater than\u003CLess than>=Greater than or equal\u003C=Less than or equal\u003C> or !=Not equal toBETWEENBetween an inclusive rangeLIKESearch for a patternINTo specify multiple valuesANDLogical operator ANDORLogical operator OR",{"id":1794,"title":1795,"titles":1796,"content":296,"level":207},"/blog/sql-my-findings#like","LIKE",[141,337],{"id":1798,"title":1799,"titles":1800,"content":1801,"level":207},"/blog/sql-my-findings#order-by","ORDER BY",[141,337],"-- Syntax: SELECT column1, column2, ... FROM table_name ORDER BY column1, column2, ... ASC|DESC;\n\n-- Select all products and order them by price (ASC - ascending) (Lowest to Highest in this example)\nSELECT * FROM products ORDER BY price;\n\n-- Select all products and order them by price (DESC - descending) (Highest to Lowest in this example)\nSELECT * FROM products ORDER BY price DESC;\n\n-- Select all products and order them by product name (ASC - ascending) (A to Z in this example)\nSELECT * FROM products ORDER BY product_name;\n\n-- Select all products and order them by product name (DESC - descending) (Z to A in this example)\nSELECT * FROM products ORDER BY product_name DESC;\n\n-- Select all products and order them by price (ASC - ascending) and then by product name (ASC - ascending)\nSELECT * FROM product ORDER BY price, product_name;\n\n-- Select all products and order them by price (DESC - descending) and then by product name (ASC - ascending)\nSELECT * FROM product ORDER BY price DESC, product_name;\n\n-- You can even order by column which you are not selecting.\nSELECT id, name FROM products ORDER BY price;",{"id":1803,"title":1804,"titles":1805,"content":1806,"level":207},"/blog/sql-my-findings#insert-into","INSERT INTO",[141,337],"-- Syntax: INSERT INTO table_name (column1, column2, column3, ...) VALUES (value1, value2, value3, ...);\n\n-- Insert a new record in the \"customers\" table. ID column is auto incremented & does not need to be specified\nINSERT INTO customers (customer_name, address, city, postal_code, country) VALUES ('Arjun', 'A-7, Satyam Complex', 'Ahmedabad', '382350', 'India');\n\n-- Insert multiple records in the \"customers\" table.\nINSERT INTO customers\n    (customer_name, address, city, postal_code, country)\n    VALUES\n    ('Arjun', 'A-7, Satyam Complex', 'Ahmedabad', '382350', 'India'),\n    ('Ashwatthama', 'A-13, Satyam Complex', 'Ahmedabad', '382350', 'India');",{"id":1808,"title":1809,"titles":1810,"content":1811,"level":207},"/blog/sql-my-findings#null-values","NULL Values",[141,337],"A field with a NULL value is a field with no value. If a field in a table is optional, it is possible to insert a new record or update a record without adding a value to this field. Then, the field will be saved with a NULL value. Do note that, NULL value and empty string '' are not the same. -- Select all customers where address is not provided or is NULL\nSELECT * FROM customers WHERE address IS NULL;\n\n-- Select all customers where address is provided\nSELECT * FROM customers WHERE address IS NOT NULL;",{"id":1813,"title":1814,"titles":1815,"content":1816,"level":207},"/blog/sql-my-findings#update","UPDATE",[141,337],"-- Syntax: UPDATE table_name SET column1=value1, column2=value2, ... WHERE condition;\n\n-- Update the address of the customer \"Arjun\" to \"A-3, Satyam Complex\"\nUPDATE customers SET address='J-3, Sanatan Complex', city='Gandhinagar' WHERE customer_name='Arjun';\n\n-- Update city of all customers who lives in Sana Complex to \"Gandhinagar\"\nUPDATE customers SET city='Gandhinagar' WHERE address LIKE '%Sanatan Complex%';\n\n-- Update city and postal code of all customers who lives in Sana Complex to \"Gandhinagar\"\nUPDATE customers SET city='Gandhinagar', postal_code='380011' WHERE address LIKE '%Sanatan Complex%';\n\n-- Update all the rows. This will set the city to \"Unknown\" for all the customers\nUPDATE customers SET city='Unknown';",{"id":1818,"title":1819,"titles":1820,"content":1821,"level":207},"/blog/sql-my-findings#delete","DELETE",[141,337],"-- Syntax: DELETE FROM table_name WHERE condition;\n\n-- Remove all the products which have a rating less than 1\nDELETE FROM products WHERE rating\u003C1;\n\n-- Delete all products\nDELETE FROM products;\n\n-- Delete table\nDROP TABLE products;",{"id":1823,"title":1824,"titles":1825,"content":1826,"level":207},"/blog/sql-my-findings#limit-offset","LIMIT & OFFSET",[141,337],"LIMIT is used to specify the number of records to return. OFFSET is used to specify the number of records to skip before starting to return the records. -- Syntax: SELECT column1, column2, ... FROM table_name LIMIT number OFFSET offset;\n\n-- Select the first 10 records from the \"customers\" table (Page 1)\nSELECT * FROM customers LIMIT 10;\n\n-- Select the next 10 records from the \"customers\" table (Page 2)\nSELECT * FROM customers LIMIT 10 OFFSET 10;\n\n-- Select the next 10 records from the \"customers\" table (Page 3)\nSELECT * FROM customers LIMIT 10 OFFSET 20;\n\n-- Select the first 5 records from the \"customers\" table, starting from record 3\nSELECT * FROM customers LIMIT 5 OFFSET 3;\n\n-- Select second highest salary from the \"employees\" table\nSELECT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1;",{"id":1828,"title":1829,"titles":1830,"content":1831,"level":207},"/blog/sql-my-findings#min-max","MIN & MAX",[141,337],"-- Syntax: SELECT MIN(column_name) FROM table_name WHERE condition;\n-- Syntax: SELECT MAX(column_name) FROM table_name WHERE condition;\n\n-- Select the lowest price from the \"products\" table\nSELECT MIN(price) FROM products;\n\n-- Select the highest price from the \"products\" table\nSELECT MAX(price) FROM products;\n\n-- Select the lowest price from the \"products\" table where rating is greater than 4\nSELECT MIN(price) FROM products WHERE rating>4;\n\n-- Alias for MIN and MAX. This is useful when you want to use the result of MIN or MAX in another query\nSELECT MIN(price) AS lowest_price FROM products;\nSELECT MAX(price) AS highest_price FROM products;",{"id":1833,"title":1834,"titles":1835,"content":1836,"level":207},"/blog/sql-my-findings#count","COUNT",[141,337],"-- Syntax: SELECT COUNT(column_name) FROM table_name WHERE condition;\n\n-- Count the number of records in the \"customers\" table\nSELECT COUNT(*) FROM customers;\n\n-- Count the number of records in the \"customers\" table where country is \"India\"\nSELECT COUNT(*) FROM customers WHERE country='India';\n\n-- Count the number of records in the \"customers\" table. Comparing to `*`, this will not count NULL values\nSELECT COUNT(customer_name) FROM customers;\n\n-- Count the number of distinct countries in the \"customers\" table\nSELECT COUNT(DISTINCT country) FROM customers;",{"id":1838,"title":1839,"titles":1840,"content":1841,"level":207},"/blog/sql-my-findings#sum-avg","SUM & AVG",[141,337],"-- Syntax: SELECT SUM(column_name) FROM table_name WHERE condition;\n-- Syntax: SELECT AVG(column_name) FROM table_name WHERE condition;\n\n-- Select the total price of all products\nSELECT SUM(price) FROM products;\n\n-- Select the average price of all products\nSELECT AVG(price) FROM products;\n\n-- Select the total price of all products where rating is greater than 4\nSELECT SUM(price) FROM products WHERE rating>4;\n\n-- Writing expressions in SUM and AVG. Below, we are converting USD to INR roughly and then calculating the total price of all products\nSELECT SUM(price * 80) FROM products;\n\n-- Select all products where price is greater than the average price of all products\nSELECT * FROM products WHERE price > (SELECT AVG(price) FROM products);",{"id":1843,"title":1844,"titles":1845,"content":1846,"level":207},"/blog/sql-my-findings#aliases","Aliases",[141,337],"-- Syntax: SELECT column_name AS column_alias_name FROM table_name;\n-- Syntax: SELECT column_name FROM table_name AS table_alias_name;\n\n-- Select all products and rename the column \"name\" to \"product_name\"\nSELECT name AS product_name FROM products;\n\n-- Select all products and rename the table \"products\" to \"items\"\nSELECT * FROM products AS items;\n\n-- Renaming multiple tables and referencing them in a query\nSELECT o.order_id, o.order_date, c.customer_name FROM customers as c, orders as o WHERE c.customer_id=o.customer_id;",{"id":1848,"title":1849,"titles":1850,"content":1851,"level":207},"/blog/sql-my-findings#union","Union",[141,337],"Resulting columns should have the same data type. For example, This is not allowed SELECT name FROM products UNION SELECT price FROM products;Resulting columns should have the same number of columns. For example, This is not allowed SELECT id, name FROM products UNION SELECT name FROM products; -- Syntax: SELECT column1, column2, ... FROM table1 UNION SELECT column1, column2, ... FROM table2;\n\n-- Select all customers from the \"customers\" table and all customers from the \"old_customers\" table\nSELECT * FROM customers UNION SELECT * FROM old_customers;\n\n-- Get first & last record from the \"customers\" table\n(SELECT * FROM customers LIMIT 1) UNION (SELECT * FROM customers ORDER BY id DESC LIMIT 1);",{"id":1853,"title":1854,"titles":1855,"content":1856,"level":207},"/blog/sql-my-findings#joins","Joins",[141,337],"🚧 WIP",{"id":1858,"title":1859,"titles":1860,"content":1861,"level":218},"/blog/sql-my-findings#join-3-or-more-tables","Join 3 or more tables",[141,337,1854],"We have three tables: students, courses, and enrollments. Table: students student_idstudent_name1John Doe2Jane Smith3Sam Brown4Alice Johnson Table: courses course_idcourse_name1Math2Science3History4Literature Table: enrollments enrollment_idstudent_idcourse_idenrollment_date1112024-01-102122024-02-123212024-03-154332024-04-20 Challenge: Write an SQL query to list all students along with the names of the courses they are enrolled in. If a student is not enrolled in any course, their name should still appear with the course name as NULL. SELECT s.student_name, c.course_name\nFROM students AS s\nLEFT JOIN enrollments AS e ON s.student_id = e.student_id\nLEFT JOIN courses AS c ON c.course_id = e.course_id; The first LEFT JOIN ensures that all students are included, even if they haven't enrolled in any courses.The second LEFT JOIN connects enrolled students with their courses, allowing us to retrieve the course names for each student.Using LEFT JOIN twice ensures that we get all combinations of students and courses, including cases where students are not enrolled in any courses.",{"id":1863,"title":1864,"titles":1865,"content":1866,"level":207},"/blog/sql-my-findings#nested-queries","Nested Queries",[141,337],"SELECT * FROM customers WHERE country_id IN (SELECT country_id FROM countries WHERE country_name='India');",{"id":1868,"title":289,"titles":1869,"content":1870,"level":191},"/blog/sql-my-findings#tips",[141],"You can do order by even if you're not selecting that column. For example, SELECT id, name FROM products ORDER BY price; html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"id":146,"title":145,"titles":1872,"content":1873,"level":185},[],"An introductory guide to SQLAlchemy, covering the basics of Core and ORM for database interactions in Python.",{"id":1875,"title":1876,"titles":1877,"content":1878,"level":191},"/blog/sqlalchemy-for-beginners#basics-terminology","Basics & Terminology",[145],"SQLAlchemy is divided in two parts:\nCore: is a low-level SQL toolkit that provides a set of classes for interacting with databases.ORM: is a high-level object-relational mapping library that provides a way to interact with databases using Python objects.Check out the full glossary here.",{"id":1880,"title":1881,"titles":1882,"content":296,"level":191},"/blog/sqlalchemy-for-beginners#core","Core",[145],{"id":1884,"title":1885,"titles":1886,"content":1887,"level":207},"/blog/sqlalchemy-for-beginners#engine","Engine",[145,1881],"Engine provides a source of connectivity to a database. It provides Connection object which should be closed as soon as it's no longer needed. Best way to use this connection is using with statement. from sqlalchemy import create_engine\n\n# Create an in-memory SQLite database engine\nengine = create_engine(\"sqlite+pysqlite:///:memory:\", echo=True)\n\n# Connect to the database using the engine\nwith engine.connect() as conn:\n    # Perform operations\n\n# Connection is closed automatically When connection is closed, ROLLBACK is called automatically. If you want to commit the transaction, you can call conn.commit() before closing the connection. from sqlalchemy import text\n\nwith engine.connect() as conn:\n    conn.execute(text(\"INSERT INTO users (name) VALUES ('Alice')\"))\n    conn.commit() # If we don't call this, the transaction will be rolled back There is also a begin() method which auto commits the transaction when the block is exited. It'll ROLLBACK the transaction if an exception is raised. with engine.begin() as conn:\n    conn.execute(text(\"INSERT INTO users (name) VALUES ('Alice')\"))\n\n# Above transaction is committed automatically In above code example, conn.execute(text(...)) will return Result object. Result represents an iterable object of result rows. Simply, Result is list of Row object. Row object is named tuple like object. result = conn.execute(text(\"select x, y from some_table\"))\n\n# Tuple Assignment\nfor x, y in result:\n    ...\n\n# Integer Index\nfor row in result:\n    x = row[0]\n\n# Attribute Name\nfor row in result:\n    y = row.y\n\n    # illustrate use with Python f-strings\n    print(f\"Row: {row.x} {y}\")\n\n# Mapping Access (Dict)\n# `result.mappings()` provides `RowMapping` objects instead of `Row` objects\nfor dict_row in result.mappings():\n    x = dict_row[\"x\"]\n    y = dict_row[\"y\"]",{"id":1889,"title":1890,"titles":1891,"content":1892,"level":218},"/blog/sqlalchemy-for-beginners#sending-parameters","Sending Parameters",[145,1881,1885],"Use :y format to send parameters to the query. text accepts second argument as dictionary of parameters. with engine.connect() as conn:\n    result = conn.execute(text(\"SELECT x, y FROM some_table WHERE y > :y\"), {\"y\": 2})\n    for row in result:\n        print(f\"x: {row.x}  y: {row.y}\") You can also pass multiple parameters in a list. with engine.connect() as conn:\n    conn.execute(\n        text(\"INSERT INTO some_table (x, y) VALUES (:x, :y)\"),\n        [{\"x\": 11, \"y\": 12}, {\"x\": 13, \"y\": 14}],\n    )\n    conn.commit()",{"id":1894,"title":1895,"titles":1896,"content":1897,"level":207},"/blog/sqlalchemy-for-beginners#metadata","Metadata",[145,1881],"Metadata is python objects that represent database concepts like tables and columns. from sqlalchemy import MetaData, Table, Column, Integer, String\n\nmetadata_obj = MetaData()\n\nuser_table = Table(\n    \"user_account\",\n    metadata_obj, # 🚨 Notice we're passing `metadata_obj` here\n    Column(\"id\", Integer, primary_key=True),\n    Column(\"name\", String(30)),\n    Column(\"fullname\", String),\n)\n\nmetadata_obj.create_all(engine) # Create tables in the database\nmetadata_obj.drop_all(engine) # Drop tables from the database",{"id":1899,"title":1900,"titles":1901,"content":1902,"level":207},"/blog/sqlalchemy-for-beginners#orm","ORM",[145,1881],"In ORM, table metadata is creating using Base class. from sqlalchemy.orm import DeclarativeBase\n\nclass Base(DeclarativeBase):\n    pass\n\n# Base.metadata => metadata object Read more about configuration here. from sqlalchemy.orm import Mapped, mapped_column, relationship\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(String(30))\n    fullname: Mapped[str | None]\n    addresses: Mapped[list[\"Address\"]] = relationship(back_populates=\"user\")\n\n    def __repr__(self) -> str:\n        return f\"User(id={self.id}, name={self.name}, fullname={self.fullname})\"\n\nclass Address(Base):\n    __tablename__ = \"address\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    email_address: Mapped[str]\n    user_id = mapped_column(ForeignKey(\"users.id\"))\n    user: Mapped[User] = relationship(back_populates=\"addresses\")\n\n    def __repr__(self) -> str:\n        return f\"Address(id={self.id}, email_address={self.email_address})\" Above style of defining the table is called \"Declarative with Imperative Table\".Above two classes refers to table in the database. __tablename__ will be the name of the table in the database.For columns with simple data types and no other options, we can indicate a Mapped type annotation alone. E.g. email_address: Mapped[str]Using data type in Mapped[\u003Ctype>] represents the column type in the database. For example, Mapped[int] refers to Integer and Mapped[str] refers to String. You can read more about it here and here.If type of column is nullable, you can use, Mapped[str | None]. You can also explicitly specify nullable=True in mapped_column function (this is optional).You can also defined the column without the mapped annotations. In this case, you've to provide the type of the column explicitly along with the nullable option. E.g. email_address = Column(String, nullable=False).Two additional attributes, User.addresses and Address.user, define a different kind of attribute called relationship(). The relationship() construct is discussed more fully at Working with ORM Related Objects.The classes are automatically given an __init__() method if we don’t declare one of our own. The default form of this method accepts all attribute names as optional keyword arguments:sandy = User(name=\"sandy\", fullname=\"Sandy Cheeks\")\nTo automatically generate a full-featured __init__() method which provides for positional arguments as well as arguments with default keyword values, the dataclasses feature introduced at Declarative Dataclass Mapping may be used.It's a good idea to use dataclass Mapping for autocompletion. Resources to Learn MoreORM Mapping Styles - full background on different ORM configurational styles.Declarative Mapping - overview of Declarative class mappingDeclarative Table with mapped_column() - detail on how to use mapped_column() and Mapped to define the columns within a table to be mapped when using Declarative. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}",{"id":150,"title":149,"titles":1904,"content":1905,"level":185},[],"Discover useful tips, best practices, and code snippets for working with SQLAlchemy in Python. SQLAlchemy Docs:Using Enum in SQLAlchemyORM migration usageSQL Datatype ObjectsDefault type mapping for Mappedmapped_column() APIORM Querying GuideUsing Dataclass via MappedAsDataclassSelect API referenceSession API referenceSQL Datatype ObjectsOther:Mastering Soft Delete: Advanced SQLAlchemy TechniquesWhat's new in SQLAlchemy 2Dataclass Known IssuesYouTube - Relationship loading techniquesFiltering Soft Deletes Globally",{"id":1907,"title":337,"titles":1908,"content":296,"level":191},"/blog/sqlalchemy-my-findings#cheatsheet",[149],{"id":1910,"title":1900,"titles":1911,"content":296,"level":207},"/blog/sqlalchemy-my-findings#orm",[149,337],{"id":1913,"title":1914,"titles":1915,"content":1916,"level":218},"/blog/sqlalchemy-my-findings#define-models","Define Models",[149,337,1900],"Type annotation map from sqlalchemy.orm import MappedAsDataclass, DeclarativeBase, Mapped, mapped_column\nfrom sqlalchemy import String, ForeignKey, func\nfrom datetime import datetime\n\nclass Base(MappedAsDataclass, DeclarativeBase):\n    pass\n\nclass User(Base):\n    # Define table name\n    __tablename__ = \"users\"\n\n    # Define primary key. `index=True` is optional when using `primary_key=True`\n    # When inheriting from `MappedAsDataclass`, `init=False` is required for primary key to be ignored in `__init__` method\n    id: Mapped[int] = mapped_column(primary_key=True, init=False)\n\n    # Define column with length\n    name: Mapped[str] = mapped_column(String(30))\n\n    # Auto infer col type from python type\n    # `str | None` => `NULL` is allowed\n    fullname: Mapped[str | None]\n\n    # `str` => `NOT NULL`\n    required_fullname: Mapped[str]\n\n    created_at: Mapped[datetime] = mapped_column(init=False, server_default=func.now())\n\n    addresses: Mapped[list[\"Address\"]] = relationship(init=False, back_populates=\"user\")\n\nclass Address(Base):\n    __tablename__ = \"addresses\"\n\n    id: Mapped[int] = mapped_column(primary_key=True, init=False)\n    email_address: Mapped[str]\n    user_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n\n    user: Mapped[\"User\"] = relationship(init=False, back_populates=\"addresses\") mapped_column() receives the same arguments as dataclasses.field(). E.g. default, init, etc.",{"id":1918,"title":1919,"titles":1920,"content":1921,"level":218},"/blog/sqlalchemy-my-findings#query-data","Query data",[149,337,1900],"from sqlalchemy import or_\n\n# Get user by id\nuser = await db.get(User, 73)\n\n# Get all users (Use this method instead of below three)\nstatement = select(User)\nresult = await db.scalars(statement)\nusers = result.all()\n\n# Get all users (Explicit)\nusers = db.execute(\n    select(User)\n).scalars().all()\n\n# Get all users with limit & offset\nstatement = select(User).offset(skip).limit(limit)\nresult = await db.scalars(result)\nusers = result.unique().all()\n\n# Filter user by email\nstatement = select(User).where(User.email == \"john@mail.com\")\nresult = await db.scalars(statement)\nuser = result.first()\n# instead of `.first()` you can also use `.one()` & `.one_or_none()`\n\n# Filter with multiple conditions (AND)\nstatement = select(User).where(User.email == \"john@mail.com\").where(User.username == data.username)\nresult = await db.scalars(statement)\nuser = result.first()\n\n# Filter with multiple conditions (OR)\nstatement = select(User).where(or_(User.email == data.email, User.username == data.username))\nresult = await db.scalars(statement)\nuser = result.first()\n\n# Order by id\nstatement = select(User).order_by(User.id.desc())\nresult = await db.scalars(statement)\nusers = result.all()\n\n# Get count\nstatement = select(func.count()).select_from(User)\ncount = await db.scalar(statement)\n\nstatement = select(func.count(User.id))\ncount = await db.scalar(statement)\n\n# Exist Query\nstatement = select(exists().where(User.email = \"admin@mail.com\"))\nhas_admin_email: bool | None = await db.scalar(statement)",{"id":1923,"title":289,"titles":1924,"content":296,"level":191},"/blog/sqlalchemy-my-findings#tips",[149],{"id":1926,"title":1927,"titles":1928,"content":1929,"level":207},"/blog/sqlalchemy-my-findings#using-default-vs-init-for-mappedasdataclass","Using default vs init for MappedAsDataclass",[149,289],"Beware when using init=False on column that means you'll be never able to set that column value while creating the record. So, use default instead of init=False for columns for flexibility. In below example, We have id column which is primary key and we don't want to set it while creating the record. So, we use init=False for id column. This is fine for regular usage however, while testing or debugging, you might need to set id manually. In this case, you can't set id because it's not allowed in __init__. from sqlalchemy.orm import (\n    DeclarativeBase,\n    Mapped,\n    MappedAsDataclass,\n    mapped_column,\n)\n\nclass Base(DeclarativeBase, MappedAsDataclass):\n    pass\n\nclass MyModel(Base):\n    __tablename__ = \"awesome\"\n\n    id: Mapped[int] = mapped_column(primary_key=True, kw_only=True, init=False)\n\nMyModel(id=2) # 🚨 Error: `id` is not allowed in __init__ To fix this, you can use default instead of init=False: id: Mapped[int] = mapped_column(primary_key=True, kw_only=True, init=False) // [!code --]\nid: Mapped[int] = mapped_column(primary_key=True, kw_only=True, default=None) // [!code ++]",{"id":1931,"title":1932,"titles":1933,"content":1934,"level":207},"/blog/sqlalchemy-my-findings#fastapi-pydantic-schemas-relationship","FastAPI, Pydantic Schemas & Relationship",[149,289],"Check this blog post.",{"id":1936,"title":1937,"titles":1938,"content":1939,"level":207},"/blog/sqlalchemy-my-findings#default-server_default","default & server_default",[149,289],"It's good idea to define both default & server_default. default is for python side and server_default is for database side Setting both ensures that the default value is used when creating a new record regardless of whether the value is set via model (ORM or python) or directly in the database (using SQL). attachments: Mapped[JSONType] = mapped_column(\n    JSONB,\n    nullable=False,\n    default=list,\n    server_default=text(\"'[]'::jsonb\"),\n)",{"id":1941,"title":1942,"titles":1943,"content":1944,"level":207},"/blog/sqlalchemy-my-findings#django-like-signals-in-sqlalchemy","Django like signals in SQLAlchemy",[149,289],"class User(Base):\n    __tablename__ = 'users'\n    id = Column(Integer, primary_key=True)\n    name = Column(String)\n\n# Define the function to run after insertion\ndef after_insert_listener(mapper, connection, target):\n    # Your custom logic here\n    print(\"New user added to the DB:\", target)\n\n# Attach the event listener to the after_insert event\nlistens_for(User, 'after_insert', after_insert_listener)",{"id":1946,"title":1947,"titles":1948,"content":1949,"level":207},"/blog/sqlalchemy-my-findings#use-dataclass-for-base-class","Use dataclass for base class",[149,289],"Making your model's base class a dataclass using MappedAsDataclass will provide autocompletion & type hints while creating record and help you find errors: from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass\n\n# Use `MappedAsDataclass` to make models dataclasses and get autocompletion\nclass Base(MappedAsDataclass, DeclarativeBase):\n    pass",{"id":1951,"title":1952,"titles":1953,"content":1954,"level":207},"/blog/sqlalchemy-my-findings#sqlalchemy-query-optimization","SQLAlchemy Query Optimization",[149,289],"Please refer to this blog post",{"id":1956,"title":1957,"titles":1958,"content":296,"level":207},"/blog/sqlalchemy-my-findings#using-with-pydantic","Using with Pydantic",[149,289],{"id":1960,"title":1961,"titles":1962,"content":1963,"level":218},"/blog/sqlalchemy-my-findings#model_dump-and-exclude_unsetnone",".model_dump() and exclude_unset=None",[149,289,1957],"When you have optional fields like name: str | None = None and you use MappedAsDataclass, you might get __init__ got unexpected param or it misses some param. In this case, best practice will be using .model_dump() (without exclude_unset=None) and .model_dump(exclude_unset=True) when you update the SQLAlchemy model.",{"id":1965,"title":1966,"titles":1967,"content":1968,"level":218},"/blog/sqlalchemy-my-findings#serializing-httpurl-for-compatibility-with-sqlalchemy-model","Serializing HttpUrl for compatibility with SQLAlchemy model",[149,289,1957],"When you use HttpUrl from pydantic, you can't directly use it with SQLAlchemy model. You need to convert it to str before saving it to the database. class MyModel(BaseModel):\n    uploaded_url: HttpUrl\n\nclass MyModelDB(Base):\n    uploaded_url: Mapped[str]\n\ndata = MyModel(uploaded_url=\"https://example.com\")\ndb.add(MyModelDB(**data.model_dump())) # Error: SQLAlchemy don't accept Url type from pydantic\n\n# Tell pydantic how to serialize the HttpUrl field\nclass MyModel(BaseModel):\n    uploaded_url: Annotated[HttpUrl, PlainSerializer(str)]\n\ndb.add(MyModelDB(**data.model_dump())) # Works",{"id":1970,"title":1971,"titles":1972,"content":1973,"level":207},"/blog/sqlalchemy-my-findings#self-referencing-table","Self referencing table",[149,289],"from sqlalchemy import ForeignKey\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\n\nclass WorkflowNode(Base):\n    __tablename__ = \"workflow_node\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    parent_node_id: Mapped[int | None] = mapped_column(ForeignKey(\"workflow_node.id\"))\n\n    # Parent relationship (many-to-one)\n    parent_node: Mapped[\"WorkflowNode | None\"] = relationship(\n        remote_side=[id],  # Specify which side is \"remote\" (the one column)\n        back_populates=\"children\"\n    )\n\n    # Children relationship (one-to-many)\n    children: Mapped[list[\"WorkflowNode\"]] = relationship(back_populates=\"parent_node\")",{"id":1975,"title":492,"titles":1976,"content":296,"level":191},"/blog/sqlalchemy-my-findings#snippets",[149],{"id":1978,"title":1979,"titles":1980,"content":1981,"level":207},"/blog/sqlalchemy-my-findings#type-annotations-for-jsonb-column","type annotations for JSONB Column",[149,492],"from typing import Any\nfrom sqlalchemy.orm import Mapped, mapped_column\nfrom sqlalchemy import text\nfrom sqlalchemy.types import JSON\nfrom sqlalchemy.dialects.postgresql import JSONB\n\ntype JSONType = str | int | float | bool | None | dict[str, \"JSONType\"] | list[\"JSONType\"]\n\n# Use `MappedAsDataclass` to make models dataclasses and get autocompletion\nclass Base(DeclarativeBase, MappedAsDataclass):\n    type_annotation_map = {JSONType: JSON}\n\nclass MyModel(Base):\n    settings: Mapped[JSONType] = mapped_column(JSONB, default=dict, server_default=text(\"'{}'::jsonb\"))",{"id":1983,"title":1984,"titles":1985,"content":1986,"level":207},"/blog/sqlalchemy-my-findings#helper-columns","Helper columns",[149,492],"from datetime import datetime\nfrom typing import ClassVar\n\nfrom sqlalchemy import DateTime, func\nfrom sqlalchemy.orm import Mapped, mapped_column\nfrom sqlalchemy.sql import false\n\nclass MixinId:\n    id: Mapped[int] = mapped_column(primary_key=True, kw_only=True, default=None)\n\nclass MixinCreatedAt:\n    created_at: Mapped[datetime] = mapped_column(\n        DateTime(timezone=True),\n        server_default=func.timezone(\"UTC\", func.now()),\n        default=None,\n        kw_only=True,\n    )\n\nclass MixinUpdatedAt:\n    updated_at: Mapped[datetime] = mapped_column(\n        DateTime(timezone=True),\n        server_default=func.timezone(\"UTC\", func.now()),\n        onupdate=func.timezone(\"UTC\", func.now()),\n        default=None,\n        kw_only=True,\n    )\n\nclass MixinIsDeleted:\n    is_deleted: Mapped[bool] = mapped_column(default=False, server_default=false(), kw_only=True)\n\nclass MixinFactory:\n    \"\"\"Factory mixin to create instances of the model.\n\n    Examples:\n        >>> MixinStartedAt = MixinFactory.get_renamed(\"created_at\", \"started_at\")\n        \u003Cclass '__main__.MixinRenamed'> # This is dummy class having \"started_at\" database column\n        >>> MixinModifiedAt = MixinFactory.get_renamed(\"updated_at\", \"modified_at\")\n        \u003Cclass '__main__.MixinRenamed'> # This is dummy class having \"modified_at\" database column\n        >>> class NewTable(Base, MixinStartedAt): ...\n\n    \"\"\"\n\n    _mixin_map: ClassVar[dict[Mixin, object]] = {\n        \"created_at\": MixinCreatedAt,\n        \"updated_at\": MixinUpdatedAt,\n        \"is_deleted\": MixinIsDeleted,\n    }\n\n    @staticmethod\n    def get_renamed(mixin_name: Mixin, renamed_col: str):\n        \"\"\"Get the mixin with the renamed column.\n\n        Args:\n            mixin_name: Original mixin name to use as template\n            renamed_col: New column name to use\n\n        Returns:\n            A new mixin class with the renamed column\n\n        Raises:\n            ValueError: If mixin_name is not valid\n\n        \"\"\"\n        source_mixin = MixinFactory._mixin_map[mixin_name]\n        original_col = next(iter(source_mixin.__annotations__))\n        column_def = getattr(source_mixin, original_col)\n        type_annotation = source_mixin.__annotations__[original_col]\n\n        # Create new mixin class with proper type annotation\n        return type(\n            \"_RenamedMixin\",\n            (),\n            {\n                renamed_col: column_def,\n                \"__annotations__\": {renamed_col: type_annotation},\n            },\n        ) html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}",{"id":154,"title":153,"titles":1988,"content":1989,"level":185},[],"Learn useful tips and best practices in SQLModel database library. SQLModel is a library for interacting with databases using Python type hints on top of Pydantic & SQLAlchemy.",{"id":1991,"title":1992,"titles":1993,"content":296,"level":191},"/blog/sqlmodel-my-findings#️-crud","🏗️ CRUD",[153],{"id":1995,"title":1996,"titles":1997,"content":1998,"level":207},"/blog/sqlmodel-my-findings#create","🌱 Create",[153,1992],"hero = Hero(name=\"Deadpond\", secret_name=\"Dive Wilson\")\nsession.add(hero)\nsession.commit()\nsession.refresh(hero)\n\n# add related table connection\nhero_spider_boy.team_id = team_preventers.id\nsession.add(hero_spider_boy)\nsession.commit()\nsession.refresh(hero_spider_boy)",{"id":2000,"title":2001,"titles":2002,"content":296,"level":207},"/blog/sqlmodel-my-findings#read","🫳 Read",[153,1992],{"id":2004,"title":2005,"titles":2006,"content":2007,"level":218},"/blog/sqlmodel-my-findings#select-all","Select all",[153,1992,2001],"# Get all\nsession.exec(select(Hero))\n\n# Get all as list\nsession.exec(select(Hero)).all()\n\n# related using where\nresults = session.exec(select(Hero, Team).where(Hero.team_id == Team.id))\nfor hero, team in results:\n print(\"Hero:\", hero, \"Team:\", team)\n\n# related using join\nresults = session.exec(select(Hero, Team).join(Team))\nfor hero, team in results:\n print(\"Hero:\", hero, \"Team:\", team)\n\n# related using left outer join\nresults = session.exec(select(Hero, Team).join(Team, isouter=True))\nfor hero, team in results:\n print(\"Hero:\", hero, \"Team:\", team)\n\n# related to only get result of single table/class\nheros = session.exec(select(Hero).join(Team).where(Team.name == \"Preventers\"))",{"id":2009,"title":2010,"titles":2011,"content":2012,"level":218},"/blog/sqlmodel-my-findings#query-using-where","Query using where",[153,1992,2001],"# Where hero name is \"Deadpond\"\nsession.exec(select(Hero).where(Hero.name == \"Deadpond\"))\n\n# Where hero name is not \"Deadpond\"\nsession.exec(select(Hero).where(Hero.name != \"Deadpond\"))\n\n# Where hero age is greater than 35\nsession.exec(select(Hero).where(Hero.age > 35))\n\n# Multiple Where (AND)\nsession.exec(select(Hero).where(Hero.age >= 35).where(Hero.age \u003C 40))\n\n# Where with Multiple Expression (AND)\nsession.exec(select(Hero).where(Hero.age >= 35, Hero.age \u003C 40))\n\n# Where with Multiple Expression (OR)\nsession.exec(select(Hero).where(or_(Hero.age \u003C= 35, Hero.age > 90)))",{"id":2014,"title":2015,"titles":2016,"content":2017,"level":218},"/blog/sqlmodel-my-findings#reading-one-row","Reading one row",[153,1992,2001],"# Get the first item from the result. May return `None` if the result is empty\nresults.first()\n\n# Returns exactly one item from the result. Raises an exception if the result doesn’t have exactly one result. The empty result also raises an exception.\nresolts.one()\n\n# Returns item by id. Returns `None` if not item found with queries id.\nsession.get(Hero, 1)",{"id":2019,"title":2020,"titles":2021,"content":2022,"level":218},"/blog/sqlmodel-my-findings#paginating-data","Paginating Data",[153,1992,2001],"# first three results. [1,2,3]\nselect(Hero).limit(3)\n\n# skips the first three and returns next three. [4,5,6]\nselect(Hero).offset(3).limit(3)\n\n# Next batch/page: select(Hero).offset(6).limit(3) => [7,8,9]",{"id":2024,"title":2025,"titles":2026,"content":2027,"level":207},"/blog/sqlmodel-my-findings#️-update","⚙️ Update",[153,1992],"spiderboy = session.exec(select(Hero).where(Hero.name == \"Spider-Boy\")).one()\nspiderboy.age = 16\nsession.add(spiderboy)\nsession.commit()\nsession.refresh(spiderboy)\n\n# add related table connection\nhero_spider_boy.team_id = team_preventers.id\nsession.add(hero_spider_boy)\nsession.commit()\nsession.refresh(hero_spider_boy)\n\n# remove related table connection\nhero_spider_boy.team_id = None\nsession.add(hero_spider_boy)\nsession.commit()\nsession.refresh(hero_spider_boy)",{"id":2029,"title":2030,"titles":2031,"content":2032,"level":207},"/blog/sqlmodel-my-findings#️️-delete","🏌️‍♂️ Delete",[153,1992],"hero = session.exec(select(Hero).where(Hero.name == \"Spider-Youngster\"))\nsession.delete(hero)\nsession.commit()\n# session.refresh(hero) => raises exception",{"id":2034,"title":2035,"titles":2036,"content":296,"level":191},"/blog/sqlmodel-my-findings#model","🧬 Model",[153],{"id":2038,"title":2039,"titles":2040,"content":2041,"level":207},"/blog/sqlmodel-my-findings#indexing","Indexing",[153,2035],"class Hero(SQLModel, table=True):\n    # ...\n    name: str = Field(index=True)",{"id":2043,"title":2044,"titles":2045,"content":296,"level":191},"/blog/sqlmodel-my-findings#️-relationships","❤️‍🔥 Relationships",[153],{"id":2047,"title":2048,"titles":2049,"content":296,"level":207},"/blog/sqlmodel-my-findings#one-to-many","One to Many",[153,2044],{"id":2051,"title":2052,"titles":2053,"content":2054,"level":218},"/blog/sqlmodel-my-findings#adding-foreign-key","Adding foreign key",[153,2044,2048],"class Team(SQLModel, table=True):\n    id: Optional[int] = Field(default=None, primary_key=True)\n    name: str = Field(index=True)\n    headquarters: str\n\nclass Hero(SQLModel, table=True):\n    id: Optional[int] = Field(default=None, primary_key=True)\n    name: str = Field(index=True)\n    secret_name: str\n    age: Optional[int] = Field(default=None, index=True)\n\n    team_id: Optional[int] = Field(default=None, foreign_key=\"team.id\") // [!code hl]",{"id":2056,"title":2057,"titles":2058,"content":2059,"level":218},"/blog/sqlmodel-my-findings#creating-connected-rows","Creating connected rows",[153,2044,2048],"team_preventers = Team(name=\"Preventers\", headquarters=\"Sharp Tower\")\nsession.add(team_preventers)\nsession.commit()\n\n'''\nHEADS UP:\nWe aren't refreshing the `team_preventers` because later when we will access\nteam_preventers.id` it will get automatically refreshed.\n'''\n\nhero_deadpond = Hero(\n name=\"Deadpond\",\n secret_name=\"Dive Wilson\",\n team_id=team_preventers.id // [!code hl]\n)",{"id":2061,"title":2062,"titles":2063,"content":296,"level":191},"/blog/sqlmodel-my-findings#type-annotations-and-errors","🔮 Type Annotations and Errors",[153],{"id":2065,"title":2066,"titles":2067,"content":2068,"level":207},"/blog/sqlmodel-my-findings#err-col-is-potentially-none-and-you-cannot-compare-none-with","Err: \u003Ccol> is potentially None, and you cannot compare None with",[153,2062],"# You will get \"Hero.age is potentially None, and you cannot compare None with >/color\"\nsession.exec(select(Hero).where(Hero.age >= 35)) // [!code error]\n\n# Wrap the column with `col`\nsession.exec(select(Hero).where(col(Hero.age) >= 35)) html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sVm5P, html code.shiki .sVm5P{--shiki-default:#FF5555;--shiki-default-font-style:italic;--shiki-default-text-decoration:underline}",{"id":158,"title":157,"titles":2070,"content":2071,"level":185},[],"Tips and tricks to enhance your usage of pushd and popd commands in the terminal. YouTube Video",{"id":2073,"title":2074,"titles":2075,"content":2076,"level":191},"/blog/taking-pushd-and-popd-to-next-level#problem","Problem",[157],"I assume you are already aware of the pushd and popd. Sometimes, When you pushd, you might need directory you navigated from. For example: pwd # ~/foo\nzip -r foo.zip foo\npushd ~/bar\nunzip ~/foo/foo.zip // [!code hl] In last line, You need previous directory name.",{"id":2078,"title":2079,"titles":2080,"content":2081,"level":191},"/blog/taking-pushd-and-popd-to-next-level#solution","Solution",[157],"You can leverage $OLDPWD variable to get previously navigated directory. pwd # ~/foo\nzip -r foo.zip foo\npushd ~/bar\nunzip $OLDPWD/foo.zip // [!code hl]",{"id":2083,"title":2084,"titles":2085,"content":2086,"level":191},"/blog/taking-pushd-and-popd-to-next-level#bonus","Bonus",[157],"Completely following DRY principal: pwd # ~/foo\nZIP_NAME=foo.zip\nzip -r $ZIP_NAME foo // [!code hl]\npushd ~/bar\nunzip $OLDPWD/$ZIP_NAME // [!code hl] We added ZIP_NAME variable to avoid repeating foo.zip in both commands. html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":162,"title":161,"titles":2088,"content":2089,"level":185},[],"A comprehensive guide to establishing clear and scalable naming conventions for Pydantic schemas in FastAPI applications. When building APIs with FastAPI and Pydantic, schemas are critical. They define how data flows between your API and its consumers, ensuring proper validation and structure. However, FastAPI doesn’t enforce or recommend any specific naming convention for schemas. This lack of guidance, combined with the basic examples in FastAPI and SQLModel documentation, can lead to confusion as your project grows. For instance, while examples like ItemCreate or ItemRead work for simple APIs, they don’t scale well when you have more complex requirements like differentiating public-facing schemas from internal ones or handling multiple types of updates. In this post, we’ll introduce a scalable naming convention for Pydantic schemas, making your codebase easier to understand, maintain, and extend.",{"id":2091,"title":2092,"titles":2093,"content":2094,"level":191},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#the-problem-with-inconsistent-naming","The Problem With Inconsistent Naming",[161],"Let’s imagine a scenario:\nYou’re building an API for managing workflows. You need schemas for creating workflows, fetching a list of workflows, viewing detailed information, updating workflows, and so on. Without a structured naming convention, you might end up with names like: WorkflowBaseWorkflowInWorkflowOutWorkflowUpdatePartial At first glance, it’s unclear what each schema is for. Is WorkflowBase for database operations or public responses? Does WorkflowIn mean creating or updating a workflow? Over time, this lack of clarity makes the code harder to navigate, especially for larger teams or when onboarding new developers.",{"id":2096,"title":2097,"titles":2098,"content":2099,"level":191},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#a-scalable-naming-convention-for-pydantic-schemas","A Scalable Naming Convention for Pydantic Schemas",[161],"Here’s a structured naming convention to solve these issues. The goal is to ensure each schema’s purpose is immediately clear.",{"id":2101,"title":2102,"titles":2103,"content":2104,"level":207},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#naming-pattern","Naming Pattern",[161,2097],"Schema NamePurposeWorkflowCreatePublic-facing schema for creating workflows.WorkflowCreateDB?Internal schema for database operations (e.g., auto-assigning owner_id).WorkflowListItemSchema for individual workflow items in a list (e.g., GET /workflows).WorkflowListRoot model schema for the entire list of workflows.WorkflowDetailsSchema for detailed view of a workflow (e.g., GET /workflows/{workflow_id}).WorkflowUpdate?Schema for full updates (via PUT, replacing the entire workflow).WorkflowUpdateDB?Internal schema for database operations during full updates.WorkflowPatchPublic-facing schema for partial updates (via PATCH).WorkflowPatchDB?Internal schema for database operations during partial updates. The ? indicates that the schema is optional and only needed in certain cases. For example, WorkflowCreateDB might be used if you need to assign internal fields like owner_id or last_updated_by during creation, but if such fields aren’t required, you can skip defining this schema.",{"id":2106,"title":2107,"titles":2108,"content":2109,"level":207},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#schema-naming-convention-breakdown","Schema Naming Convention Breakdown",[161,2097],"Let’s dive into each schema to understand its purpose and usage.",{"id":2111,"title":2112,"titles":2113,"content":2114,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_1-workflowcreate","1. WorkflowCreate",[161,2097,2107],"This schema is for public-facing API operations that create workflows. It defines the fields clients can send in a request to create a workflow. class WorkflowCreate(BaseModel):\n    name: str\n    description: str | None = None",{"id":2116,"title":2117,"titles":2118,"content":2119,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_2-workflowcreatedb","2. WorkflowCreateDB",[161,2097,2107],"This schema extends WorkflowCreate to include internal fields like owner_id. It’s used internally to process additional data before saving to the database. class WorkflowCreateDB(WorkflowCreate):\n    owner_id: int  # Automatically assigned based on the authenticated user",{"id":2121,"title":2122,"titles":2123,"content":2124,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_3-workflowlistitem","3. WorkflowListItem",[161,2097,2107],"This schema represents an individual workflow in a list of workflows. It defines the data clients receive when fetching multiple workflows. class WorkflowListItem(BaseModel):\n    id: int\n    name: str",{"id":2126,"title":2127,"titles":2128,"content":2129,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_4-workflowlist","4. WorkflowList",[161,2097,2107],"This is a root model for the entire list of workflows, providing type validation for list responses. It’s often a helper model for endpoints like GET /workflows. WorkflowList = RootModel[Sequence[WorkflowListItem]]",{"id":2131,"title":2132,"titles":2133,"content":2134,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_5-workflowdetails","5. WorkflowDetails",[161,2097,2107],"This schema is for endpoints like GET /workflows/{workflow_id} that return detailed information about a specific workflow. class WorkflowDetails(BaseModel):\n    id: int\n    name: str\n    description: str | None\n    owner_id: int\n    created_at: datetime",{"id":2136,"title":2137,"titles":2138,"content":2139,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_6-workflowupdate","6. WorkflowUpdate",[161,2097,2107],"This schema defines the structure for full updates (PUT requests), where the client replaces the entire resource. Generally, we'll create WorkflowPatch schema for partial updates, but if you need to support full updates, you can use this schema. class WorkflowUpdate(BaseModel):\n    name: str\n    description: str | None = None",{"id":2141,"title":2142,"titles":2143,"content":2144,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_7-workflowupdatedb","7. WorkflowUpdateDB",[161,2097,2107],"This schema extends WorkflowUpdate for internal use, such as tracking who updated the workflow. Generally, we'll create WorkflowPatchDB schema for partial updates, but if you need to support full updates, you can use this schema. class WorkflowUpdateDB(WorkflowUpdate):\n    updated_by: int  # ID of the user making the update",{"id":2146,"title":2147,"titles":2148,"content":2149,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_8-workflowpatch","8. WorkflowPatch",[161,2097,2107],"This schema supports partial updates (PATCH requests), allowing clients to update specific fields. class WorkflowPatch(BaseModel):\n    name: str | None = None\n    description: str | None = None",{"id":2151,"title":2152,"titles":2153,"content":2154,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_9-workflowpatchdb","9. WorkflowPatchDB",[161,2097,2107],"This schema is used internally to handle database-specific logic for partial updates, such as recording the updater’s ID. class WorkflowPatchDB(WorkflowPatch):\n    updated_by: int",{"id":2156,"title":2157,"titles":2158,"content":296,"level":207},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#benefits-of-this-convention","Benefits of This Convention",[161,2097],{"id":2160,"title":2161,"titles":2162,"content":2163,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_1-clarity","1. Clarity",[161,2097,2157],"Schemas are self-explanatory. Developers can immediately understand what a schema does based on its name.",{"id":2165,"title":2166,"titles":2167,"content":2168,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_2-separation-of-concerns","2. Separation of Concerns",[161,2097,2157],"Public-facing schemas and internal schemas are clearly separated. This ensures a cleaner, more secure API design.",{"id":2170,"title":2171,"titles":2172,"content":2173,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_3-scalability","3. Scalability",[161,2097,2157],"As your application grows, this convention easily adapts. For instance: Adding a feature like archiving workflows? Simply create WorkflowArchive and WorkflowArchiveDB schemas.Need a schema for cloning workflows? Add WorkflowClone and WorkflowCloneDB. By following this pattern, you avoid ad-hoc naming that can clutter your codebase.",{"id":2175,"title":2176,"titles":2177,"content":2178,"level":218},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#_4-consistency","4. Consistency",[161,2097,2157],"A consistent structure across schemas makes it easier for teams to collaborate and onboard new developers.",{"id":2180,"title":2181,"titles":2182,"content":2183,"level":207},"/blog/the-ultimate-guide-to-naming-conventions-for-pydantic-schemas-in-fastapi#when-to-use-internal-schemas-db-suffix","When to Use Internal Schemas (DB Suffix)",[161,2097],"Internal schemas with the DB suffix should only be created when you need additional processing for fields like: owner_id: Assigning ownership of a resource to the authenticated user.last_updated_by: Tracking who last modified a resource. If no such fields are required, you can skip creating the DB schema and directly use the public-facing schema. html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"id":166,"title":165,"titles":2185,"content":2186,"level":185},[],"A comprehensive guide on setting up and using prompts effectively in various AI-assisted coding editors. VSCode Prompting We'll focus on VS Code, Cursor, Claude Code & OpenCode as they are the most popular AI-assisted coding editors.",{"id":2188,"title":299,"titles":2189,"content":2190,"level":191},"/blog/ultimate-guide-to-prompting-editors#agentsmd",[165],"Create AGENTS.md in root of your project.Symlink AGENTS.md to CLAUDE.md for claude code support ln -s AGENTS.md CLAUDE.md This will now support most of the editors and Claude Code also.",{"id":2192,"title":2193,"titles":2194,"content":2195,"level":191},"/blog/ultimate-guide-to-prompting-editors#slash-commands-custom-agents","Slash Commands & Custom Agents",[165],"Create slash commands or custom agents under .ai/agents/\u003Cname>.md. This will be your slash command or custom agent for VS Code.Use symlink to add support for all editors & Claude Code. mkdir -p .github && ln -s ../.ai/commands .github/agents\nmkdir -p .cursor && ln -s ../.ai/commands .cursor/commands\nmkdir -p .claude && ln -s ../.ai/commands .claude/commands",{"id":2197,"title":2198,"titles":2199,"content":2200,"level":191},"/blog/ultimate-guide-to-prompting-editors#skills","Skills",[165],"Store rules under .ai/skills/\u003Cskill>/SKILL.mdCreate symlink to for Copilot, Cursor & Claude Code support mkdir -p .claude && ln -s ../.ai/skills .claude/skills\nmkdir -p .github && ln -s ../.ai/skills .github/skills\nmkdir -p .cursor && ln -s ../.ai/skills .cursor/skills\nmkdir -p .opencode && ln -s ../.ai/skills .opencode/skills",{"id":2202,"title":2203,"titles":2204,"content":2205,"level":191},"/blog/ultimate-guide-to-prompting-editors#mcps","MCPs",[165],"Syncing MCP across different tooling requires some manual work. Create various MCPs under .ai/mcp/mcp.\u003Ctool>.json// .ai/mcp/mcp.vscode.json\n{\n  \"servers\": {\n    \"context7\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.context7.com/mcp\",\n      \"headers\": {\n        \"CONTEXT7_API_KEY\": \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\n// .ai/mcp/mcp.cc.json\n{\n  \"mcpServers\": {\n    \"context7\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.context7.com/mcp\",\n      \"headers\": {\n        \"CONTEXT7_API_KEY\": \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\nSync MCPs via symlink # VS Code\nmkdir -p .vscode && ln -s ../.ai/mcp/mcp.vscode.json .vscode/mcp.json\n\n# Claude Code\nln -s ./.ai/mcp/mcp.cc.json .mcp.json\n\n# Cursor\nmkdir -p .cursor && ln -s ../.ai/mcp/mcp.cc.json .cursor/mcp.json We can't sync OpenCode MCPs via symlink as it requires editing their settings file. Maintain following: // .opencode.json\n{\n  \"mcp\": {\n    \"context7\": {\n      \"type\": \"remote\",\n      \"url\": \"https://mcp.context7.com/mcp\",\n      \"headers\": {\n        \"CONTEXT7_API_KEY\": \"YOUR_API_KEY\"\n      },\n      \"enabled\": true\n    }\n  }\n}",{"id":2207,"title":2208,"titles":2209,"content":2210,"level":191},"/blog/ultimate-guide-to-prompting-editors#rules","Rules",[165],"Store rules under .ai/rules/\u003Cname>.instructions.mdCursor support via symlink ln -s ../.ai/rules .cursor/rules Copilot support by adding this setting in .vscode/settings.json {\n  \"chat.instructionsFilesLocations\": {\n    \".ai/rules\": true\n  }\n} Copilot calls it instructions and requires files to have .instructions.md suffix Claude Code doesn't support rules You might also want to have a look at prompts. html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sY8FZ, html code.shiki .sY8FZ{--shiki-default:#8BE9FE}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"id":170,"title":169,"titles":2212,"content":2213,"level":185},[],"A comprehensive guide on the various use cases of underscores in Python, including conventions for private variables, throwaway variables, and more. Underscores have several special meanings that can be helpful to know when working with Python.",{"id":2215,"title":2216,"titles":2217,"content":2218,"level":191},"/blog/underscore-use-cases-in-python#_1-capturing-the-last-value-in-interpreter-session","1. Capturing the Last Value in Interpreter Session",[169],">>> 5 + 5\n10\n>>> _ + 5\n15",{"id":2220,"title":2221,"titles":2222,"content":2223,"level":191},"/blog/underscore-use-cases-in-python#_2-internal-methods-and-variables","2. Internal Methods and Variables",[169],"These are methods and variables that are not intended to be accessed from outside of the module or class. By starting them with an underscore, you're indicating to other developers that these are private elements that should not be used directly. class Validator:\n    def __init__(self):\n        self._data = None\n        self.__secret = \"🤫\"\n\n    def set_data(self, data):\n        self._data = data\n\n    def validate(self):\n        if self._data is not None:\n            print(\"Validating data...\")\n            # Validation logic here\n        else:\n            print(\"No data to validate.\")\n\nvalidator = Validator()\nvalidator.set_data(\"Sample data\")\nvalidator.validate() # Validating data...\n\n# Accessing single underscore private variable\nprint(validator._data) # Sample data\n\n# Accessing double underscore private variable\nprint(validator.__secret) # AttributeError: 'Validator' object has no attribute '__secret'\n\n# Accessing double underscore private variable using name mangling\nprint(validator._Validator__secret) # 🤫 Single Underscore vs Double UnderscoreSingle Underscore: It is only a convention. A way for the programmer to indicate that the variable is private. It is not enforced by the Python interpreter.Double Underscore:  is a way to prevent name conflicts between attributes and methods with the same name. Python mangles these names with the class name: if class Foo has an attribute named __a, it cannot be accessed by Foo.__a. (An insistent user could still gain access by calling Foo._Foo__a.) Generally, double underscores should be used only to avoid name conflicts with attributes in classes designed to be subclassed.",{"id":2225,"title":2226,"titles":2227,"content":2228,"level":191},"/blog/underscore-use-cases-in-python#_3-improving-readability-of-large-numbers","3. Improving Readability of Large Numbers",[169],">>> one_million = 1_000_000\n>>> one_million\n1000000",{"id":2230,"title":2231,"titles":2232,"content":2233,"level":191},"/blog/underscore-use-cases-in-python#_4-avoids-naming-conflicts-with-python-keywords-and-built-in-names","4. Avoids naming conflicts with Python keywords and built-in names",[169],"# class\nclass = \"Python\" # SyntaxError: invalid syntax\n\nclass_ = \"Python\"",{"id":2235,"title":2236,"titles":2237,"content":2238,"level":191},"/blog/underscore-use-cases-in-python#_5-throwaway-variables","5. Throwaway Variables",[169],"# Throwaway variable\n_, y = (1, 2)\n\n# Throwaway variable in for loop\nfor _ in range(10):\n    print(\"Hello World\") html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"id":174,"title":173,"titles":2240,"content":2241,"level":185},[],"Different methods to approve memberships in private communities. I was building support system for communities and wanted to know something from my friends on what they use. Gathering their and mine knowledge here's what I found. When building community you might not want to public your community for various reasons. This includes, You want to build a community/(support system) for your customersYou want to avoid spamYou want to build community for your group/friends/employees With private community there are several ways you can approve membership. Here are some of them,",{"id":2243,"title":2244,"titles":2245,"content":2246,"level":191},"/blog/ways-to-approve-private-memberships#approve-by-human-review","🧐 Approve by human review",[173],"You can approve membership by reviewing each and every request. In this kind of community, community is visible to public but don't have public access. User will find your organization and request for join. This is good for small communities but as your community grows it becomes difficult to approve each and every request.",{"id":2248,"title":2249,"titles":2250,"content":2251,"level":218},"/blog/ways-to-approve-private-memberships#pros","Pros",[173,2244],"You can control who joins your communityYou can avoid spam",{"id":2253,"title":2254,"titles":2255,"content":2256,"level":218},"/blog/ways-to-approve-private-memberships#cons","Cons",[173,2244],"It's time consuming",{"id":2258,"title":2259,"titles":2260,"content":2261,"level":218},"/blog/ways-to-approve-private-memberships#for-whom","For whom",[173,2244],"Small communitiesCommunities where you want to control who joins",{"id":2263,"title":2264,"titles":2265,"content":2266,"level":191},"/blog/ways-to-approve-private-memberships#approve-by-invite","📭 Approve by invite",[173],"You can approve membership by sending invite to user. This kind of community is not visible to public. User can join only if they have invite they received from community maintainer/moderator. This is good for private communities where you explicitly want to provide access and don't want any public requests.",{"id":2268,"title":2249,"titles":2269,"content":2270,"level":218},"/blog/ways-to-approve-private-memberships#pros-1",[173,2264],"You can control who joins your communityYou can avoid spamYou can avoid public requests",{"id":2272,"title":2254,"titles":2273,"content":2274,"level":218},"/blog/ways-to-approve-private-memberships#cons-1",[173,2264],"You need to send invite to each and every user manually",{"id":2276,"title":2259,"titles":2277,"content":2278,"level":218},"/blog/ways-to-approve-private-memberships#for-whom-1",[173,2264],"Private communities where owner wants to only provide access to specific users. For example, communities for employees, communities for friends, communities for customers.",{"id":2280,"title":2281,"titles":2282,"content":2283,"level":191},"/blog/ways-to-approve-private-memberships#third-party-authentication","🔐 Third party authentication",[173],"You can approve membership by authenticating user with third party services like various marketplaces or subscription services etc. This is good for communities who runs the business and want to provide support to their customers or subscribers. For example, if you are building community for product you sell on marketplace then you can authenticate user with that marketplace and provide access to your community. After user authenticate using third party service, you can check if user has purchased your product or not and provide access & role accordingly.",{"id":2285,"title":2249,"titles":2286,"content":2287,"level":218},"/blog/ways-to-approve-private-memberships#pros-2",[173,2281],"Automate membership approvalOnly users who purchased your product can join your communityNo spam",{"id":2289,"title":2254,"titles":2290,"content":2291,"level":218},"/blog/ways-to-approve-private-memberships#cons-2",[173,2281],"You need to provide integration with third party services",{"id":2293,"title":2259,"titles":2294,"content":2295,"level":218},"/blog/ways-to-approve-private-memberships#for-whom-2",[173,2281],"Communities for customers/subscribers",{"id":178,"title":177,"titles":2297,"content":2298,"level":185},[],"An exploration of the limitations and challenges of Python's type system.",{"id":2300,"title":2301,"titles":2302,"content":2303,"level":191},"/blog/why-python-type-system-sucks#unpredictable-type-inference","Unpredictable Type Inference",[177],"Even with all possible type hints, Python type system don't allow passing TypedDict value to param. from collections.abc import Mapping, MutableMapping\nfrom typing import TypedDict\n\ndef my_func(v: Mapping[str, str] | dict[str, str] | MutableMapping[str, str]):  ...\n\nclass MyTypedDict(TypedDict): ...\n\nval: MyTypedDict = {}\nmy_func(val) # 🚨 ERROR: \"MyTypedDict\" is not assignable",{"id":2305,"title":2306,"titles":2307,"content":2308,"level":191},"/blog/why-python-type-system-sucks#nested-types-are-hard","Nested Types are hard",[177],"Python type system is not good at handling nested types. We've to define a new type for each nested type. from typing import Any, TypedDict\n\nclass ContextNode(TypedDict):\n    output: Any\n\nclass Context(TypedDict):\n    nodes: ContextNode In typescript this is as simple as: interface Context {\n  nodes: {\n    output: any\n  }\n}",{"id":2310,"title":2311,"titles":2312,"content":2313,"level":191},"/blog/why-python-type-system-sucks#beware-with-sequence","Beware with Sequence",[177],"from collections.abc import Sequence\n\ndef my_func(errors: Sequence[str]): ...\n\nmy_func([\"Some Error\"])\nmy_func(\"Some Error\") # Valid, Because strings are sequence as well html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}",{"id":2315,"title":30,"body":2316,"description":567,"extension":2849,"meta":2850,"navigation":2852,"path":34,"private":180,"seo":2853,"stem":35,"__hash__":2854},"blog/blog/choosing-between-s3-standard-vs-glacier copy.md",{"type":2317,"value":2318,"toc":2832},"minimark",[2319,2326,2330,2337,2399,2410,2420,2520,2564,2567,2570,2582,2600,2605,2632,2638,2668,2673,2703,2705,2708,2744,2746,2749,2775,2777,2780,2799,2801,2804],[2320,2321,2322],"h1",{"id":296},[2323,2324],"binding",{"value":2325},"$frontmatter.title",[2327,2328,509],"h2",{"id":2329},"overview-s3-standard-vs-glacier-storage-classes",[2331,2332,2334],"h3",{"id":2333},"s3-standard-and-standardia",[2335,2336,513],"strong",{},[2338,2339,2340,2347,2353,2375,2384,2390],"ul",{},[2341,2342,2343,2346],"li",{},[2335,2344,2345],{},"Purpose",": Frequent or predictable access.",[2341,2348,2349,2352],{},[2335,2350,2351],{},"Access latency",": Milliseconds.",[2341,2354,2355,2358,2359,2367,2368,2374],{},[2335,2356,2357],{},"Durability",": 99.999999999% (“11 nines”) (",[2360,2361,2366],"a",{"href":2362,"rel":2363,"title":2365},"https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html",[2364],"nofollow","Understanding and managing Amazon S3 storage classes - Amazon Simple Storage Service","AWS Documentation",", ",[2360,2369,2373],{"href":2370,"rel":2371,"title":2372},"https://cloudwithdj.com/s3-glacier-vs-s3-standard-choosing-the-right-storage-class-for-your-data/",[2364],"S3 Glacier VS S3 Standard: Choosing the Right Storage Class for Your Data – Cloud with DJ","cloudwithdj.com",")",[2341,2376,2377,2380,2381,2374],{},[2335,2378,2379],{},"Availability",": ~99.99% (Standard), ~99.9% (Standard‑IA) (",[2360,2382,2366],{"href":2362,"rel":2383,"title":2365},[2364],[2341,2385,2386,2389],{},[2335,2387,2388],{},"Cost",": Higher per‑GB; minimal or no retrieval fees. Standard‑IA cheaper storage but charges per retrieval.",[2341,2391,2392,2395,2396,2374],{},[2335,2393,2394],{},"Minimum durations",": None for Standard; 30 days minimum for Standard‑IA/One‑Zone‑IA (",[2360,2397,2366],{"href":2362,"rel":2398,"title":2365},[2364],[2331,2400,2402,2405,2406],{"id":2401},"glacier-storage-classes-within-s3",[2335,2403,2404],{},"Glacier Storage Classes"," ",[2407,2408,2409],"em",{},"(within S3)",[2411,2412,2413,2414,2419],"p",{},"Three archival tiers, each offering the same 11‑nines durability as S3 Standard (",[2360,2415,2366],{"href":2416,"rel":2417,"title":2418},"https://docs.aws.amazon.com/AmazonS3/latest/userguide/glacier-storage-classes.html",[2364],"Understanding S3 Glacier storage classes for long-term data storage - Amazon Simple Storage Service",").",[2421,2422,2423,2445],"table",{},[2424,2425,2426],"thead",{},[2427,2428,2429,2433,2436,2439,2442],"tr",{},[2430,2431,2432],"th",{},"Tier",[2430,2434,2435],{},"Min Storage Duration",[2430,2437,2438],{},"Typical Access Frequency",[2430,2440,2441],{},"Retrieval Time",[2430,2443,2444],{},"Real‑time Access?",[2446,2447,2448,2469,2494],"tbody",{},[2427,2449,2450,2454,2457,2460,2463],{},[2451,2452,2453],"td",{},"Glacier Instant Retrieval",[2451,2455,2456],{},"90 days",[2451,2458,2459],{},"Quarterly or less",[2451,2461,2462],{},"Milliseconds",[2451,2464,2465,2466,2374],{},"✓ (",[2360,2467,2366],{"href":2416,"rel":2468,"title":2418},[2364],[2427,2470,2471,2474,2476,2479,2482],{},[2451,2472,2473],{},"Glacier Flexible Retrieval (formerly Glacier)",[2451,2475,2456],{},[2451,2477,2478],{},"1–2 times/year",[2451,2480,2481],{},"Minutes to hours",[2451,2483,2484,2485,2367,2488,2374],{},"✗ (must restore) (",[2360,2486,2366],{"href":2416,"rel":2487,"title":2418},[2364],[2360,2489,2493],{"href":2490,"rel":2491,"title":2492},"https://digitalcloud.training/amazon-s3-and-glacier/",[2364],"Amazon S3 and Glacier | AWS Cheat Sheet","Digital Cloud",[2427,2495,2496,2499,2502,2505,2508],{},[2451,2497,2498],{},"Glacier Deep Archive",[2451,2500,2501],{},"180 days",[2451,2503,2504],{},"≤ once/year",[2451,2506,2507],{},"12–48 hours",[2451,2509,2510,2511,2367,2514,2374],{},"✗ (restore required) (",[2360,2512,2366],{"href":2416,"rel":2513,"title":2418},[2364],[2360,2515,2519],{"href":2516,"rel":2517,"title":2518},"https://aws.amazon.com/s3/storage-classes//",[2364],"Object Storage Classes – Amazon S3","Amazon Web Services, Inc.",[2338,2521,2522,2534,2546,2555],{},[2341,2523,2524,2527,2528,2374],{},[2335,2525,2526],{},"Storage cost",": up to ~90% lower vs Standard (e.g. ~$0.004 /GB/mo) (",[2360,2529,2533],{"href":2530,"rel":2531,"title":2532},"https://medium.com/%40christopheradamson253/introduction-to-aws-s3-glacier-archiving-data-in-the-cloud-3ac456db4259",[2364],"Introduction to AWS S3 Glacier: Archiving Data in the Cloud | by Christopher Adamson | Medium","Medium",[2341,2535,2536,2539,2540,2374],{},[2335,2537,2538],{},"Retrieval cost",": charged per GB and request; faster tiers cost more (",[2360,2541,2545],{"href":2542,"rel":2543,"title":2544},"https://en.wikipedia.org/wiki/Amazon_S3_Glacier",[2364],"Amazon S3 Glacier","Wikipedia",[2341,2547,2548,2551,2552,2374],{},[2335,2549,2550],{},"Deletion charges",": deleting objects before minimum duration still bills for full minimum term (",[2360,2553,2366],{"href":2416,"rel":2554,"title":2418},[2364],[2341,2556,2557,2560,2561,2374],{},[2335,2558,2559],{},"Object size note",": Glacier Instant requires ≥ 128 KB minimum (billed as 128 KB if smaller) (",[2360,2562,2366],{"href":2416,"rel":2563,"title":2418},[2364],[2565,2566],"hr",{},[2327,2568,523],{"id":2569},"when-to-use-which",[2331,2571,2573,2574,2577,2578,2581],{"id":2572},"use-s3-standard-or-standardia-when","Use ",[2335,2575,2576],{},"S3 Standard"," or ",[2335,2579,2580],{},"Standard‑IA"," when",[2338,2583,2584,2591,2597],{},[2341,2585,2586,2587,2590],{},"Data is accessed ",[2335,2588,2589],{},"frequently or predictably"," (Standard: > monthly; IA: ~monthly).",[2341,2592,2593,2596],{},[2335,2594,2595],{},"Immediate, high‑performance access"," is required.",[2341,2598,2599],{},"You want no additional retrieval latency or fees.",[2331,2601,2573,2603,2581],{"id":2602},"use-glacier-instant-retrieval-when",[2335,2604,2453],{},[2338,2606,2607,2618,2629],{},[2341,2608,2609,2610,2613,2614,2617],{},"Data is ",[2335,2611,2612],{},"rarely accessed"," (e.g. quarterly or less), but ",[2335,2615,2616],{},"must be available instantly"," if needed.",[2341,2619,2620,2621,2367,2624,2374],{},"Example: medical images, satellite imagery, media archives that might be served dynamically (",[2360,2622,2366],{"href":2362,"rel":2623,"title":2365},[2364],[2360,2625,2519],{"href":2626,"rel":2627,"title":2628},"https://aws.amazon.com/s3/storage-classes/glacier/",[2364],"Secure archive storage – Amazon S3 Glacier storage classes – AWS",[2341,2630,2631],{},"Cost‑sensitive vs Standard‑IA for low access frequency.",[2331,2633,2573,2635,2581],{"id":2634},"use-glacier-flexible-retrieval-when",[2335,2636,2637],{},"Glacier Flexible Retrieval",[2338,2639,2640,2647,2665],{},[2341,2641,2642,2643,2646],{},"Data access is ",[2335,2644,2645],{},"very infrequent"," (once or twice a year).",[2341,2648,2649,2652,2653,2367,2656,2367,2662,2374],{},[2335,2650,2651],{},"Retrieval delays of minutes to hours are acceptable"," (Expedited: 1–5 min; Standard: 3–5 h; Bulk: 5–12 h) (",[2360,2654,2519],{"href":2626,"rel":2655,"title":2628},[2364],[2360,2657,2661],{"href":2658,"rel":2659,"title":2660},"https://www.reddit.com/r/aws/comments/p780wd",[2364],"Is S3 glacier right option for my use case of storing 15TB of data?","Reddit",[2360,2663,2366],{"href":2416,"rel":2664,"title":2418},[2364],[2341,2666,2667],{},"Use case: backups, disaster recovery, audit logs.",[2331,2669,2573,2671,2581],{"id":2670},"use-glacier-deep-archive-when",[2335,2672,2498],{},[2338,2674,2675,2682,2700],{},[2341,2676,2677,2678,2681],{},"Data is archived for ",[2335,2679,2680],{},"long-term compliance"," (e.g. 7–10 years) or rarely needed (\u003C once/year).",[2341,2683,2684,2687,2688,2367,2691,2367,2697,2374],{},[2335,2685,2686],{},"Lowest possible storage cost"," outweighs retrieval latency (makes sense if you can wait 12‑48 hours) (",[2360,2689,2519],{"href":2516,"rel":2690,"title":2518},[2364],[2360,2692,2696],{"href":2693,"rel":2694,"title":2695},"https://awsforengineers.com/blog/aws-s3-getting-started-understanding-storage-classes/",[2364],"AWS S3 Getting Started: Understanding Storage Classes","AWS for Engineers",[2360,2698,2373],{"href":2370,"rel":2699,"title":2372},[2364],[2341,2701,2702],{},"Ideal replacement for tape libraries, regulatory archives, digital preservation.",[2565,2704],{},[2327,2706,547],{"id":2707},"️-lifecycle-cost-considerations",[2338,2709,2710,2716,2733],{},[2341,2711,2712,2715],{},[2335,2713,2714],{},"Automatically transition"," objects from Standard to Glacier via S3 Lifecycle policies based on age, tags, etc.",[2341,2717,2718,2719,2722,2723,2367,2728,2419],{},"Transition ",[2335,2720,2721],{},"into Glacier has no minimum wait time",", but billing still honors the 90/180‑day minimum (",[2360,2724,2533],{"href":2725,"rel":2726,"title":2727},"https://medium.com/analytics-vidhya/amazon-s3-storage-classes-d77de43df23d",[2364],"Amazon Web Service S3 Storage Classes | by Ankit Gupta | Analytics Vidhya | Medium",[2360,2729,2661],{"href":2730,"rel":2731,"title":2732},"https://www.reddit.com/r/AWSCertifications/comments/1f9f42f",[2364],"S3 to Glacier question",[2341,2734,2735,2738,2739,2419],{},[2335,2736,2737],{},"Lifecycle transitions cost"," per request, especially when transitioning many small objects. Sometimes batching small files (e.g. zipping) helps (",[2360,2740,2661],{"href":2741,"rel":2742,"title":2743},"https://www.reddit.com/r/aws/comments/rylpxq",[2364],"Reduced S3 costs by 60% with S3 Glacier Instant Retrieval storage class",[2565,2745],{},[2327,2747,552],{"id":2748},"quick-usecase-scenarios",[2338,2750,2751,2757,2763,2769],{},[2341,2752,2753,2756],{},[2335,2754,2755],{},"Website assets (images/videos)",": S3 Standard (low latency, frequent use).",[2341,2758,2759,2762],{},[2335,2760,2761],{},"Monthly backup archives",": Standard‑IA or Glacier Instant if access needs are rare but quick.",[2341,2764,2765,2768],{},[2335,2766,2767],{},"Yearly audit logs or compliance data",": Glacier Flexible Retrieval – cost effective with some delay.",[2341,2770,2771,2774],{},[2335,2772,2773],{},"Multi-year retention archives (e.g. legal, financial)",": Glacier Deep Archive — ultra‑low cost, slow access is acceptable.",[2565,2776],{},[2327,2778,557],{"id":2779},"summary-comparison",[2338,2781,2782,2788,2793],{},[2341,2783,2784,2787],{},[2335,2785,2786],{},"Access frequency & latency",": Standard (frequent/millisecond) → Instant Glacier (rare/millisecond) → Flexible (rare/minutes–hours) → Deep Archive (very rare/hours).",[2341,2789,2790,2792],{},[2335,2791,2526],{},": Standard > Standard‑IA > Instant Retrieval > Flexible Retrieval > Deep Archive.",[2341,2794,2795,2798],{},[2335,2796,2797],{},"Retrieval cost & delay",": Standard has none; Glacier tiers trade cost for delay.",[2565,2800],{},[2331,2802,562],{"id":2803},"️-recommendation-tips",[2338,2805,2806,2823,2826,2829],{},[2341,2807,2808,2809,2367,2812,2367,2815,2818,2819,2822],{},"Define your data by ",[2335,2810,2811],{},"access pattern",[2335,2813,2814],{},"latency requirement",[2335,2816,2817],{},"frequency of retrieval",", and ",[2335,2820,2821],{},"retention policy",".",[2341,2824,2825],{},"Use S3 Lifecycle rules to automatically tier data over time.",[2341,2827,2828],{},"Monitor retrieval volumes and sizes to avoid surprising retrieval costs (especially for large restores).",[2341,2830,2831],{},"Batch small files to minimize request count and extra overhead.",{"title":296,"searchDepth":191,"depth":191,"links":2833},[2834,2838,2844,2845,2846],{"id":2329,"depth":191,"text":509,"children":2835},[2836,2837],{"id":2333,"depth":207,"text":513},{"id":2401,"depth":207,"text":518},{"id":2569,"depth":191,"text":523,"children":2839},[2840,2841,2842,2843],{"id":2572,"depth":207,"text":527},{"id":2602,"depth":207,"text":532},{"id":2634,"depth":207,"text":537},{"id":2670,"depth":207,"text":542},{"id":2707,"depth":191,"text":547},{"id":2748,"depth":191,"text":552},{"id":2779,"depth":191,"text":557,"children":2847},[2848],{"id":2803,"depth":207,"text":562},"md",{"date":2851},"2025-07-21",true,{"title":30,"description":567},"JTxVLGsJnoOlnV9vkTWAK5IoyG7VEhZRCBBE8rThuq8",[2856,2857],{"title":30,"path":31,"stem":32,"description":506,"children":-1},{"title":37,"path":38,"stem":39,"description":2858,"children":-1},"Learn how to display announcement & temporary information banner simultaneously on your website",1781877445330]