@mytechtoday/augment-extensions 0.7.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +265 -232
- package/README.md +956 -771
- package/augment-extensions/coding-standards/bash/README.md +196 -196
- package/augment-extensions/coding-standards/bash/module.json +163 -163
- package/augment-extensions/coding-standards/bash/rules/naming-conventions.md +336 -336
- package/augment-extensions/coding-standards/bash/rules/universal-standards.md +289 -289
- package/augment-extensions/coding-standards/css/README.md +40 -40
- package/augment-extensions/coding-standards/css/examples/css-examples.css +550 -550
- package/augment-extensions/coding-standards/css/module.json +44 -44
- package/augment-extensions/coding-standards/css/rules/css-modern-features.md +448 -448
- package/augment-extensions/coding-standards/css/rules/css-standards.md +492 -492
- package/augment-extensions/coding-standards/html/README.md +40 -40
- package/augment-extensions/coding-standards/html/examples/html-examples.html +267 -267
- package/augment-extensions/coding-standards/html/examples/responsive-layout.html +505 -505
- package/augment-extensions/coding-standards/html/module.json +44 -44
- package/augment-extensions/coding-standards/html/rules/html-standards.md +349 -349
- package/augment-extensions/coding-standards/html-css-js/README.md +194 -194
- package/augment-extensions/coding-standards/html-css-js/examples/async-examples.js +487 -487
- package/augment-extensions/coding-standards/html-css-js/examples/css-examples.css +550 -550
- package/augment-extensions/coding-standards/html-css-js/examples/dom-examples.js +667 -667
- package/augment-extensions/coding-standards/html-css-js/examples/html-examples.html +267 -267
- package/augment-extensions/coding-standards/html-css-js/examples/javascript-examples.js +612 -612
- package/augment-extensions/coding-standards/html-css-js/examples/responsive-layout.html +505 -505
- package/augment-extensions/coding-standards/html-css-js/module.json +48 -48
- package/augment-extensions/coding-standards/html-css-js/rules/async-patterns.md +515 -515
- package/augment-extensions/coding-standards/html-css-js/rules/css-modern-features.md +448 -448
- package/augment-extensions/coding-standards/html-css-js/rules/css-standards.md +492 -492
- package/augment-extensions/coding-standards/html-css-js/rules/dom-manipulation.md +439 -439
- package/augment-extensions/coding-standards/html-css-js/rules/html-standards.md +349 -349
- package/augment-extensions/coding-standards/html-css-js/rules/javascript-standards.md +486 -486
- package/augment-extensions/coding-standards/html-css-js/rules/performance.md +463 -463
- package/augment-extensions/coding-standards/html-css-js/rules/tooling.md +543 -543
- package/augment-extensions/coding-standards/js/README.md +46 -46
- package/augment-extensions/coding-standards/js/examples/async-examples.js +487 -487
- package/augment-extensions/coding-standards/js/examples/dom-examples.js +667 -667
- package/augment-extensions/coding-standards/js/examples/javascript-examples.js +612 -612
- package/augment-extensions/coding-standards/js/module.json +49 -49
- package/augment-extensions/coding-standards/js/rules/async-patterns.md +515 -515
- package/augment-extensions/coding-standards/js/rules/dom-manipulation.md +439 -439
- package/augment-extensions/coding-standards/js/rules/javascript-standards.md +486 -486
- package/augment-extensions/coding-standards/js/rules/performance.md +463 -463
- package/augment-extensions/coding-standards/js/rules/tooling.md +543 -543
- package/augment-extensions/coding-standards/php/README.md +248 -248
- package/augment-extensions/coding-standards/php/examples/api-endpoint-example.php +204 -204
- package/augment-extensions/coding-standards/php/examples/cli-command-example.php +206 -206
- package/augment-extensions/coding-standards/php/examples/legacy-refactoring-example.php +234 -234
- package/augment-extensions/coding-standards/php/examples/web-application-example.php +211 -211
- package/augment-extensions/coding-standards/php/examples/woocommerce-extension-example.php +215 -215
- package/augment-extensions/coding-standards/php/examples/wordpress-plugin-example.php +189 -189
- package/augment-extensions/coding-standards/php/module.json +166 -166
- package/augment-extensions/coding-standards/php/rules/api-development.md +480 -480
- package/augment-extensions/coding-standards/php/rules/category-configuration.md +332 -332
- package/augment-extensions/coding-standards/php/rules/cli-tools.md +472 -472
- package/augment-extensions/coding-standards/php/rules/cms-integration.md +561 -561
- package/augment-extensions/coding-standards/php/rules/code-quality.md +402 -402
- package/augment-extensions/coding-standards/php/rules/documentation.md +425 -425
- package/augment-extensions/coding-standards/php/rules/ecommerce.md +627 -627
- package/augment-extensions/coding-standards/php/rules/error-handling.md +336 -336
- package/augment-extensions/coding-standards/php/rules/legacy-migration.md +677 -677
- package/augment-extensions/coding-standards/php/rules/naming-conventions.md +279 -279
- package/augment-extensions/coding-standards/php/rules/performance.md +392 -392
- package/augment-extensions/coding-standards/php/rules/psr-standards.md +186 -186
- package/augment-extensions/coding-standards/php/rules/security.md +358 -358
- package/augment-extensions/coding-standards/php/rules/testing.md +403 -403
- package/augment-extensions/coding-standards/php/rules/type-declarations.md +331 -331
- package/augment-extensions/coding-standards/php/rules/web-applications.md +426 -426
- package/augment-extensions/coding-standards/powershell/README.md +154 -154
- package/augment-extensions/coding-standards/powershell/examples/admin-example.ps1 +272 -272
- package/augment-extensions/coding-standards/powershell/examples/automation-example.ps1 +173 -173
- package/augment-extensions/coding-standards/powershell/examples/cloud-example.ps1 +243 -243
- package/augment-extensions/coding-standards/powershell/examples/cross-platform-example.ps1 +297 -297
- package/augment-extensions/coding-standards/powershell/examples/dsc-example.ps1 +224 -224
- package/augment-extensions/coding-standards/powershell/examples/legacy-migration-example.ps1 +340 -340
- package/augment-extensions/coding-standards/powershell/examples/module-example.psm1 +255 -255
- package/augment-extensions/coding-standards/powershell/module.json +165 -165
- package/augment-extensions/coding-standards/powershell/rules/administrative-tools.md +439 -439
- package/augment-extensions/coding-standards/powershell/rules/automation-scripts.md +240 -240
- package/augment-extensions/coding-standards/powershell/rules/cloud-orchestration.md +384 -384
- package/augment-extensions/coding-standards/powershell/rules/configuration-schema.md +383 -383
- package/augment-extensions/coding-standards/powershell/rules/cross-platform-scripts.md +482 -482
- package/augment-extensions/coding-standards/powershell/rules/dsc-configurations.md +296 -296
- package/augment-extensions/coding-standards/powershell/rules/error-handling.md +314 -314
- package/augment-extensions/coding-standards/powershell/rules/legacy-migrations.md +466 -466
- package/augment-extensions/coding-standards/powershell/rules/modules-functions.md +244 -244
- package/augment-extensions/coding-standards/powershell/rules/naming-conventions.md +266 -266
- package/augment-extensions/coding-standards/powershell/rules/performance-optimization.md +209 -209
- package/augment-extensions/coding-standards/powershell/rules/security-practices.md +314 -314
- package/augment-extensions/coding-standards/powershell/rules/testing-guidelines.md +268 -268
- package/augment-extensions/coding-standards/powershell/rules/universal-standards.md +197 -197
- package/augment-extensions/coding-standards/python/README.md +48 -48
- package/augment-extensions/coding-standards/python/examples/best-practices.py +373 -373
- package/augment-extensions/coding-standards/python/module.json +30 -30
- package/augment-extensions/coding-standards/python/rules/async-patterns.md +884 -884
- package/augment-extensions/coding-standards/python/rules/best-practices.md +232 -232
- package/augment-extensions/coding-standards/python/rules/code-organization.md +220 -220
- package/augment-extensions/coding-standards/python/rules/documentation.md +831 -831
- package/augment-extensions/coding-standards/python/rules/error-handling.md +1008 -1008
- package/augment-extensions/coding-standards/python/rules/naming-conventions.md +172 -172
- package/augment-extensions/coding-standards/python/rules/testing.md +409 -409
- package/augment-extensions/coding-standards/python/rules/tooling.md +446 -446
- package/augment-extensions/coding-standards/python/rules/type-hints.md +253 -253
- package/augment-extensions/coding-standards/react/README.md +45 -45
- package/augment-extensions/coding-standards/react/module.json +27 -27
- package/augment-extensions/coding-standards/react/rules/component-patterns.md +214 -214
- package/augment-extensions/coding-standards/react/rules/hooks-best-practices.md +235 -235
- package/augment-extensions/coding-standards/react/rules/performance.md +300 -300
- package/augment-extensions/coding-standards/react/rules/state-management.md +265 -265
- package/augment-extensions/coding-standards/react/rules/typescript-react.md +271 -271
- package/augment-extensions/coding-standards/typescript/README.md +45 -45
- package/augment-extensions/coding-standards/typescript/module.json +27 -27
- package/augment-extensions/coding-standards/typescript/rules/naming-conventions.md +225 -225
- package/augment-extensions/collections/html-css-js/README.md +82 -82
- package/augment-extensions/collections/html-css-js/collection.json +41 -41
- package/augment-extensions/domain-rules/api-design/README.md +41 -41
- package/augment-extensions/domain-rules/api-design/module.json +27 -27
- package/augment-extensions/domain-rules/api-design/rules/authentication.md +263 -263
- package/augment-extensions/domain-rules/api-design/rules/documentation.md +395 -395
- package/augment-extensions/domain-rules/api-design/rules/error-handling.md +290 -290
- package/augment-extensions/domain-rules/api-design/rules/graphql-api.md +313 -313
- package/augment-extensions/domain-rules/api-design/rules/rest-api.md +214 -214
- package/augment-extensions/domain-rules/api-design/rules/versioning.md +268 -268
- package/augment-extensions/domain-rules/database/README.md +161 -161
- package/augment-extensions/domain-rules/database/examples/flat-database-example.md +793 -793
- package/augment-extensions/domain-rules/database/examples/hybrid-database-example.md +1132 -1132
- package/augment-extensions/domain-rules/database/examples/nosql-document-example.md +868 -868
- package/augment-extensions/domain-rules/database/examples/nosql-graph-example.md +805 -805
- package/augment-extensions/domain-rules/database/examples/relational-schema-example.md +621 -621
- package/augment-extensions/domain-rules/database/examples/vector-database-example.md +965 -965
- package/augment-extensions/domain-rules/database/module.json +28 -28
- package/augment-extensions/domain-rules/database/rules/flat-databases.md +624 -624
- package/augment-extensions/domain-rules/database/rules/nosql-databases.md +588 -588
- package/augment-extensions/domain-rules/database/rules/nosql-document-stores.md +856 -856
- package/augment-extensions/domain-rules/database/rules/nosql-graph-databases.md +778 -778
- package/augment-extensions/domain-rules/database/rules/nosql-key-value-stores.md +963 -963
- package/augment-extensions/domain-rules/database/rules/performance-optimization.md +1076 -1076
- package/augment-extensions/domain-rules/database/rules/relational-databases.md +697 -697
- package/augment-extensions/domain-rules/database/rules/relational-indexing.md +671 -671
- package/augment-extensions/domain-rules/database/rules/relational-query-optimization.md +607 -607
- package/augment-extensions/domain-rules/database/rules/relational-schema-design.md +907 -907
- package/augment-extensions/domain-rules/database/rules/relational-transactions.md +783 -783
- package/augment-extensions/domain-rules/database/rules/security-standards.md +980 -980
- package/augment-extensions/domain-rules/database/rules/universal-best-practices.md +485 -485
- package/augment-extensions/domain-rules/database/rules/vector-databases.md +521 -521
- package/augment-extensions/domain-rules/database/rules/vector-embeddings.md +858 -858
- package/augment-extensions/domain-rules/database/rules/vector-indexing.md +934 -934
- package/augment-extensions/domain-rules/design/color/themes/catppuccin-latte/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/catppuccin-latte/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/catppuccin-mocha/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/catppuccin-mocha/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/dracula/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/dracula/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/gruvbox-dark/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/gruvbox-dark/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/gruvbox-light/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/gruvbox-light/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/high-contrast/README.md +27 -27
- package/augment-extensions/domain-rules/design/color/themes/high-contrast/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/monokai/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/monokai/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/nord/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/nord/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/one-dark/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/one-dark/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/one-light/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/one-light/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/solarized-dark/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/solarized-dark/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/solarized-light/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/solarized-light/module.json +26 -26
- package/augment-extensions/domain-rules/design/color/themes/tokyo-night/README.md +23 -23
- package/augment-extensions/domain-rules/design/color/themes/tokyo-night/module.json +26 -26
- package/augment-extensions/domain-rules/mcp/README.md +150 -150
- package/augment-extensions/domain-rules/mcp/examples/compressed-example.md +522 -522
- package/augment-extensions/domain-rules/mcp/examples/graph-augmented-example.md +520 -520
- package/augment-extensions/domain-rules/mcp/examples/hybrid-example.md +570 -570
- package/augment-extensions/domain-rules/mcp/examples/state-based-example.md +427 -427
- package/augment-extensions/domain-rules/mcp/examples/token-based-example.md +435 -435
- package/augment-extensions/domain-rules/mcp/examples/vector-based-example.md +502 -502
- package/augment-extensions/domain-rules/mcp/module.json +49 -49
- package/augment-extensions/domain-rules/mcp/rules/compressed-mcp.md +595 -595
- package/augment-extensions/domain-rules/mcp/rules/configuration.md +345 -345
- package/augment-extensions/domain-rules/mcp/rules/graph-augmented-mcp.md +687 -687
- package/augment-extensions/domain-rules/mcp/rules/hybrid-mcp.md +636 -636
- package/augment-extensions/domain-rules/mcp/rules/state-based-mcp.md +484 -484
- package/augment-extensions/domain-rules/mcp/rules/testing-validation.md +360 -360
- package/augment-extensions/domain-rules/mcp/rules/token-based-mcp.md +393 -393
- package/augment-extensions/domain-rules/mcp/rules/universal-rules.md +194 -194
- package/augment-extensions/domain-rules/mcp/rules/vector-based-mcp.md +625 -625
- package/augment-extensions/domain-rules/security/README.md +41 -41
- package/augment-extensions/domain-rules/security/module.json +28 -28
- package/augment-extensions/domain-rules/security/rules/authentication-security.md +361 -361
- package/augment-extensions/domain-rules/security/rules/encryption.md +208 -208
- package/augment-extensions/domain-rules/security/rules/input-validation.md +294 -294
- package/augment-extensions/domain-rules/security/rules/owasp-top-10.md +339 -339
- package/augment-extensions/domain-rules/security/rules/secure-coding.md +293 -293
- package/augment-extensions/domain-rules/security/rules/web-security.md +268 -268
- package/augment-extensions/domain-rules/seo-sales-marketing/ANNOUNCEMENT.md +143 -0
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/README.md +140 -136
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/SCHEMA-VALIDATION-REPORT.md +216 -216
- package/augment-extensions/domain-rules/seo-sales-marketing/TEST-VALIDATION.md +129 -0
- package/augment-extensions/domain-rules/seo-sales-marketing/USAGE-GUIDES.md +254 -0
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/brand-kit-example.yaml +292 -292
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/campaign-brief-example.yaml +389 -389
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/content-calendar-example.yaml +643 -643
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/email-newsletter-example.md +376 -376
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/landing-page-example.md +934 -934
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/ppc-ad-copy-example.md +301 -301
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/seo-blog-post-example.md +347 -347
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/social-media-campaign-example.md +606 -606
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/module.json +50 -50
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/affiliate-influencer-marketing.md +593 -593
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/asset-management.md +418 -418
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/brand-consistency.md +210 -210
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/content-marketing.md +337 -337
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/conversion-optimization.md +455 -455
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/direct-sales.md +499 -499
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/email-marketing.md +439 -439
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/legal-compliance.md +227 -227
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/ppc-advertising.md +569 -569
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/seo-optimization.md +470 -470
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/social-media-marketing.md +414 -414
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/universal-marketing.md +177 -177
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/asset-inventory.schema.json +247 -247
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/brand-kit.schema.json +326 -326
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/campaign-brief.schema.json +342 -342
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/color-palette.schema.json +223 -223
- package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/content-template.schema.json +383 -383
- package/augment-extensions/domain-rules/wordpress/README.md +163 -163
- package/augment-extensions/domain-rules/wordpress/module.json +32 -32
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -617
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -270
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -423
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -493
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -568
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -510
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -251
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -501
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -564
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -388
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -441
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -139
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -1599
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -1727
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -428
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -422
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -1623
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -1343
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -734
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -1350
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -503
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -971
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -53
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -770
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -874
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -629
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -559
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -709
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -736
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -1057
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -463
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -478
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -818
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -416
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -667
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -878
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -693
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -352
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -818
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -624
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -866
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -1165
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -414
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -751
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -949
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -458
- package/augment-extensions/examples/design-patterns/README.md +37 -37
- package/augment-extensions/examples/design-patterns/examples/behavioral-patterns.md +370 -370
- package/augment-extensions/examples/design-patterns/examples/creational-patterns.md +250 -250
- package/augment-extensions/examples/design-patterns/examples/structural-patterns.md +264 -264
- package/augment-extensions/examples/design-patterns/module.json +27 -27
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -101
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -428
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -40
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -98
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -1299
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -40
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -98
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -763
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -40
- package/augment-extensions/workflows/beads/README.md +135 -135
- package/augment-extensions/workflows/beads/examples/complete-workflow-example.md +278 -278
- package/augment-extensions/workflows/beads/module.json +55 -55
- package/augment-extensions/workflows/beads/rules/best-practices.md +398 -398
- package/augment-extensions/workflows/beads/rules/file-format.md +327 -327
- package/augment-extensions/workflows/beads/rules/manual-setup.md +315 -315
- package/augment-extensions/workflows/beads/rules/workflow.md +326 -326
- package/augment-extensions/workflows/beads-integration/IMPLEMENTATION-STATUS.md +145 -145
- package/augment-extensions/workflows/beads-integration/README.md +143 -143
- package/augment-extensions/workflows/beads-integration/config/defaults.json +32 -32
- package/augment-extensions/workflows/beads-integration/config/schema.json +140 -140
- package/augment-extensions/workflows/beads-integration/examples/basic-task-generation.md +293 -293
- package/augment-extensions/workflows/beads-integration/module.json +75 -75
- package/augment-extensions/workflows/beads-integration/rules/core-rules.md +219 -219
- package/augment-extensions/workflows/beads-integration/rules/effectiveness-standards.md +256 -256
- package/augment-extensions/workflows/beads-integration/rules/task-generation.md +607 -607
- package/augment-extensions/workflows/database/README.md +195 -195
- package/augment-extensions/workflows/database/ai-prompt-testing.md +295 -295
- package/augment-extensions/workflows/database/examples/migration-example.md +498 -498
- package/augment-extensions/workflows/database/examples/optimization-example.md +496 -496
- package/augment-extensions/workflows/database/examples/schema-design-example.md +444 -444
- package/augment-extensions/workflows/database/module.json +42 -42
- package/augment-extensions/workflows/database/rules/data-migration.md +249 -249
- package/augment-extensions/workflows/database/rules/documentation-standards.md +339 -339
- package/augment-extensions/workflows/database/rules/migration-workflow.md +352 -352
- package/augment-extensions/workflows/database/rules/optimization-workflow.md +435 -435
- package/augment-extensions/workflows/database/rules/schema-design-workflow.md +535 -535
- package/augment-extensions/workflows/database/rules/testing-patterns.md +305 -305
- package/augment-extensions/workflows/database/rules/workflow.md +458 -458
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -232
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -839
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -854
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -540
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -1083
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -669
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -597
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -925
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -752
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -773
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -49
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -942
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -702
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -728
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -775
- package/augment-extensions/writing-standards/screenplay/README.md +339 -300
- package/augment-extensions/writing-standards/screenplay/_templates/README.md +121 -121
- package/augment-extensions/writing-standards/screenplay/_templates/genre-template.md +153 -153
- package/augment-extensions/writing-standards/screenplay/_templates/style-template.md +243 -243
- package/augment-extensions/writing-standards/screenplay/_templates/theme-template.md +213 -213
- package/augment-extensions/writing-standards/screenplay/examples/aaa-hollywood-scene.fountain +164 -164
- package/augment-extensions/writing-standards/screenplay/examples/beat-sheet-example.yaml +95 -95
- package/augment-extensions/writing-standards/screenplay/examples/character-profile-example.yaml +116 -116
- package/augment-extensions/writing-standards/screenplay/examples/commercial-30sec.fountain +151 -151
- package/augment-extensions/writing-standards/screenplay/examples/independent-monologue.fountain +67 -67
- package/augment-extensions/writing-standards/screenplay/examples/news-segment.fountain +142 -142
- package/augment-extensions/writing-standards/screenplay/examples/plot-outline-example.yaml +184 -184
- package/augment-extensions/writing-standards/screenplay/examples/tv-episode-teaser.fountain +204 -204
- package/augment-extensions/writing-standards/screenplay/genres/README.md +181 -181
- package/augment-extensions/writing-standards/screenplay/genres/examples/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/genres/module.json +70 -70
- package/augment-extensions/writing-standards/screenplay/genres/rules/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/genres/rules/action.md +399 -399
- package/augment-extensions/writing-standards/screenplay/genres/rules/adventure.md +407 -407
- package/augment-extensions/writing-standards/screenplay/genres/rules/animation.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/biographical.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/comedy.md +401 -401
- package/augment-extensions/writing-standards/screenplay/genres/rules/documentary.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/drama.md +409 -409
- package/augment-extensions/writing-standards/screenplay/genres/rules/fantasy.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/historical.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/horror.md +268 -268
- package/augment-extensions/writing-standards/screenplay/genres/rules/musical.md +294 -294
- package/augment-extensions/writing-standards/screenplay/genres/rules/mystery.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/noir.md +294 -294
- package/augment-extensions/writing-standards/screenplay/genres/rules/romance.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/sci-fi.md +289 -289
- package/augment-extensions/writing-standards/screenplay/genres/rules/superhero.md +293 -293
- package/augment-extensions/writing-standards/screenplay/genres/rules/thriller.md +294 -294
- package/augment-extensions/writing-standards/screenplay/genres/rules/western.md +293 -293
- package/augment-extensions/writing-standards/screenplay/module.json +124 -124
- package/augment-extensions/writing-standards/screenplay/rules/aaa-hollywood-films.md +339 -339
- package/augment-extensions/writing-standards/screenplay/rules/ai-integration-testing.md +329 -329
- package/augment-extensions/writing-standards/screenplay/rules/character-development.md +169 -169
- package/augment-extensions/writing-standards/screenplay/rules/commercials.md +437 -437
- package/augment-extensions/writing-standards/screenplay/rules/dialogue-writing.md +263 -263
- package/augment-extensions/writing-standards/screenplay/rules/diversity-inclusion.md +261 -261
- package/augment-extensions/writing-standards/screenplay/rules/examples-guide.md +315 -315
- package/augment-extensions/writing-standards/screenplay/rules/file-organization.md +213 -0
- package/augment-extensions/writing-standards/screenplay/rules/formatting-validation.md +413 -413
- package/augment-extensions/writing-standards/screenplay/rules/fountain-format.md +372 -372
- package/augment-extensions/writing-standards/screenplay/rules/independent-films.md +374 -374
- package/augment-extensions/writing-standards/screenplay/rules/live-tv-productions.md +443 -443
- package/augment-extensions/writing-standards/screenplay/rules/narrative-structures.md +207 -207
- package/augment-extensions/writing-standards/screenplay/rules/news-broadcasts.md +444 -444
- package/augment-extensions/writing-standards/screenplay/rules/pacing-timing.md +331 -331
- package/augment-extensions/writing-standards/screenplay/rules/quality-review-checklist.md +334 -334
- package/augment-extensions/writing-standards/screenplay/rules/quick-reference.md +299 -299
- package/augment-extensions/writing-standards/screenplay/rules/screen-continuity.md +263 -263
- package/augment-extensions/writing-standards/screenplay/rules/streaming-content.md +412 -412
- package/augment-extensions/writing-standards/screenplay/rules/trope-management.md +370 -370
- package/augment-extensions/writing-standards/screenplay/rules/tv-series.md +374 -374
- package/augment-extensions/writing-standards/screenplay/rules/universal-formatting.md +339 -339
- package/augment-extensions/writing-standards/screenplay/rules/vscode-integration.md +277 -277
- package/augment-extensions/writing-standards/screenplay/rules/web-content.md +393 -393
- package/augment-extensions/writing-standards/screenplay/schemas/beat-sheet.json +332 -332
- package/augment-extensions/writing-standards/screenplay/schemas/character-profile.json +247 -247
- package/augment-extensions/writing-standards/screenplay/schemas/feature-selection.json +200 -200
- package/augment-extensions/writing-standards/screenplay/schemas/plot-outline.json +233 -233
- package/augment-extensions/writing-standards/screenplay/schemas/screenplay-config.json +245 -245
- package/augment-extensions/writing-standards/screenplay/schemas/trope-inventory.json +221 -221
- package/augment-extensions/writing-standards/screenplay/styles/README.md +159 -159
- package/augment-extensions/writing-standards/screenplay/styles/examples/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/styles/examples/style-applications.md +1449 -1449
- package/augment-extensions/writing-standards/screenplay/styles/module.json +64 -64
- package/augment-extensions/writing-standards/screenplay/styles/rules/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/styles/rules/dialogue-centric.md +520 -520
- package/augment-extensions/writing-standards/screenplay/styles/rules/ensemble.md +499 -499
- package/augment-extensions/writing-standards/screenplay/styles/rules/epic.md +497 -497
- package/augment-extensions/writing-standards/screenplay/styles/rules/experimental.md +492 -492
- package/augment-extensions/writing-standards/screenplay/styles/rules/flashback.md +509 -509
- package/augment-extensions/writing-standards/screenplay/styles/rules/linear.md +490 -490
- package/augment-extensions/writing-standards/screenplay/styles/rules/minimalist.md +499 -499
- package/augment-extensions/writing-standards/screenplay/styles/rules/non-linear.md +501 -501
- package/augment-extensions/writing-standards/screenplay/styles/rules/poetic.md +499 -499
- package/augment-extensions/writing-standards/screenplay/styles/rules/realistic.md +498 -498
- package/augment-extensions/writing-standards/screenplay/styles/rules/satirical.md +499 -499
- package/augment-extensions/writing-standards/screenplay/styles/rules/surreal.md +508 -508
- package/augment-extensions/writing-standards/screenplay/styles/rules/voice-over.md +500 -500
- package/augment-extensions/writing-standards/screenplay/themes/README.md +158 -158
- package/augment-extensions/writing-standards/screenplay/themes/examples/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/themes/examples/common-mistakes-and-fixes.md +643 -643
- package/augment-extensions/writing-standards/screenplay/themes/examples/complete-scene-example.md +311 -311
- package/augment-extensions/writing-standards/screenplay/themes/examples/individual-theme-examples.md +562 -562
- package/augment-extensions/writing-standards/screenplay/themes/examples/multi-theme-weaving.md +538 -538
- package/augment-extensions/writing-standards/screenplay/themes/examples/theme-application-guide.md +432 -432
- package/augment-extensions/writing-standards/screenplay/themes/examples/theme-integration-across-acts.md +637 -637
- package/augment-extensions/writing-standards/screenplay/themes/module.json +66 -66
- package/augment-extensions/writing-standards/screenplay/themes/rules/.gitkeep +2 -2
- package/augment-extensions/writing-standards/screenplay/themes/rules/ambition.md +458 -458
- package/augment-extensions/writing-standards/screenplay/themes/rules/betrayal.md +490 -490
- package/augment-extensions/writing-standards/screenplay/themes/rules/environment.md +458 -458
- package/augment-extensions/writing-standards/screenplay/themes/rules/fate.md +459 -459
- package/augment-extensions/writing-standards/screenplay/themes/rules/friendship.md +491 -491
- package/augment-extensions/writing-standards/screenplay/themes/rules/growth.md +491 -491
- package/augment-extensions/writing-standards/screenplay/themes/rules/identity.md +490 -490
- package/augment-extensions/writing-standards/screenplay/themes/rules/isolation.md +464 -464
- package/augment-extensions/writing-standards/screenplay/themes/rules/justice.md +461 -461
- package/augment-extensions/writing-standards/screenplay/themes/rules/love.md +489 -489
- package/augment-extensions/writing-standards/screenplay/themes/rules/power.md +494 -494
- package/augment-extensions/writing-standards/screenplay/themes/rules/redemption.md +483 -483
- package/augment-extensions/writing-standards/screenplay/themes/rules/revenge.md +489 -489
- package/augment-extensions/writing-standards/screenplay/themes/rules/survival.md +496 -496
- package/augment-extensions/writing-standards/screenplay/themes/rules/technology.md +463 -463
- package/augment-extensions/writing-standards/screenplay/utils/__tests__/file-organization.test.ts +169 -0
- package/augment-extensions/writing-standards/screenplay/utils/file-organization.ts +165 -0
- package/cli/MODULES.md +302 -302
- package/cli/dist/cli.js +109 -22
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/gui.d.ts.map +1 -1
- package/cli/dist/commands/gui.js +54 -6
- package/cli/dist/commands/gui.js.map +1 -1
- package/cli/dist/commands/init.d.ts.map +1 -1
- package/cli/dist/commands/init.js +76 -23
- package/cli/dist/commands/init.js.map +1 -1
- package/cli/dist/commands/self-remove.d.ts.map +1 -1
- package/cli/dist/commands/self-remove.js +48 -74
- package/cli/dist/commands/self-remove.js.map +1 -1
- package/cli/dist/commands/show.d.ts +11 -0
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +120 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/cli/dist/commands/showCompleted.d.ts +21 -0
- package/cli/dist/commands/showCompleted.d.ts.map +1 -0
- package/cli/dist/commands/showCompleted.js +225 -0
- package/cli/dist/commands/showCompleted.js.map +1 -0
- package/cli/dist/commands/skill.js +88 -88
- package/cli/dist/commands/update.d.ts +2 -0
- package/cli/dist/commands/update.d.ts.map +1 -1
- package/cli/dist/commands/update.js +67 -1
- package/cli/dist/commands/update.js.map +1 -1
- package/cli/dist/utils/beadsCompletedChecker.d.ts +72 -0
- package/cli/dist/utils/beadsCompletedChecker.d.ts.map +1 -0
- package/cli/dist/utils/beadsCompletedChecker.js +198 -0
- package/cli/dist/utils/beadsCompletedChecker.js.map +1 -0
- package/cli/dist/utils/catalog-sync.js +13 -13
- package/cli/dist/utils/extractCommandHelp.d.ts +51 -0
- package/cli/dist/utils/extractCommandHelp.d.ts.map +1 -0
- package/cli/dist/utils/extractCommandHelp.js +250 -0
- package/cli/dist/utils/extractCommandHelp.js.map +1 -0
- package/cli/dist/utils/install-rules.js +55 -55
- package/cli/dist/utils/mcp-integration.js +44 -44
- package/cli/dist/utils/rule-install-hooks.js +8 -8
- package/modules.md +667 -630
- package/package.json +85 -85
|
@@ -1,1008 +1,1008 @@
|
|
|
1
|
-
# Python Error Handling
|
|
2
|
-
|
|
3
|
-
Comprehensive exception handling patterns for robust Python code, including custom exceptions, context managers, and contextlib utilities.
|
|
4
|
-
|
|
5
|
-
## Basic Exception Handling
|
|
6
|
-
|
|
7
|
-
### Specific Exceptions
|
|
8
|
-
|
|
9
|
-
Always catch specific exceptions rather than using bare `except:` clauses.
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
# Good - Specific exception
|
|
13
|
-
try:
|
|
14
|
-
result = int(user_input)
|
|
15
|
-
except ValueError as e:
|
|
16
|
-
logger.error(f"Invalid input: {e}")
|
|
17
|
-
result = 0
|
|
18
|
-
|
|
19
|
-
# Bad - Bare except
|
|
20
|
-
try:
|
|
21
|
-
result = int(user_input)
|
|
22
|
-
except: # Don't do this - catches SystemExit, KeyboardInterrupt, etc.
|
|
23
|
-
result = 0
|
|
24
|
-
|
|
25
|
-
# Bad - Too broad
|
|
26
|
-
try:
|
|
27
|
-
result = int(user_input)
|
|
28
|
-
except Exception: # Still too broad for most cases
|
|
29
|
-
result = 0
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Multiple Exceptions
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
# Handle different exceptions differently
|
|
36
|
-
try:
|
|
37
|
-
with open(file_path) as f:
|
|
38
|
-
data = json.load(f)
|
|
39
|
-
except FileNotFoundError:
|
|
40
|
-
logger.warning(f"File not found: {file_path}")
|
|
41
|
-
data = {}
|
|
42
|
-
except json.JSONDecodeError as e:
|
|
43
|
-
logger.error(f"Invalid JSON in {file_path}: {e}")
|
|
44
|
-
data = {}
|
|
45
|
-
except PermissionError:
|
|
46
|
-
logger.error(f"Permission denied: {file_path}")
|
|
47
|
-
raise # Re-raise if we can't handle it
|
|
48
|
-
|
|
49
|
-
# Handle multiple exceptions the same way
|
|
50
|
-
try:
|
|
51
|
-
result = perform_operation()
|
|
52
|
-
except (ValueError, TypeError, KeyError) as e:
|
|
53
|
-
logger.error(f"Operation failed: {e}")
|
|
54
|
-
result = None
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Try-Except-Else-Finally
|
|
58
|
-
|
|
59
|
-
### The Complete Pattern
|
|
60
|
-
|
|
61
|
-
```python
|
|
62
|
-
try:
|
|
63
|
-
# Code that might raise exceptions
|
|
64
|
-
result = risky_operation()
|
|
65
|
-
except ValueError as e:
|
|
66
|
-
# Handle specific exception
|
|
67
|
-
logger.error(f"Value error: {e}")
|
|
68
|
-
result = None
|
|
69
|
-
except TypeError as e:
|
|
70
|
-
# Handle another specific exception
|
|
71
|
-
logger.error(f"Type error: {e}")
|
|
72
|
-
result = None
|
|
73
|
-
else:
|
|
74
|
-
# Runs only if no exception was raised
|
|
75
|
-
logger.info("Operation succeeded")
|
|
76
|
-
process_result(result)
|
|
77
|
-
finally:
|
|
78
|
-
# Always runs, even if exception occurred or return was called
|
|
79
|
-
cleanup_resources()
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Using Else Clause
|
|
83
|
-
|
|
84
|
-
The `else` clause runs only if no exception was raised in the `try` block.
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
# Good - Separates success logic from try block
|
|
88
|
-
try:
|
|
89
|
-
data = load_data(file_path)
|
|
90
|
-
except FileNotFoundError:
|
|
91
|
-
logger.error(f"File not found: {file_path}")
|
|
92
|
-
return None
|
|
93
|
-
else:
|
|
94
|
-
# Only runs if load_data succeeded
|
|
95
|
-
validate_data(data)
|
|
96
|
-
return process_data(data)
|
|
97
|
-
|
|
98
|
-
# Less clear - success logic mixed with risky code
|
|
99
|
-
try:
|
|
100
|
-
data = load_data(file_path)
|
|
101
|
-
validate_data(data) # This is also in the try block
|
|
102
|
-
return process_data(data)
|
|
103
|
-
except FileNotFoundError:
|
|
104
|
-
logger.error(f"File not found: {file_path}")
|
|
105
|
-
return None
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Using Finally for Cleanup
|
|
109
|
-
|
|
110
|
-
```python
|
|
111
|
-
# Manual cleanup with finally
|
|
112
|
-
file = None
|
|
113
|
-
try:
|
|
114
|
-
file = open(file_path)
|
|
115
|
-
data = file.read()
|
|
116
|
-
except FileNotFoundError:
|
|
117
|
-
logger.error("File not found")
|
|
118
|
-
data = None
|
|
119
|
-
finally:
|
|
120
|
-
if file is not None:
|
|
121
|
-
file.close()
|
|
122
|
-
|
|
123
|
-
# Better - Use context manager instead (see below)
|
|
124
|
-
try:
|
|
125
|
-
with open(file_path) as file:
|
|
126
|
-
data = file.read()
|
|
127
|
-
except FileNotFoundError:
|
|
128
|
-
logger.error("File not found")
|
|
129
|
-
data = None
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Context Managers
|
|
133
|
-
|
|
134
|
-
Context managers provide automatic resource management using the `with` statement. They ensure cleanup code runs even if exceptions occur.
|
|
135
|
-
|
|
136
|
-
### Built-in Context Managers
|
|
137
|
-
|
|
138
|
-
```python
|
|
139
|
-
# File handling - Automatic close
|
|
140
|
-
with open(file_path) as f:
|
|
141
|
-
data = f.read()
|
|
142
|
-
# File is automatically closed here, even if exception occurred
|
|
143
|
-
|
|
144
|
-
# Multiple context managers (Python 3.1+)
|
|
145
|
-
with open(input_file) as f_in, open(output_file, 'w') as f_out:
|
|
146
|
-
data = f_in.read()
|
|
147
|
-
f_out.write(process(data))
|
|
148
|
-
|
|
149
|
-
# Parenthesized context managers (Python 3.10+)
|
|
150
|
-
with (
|
|
151
|
-
open(input_file) as f_in,
|
|
152
|
-
open(output_file, 'w') as f_out,
|
|
153
|
-
open(log_file, 'a') as f_log,
|
|
154
|
-
):
|
|
155
|
-
data = f_in.read()
|
|
156
|
-
f_out.write(process(data))
|
|
157
|
-
f_log.write(f"Processed {input_file}\n")
|
|
158
|
-
|
|
159
|
-
# Threading locks
|
|
160
|
-
import threading
|
|
161
|
-
|
|
162
|
-
lock = threading.Lock()
|
|
163
|
-
with lock:
|
|
164
|
-
# Critical section - lock is automatically released
|
|
165
|
-
shared_resource.modify()
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Custom Context Managers - Class-Based
|
|
169
|
-
|
|
170
|
-
```python
|
|
171
|
-
from typing import Optional
|
|
172
|
-
|
|
173
|
-
class DatabaseConnection:
|
|
174
|
-
"""Context manager for database connections."""
|
|
175
|
-
|
|
176
|
-
def __init__(self, db_url: str):
|
|
177
|
-
self.db_url = db_url
|
|
178
|
-
self.conn: Optional[Connection] = None
|
|
179
|
-
|
|
180
|
-
def __enter__(self) -> Connection:
|
|
181
|
-
"""Establish connection when entering context."""
|
|
182
|
-
self.conn = connect(self.db_url)
|
|
183
|
-
logger.info(f"Connected to {self.db_url}")
|
|
184
|
-
return self.conn
|
|
185
|
-
|
|
186
|
-
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
187
|
-
"""Close connection when exiting context.
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
exc_type: Exception type if exception occurred, None otherwise
|
|
191
|
-
exc_val: Exception value if exception occurred, None otherwise
|
|
192
|
-
exc_tb: Exception traceback if exception occurred, None otherwise
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
False to propagate exceptions, True to suppress them
|
|
196
|
-
"""
|
|
197
|
-
if self.conn is not None:
|
|
198
|
-
self.conn.close()
|
|
199
|
-
logger.info(f"Closed connection to {self.db_url}")
|
|
200
|
-
|
|
201
|
-
# Log exception if one occurred
|
|
202
|
-
if exc_type is not None:
|
|
203
|
-
logger.error(f"Exception in database context: {exc_val}")
|
|
204
|
-
|
|
205
|
-
# Return False to propagate exception, True to suppress it
|
|
206
|
-
return False
|
|
207
|
-
|
|
208
|
-
# Usage
|
|
209
|
-
with DatabaseConnection("postgresql://localhost/mydb") as conn:
|
|
210
|
-
conn.execute("SELECT * FROM users")
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Custom Context Managers - contextlib.contextmanager
|
|
214
|
-
|
|
215
|
-
The `@contextmanager` decorator provides a simpler way to create context managers using generators.
|
|
216
|
-
|
|
217
|
-
```python
|
|
218
|
-
from contextlib import contextmanager
|
|
219
|
-
from typing import Generator, Optional
|
|
220
|
-
import time
|
|
221
|
-
|
|
222
|
-
@contextmanager
|
|
223
|
-
def database_connection(db_url: str) -> Generator[Connection, None, None]:
|
|
224
|
-
"""Context manager for database connections.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
db_url: Database connection URL
|
|
228
|
-
|
|
229
|
-
Yields:
|
|
230
|
-
Active database connection
|
|
231
|
-
|
|
232
|
-
Example:
|
|
233
|
-
with database_connection("postgresql://...") as conn:
|
|
234
|
-
conn.execute("SELECT * FROM users")
|
|
235
|
-
"""
|
|
236
|
-
conn = connect(db_url)
|
|
237
|
-
try:
|
|
238
|
-
logger.info(f"Connected to {db_url}")
|
|
239
|
-
yield conn
|
|
240
|
-
except Exception as e:
|
|
241
|
-
logger.error(f"Database error: {e}")
|
|
242
|
-
raise
|
|
243
|
-
finally:
|
|
244
|
-
conn.close()
|
|
245
|
-
logger.info(f"Closed connection to {db_url}")
|
|
246
|
-
|
|
247
|
-
@contextmanager
|
|
248
|
-
def timer(name: str) -> Generator[None, None, None]:
|
|
249
|
-
"""Context manager to time code execution.
|
|
250
|
-
|
|
251
|
-
Args:
|
|
252
|
-
name: Name of the timed operation
|
|
253
|
-
|
|
254
|
-
Example:
|
|
255
|
-
with timer("data processing"):
|
|
256
|
-
process_large_dataset()
|
|
257
|
-
"""
|
|
258
|
-
start = time.time()
|
|
259
|
-
try:
|
|
260
|
-
yield
|
|
261
|
-
finally:
|
|
262
|
-
elapsed = time.time() - start
|
|
263
|
-
logger.info(f"{name} took {elapsed:.2f} seconds")
|
|
264
|
-
|
|
265
|
-
@contextmanager
|
|
266
|
-
def temporary_directory() -> Generator[Path, None, None]:
|
|
267
|
-
"""Context manager for temporary directory.
|
|
268
|
-
|
|
269
|
-
Yields:
|
|
270
|
-
Path to temporary directory
|
|
271
|
-
|
|
272
|
-
Example:
|
|
273
|
-
with temporary_directory() as tmpdir:
|
|
274
|
-
(tmpdir / "file.txt").write_text("data")
|
|
275
|
-
# Directory is automatically deleted here
|
|
276
|
-
"""
|
|
277
|
-
import tempfile
|
|
278
|
-
import shutil
|
|
279
|
-
|
|
280
|
-
tmpdir = Path(tempfile.mkdtemp())
|
|
281
|
-
try:
|
|
282
|
-
yield tmpdir
|
|
283
|
-
finally:
|
|
284
|
-
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### contextlib Utilities
|
|
288
|
-
|
|
289
|
-
```python
|
|
290
|
-
from contextlib import (
|
|
291
|
-
contextmanager,
|
|
292
|
-
suppress,
|
|
293
|
-
redirect_stdout,
|
|
294
|
-
redirect_stderr,
|
|
295
|
-
ExitStack,
|
|
296
|
-
nullcontext,
|
|
297
|
-
)
|
|
298
|
-
import io
|
|
299
|
-
|
|
300
|
-
# suppress - Ignore specific exceptions
|
|
301
|
-
from contextlib import suppress
|
|
302
|
-
|
|
303
|
-
# Instead of try-except
|
|
304
|
-
try:
|
|
305
|
-
os.remove(file_path)
|
|
306
|
-
except FileNotFoundError:
|
|
307
|
-
pass
|
|
308
|
-
|
|
309
|
-
# Use suppress
|
|
310
|
-
with suppress(FileNotFoundError):
|
|
311
|
-
os.remove(file_path)
|
|
312
|
-
|
|
313
|
-
# Suppress multiple exceptions
|
|
314
|
-
with suppress(FileNotFoundError, PermissionError):
|
|
315
|
-
os.remove(file_path)
|
|
316
|
-
|
|
317
|
-
# redirect_stdout/redirect_stderr - Redirect output
|
|
318
|
-
output = io.StringIO()
|
|
319
|
-
with redirect_stdout(output):
|
|
320
|
-
print("This goes to output variable")
|
|
321
|
-
print("Not to console")
|
|
322
|
-
|
|
323
|
-
captured = output.getvalue() # "This goes to output variable\nNot to console\n"
|
|
324
|
-
|
|
325
|
-
# ExitStack - Manage dynamic number of context managers
|
|
326
|
-
from contextlib import ExitStack
|
|
327
|
-
|
|
328
|
-
def process_files(file_paths: list[str]) -> None:
|
|
329
|
-
"""Process multiple files with dynamic context managers."""
|
|
330
|
-
with ExitStack() as stack:
|
|
331
|
-
# Open all files and register them with the stack
|
|
332
|
-
files = [stack.enter_context(open(path)) for path in file_paths]
|
|
333
|
-
|
|
334
|
-
# Process all files
|
|
335
|
-
for f in files:
|
|
336
|
-
process_file(f)
|
|
337
|
-
|
|
338
|
-
# All files automatically closed when exiting
|
|
339
|
-
|
|
340
|
-
# ExitStack with callbacks
|
|
341
|
-
with ExitStack() as stack:
|
|
342
|
-
# Register cleanup callbacks
|
|
343
|
-
stack.callback(cleanup_temp_files)
|
|
344
|
-
stack.callback(logger.info, "Processing complete")
|
|
345
|
-
|
|
346
|
-
# Do work
|
|
347
|
-
process_data()
|
|
348
|
-
|
|
349
|
-
# Callbacks run in LIFO order when exiting
|
|
350
|
-
|
|
351
|
-
# nullcontext - Conditional context manager (Python 3.7+)
|
|
352
|
-
from contextlib import nullcontext
|
|
353
|
-
|
|
354
|
-
def process_data(use_lock: bool = False) -> None:
|
|
355
|
-
"""Process data with optional locking."""
|
|
356
|
-
lock = threading.Lock() if use_lock else nullcontext()
|
|
357
|
-
|
|
358
|
-
with lock:
|
|
359
|
-
# This works whether lock is a real Lock or nullcontext
|
|
360
|
-
modify_shared_resource()
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Async Context Managers
|
|
364
|
-
|
|
365
|
-
```python
|
|
366
|
-
from contextlib import asynccontextmanager
|
|
367
|
-
from typing import AsyncGenerator
|
|
368
|
-
|
|
369
|
-
class AsyncDatabaseConnection:
|
|
370
|
-
"""Async context manager for database connections."""
|
|
371
|
-
|
|
372
|
-
async def __aenter__(self) -> AsyncConnection:
|
|
373
|
-
self.conn = await async_connect(self.db_url)
|
|
374
|
-
return self.conn
|
|
375
|
-
|
|
376
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
377
|
-
await self.conn.close()
|
|
378
|
-
return False
|
|
379
|
-
|
|
380
|
-
# Using @asynccontextmanager decorator
|
|
381
|
-
@asynccontextmanager
|
|
382
|
-
async def async_database_connection(
|
|
383
|
-
db_url: str
|
|
384
|
-
) -> AsyncGenerator[AsyncConnection, None]:
|
|
385
|
-
"""Async context manager for database connections."""
|
|
386
|
-
conn = await async_connect(db_url)
|
|
387
|
-
try:
|
|
388
|
-
yield conn
|
|
389
|
-
finally:
|
|
390
|
-
await conn.close()
|
|
391
|
-
|
|
392
|
-
# Usage
|
|
393
|
-
async def fetch_users():
|
|
394
|
-
async with async_database_connection("postgresql://...") as conn:
|
|
395
|
-
return await conn.fetch("SELECT * FROM users")
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
## Custom Exceptions
|
|
399
|
-
|
|
400
|
-
Create custom exceptions for domain-specific errors. This makes error handling more precise and code more maintainable.
|
|
401
|
-
|
|
402
|
-
### Basic Custom Exceptions
|
|
403
|
-
|
|
404
|
-
```python
|
|
405
|
-
# Simple custom exception
|
|
406
|
-
class ValidationError(Exception):
|
|
407
|
-
"""Raised when validation fails."""
|
|
408
|
-
pass
|
|
409
|
-
|
|
410
|
-
# Custom exception with additional attributes
|
|
411
|
-
class AuthenticationError(Exception):
|
|
412
|
-
"""Raised when authentication fails."""
|
|
413
|
-
|
|
414
|
-
def __init__(self, user_id: int, message: str = "Authentication failed"):
|
|
415
|
-
self.user_id = user_id
|
|
416
|
-
self.message = message
|
|
417
|
-
super().__init__(self.message)
|
|
418
|
-
|
|
419
|
-
def __str__(self) -> str:
|
|
420
|
-
return f"AuthenticationError(user_id={self.user_id}): {self.message}"
|
|
421
|
-
|
|
422
|
-
# Custom exception with multiple attributes
|
|
423
|
-
class DatabaseError(Exception):
|
|
424
|
-
"""Raised when database operation fails."""
|
|
425
|
-
|
|
426
|
-
def __init__(
|
|
427
|
-
self,
|
|
428
|
-
message: str,
|
|
429
|
-
query: str,
|
|
430
|
-
error_code: Optional[int] = None,
|
|
431
|
-
):
|
|
432
|
-
self.message = message
|
|
433
|
-
self.query = query
|
|
434
|
-
self.error_code = error_code
|
|
435
|
-
super().__init__(self.message)
|
|
436
|
-
|
|
437
|
-
# Usage
|
|
438
|
-
def validate_email(email: str) -> None:
|
|
439
|
-
"""Validate email format.
|
|
440
|
-
|
|
441
|
-
Args:
|
|
442
|
-
email: Email address to validate
|
|
443
|
-
|
|
444
|
-
Raises:
|
|
445
|
-
ValidationError: If email format is invalid
|
|
446
|
-
"""
|
|
447
|
-
if '@' not in email:
|
|
448
|
-
raise ValidationError(f"Invalid email format: {email}")
|
|
449
|
-
|
|
450
|
-
if not email.endswith(('.com', '.org', '.net')):
|
|
451
|
-
raise ValidationError(f"Invalid email domain: {email}")
|
|
452
|
-
|
|
453
|
-
def authenticate_user(user_id: int, password: str) -> User:
|
|
454
|
-
"""Authenticate user with password.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
user_id: User ID
|
|
458
|
-
password: User password
|
|
459
|
-
|
|
460
|
-
Returns:
|
|
461
|
-
Authenticated user object
|
|
462
|
-
|
|
463
|
-
Raises:
|
|
464
|
-
AuthenticationError: If authentication fails
|
|
465
|
-
"""
|
|
466
|
-
if not verify_password(user_id, password):
|
|
467
|
-
raise AuthenticationError(user_id, "Invalid password")
|
|
468
|
-
return get_user(user_id)
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Exception Hierarchies
|
|
472
|
-
|
|
473
|
-
Create exception hierarchies for related errors.
|
|
474
|
-
|
|
475
|
-
```python
|
|
476
|
-
# Base exception for application
|
|
477
|
-
class AppError(Exception):
|
|
478
|
-
"""Base exception for all application errors."""
|
|
479
|
-
pass
|
|
480
|
-
|
|
481
|
-
# Database errors
|
|
482
|
-
class DatabaseError(AppError):
|
|
483
|
-
"""Base exception for database-related errors."""
|
|
484
|
-
pass
|
|
485
|
-
|
|
486
|
-
class ConnectionError(DatabaseError):
|
|
487
|
-
"""Database connection errors."""
|
|
488
|
-
pass
|
|
489
|
-
|
|
490
|
-
class QueryError(DatabaseError):
|
|
491
|
-
"""Database query errors."""
|
|
492
|
-
|
|
493
|
-
def __init__(self, message: str, query: str):
|
|
494
|
-
self.query = query
|
|
495
|
-
super().__init__(f"{message}: {query}")
|
|
496
|
-
|
|
497
|
-
class TransactionError(DatabaseError):
|
|
498
|
-
"""Database transaction errors."""
|
|
499
|
-
pass
|
|
500
|
-
|
|
501
|
-
# API errors
|
|
502
|
-
class APIError(AppError):
|
|
503
|
-
"""Base exception for API-related errors."""
|
|
504
|
-
|
|
505
|
-
def __init__(self, message: str, status_code: int):
|
|
506
|
-
self.status_code = status_code
|
|
507
|
-
super().__init__(f"[{status_code}] {message}")
|
|
508
|
-
|
|
509
|
-
class NotFoundError(APIError):
|
|
510
|
-
"""Resource not found."""
|
|
511
|
-
|
|
512
|
-
def __init__(self, resource: str, resource_id: str):
|
|
513
|
-
self.resource = resource
|
|
514
|
-
self.resource_id = resource_id
|
|
515
|
-
super().__init__(
|
|
516
|
-
f"{resource} not found: {resource_id}",
|
|
517
|
-
status_code=404,
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
class UnauthorizedError(APIError):
|
|
521
|
-
"""Unauthorized access."""
|
|
522
|
-
|
|
523
|
-
def __init__(self, message: str = "Unauthorized"):
|
|
524
|
-
super().__init__(message, status_code=401)
|
|
525
|
-
|
|
526
|
-
# Usage - Catch specific or broad exceptions
|
|
527
|
-
try:
|
|
528
|
-
user = get_user(user_id)
|
|
529
|
-
except NotFoundError as e:
|
|
530
|
-
# Handle specific error
|
|
531
|
-
logger.warning(f"User not found: {e.resource_id}")
|
|
532
|
-
except APIError as e:
|
|
533
|
-
# Handle any API error
|
|
534
|
-
logger.error(f"API error: {e}")
|
|
535
|
-
except AppError as e:
|
|
536
|
-
# Handle any application error
|
|
537
|
-
logger.error(f"Application error: {e}")
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### Rich Exception Information
|
|
541
|
-
|
|
542
|
-
```python
|
|
543
|
-
from typing import Any, Optional
|
|
544
|
-
from dataclasses import dataclass
|
|
545
|
-
|
|
546
|
-
@dataclass
|
|
547
|
-
class ErrorContext:
|
|
548
|
-
"""Context information for errors."""
|
|
549
|
-
operation: str
|
|
550
|
-
user_id: Optional[int] = None
|
|
551
|
-
request_id: Optional[str] = None
|
|
552
|
-
metadata: dict[str, Any] = None
|
|
553
|
-
|
|
554
|
-
def __post_init__(self):
|
|
555
|
-
if self.metadata is None:
|
|
556
|
-
self.metadata = {}
|
|
557
|
-
|
|
558
|
-
class OperationError(Exception):
|
|
559
|
-
"""Exception with rich context information."""
|
|
560
|
-
|
|
561
|
-
def __init__(self, message: str, context: ErrorContext):
|
|
562
|
-
self.message = message
|
|
563
|
-
self.context = context
|
|
564
|
-
super().__init__(self.message)
|
|
565
|
-
|
|
566
|
-
def __str__(self) -> str:
|
|
567
|
-
ctx = self.context
|
|
568
|
-
parts = [f"OperationError: {self.message}"]
|
|
569
|
-
parts.append(f" Operation: {ctx.operation}")
|
|
570
|
-
if ctx.user_id:
|
|
571
|
-
parts.append(f" User ID: {ctx.user_id}")
|
|
572
|
-
if ctx.request_id:
|
|
573
|
-
parts.append(f" Request ID: {ctx.request_id}")
|
|
574
|
-
if ctx.metadata:
|
|
575
|
-
parts.append(f" Metadata: {ctx.metadata}")
|
|
576
|
-
return "\n".join(parts)
|
|
577
|
-
|
|
578
|
-
# Usage
|
|
579
|
-
def process_payment(user_id: int, amount: float, request_id: str) -> None:
|
|
580
|
-
"""Process payment with rich error context."""
|
|
581
|
-
context = ErrorContext(
|
|
582
|
-
operation="process_payment",
|
|
583
|
-
user_id=user_id,
|
|
584
|
-
request_id=request_id,
|
|
585
|
-
metadata={"amount": amount},
|
|
586
|
-
)
|
|
587
|
-
|
|
588
|
-
try:
|
|
589
|
-
charge_card(user_id, amount)
|
|
590
|
-
except CardDeclinedError as e:
|
|
591
|
-
raise OperationError("Payment declined", context) from e
|
|
592
|
-
except InsufficientFundsError as e:
|
|
593
|
-
raise OperationError("Insufficient funds", context) from e
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
## Exception Chaining
|
|
597
|
-
|
|
598
|
-
Exception chaining preserves the original exception context, making debugging easier.
|
|
599
|
-
|
|
600
|
-
### Using 'from' for Chaining
|
|
601
|
-
|
|
602
|
-
```python
|
|
603
|
-
# Good - Preserve original exception with 'from'
|
|
604
|
-
try:
|
|
605
|
-
result = process_data(data)
|
|
606
|
-
except ValueError as e:
|
|
607
|
-
raise ProcessingError("Failed to process data") from e
|
|
608
|
-
# Traceback will show both ProcessingError and original ValueError
|
|
609
|
-
|
|
610
|
-
# Good - Explicit chaining with context
|
|
611
|
-
try:
|
|
612
|
-
user_data = json.loads(raw_data)
|
|
613
|
-
except json.JSONDecodeError as e:
|
|
614
|
-
raise ValidationError(
|
|
615
|
-
f"Invalid JSON in user data: {e.msg}"
|
|
616
|
-
) from e
|
|
617
|
-
|
|
618
|
-
# Rare - Suppress original exception with 'from None'
|
|
619
|
-
try:
|
|
620
|
-
result = process_data(data)
|
|
621
|
-
except ValueError:
|
|
622
|
-
# Only use 'from None' when original exception is not relevant
|
|
623
|
-
raise ProcessingError("Failed to process data") from None
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
### Implicit Chaining
|
|
627
|
-
|
|
628
|
-
```python
|
|
629
|
-
# Implicit chaining - exception raised during exception handling
|
|
630
|
-
try:
|
|
631
|
-
result = process_data(data)
|
|
632
|
-
except ValueError as e:
|
|
633
|
-
# If log_error raises an exception, both will be in traceback
|
|
634
|
-
log_error(e)
|
|
635
|
-
raise ProcessingError("Failed to process data")
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Accessing Exception Chain
|
|
639
|
-
|
|
640
|
-
```python
|
|
641
|
-
try:
|
|
642
|
-
try:
|
|
643
|
-
risky_operation()
|
|
644
|
-
except ValueError as e:
|
|
645
|
-
raise ProcessingError("Processing failed") from e
|
|
646
|
-
except ProcessingError as e:
|
|
647
|
-
# Access the original exception
|
|
648
|
-
original = e.__cause__ # The exception after 'from'
|
|
649
|
-
context = e.__context__ # The exception being handled when this was raised
|
|
650
|
-
|
|
651
|
-
logger.error(f"Processing error: {e}")
|
|
652
|
-
logger.error(f"Original error: {original}")
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
## Logging Exceptions
|
|
656
|
-
|
|
657
|
-
Proper exception logging is crucial for debugging and monitoring.
|
|
658
|
-
|
|
659
|
-
### Basic Exception Logging
|
|
660
|
-
|
|
661
|
-
```python
|
|
662
|
-
import logging
|
|
663
|
-
|
|
664
|
-
logger = logging.getLogger(__name__)
|
|
665
|
-
|
|
666
|
-
# Log exception with full traceback
|
|
667
|
-
try:
|
|
668
|
-
result = risky_operation()
|
|
669
|
-
except Exception as e:
|
|
670
|
-
logger.exception("Operation failed") # Includes full traceback
|
|
671
|
-
raise # Re-raise after logging
|
|
672
|
-
|
|
673
|
-
# Log without traceback
|
|
674
|
-
try:
|
|
675
|
-
result = risky_operation()
|
|
676
|
-
except ValueError as e:
|
|
677
|
-
logger.error(f"Invalid value: {e}") # No traceback
|
|
678
|
-
result = default_value
|
|
679
|
-
|
|
680
|
-
# Log with different levels
|
|
681
|
-
try:
|
|
682
|
-
result = optional_operation()
|
|
683
|
-
except FileNotFoundError:
|
|
684
|
-
logger.warning("Optional file not found, using defaults")
|
|
685
|
-
result = default_value
|
|
686
|
-
except PermissionError as e:
|
|
687
|
-
logger.error(f"Permission denied: {e}")
|
|
688
|
-
raise
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### Structured Logging
|
|
692
|
-
|
|
693
|
-
```python
|
|
694
|
-
import logging
|
|
695
|
-
from typing import Any
|
|
696
|
-
|
|
697
|
-
logger = logging.getLogger(__name__)
|
|
698
|
-
|
|
699
|
-
# Log with structured data
|
|
700
|
-
try:
|
|
701
|
-
process_user_data(user_id, data)
|
|
702
|
-
except ValidationError as e:
|
|
703
|
-
logger.error(
|
|
704
|
-
"Validation failed",
|
|
705
|
-
extra={
|
|
706
|
-
"user_id": user_id,
|
|
707
|
-
"error_type": type(e).__name__,
|
|
708
|
-
"error_message": str(e),
|
|
709
|
-
},
|
|
710
|
-
)
|
|
711
|
-
raise
|
|
712
|
-
|
|
713
|
-
# Log with exception info without traceback
|
|
714
|
-
try:
|
|
715
|
-
result = risky_operation()
|
|
716
|
-
except ValueError as e:
|
|
717
|
-
logger.error(
|
|
718
|
-
"Operation failed: %s",
|
|
719
|
-
e,
|
|
720
|
-
exc_info=False, # Don't include traceback
|
|
721
|
-
)
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
### Custom Exception Logging
|
|
725
|
-
|
|
726
|
-
```python
|
|
727
|
-
class LoggingException(Exception):
|
|
728
|
-
"""Exception that logs itself when raised."""
|
|
729
|
-
|
|
730
|
-
def __init__(self, message: str, level: int = logging.ERROR):
|
|
731
|
-
self.message = message
|
|
732
|
-
self.level = level
|
|
733
|
-
super().__init__(self.message)
|
|
734
|
-
|
|
735
|
-
# Log when exception is created
|
|
736
|
-
logger = logging.getLogger(self.__class__.__module__)
|
|
737
|
-
logger.log(self.level, f"{self.__class__.__name__}: {message}")
|
|
738
|
-
|
|
739
|
-
# Usage
|
|
740
|
-
def process_data(data: dict[str, Any]) -> None:
|
|
741
|
-
if not data:
|
|
742
|
-
raise LoggingException("Empty data received", level=logging.WARNING)
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
## Best Practices
|
|
746
|
-
|
|
747
|
-
### DO
|
|
748
|
-
|
|
749
|
-
✅ **Catch specific exceptions** - Never use bare `except:` or catch `Exception` unless absolutely necessary
|
|
750
|
-
|
|
751
|
-
✅ **Use context managers** - Prefer `with` statement for resource management
|
|
752
|
-
|
|
753
|
-
✅ **Log exceptions properly** - Use `logger.exception()` to include tracebacks
|
|
754
|
-
|
|
755
|
-
✅ **Create custom exceptions** - For domain-specific errors with clear names
|
|
756
|
-
|
|
757
|
-
✅ **Use exception chaining** - Preserve error context with `from`
|
|
758
|
-
|
|
759
|
-
✅ **Document exceptions** - List raised exceptions in docstrings
|
|
760
|
-
|
|
761
|
-
✅ **Fail fast** - Don't catch exceptions you can't handle
|
|
762
|
-
|
|
763
|
-
✅ **Clean up resources** - Use `finally` or context managers
|
|
764
|
-
|
|
765
|
-
✅ **Use contextlib utilities** - `suppress`, `ExitStack`, etc. for cleaner code
|
|
766
|
-
|
|
767
|
-
✅ **Create exception hierarchies** - For related errors
|
|
768
|
-
|
|
769
|
-
### DON'T
|
|
770
|
-
|
|
771
|
-
❌ **Don't use bare except** - Catches SystemExit, KeyboardInterrupt, etc.
|
|
772
|
-
|
|
773
|
-
```python
|
|
774
|
-
# Bad
|
|
775
|
-
try:
|
|
776
|
-
do_something()
|
|
777
|
-
except: # Catches everything, including KeyboardInterrupt
|
|
778
|
-
pass
|
|
779
|
-
|
|
780
|
-
# Good
|
|
781
|
-
try:
|
|
782
|
-
do_something()
|
|
783
|
-
except (ValueError, TypeError) as e:
|
|
784
|
-
logger.error(f"Expected error: {e}")
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
❌ **Don't silence exceptions** - Always log or handle them
|
|
788
|
-
|
|
789
|
-
```python
|
|
790
|
-
# Bad
|
|
791
|
-
try:
|
|
792
|
-
critical_operation()
|
|
793
|
-
except Exception:
|
|
794
|
-
pass # Silent failure
|
|
795
|
-
|
|
796
|
-
# Good
|
|
797
|
-
try:
|
|
798
|
-
critical_operation()
|
|
799
|
-
except Exception as e:
|
|
800
|
-
logger.exception("Critical operation failed")
|
|
801
|
-
raise
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
❌ **Don't use exceptions for flow control** - Use appropriate methods
|
|
805
|
-
|
|
806
|
-
```python
|
|
807
|
-
# Bad - Using exception for flow control
|
|
808
|
-
try:
|
|
809
|
-
value = my_dict[key]
|
|
810
|
-
except KeyError:
|
|
811
|
-
value = default
|
|
812
|
-
|
|
813
|
-
# Good - Use dict.get()
|
|
814
|
-
value = my_dict.get(key, default)
|
|
815
|
-
|
|
816
|
-
# Bad - Using exception for flow control
|
|
817
|
-
try:
|
|
818
|
-
index = my_list.index(item)
|
|
819
|
-
except ValueError:
|
|
820
|
-
index = -1
|
|
821
|
-
|
|
822
|
-
# Good - Use 'in' operator
|
|
823
|
-
index = my_list.index(item) if item in my_list else -1
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
❌ **Don't catch Exception without re-raising** - Unless you can truly handle it
|
|
827
|
-
|
|
828
|
-
```python
|
|
829
|
-
# Bad
|
|
830
|
-
try:
|
|
831
|
-
critical_operation()
|
|
832
|
-
except Exception:
|
|
833
|
-
print("Error occurred") # Swallows the exception
|
|
834
|
-
|
|
835
|
-
# Good
|
|
836
|
-
try:
|
|
837
|
-
critical_operation()
|
|
838
|
-
except Exception as e:
|
|
839
|
-
logger.exception("Critical operation failed")
|
|
840
|
-
raise # Re-raise after logging
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
❌ **Don't lose exception context** - Use exception chaining
|
|
844
|
-
|
|
845
|
-
```python
|
|
846
|
-
# Bad - Loses original exception
|
|
847
|
-
try:
|
|
848
|
-
process_data(data)
|
|
849
|
-
except ValueError:
|
|
850
|
-
raise ProcessingError("Failed") # Original error is lost
|
|
851
|
-
|
|
852
|
-
# Good - Preserves original exception
|
|
853
|
-
try:
|
|
854
|
-
process_data(data)
|
|
855
|
-
except ValueError as e:
|
|
856
|
-
raise ProcessingError("Failed") from e
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
## Advanced Patterns
|
|
860
|
-
|
|
861
|
-
### Exception Groups (Python 3.11+)
|
|
862
|
-
|
|
863
|
-
```python
|
|
864
|
-
# Raise multiple exceptions at once
|
|
865
|
-
raise ExceptionGroup("Multiple errors occurred", [
|
|
866
|
-
ValueError("Invalid value"),
|
|
867
|
-
TypeError("Invalid type"),
|
|
868
|
-
])
|
|
869
|
-
|
|
870
|
-
# Catch exception groups
|
|
871
|
-
try:
|
|
872
|
-
raise ExceptionGroup("errors", [ValueError("bad"), TypeError("wrong")])
|
|
873
|
-
except* ValueError as eg:
|
|
874
|
-
# Handle all ValueError instances
|
|
875
|
-
for e in eg.exceptions:
|
|
876
|
-
logger.error(f"Value error: {e}")
|
|
877
|
-
except* TypeError as eg:
|
|
878
|
-
# Handle all TypeError instances
|
|
879
|
-
for e in eg.exceptions:
|
|
880
|
-
logger.error(f"Type error: {e}")
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
### Retry Logic with Exceptions
|
|
884
|
-
|
|
885
|
-
```python
|
|
886
|
-
from typing import TypeVar, Callable
|
|
887
|
-
import time
|
|
888
|
-
|
|
889
|
-
T = TypeVar('T')
|
|
890
|
-
|
|
891
|
-
def retry(
|
|
892
|
-
func: Callable[..., T],
|
|
893
|
-
max_attempts: int = 3,
|
|
894
|
-
delay: float = 1.0,
|
|
895
|
-
exceptions: tuple[type[Exception], ...] = (Exception,),
|
|
896
|
-
) -> T:
|
|
897
|
-
"""Retry function on exception.
|
|
898
|
-
|
|
899
|
-
Args:
|
|
900
|
-
func: Function to retry
|
|
901
|
-
max_attempts: Maximum number of attempts
|
|
902
|
-
delay: Delay between attempts in seconds
|
|
903
|
-
exceptions: Tuple of exceptions to catch
|
|
904
|
-
|
|
905
|
-
Returns:
|
|
906
|
-
Result of successful function call
|
|
907
|
-
|
|
908
|
-
Raises:
|
|
909
|
-
Last exception if all attempts fail
|
|
910
|
-
"""
|
|
911
|
-
last_exception = None
|
|
912
|
-
|
|
913
|
-
for attempt in range(max_attempts):
|
|
914
|
-
try:
|
|
915
|
-
return func()
|
|
916
|
-
except exceptions as e:
|
|
917
|
-
last_exception = e
|
|
918
|
-
logger.warning(
|
|
919
|
-
f"Attempt {attempt + 1}/{max_attempts} failed: {e}"
|
|
920
|
-
)
|
|
921
|
-
if attempt < max_attempts - 1:
|
|
922
|
-
time.sleep(delay)
|
|
923
|
-
|
|
924
|
-
# All attempts failed
|
|
925
|
-
raise last_exception
|
|
926
|
-
|
|
927
|
-
# Usage
|
|
928
|
-
result = retry(
|
|
929
|
-
lambda: fetch_data_from_api(),
|
|
930
|
-
max_attempts=3,
|
|
931
|
-
delay=2.0,
|
|
932
|
-
exceptions=(ConnectionError, TimeoutError),
|
|
933
|
-
)
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
### Validation with Multiple Errors
|
|
937
|
-
|
|
938
|
-
```python
|
|
939
|
-
from typing import List
|
|
940
|
-
|
|
941
|
-
class ValidationErrors(Exception):
|
|
942
|
-
"""Exception containing multiple validation errors."""
|
|
943
|
-
|
|
944
|
-
def __init__(self, errors: List[str]):
|
|
945
|
-
self.errors = errors
|
|
946
|
-
super().__init__(f"{len(errors)} validation errors")
|
|
947
|
-
|
|
948
|
-
def __str__(self) -> str:
|
|
949
|
-
return "\n".join([
|
|
950
|
-
f"Validation failed with {len(self.errors)} errors:",
|
|
951
|
-
*[f" - {error}" for error in self.errors],
|
|
952
|
-
])
|
|
953
|
-
|
|
954
|
-
def validate_user(user_data: dict) -> None:
|
|
955
|
-
"""Validate user data, collecting all errors.
|
|
956
|
-
|
|
957
|
-
Args:
|
|
958
|
-
user_data: User data to validate
|
|
959
|
-
|
|
960
|
-
Raises:
|
|
961
|
-
ValidationErrors: If validation fails
|
|
962
|
-
"""
|
|
963
|
-
errors = []
|
|
964
|
-
|
|
965
|
-
if not user_data.get("email"):
|
|
966
|
-
errors.append("Email is required")
|
|
967
|
-
elif "@" not in user_data["email"]:
|
|
968
|
-
errors.append("Email must contain @")
|
|
969
|
-
|
|
970
|
-
if not user_data.get("username"):
|
|
971
|
-
errors.append("Username is required")
|
|
972
|
-
elif len(user_data["username"]) < 3:
|
|
973
|
-
errors.append("Username must be at least 3 characters")
|
|
974
|
-
|
|
975
|
-
if not user_data.get("age"):
|
|
976
|
-
errors.append("Age is required")
|
|
977
|
-
elif user_data["age"] < 18:
|
|
978
|
-
errors.append("User must be at least 18 years old")
|
|
979
|
-
|
|
980
|
-
if errors:
|
|
981
|
-
raise ValidationErrors(errors)
|
|
982
|
-
|
|
983
|
-
# Usage
|
|
984
|
-
try:
|
|
985
|
-
validate_user({"email": "invalid", "username": "ab"})
|
|
986
|
-
except ValidationErrors as e:
|
|
987
|
-
logger.error(str(e))
|
|
988
|
-
# Validation failed with 3 errors:
|
|
989
|
-
# - Email must contain @
|
|
990
|
-
# - Username must be at least 3 characters
|
|
991
|
-
# - Age is required
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
## Summary
|
|
995
|
-
|
|
996
|
-
**Key Takeaways:**
|
|
997
|
-
|
|
998
|
-
1. **Always catch specific exceptions** - Never use bare `except:`
|
|
999
|
-
2. **Use context managers** - For automatic resource cleanup
|
|
1000
|
-
3. **Create custom exceptions** - With clear hierarchies for domain errors
|
|
1001
|
-
4. **Use contextlib utilities** - `@contextmanager`, `suppress`, `ExitStack`
|
|
1002
|
-
5. **Chain exceptions** - Preserve error context with `from`
|
|
1003
|
-
6. **Log exceptions properly** - Use `logger.exception()` for tracebacks
|
|
1004
|
-
7. **Document exceptions** - In docstrings with `Raises:` section
|
|
1005
|
-
8. **Don't use exceptions for flow control** - Use appropriate methods instead
|
|
1006
|
-
9. **Fail fast** - Don't catch exceptions you can't handle
|
|
1007
|
-
10. **Clean up resources** - Use `finally` or context managers
|
|
1008
|
-
|
|
1
|
+
# Python Error Handling
|
|
2
|
+
|
|
3
|
+
Comprehensive exception handling patterns for robust Python code, including custom exceptions, context managers, and contextlib utilities.
|
|
4
|
+
|
|
5
|
+
## Basic Exception Handling
|
|
6
|
+
|
|
7
|
+
### Specific Exceptions
|
|
8
|
+
|
|
9
|
+
Always catch specific exceptions rather than using bare `except:` clauses.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# Good - Specific exception
|
|
13
|
+
try:
|
|
14
|
+
result = int(user_input)
|
|
15
|
+
except ValueError as e:
|
|
16
|
+
logger.error(f"Invalid input: {e}")
|
|
17
|
+
result = 0
|
|
18
|
+
|
|
19
|
+
# Bad - Bare except
|
|
20
|
+
try:
|
|
21
|
+
result = int(user_input)
|
|
22
|
+
except: # Don't do this - catches SystemExit, KeyboardInterrupt, etc.
|
|
23
|
+
result = 0
|
|
24
|
+
|
|
25
|
+
# Bad - Too broad
|
|
26
|
+
try:
|
|
27
|
+
result = int(user_input)
|
|
28
|
+
except Exception: # Still too broad for most cases
|
|
29
|
+
result = 0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Multiple Exceptions
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# Handle different exceptions differently
|
|
36
|
+
try:
|
|
37
|
+
with open(file_path) as f:
|
|
38
|
+
data = json.load(f)
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
logger.warning(f"File not found: {file_path}")
|
|
41
|
+
data = {}
|
|
42
|
+
except json.JSONDecodeError as e:
|
|
43
|
+
logger.error(f"Invalid JSON in {file_path}: {e}")
|
|
44
|
+
data = {}
|
|
45
|
+
except PermissionError:
|
|
46
|
+
logger.error(f"Permission denied: {file_path}")
|
|
47
|
+
raise # Re-raise if we can't handle it
|
|
48
|
+
|
|
49
|
+
# Handle multiple exceptions the same way
|
|
50
|
+
try:
|
|
51
|
+
result = perform_operation()
|
|
52
|
+
except (ValueError, TypeError, KeyError) as e:
|
|
53
|
+
logger.error(f"Operation failed: {e}")
|
|
54
|
+
result = None
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Try-Except-Else-Finally
|
|
58
|
+
|
|
59
|
+
### The Complete Pattern
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
try:
|
|
63
|
+
# Code that might raise exceptions
|
|
64
|
+
result = risky_operation()
|
|
65
|
+
except ValueError as e:
|
|
66
|
+
# Handle specific exception
|
|
67
|
+
logger.error(f"Value error: {e}")
|
|
68
|
+
result = None
|
|
69
|
+
except TypeError as e:
|
|
70
|
+
# Handle another specific exception
|
|
71
|
+
logger.error(f"Type error: {e}")
|
|
72
|
+
result = None
|
|
73
|
+
else:
|
|
74
|
+
# Runs only if no exception was raised
|
|
75
|
+
logger.info("Operation succeeded")
|
|
76
|
+
process_result(result)
|
|
77
|
+
finally:
|
|
78
|
+
# Always runs, even if exception occurred or return was called
|
|
79
|
+
cleanup_resources()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Using Else Clause
|
|
83
|
+
|
|
84
|
+
The `else` clause runs only if no exception was raised in the `try` block.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Good - Separates success logic from try block
|
|
88
|
+
try:
|
|
89
|
+
data = load_data(file_path)
|
|
90
|
+
except FileNotFoundError:
|
|
91
|
+
logger.error(f"File not found: {file_path}")
|
|
92
|
+
return None
|
|
93
|
+
else:
|
|
94
|
+
# Only runs if load_data succeeded
|
|
95
|
+
validate_data(data)
|
|
96
|
+
return process_data(data)
|
|
97
|
+
|
|
98
|
+
# Less clear - success logic mixed with risky code
|
|
99
|
+
try:
|
|
100
|
+
data = load_data(file_path)
|
|
101
|
+
validate_data(data) # This is also in the try block
|
|
102
|
+
return process_data(data)
|
|
103
|
+
except FileNotFoundError:
|
|
104
|
+
logger.error(f"File not found: {file_path}")
|
|
105
|
+
return None
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Using Finally for Cleanup
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Manual cleanup with finally
|
|
112
|
+
file = None
|
|
113
|
+
try:
|
|
114
|
+
file = open(file_path)
|
|
115
|
+
data = file.read()
|
|
116
|
+
except FileNotFoundError:
|
|
117
|
+
logger.error("File not found")
|
|
118
|
+
data = None
|
|
119
|
+
finally:
|
|
120
|
+
if file is not None:
|
|
121
|
+
file.close()
|
|
122
|
+
|
|
123
|
+
# Better - Use context manager instead (see below)
|
|
124
|
+
try:
|
|
125
|
+
with open(file_path) as file:
|
|
126
|
+
data = file.read()
|
|
127
|
+
except FileNotFoundError:
|
|
128
|
+
logger.error("File not found")
|
|
129
|
+
data = None
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Context Managers
|
|
133
|
+
|
|
134
|
+
Context managers provide automatic resource management using the `with` statement. They ensure cleanup code runs even if exceptions occur.
|
|
135
|
+
|
|
136
|
+
### Built-in Context Managers
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# File handling - Automatic close
|
|
140
|
+
with open(file_path) as f:
|
|
141
|
+
data = f.read()
|
|
142
|
+
# File is automatically closed here, even if exception occurred
|
|
143
|
+
|
|
144
|
+
# Multiple context managers (Python 3.1+)
|
|
145
|
+
with open(input_file) as f_in, open(output_file, 'w') as f_out:
|
|
146
|
+
data = f_in.read()
|
|
147
|
+
f_out.write(process(data))
|
|
148
|
+
|
|
149
|
+
# Parenthesized context managers (Python 3.10+)
|
|
150
|
+
with (
|
|
151
|
+
open(input_file) as f_in,
|
|
152
|
+
open(output_file, 'w') as f_out,
|
|
153
|
+
open(log_file, 'a') as f_log,
|
|
154
|
+
):
|
|
155
|
+
data = f_in.read()
|
|
156
|
+
f_out.write(process(data))
|
|
157
|
+
f_log.write(f"Processed {input_file}\n")
|
|
158
|
+
|
|
159
|
+
# Threading locks
|
|
160
|
+
import threading
|
|
161
|
+
|
|
162
|
+
lock = threading.Lock()
|
|
163
|
+
with lock:
|
|
164
|
+
# Critical section - lock is automatically released
|
|
165
|
+
shared_resource.modify()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Custom Context Managers - Class-Based
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from typing import Optional
|
|
172
|
+
|
|
173
|
+
class DatabaseConnection:
|
|
174
|
+
"""Context manager for database connections."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, db_url: str):
|
|
177
|
+
self.db_url = db_url
|
|
178
|
+
self.conn: Optional[Connection] = None
|
|
179
|
+
|
|
180
|
+
def __enter__(self) -> Connection:
|
|
181
|
+
"""Establish connection when entering context."""
|
|
182
|
+
self.conn = connect(self.db_url)
|
|
183
|
+
logger.info(f"Connected to {self.db_url}")
|
|
184
|
+
return self.conn
|
|
185
|
+
|
|
186
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
187
|
+
"""Close connection when exiting context.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
exc_type: Exception type if exception occurred, None otherwise
|
|
191
|
+
exc_val: Exception value if exception occurred, None otherwise
|
|
192
|
+
exc_tb: Exception traceback if exception occurred, None otherwise
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
False to propagate exceptions, True to suppress them
|
|
196
|
+
"""
|
|
197
|
+
if self.conn is not None:
|
|
198
|
+
self.conn.close()
|
|
199
|
+
logger.info(f"Closed connection to {self.db_url}")
|
|
200
|
+
|
|
201
|
+
# Log exception if one occurred
|
|
202
|
+
if exc_type is not None:
|
|
203
|
+
logger.error(f"Exception in database context: {exc_val}")
|
|
204
|
+
|
|
205
|
+
# Return False to propagate exception, True to suppress it
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
# Usage
|
|
209
|
+
with DatabaseConnection("postgresql://localhost/mydb") as conn:
|
|
210
|
+
conn.execute("SELECT * FROM users")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Custom Context Managers - contextlib.contextmanager
|
|
214
|
+
|
|
215
|
+
The `@contextmanager` decorator provides a simpler way to create context managers using generators.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from contextlib import contextmanager
|
|
219
|
+
from typing import Generator, Optional
|
|
220
|
+
import time
|
|
221
|
+
|
|
222
|
+
@contextmanager
|
|
223
|
+
def database_connection(db_url: str) -> Generator[Connection, None, None]:
|
|
224
|
+
"""Context manager for database connections.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
db_url: Database connection URL
|
|
228
|
+
|
|
229
|
+
Yields:
|
|
230
|
+
Active database connection
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
with database_connection("postgresql://...") as conn:
|
|
234
|
+
conn.execute("SELECT * FROM users")
|
|
235
|
+
"""
|
|
236
|
+
conn = connect(db_url)
|
|
237
|
+
try:
|
|
238
|
+
logger.info(f"Connected to {db_url}")
|
|
239
|
+
yield conn
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Database error: {e}")
|
|
242
|
+
raise
|
|
243
|
+
finally:
|
|
244
|
+
conn.close()
|
|
245
|
+
logger.info(f"Closed connection to {db_url}")
|
|
246
|
+
|
|
247
|
+
@contextmanager
|
|
248
|
+
def timer(name: str) -> Generator[None, None, None]:
|
|
249
|
+
"""Context manager to time code execution.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
name: Name of the timed operation
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
with timer("data processing"):
|
|
256
|
+
process_large_dataset()
|
|
257
|
+
"""
|
|
258
|
+
start = time.time()
|
|
259
|
+
try:
|
|
260
|
+
yield
|
|
261
|
+
finally:
|
|
262
|
+
elapsed = time.time() - start
|
|
263
|
+
logger.info(f"{name} took {elapsed:.2f} seconds")
|
|
264
|
+
|
|
265
|
+
@contextmanager
|
|
266
|
+
def temporary_directory() -> Generator[Path, None, None]:
|
|
267
|
+
"""Context manager for temporary directory.
|
|
268
|
+
|
|
269
|
+
Yields:
|
|
270
|
+
Path to temporary directory
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
with temporary_directory() as tmpdir:
|
|
274
|
+
(tmpdir / "file.txt").write_text("data")
|
|
275
|
+
# Directory is automatically deleted here
|
|
276
|
+
"""
|
|
277
|
+
import tempfile
|
|
278
|
+
import shutil
|
|
279
|
+
|
|
280
|
+
tmpdir = Path(tempfile.mkdtemp())
|
|
281
|
+
try:
|
|
282
|
+
yield tmpdir
|
|
283
|
+
finally:
|
|
284
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### contextlib Utilities
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
from contextlib import (
|
|
291
|
+
contextmanager,
|
|
292
|
+
suppress,
|
|
293
|
+
redirect_stdout,
|
|
294
|
+
redirect_stderr,
|
|
295
|
+
ExitStack,
|
|
296
|
+
nullcontext,
|
|
297
|
+
)
|
|
298
|
+
import io
|
|
299
|
+
|
|
300
|
+
# suppress - Ignore specific exceptions
|
|
301
|
+
from contextlib import suppress
|
|
302
|
+
|
|
303
|
+
# Instead of try-except
|
|
304
|
+
try:
|
|
305
|
+
os.remove(file_path)
|
|
306
|
+
except FileNotFoundError:
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
# Use suppress
|
|
310
|
+
with suppress(FileNotFoundError):
|
|
311
|
+
os.remove(file_path)
|
|
312
|
+
|
|
313
|
+
# Suppress multiple exceptions
|
|
314
|
+
with suppress(FileNotFoundError, PermissionError):
|
|
315
|
+
os.remove(file_path)
|
|
316
|
+
|
|
317
|
+
# redirect_stdout/redirect_stderr - Redirect output
|
|
318
|
+
output = io.StringIO()
|
|
319
|
+
with redirect_stdout(output):
|
|
320
|
+
print("This goes to output variable")
|
|
321
|
+
print("Not to console")
|
|
322
|
+
|
|
323
|
+
captured = output.getvalue() # "This goes to output variable\nNot to console\n"
|
|
324
|
+
|
|
325
|
+
# ExitStack - Manage dynamic number of context managers
|
|
326
|
+
from contextlib import ExitStack
|
|
327
|
+
|
|
328
|
+
def process_files(file_paths: list[str]) -> None:
|
|
329
|
+
"""Process multiple files with dynamic context managers."""
|
|
330
|
+
with ExitStack() as stack:
|
|
331
|
+
# Open all files and register them with the stack
|
|
332
|
+
files = [stack.enter_context(open(path)) for path in file_paths]
|
|
333
|
+
|
|
334
|
+
# Process all files
|
|
335
|
+
for f in files:
|
|
336
|
+
process_file(f)
|
|
337
|
+
|
|
338
|
+
# All files automatically closed when exiting
|
|
339
|
+
|
|
340
|
+
# ExitStack with callbacks
|
|
341
|
+
with ExitStack() as stack:
|
|
342
|
+
# Register cleanup callbacks
|
|
343
|
+
stack.callback(cleanup_temp_files)
|
|
344
|
+
stack.callback(logger.info, "Processing complete")
|
|
345
|
+
|
|
346
|
+
# Do work
|
|
347
|
+
process_data()
|
|
348
|
+
|
|
349
|
+
# Callbacks run in LIFO order when exiting
|
|
350
|
+
|
|
351
|
+
# nullcontext - Conditional context manager (Python 3.7+)
|
|
352
|
+
from contextlib import nullcontext
|
|
353
|
+
|
|
354
|
+
def process_data(use_lock: bool = False) -> None:
|
|
355
|
+
"""Process data with optional locking."""
|
|
356
|
+
lock = threading.Lock() if use_lock else nullcontext()
|
|
357
|
+
|
|
358
|
+
with lock:
|
|
359
|
+
# This works whether lock is a real Lock or nullcontext
|
|
360
|
+
modify_shared_resource()
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Async Context Managers
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from contextlib import asynccontextmanager
|
|
367
|
+
from typing import AsyncGenerator
|
|
368
|
+
|
|
369
|
+
class AsyncDatabaseConnection:
|
|
370
|
+
"""Async context manager for database connections."""
|
|
371
|
+
|
|
372
|
+
async def __aenter__(self) -> AsyncConnection:
|
|
373
|
+
self.conn = await async_connect(self.db_url)
|
|
374
|
+
return self.conn
|
|
375
|
+
|
|
376
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
377
|
+
await self.conn.close()
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
# Using @asynccontextmanager decorator
|
|
381
|
+
@asynccontextmanager
|
|
382
|
+
async def async_database_connection(
|
|
383
|
+
db_url: str
|
|
384
|
+
) -> AsyncGenerator[AsyncConnection, None]:
|
|
385
|
+
"""Async context manager for database connections."""
|
|
386
|
+
conn = await async_connect(db_url)
|
|
387
|
+
try:
|
|
388
|
+
yield conn
|
|
389
|
+
finally:
|
|
390
|
+
await conn.close()
|
|
391
|
+
|
|
392
|
+
# Usage
|
|
393
|
+
async def fetch_users():
|
|
394
|
+
async with async_database_connection("postgresql://...") as conn:
|
|
395
|
+
return await conn.fetch("SELECT * FROM users")
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Custom Exceptions
|
|
399
|
+
|
|
400
|
+
Create custom exceptions for domain-specific errors. This makes error handling more precise and code more maintainable.
|
|
401
|
+
|
|
402
|
+
### Basic Custom Exceptions
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
# Simple custom exception
|
|
406
|
+
class ValidationError(Exception):
|
|
407
|
+
"""Raised when validation fails."""
|
|
408
|
+
pass
|
|
409
|
+
|
|
410
|
+
# Custom exception with additional attributes
|
|
411
|
+
class AuthenticationError(Exception):
|
|
412
|
+
"""Raised when authentication fails."""
|
|
413
|
+
|
|
414
|
+
def __init__(self, user_id: int, message: str = "Authentication failed"):
|
|
415
|
+
self.user_id = user_id
|
|
416
|
+
self.message = message
|
|
417
|
+
super().__init__(self.message)
|
|
418
|
+
|
|
419
|
+
def __str__(self) -> str:
|
|
420
|
+
return f"AuthenticationError(user_id={self.user_id}): {self.message}"
|
|
421
|
+
|
|
422
|
+
# Custom exception with multiple attributes
|
|
423
|
+
class DatabaseError(Exception):
|
|
424
|
+
"""Raised when database operation fails."""
|
|
425
|
+
|
|
426
|
+
def __init__(
|
|
427
|
+
self,
|
|
428
|
+
message: str,
|
|
429
|
+
query: str,
|
|
430
|
+
error_code: Optional[int] = None,
|
|
431
|
+
):
|
|
432
|
+
self.message = message
|
|
433
|
+
self.query = query
|
|
434
|
+
self.error_code = error_code
|
|
435
|
+
super().__init__(self.message)
|
|
436
|
+
|
|
437
|
+
# Usage
|
|
438
|
+
def validate_email(email: str) -> None:
|
|
439
|
+
"""Validate email format.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
email: Email address to validate
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
ValidationError: If email format is invalid
|
|
446
|
+
"""
|
|
447
|
+
if '@' not in email:
|
|
448
|
+
raise ValidationError(f"Invalid email format: {email}")
|
|
449
|
+
|
|
450
|
+
if not email.endswith(('.com', '.org', '.net')):
|
|
451
|
+
raise ValidationError(f"Invalid email domain: {email}")
|
|
452
|
+
|
|
453
|
+
def authenticate_user(user_id: int, password: str) -> User:
|
|
454
|
+
"""Authenticate user with password.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
user_id: User ID
|
|
458
|
+
password: User password
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Authenticated user object
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
AuthenticationError: If authentication fails
|
|
465
|
+
"""
|
|
466
|
+
if not verify_password(user_id, password):
|
|
467
|
+
raise AuthenticationError(user_id, "Invalid password")
|
|
468
|
+
return get_user(user_id)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Exception Hierarchies
|
|
472
|
+
|
|
473
|
+
Create exception hierarchies for related errors.
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
# Base exception for application
|
|
477
|
+
class AppError(Exception):
|
|
478
|
+
"""Base exception for all application errors."""
|
|
479
|
+
pass
|
|
480
|
+
|
|
481
|
+
# Database errors
|
|
482
|
+
class DatabaseError(AppError):
|
|
483
|
+
"""Base exception for database-related errors."""
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
class ConnectionError(DatabaseError):
|
|
487
|
+
"""Database connection errors."""
|
|
488
|
+
pass
|
|
489
|
+
|
|
490
|
+
class QueryError(DatabaseError):
|
|
491
|
+
"""Database query errors."""
|
|
492
|
+
|
|
493
|
+
def __init__(self, message: str, query: str):
|
|
494
|
+
self.query = query
|
|
495
|
+
super().__init__(f"{message}: {query}")
|
|
496
|
+
|
|
497
|
+
class TransactionError(DatabaseError):
|
|
498
|
+
"""Database transaction errors."""
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
# API errors
|
|
502
|
+
class APIError(AppError):
|
|
503
|
+
"""Base exception for API-related errors."""
|
|
504
|
+
|
|
505
|
+
def __init__(self, message: str, status_code: int):
|
|
506
|
+
self.status_code = status_code
|
|
507
|
+
super().__init__(f"[{status_code}] {message}")
|
|
508
|
+
|
|
509
|
+
class NotFoundError(APIError):
|
|
510
|
+
"""Resource not found."""
|
|
511
|
+
|
|
512
|
+
def __init__(self, resource: str, resource_id: str):
|
|
513
|
+
self.resource = resource
|
|
514
|
+
self.resource_id = resource_id
|
|
515
|
+
super().__init__(
|
|
516
|
+
f"{resource} not found: {resource_id}",
|
|
517
|
+
status_code=404,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
class UnauthorizedError(APIError):
|
|
521
|
+
"""Unauthorized access."""
|
|
522
|
+
|
|
523
|
+
def __init__(self, message: str = "Unauthorized"):
|
|
524
|
+
super().__init__(message, status_code=401)
|
|
525
|
+
|
|
526
|
+
# Usage - Catch specific or broad exceptions
|
|
527
|
+
try:
|
|
528
|
+
user = get_user(user_id)
|
|
529
|
+
except NotFoundError as e:
|
|
530
|
+
# Handle specific error
|
|
531
|
+
logger.warning(f"User not found: {e.resource_id}")
|
|
532
|
+
except APIError as e:
|
|
533
|
+
# Handle any API error
|
|
534
|
+
logger.error(f"API error: {e}")
|
|
535
|
+
except AppError as e:
|
|
536
|
+
# Handle any application error
|
|
537
|
+
logger.error(f"Application error: {e}")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Rich Exception Information
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
from typing import Any, Optional
|
|
544
|
+
from dataclasses import dataclass
|
|
545
|
+
|
|
546
|
+
@dataclass
|
|
547
|
+
class ErrorContext:
|
|
548
|
+
"""Context information for errors."""
|
|
549
|
+
operation: str
|
|
550
|
+
user_id: Optional[int] = None
|
|
551
|
+
request_id: Optional[str] = None
|
|
552
|
+
metadata: dict[str, Any] = None
|
|
553
|
+
|
|
554
|
+
def __post_init__(self):
|
|
555
|
+
if self.metadata is None:
|
|
556
|
+
self.metadata = {}
|
|
557
|
+
|
|
558
|
+
class OperationError(Exception):
|
|
559
|
+
"""Exception with rich context information."""
|
|
560
|
+
|
|
561
|
+
def __init__(self, message: str, context: ErrorContext):
|
|
562
|
+
self.message = message
|
|
563
|
+
self.context = context
|
|
564
|
+
super().__init__(self.message)
|
|
565
|
+
|
|
566
|
+
def __str__(self) -> str:
|
|
567
|
+
ctx = self.context
|
|
568
|
+
parts = [f"OperationError: {self.message}"]
|
|
569
|
+
parts.append(f" Operation: {ctx.operation}")
|
|
570
|
+
if ctx.user_id:
|
|
571
|
+
parts.append(f" User ID: {ctx.user_id}")
|
|
572
|
+
if ctx.request_id:
|
|
573
|
+
parts.append(f" Request ID: {ctx.request_id}")
|
|
574
|
+
if ctx.metadata:
|
|
575
|
+
parts.append(f" Metadata: {ctx.metadata}")
|
|
576
|
+
return "\n".join(parts)
|
|
577
|
+
|
|
578
|
+
# Usage
|
|
579
|
+
def process_payment(user_id: int, amount: float, request_id: str) -> None:
|
|
580
|
+
"""Process payment with rich error context."""
|
|
581
|
+
context = ErrorContext(
|
|
582
|
+
operation="process_payment",
|
|
583
|
+
user_id=user_id,
|
|
584
|
+
request_id=request_id,
|
|
585
|
+
metadata={"amount": amount},
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
try:
|
|
589
|
+
charge_card(user_id, amount)
|
|
590
|
+
except CardDeclinedError as e:
|
|
591
|
+
raise OperationError("Payment declined", context) from e
|
|
592
|
+
except InsufficientFundsError as e:
|
|
593
|
+
raise OperationError("Insufficient funds", context) from e
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Exception Chaining
|
|
597
|
+
|
|
598
|
+
Exception chaining preserves the original exception context, making debugging easier.
|
|
599
|
+
|
|
600
|
+
### Using 'from' for Chaining
|
|
601
|
+
|
|
602
|
+
```python
|
|
603
|
+
# Good - Preserve original exception with 'from'
|
|
604
|
+
try:
|
|
605
|
+
result = process_data(data)
|
|
606
|
+
except ValueError as e:
|
|
607
|
+
raise ProcessingError("Failed to process data") from e
|
|
608
|
+
# Traceback will show both ProcessingError and original ValueError
|
|
609
|
+
|
|
610
|
+
# Good - Explicit chaining with context
|
|
611
|
+
try:
|
|
612
|
+
user_data = json.loads(raw_data)
|
|
613
|
+
except json.JSONDecodeError as e:
|
|
614
|
+
raise ValidationError(
|
|
615
|
+
f"Invalid JSON in user data: {e.msg}"
|
|
616
|
+
) from e
|
|
617
|
+
|
|
618
|
+
# Rare - Suppress original exception with 'from None'
|
|
619
|
+
try:
|
|
620
|
+
result = process_data(data)
|
|
621
|
+
except ValueError:
|
|
622
|
+
# Only use 'from None' when original exception is not relevant
|
|
623
|
+
raise ProcessingError("Failed to process data") from None
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Implicit Chaining
|
|
627
|
+
|
|
628
|
+
```python
|
|
629
|
+
# Implicit chaining - exception raised during exception handling
|
|
630
|
+
try:
|
|
631
|
+
result = process_data(data)
|
|
632
|
+
except ValueError as e:
|
|
633
|
+
# If log_error raises an exception, both will be in traceback
|
|
634
|
+
log_error(e)
|
|
635
|
+
raise ProcessingError("Failed to process data")
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Accessing Exception Chain
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
try:
|
|
642
|
+
try:
|
|
643
|
+
risky_operation()
|
|
644
|
+
except ValueError as e:
|
|
645
|
+
raise ProcessingError("Processing failed") from e
|
|
646
|
+
except ProcessingError as e:
|
|
647
|
+
# Access the original exception
|
|
648
|
+
original = e.__cause__ # The exception after 'from'
|
|
649
|
+
context = e.__context__ # The exception being handled when this was raised
|
|
650
|
+
|
|
651
|
+
logger.error(f"Processing error: {e}")
|
|
652
|
+
logger.error(f"Original error: {original}")
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## Logging Exceptions
|
|
656
|
+
|
|
657
|
+
Proper exception logging is crucial for debugging and monitoring.
|
|
658
|
+
|
|
659
|
+
### Basic Exception Logging
|
|
660
|
+
|
|
661
|
+
```python
|
|
662
|
+
import logging
|
|
663
|
+
|
|
664
|
+
logger = logging.getLogger(__name__)
|
|
665
|
+
|
|
666
|
+
# Log exception with full traceback
|
|
667
|
+
try:
|
|
668
|
+
result = risky_operation()
|
|
669
|
+
except Exception as e:
|
|
670
|
+
logger.exception("Operation failed") # Includes full traceback
|
|
671
|
+
raise # Re-raise after logging
|
|
672
|
+
|
|
673
|
+
# Log without traceback
|
|
674
|
+
try:
|
|
675
|
+
result = risky_operation()
|
|
676
|
+
except ValueError as e:
|
|
677
|
+
logger.error(f"Invalid value: {e}") # No traceback
|
|
678
|
+
result = default_value
|
|
679
|
+
|
|
680
|
+
# Log with different levels
|
|
681
|
+
try:
|
|
682
|
+
result = optional_operation()
|
|
683
|
+
except FileNotFoundError:
|
|
684
|
+
logger.warning("Optional file not found, using defaults")
|
|
685
|
+
result = default_value
|
|
686
|
+
except PermissionError as e:
|
|
687
|
+
logger.error(f"Permission denied: {e}")
|
|
688
|
+
raise
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Structured Logging
|
|
692
|
+
|
|
693
|
+
```python
|
|
694
|
+
import logging
|
|
695
|
+
from typing import Any
|
|
696
|
+
|
|
697
|
+
logger = logging.getLogger(__name__)
|
|
698
|
+
|
|
699
|
+
# Log with structured data
|
|
700
|
+
try:
|
|
701
|
+
process_user_data(user_id, data)
|
|
702
|
+
except ValidationError as e:
|
|
703
|
+
logger.error(
|
|
704
|
+
"Validation failed",
|
|
705
|
+
extra={
|
|
706
|
+
"user_id": user_id,
|
|
707
|
+
"error_type": type(e).__name__,
|
|
708
|
+
"error_message": str(e),
|
|
709
|
+
},
|
|
710
|
+
)
|
|
711
|
+
raise
|
|
712
|
+
|
|
713
|
+
# Log with exception info without traceback
|
|
714
|
+
try:
|
|
715
|
+
result = risky_operation()
|
|
716
|
+
except ValueError as e:
|
|
717
|
+
logger.error(
|
|
718
|
+
"Operation failed: %s",
|
|
719
|
+
e,
|
|
720
|
+
exc_info=False, # Don't include traceback
|
|
721
|
+
)
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Custom Exception Logging
|
|
725
|
+
|
|
726
|
+
```python
|
|
727
|
+
class LoggingException(Exception):
|
|
728
|
+
"""Exception that logs itself when raised."""
|
|
729
|
+
|
|
730
|
+
def __init__(self, message: str, level: int = logging.ERROR):
|
|
731
|
+
self.message = message
|
|
732
|
+
self.level = level
|
|
733
|
+
super().__init__(self.message)
|
|
734
|
+
|
|
735
|
+
# Log when exception is created
|
|
736
|
+
logger = logging.getLogger(self.__class__.__module__)
|
|
737
|
+
logger.log(self.level, f"{self.__class__.__name__}: {message}")
|
|
738
|
+
|
|
739
|
+
# Usage
|
|
740
|
+
def process_data(data: dict[str, Any]) -> None:
|
|
741
|
+
if not data:
|
|
742
|
+
raise LoggingException("Empty data received", level=logging.WARNING)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## Best Practices
|
|
746
|
+
|
|
747
|
+
### DO
|
|
748
|
+
|
|
749
|
+
✅ **Catch specific exceptions** - Never use bare `except:` or catch `Exception` unless absolutely necessary
|
|
750
|
+
|
|
751
|
+
✅ **Use context managers** - Prefer `with` statement for resource management
|
|
752
|
+
|
|
753
|
+
✅ **Log exceptions properly** - Use `logger.exception()` to include tracebacks
|
|
754
|
+
|
|
755
|
+
✅ **Create custom exceptions** - For domain-specific errors with clear names
|
|
756
|
+
|
|
757
|
+
✅ **Use exception chaining** - Preserve error context with `from`
|
|
758
|
+
|
|
759
|
+
✅ **Document exceptions** - List raised exceptions in docstrings
|
|
760
|
+
|
|
761
|
+
✅ **Fail fast** - Don't catch exceptions you can't handle
|
|
762
|
+
|
|
763
|
+
✅ **Clean up resources** - Use `finally` or context managers
|
|
764
|
+
|
|
765
|
+
✅ **Use contextlib utilities** - `suppress`, `ExitStack`, etc. for cleaner code
|
|
766
|
+
|
|
767
|
+
✅ **Create exception hierarchies** - For related errors
|
|
768
|
+
|
|
769
|
+
### DON'T
|
|
770
|
+
|
|
771
|
+
❌ **Don't use bare except** - Catches SystemExit, KeyboardInterrupt, etc.
|
|
772
|
+
|
|
773
|
+
```python
|
|
774
|
+
# Bad
|
|
775
|
+
try:
|
|
776
|
+
do_something()
|
|
777
|
+
except: # Catches everything, including KeyboardInterrupt
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
# Good
|
|
781
|
+
try:
|
|
782
|
+
do_something()
|
|
783
|
+
except (ValueError, TypeError) as e:
|
|
784
|
+
logger.error(f"Expected error: {e}")
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
❌ **Don't silence exceptions** - Always log or handle them
|
|
788
|
+
|
|
789
|
+
```python
|
|
790
|
+
# Bad
|
|
791
|
+
try:
|
|
792
|
+
critical_operation()
|
|
793
|
+
except Exception:
|
|
794
|
+
pass # Silent failure
|
|
795
|
+
|
|
796
|
+
# Good
|
|
797
|
+
try:
|
|
798
|
+
critical_operation()
|
|
799
|
+
except Exception as e:
|
|
800
|
+
logger.exception("Critical operation failed")
|
|
801
|
+
raise
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
❌ **Don't use exceptions for flow control** - Use appropriate methods
|
|
805
|
+
|
|
806
|
+
```python
|
|
807
|
+
# Bad - Using exception for flow control
|
|
808
|
+
try:
|
|
809
|
+
value = my_dict[key]
|
|
810
|
+
except KeyError:
|
|
811
|
+
value = default
|
|
812
|
+
|
|
813
|
+
# Good - Use dict.get()
|
|
814
|
+
value = my_dict.get(key, default)
|
|
815
|
+
|
|
816
|
+
# Bad - Using exception for flow control
|
|
817
|
+
try:
|
|
818
|
+
index = my_list.index(item)
|
|
819
|
+
except ValueError:
|
|
820
|
+
index = -1
|
|
821
|
+
|
|
822
|
+
# Good - Use 'in' operator
|
|
823
|
+
index = my_list.index(item) if item in my_list else -1
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
❌ **Don't catch Exception without re-raising** - Unless you can truly handle it
|
|
827
|
+
|
|
828
|
+
```python
|
|
829
|
+
# Bad
|
|
830
|
+
try:
|
|
831
|
+
critical_operation()
|
|
832
|
+
except Exception:
|
|
833
|
+
print("Error occurred") # Swallows the exception
|
|
834
|
+
|
|
835
|
+
# Good
|
|
836
|
+
try:
|
|
837
|
+
critical_operation()
|
|
838
|
+
except Exception as e:
|
|
839
|
+
logger.exception("Critical operation failed")
|
|
840
|
+
raise # Re-raise after logging
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
❌ **Don't lose exception context** - Use exception chaining
|
|
844
|
+
|
|
845
|
+
```python
|
|
846
|
+
# Bad - Loses original exception
|
|
847
|
+
try:
|
|
848
|
+
process_data(data)
|
|
849
|
+
except ValueError:
|
|
850
|
+
raise ProcessingError("Failed") # Original error is lost
|
|
851
|
+
|
|
852
|
+
# Good - Preserves original exception
|
|
853
|
+
try:
|
|
854
|
+
process_data(data)
|
|
855
|
+
except ValueError as e:
|
|
856
|
+
raise ProcessingError("Failed") from e
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## Advanced Patterns
|
|
860
|
+
|
|
861
|
+
### Exception Groups (Python 3.11+)
|
|
862
|
+
|
|
863
|
+
```python
|
|
864
|
+
# Raise multiple exceptions at once
|
|
865
|
+
raise ExceptionGroup("Multiple errors occurred", [
|
|
866
|
+
ValueError("Invalid value"),
|
|
867
|
+
TypeError("Invalid type"),
|
|
868
|
+
])
|
|
869
|
+
|
|
870
|
+
# Catch exception groups
|
|
871
|
+
try:
|
|
872
|
+
raise ExceptionGroup("errors", [ValueError("bad"), TypeError("wrong")])
|
|
873
|
+
except* ValueError as eg:
|
|
874
|
+
# Handle all ValueError instances
|
|
875
|
+
for e in eg.exceptions:
|
|
876
|
+
logger.error(f"Value error: {e}")
|
|
877
|
+
except* TypeError as eg:
|
|
878
|
+
# Handle all TypeError instances
|
|
879
|
+
for e in eg.exceptions:
|
|
880
|
+
logger.error(f"Type error: {e}")
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Retry Logic with Exceptions
|
|
884
|
+
|
|
885
|
+
```python
|
|
886
|
+
from typing import TypeVar, Callable
|
|
887
|
+
import time
|
|
888
|
+
|
|
889
|
+
T = TypeVar('T')
|
|
890
|
+
|
|
891
|
+
def retry(
|
|
892
|
+
func: Callable[..., T],
|
|
893
|
+
max_attempts: int = 3,
|
|
894
|
+
delay: float = 1.0,
|
|
895
|
+
exceptions: tuple[type[Exception], ...] = (Exception,),
|
|
896
|
+
) -> T:
|
|
897
|
+
"""Retry function on exception.
|
|
898
|
+
|
|
899
|
+
Args:
|
|
900
|
+
func: Function to retry
|
|
901
|
+
max_attempts: Maximum number of attempts
|
|
902
|
+
delay: Delay between attempts in seconds
|
|
903
|
+
exceptions: Tuple of exceptions to catch
|
|
904
|
+
|
|
905
|
+
Returns:
|
|
906
|
+
Result of successful function call
|
|
907
|
+
|
|
908
|
+
Raises:
|
|
909
|
+
Last exception if all attempts fail
|
|
910
|
+
"""
|
|
911
|
+
last_exception = None
|
|
912
|
+
|
|
913
|
+
for attempt in range(max_attempts):
|
|
914
|
+
try:
|
|
915
|
+
return func()
|
|
916
|
+
except exceptions as e:
|
|
917
|
+
last_exception = e
|
|
918
|
+
logger.warning(
|
|
919
|
+
f"Attempt {attempt + 1}/{max_attempts} failed: {e}"
|
|
920
|
+
)
|
|
921
|
+
if attempt < max_attempts - 1:
|
|
922
|
+
time.sleep(delay)
|
|
923
|
+
|
|
924
|
+
# All attempts failed
|
|
925
|
+
raise last_exception
|
|
926
|
+
|
|
927
|
+
# Usage
|
|
928
|
+
result = retry(
|
|
929
|
+
lambda: fetch_data_from_api(),
|
|
930
|
+
max_attempts=3,
|
|
931
|
+
delay=2.0,
|
|
932
|
+
exceptions=(ConnectionError, TimeoutError),
|
|
933
|
+
)
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Validation with Multiple Errors
|
|
937
|
+
|
|
938
|
+
```python
|
|
939
|
+
from typing import List
|
|
940
|
+
|
|
941
|
+
class ValidationErrors(Exception):
|
|
942
|
+
"""Exception containing multiple validation errors."""
|
|
943
|
+
|
|
944
|
+
def __init__(self, errors: List[str]):
|
|
945
|
+
self.errors = errors
|
|
946
|
+
super().__init__(f"{len(errors)} validation errors")
|
|
947
|
+
|
|
948
|
+
def __str__(self) -> str:
|
|
949
|
+
return "\n".join([
|
|
950
|
+
f"Validation failed with {len(self.errors)} errors:",
|
|
951
|
+
*[f" - {error}" for error in self.errors],
|
|
952
|
+
])
|
|
953
|
+
|
|
954
|
+
def validate_user(user_data: dict) -> None:
|
|
955
|
+
"""Validate user data, collecting all errors.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
user_data: User data to validate
|
|
959
|
+
|
|
960
|
+
Raises:
|
|
961
|
+
ValidationErrors: If validation fails
|
|
962
|
+
"""
|
|
963
|
+
errors = []
|
|
964
|
+
|
|
965
|
+
if not user_data.get("email"):
|
|
966
|
+
errors.append("Email is required")
|
|
967
|
+
elif "@" not in user_data["email"]:
|
|
968
|
+
errors.append("Email must contain @")
|
|
969
|
+
|
|
970
|
+
if not user_data.get("username"):
|
|
971
|
+
errors.append("Username is required")
|
|
972
|
+
elif len(user_data["username"]) < 3:
|
|
973
|
+
errors.append("Username must be at least 3 characters")
|
|
974
|
+
|
|
975
|
+
if not user_data.get("age"):
|
|
976
|
+
errors.append("Age is required")
|
|
977
|
+
elif user_data["age"] < 18:
|
|
978
|
+
errors.append("User must be at least 18 years old")
|
|
979
|
+
|
|
980
|
+
if errors:
|
|
981
|
+
raise ValidationErrors(errors)
|
|
982
|
+
|
|
983
|
+
# Usage
|
|
984
|
+
try:
|
|
985
|
+
validate_user({"email": "invalid", "username": "ab"})
|
|
986
|
+
except ValidationErrors as e:
|
|
987
|
+
logger.error(str(e))
|
|
988
|
+
# Validation failed with 3 errors:
|
|
989
|
+
# - Email must contain @
|
|
990
|
+
# - Username must be at least 3 characters
|
|
991
|
+
# - Age is required
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
## Summary
|
|
995
|
+
|
|
996
|
+
**Key Takeaways:**
|
|
997
|
+
|
|
998
|
+
1. **Always catch specific exceptions** - Never use bare `except:`
|
|
999
|
+
2. **Use context managers** - For automatic resource cleanup
|
|
1000
|
+
3. **Create custom exceptions** - With clear hierarchies for domain errors
|
|
1001
|
+
4. **Use contextlib utilities** - `@contextmanager`, `suppress`, `ExitStack`
|
|
1002
|
+
5. **Chain exceptions** - Preserve error context with `from`
|
|
1003
|
+
6. **Log exceptions properly** - Use `logger.exception()` for tracebacks
|
|
1004
|
+
7. **Document exceptions** - In docstrings with `Raises:` section
|
|
1005
|
+
8. **Don't use exceptions for flow control** - Use appropriate methods instead
|
|
1006
|
+
9. **Fail fast** - Don't catch exceptions you can't handle
|
|
1007
|
+
10. **Clean up resources** - Use `finally` or context managers
|
|
1008
|
+
|