@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.
Files changed (483) hide show
  1. package/AGENTS.md +265 -232
  2. package/README.md +956 -771
  3. package/augment-extensions/coding-standards/bash/README.md +196 -196
  4. package/augment-extensions/coding-standards/bash/module.json +163 -163
  5. package/augment-extensions/coding-standards/bash/rules/naming-conventions.md +336 -336
  6. package/augment-extensions/coding-standards/bash/rules/universal-standards.md +289 -289
  7. package/augment-extensions/coding-standards/css/README.md +40 -40
  8. package/augment-extensions/coding-standards/css/examples/css-examples.css +550 -550
  9. package/augment-extensions/coding-standards/css/module.json +44 -44
  10. package/augment-extensions/coding-standards/css/rules/css-modern-features.md +448 -448
  11. package/augment-extensions/coding-standards/css/rules/css-standards.md +492 -492
  12. package/augment-extensions/coding-standards/html/README.md +40 -40
  13. package/augment-extensions/coding-standards/html/examples/html-examples.html +267 -267
  14. package/augment-extensions/coding-standards/html/examples/responsive-layout.html +505 -505
  15. package/augment-extensions/coding-standards/html/module.json +44 -44
  16. package/augment-extensions/coding-standards/html/rules/html-standards.md +349 -349
  17. package/augment-extensions/coding-standards/html-css-js/README.md +194 -194
  18. package/augment-extensions/coding-standards/html-css-js/examples/async-examples.js +487 -487
  19. package/augment-extensions/coding-standards/html-css-js/examples/css-examples.css +550 -550
  20. package/augment-extensions/coding-standards/html-css-js/examples/dom-examples.js +667 -667
  21. package/augment-extensions/coding-standards/html-css-js/examples/html-examples.html +267 -267
  22. package/augment-extensions/coding-standards/html-css-js/examples/javascript-examples.js +612 -612
  23. package/augment-extensions/coding-standards/html-css-js/examples/responsive-layout.html +505 -505
  24. package/augment-extensions/coding-standards/html-css-js/module.json +48 -48
  25. package/augment-extensions/coding-standards/html-css-js/rules/async-patterns.md +515 -515
  26. package/augment-extensions/coding-standards/html-css-js/rules/css-modern-features.md +448 -448
  27. package/augment-extensions/coding-standards/html-css-js/rules/css-standards.md +492 -492
  28. package/augment-extensions/coding-standards/html-css-js/rules/dom-manipulation.md +439 -439
  29. package/augment-extensions/coding-standards/html-css-js/rules/html-standards.md +349 -349
  30. package/augment-extensions/coding-standards/html-css-js/rules/javascript-standards.md +486 -486
  31. package/augment-extensions/coding-standards/html-css-js/rules/performance.md +463 -463
  32. package/augment-extensions/coding-standards/html-css-js/rules/tooling.md +543 -543
  33. package/augment-extensions/coding-standards/js/README.md +46 -46
  34. package/augment-extensions/coding-standards/js/examples/async-examples.js +487 -487
  35. package/augment-extensions/coding-standards/js/examples/dom-examples.js +667 -667
  36. package/augment-extensions/coding-standards/js/examples/javascript-examples.js +612 -612
  37. package/augment-extensions/coding-standards/js/module.json +49 -49
  38. package/augment-extensions/coding-standards/js/rules/async-patterns.md +515 -515
  39. package/augment-extensions/coding-standards/js/rules/dom-manipulation.md +439 -439
  40. package/augment-extensions/coding-standards/js/rules/javascript-standards.md +486 -486
  41. package/augment-extensions/coding-standards/js/rules/performance.md +463 -463
  42. package/augment-extensions/coding-standards/js/rules/tooling.md +543 -543
  43. package/augment-extensions/coding-standards/php/README.md +248 -248
  44. package/augment-extensions/coding-standards/php/examples/api-endpoint-example.php +204 -204
  45. package/augment-extensions/coding-standards/php/examples/cli-command-example.php +206 -206
  46. package/augment-extensions/coding-standards/php/examples/legacy-refactoring-example.php +234 -234
  47. package/augment-extensions/coding-standards/php/examples/web-application-example.php +211 -211
  48. package/augment-extensions/coding-standards/php/examples/woocommerce-extension-example.php +215 -215
  49. package/augment-extensions/coding-standards/php/examples/wordpress-plugin-example.php +189 -189
  50. package/augment-extensions/coding-standards/php/module.json +166 -166
  51. package/augment-extensions/coding-standards/php/rules/api-development.md +480 -480
  52. package/augment-extensions/coding-standards/php/rules/category-configuration.md +332 -332
  53. package/augment-extensions/coding-standards/php/rules/cli-tools.md +472 -472
  54. package/augment-extensions/coding-standards/php/rules/cms-integration.md +561 -561
  55. package/augment-extensions/coding-standards/php/rules/code-quality.md +402 -402
  56. package/augment-extensions/coding-standards/php/rules/documentation.md +425 -425
  57. package/augment-extensions/coding-standards/php/rules/ecommerce.md +627 -627
  58. package/augment-extensions/coding-standards/php/rules/error-handling.md +336 -336
  59. package/augment-extensions/coding-standards/php/rules/legacy-migration.md +677 -677
  60. package/augment-extensions/coding-standards/php/rules/naming-conventions.md +279 -279
  61. package/augment-extensions/coding-standards/php/rules/performance.md +392 -392
  62. package/augment-extensions/coding-standards/php/rules/psr-standards.md +186 -186
  63. package/augment-extensions/coding-standards/php/rules/security.md +358 -358
  64. package/augment-extensions/coding-standards/php/rules/testing.md +403 -403
  65. package/augment-extensions/coding-standards/php/rules/type-declarations.md +331 -331
  66. package/augment-extensions/coding-standards/php/rules/web-applications.md +426 -426
  67. package/augment-extensions/coding-standards/powershell/README.md +154 -154
  68. package/augment-extensions/coding-standards/powershell/examples/admin-example.ps1 +272 -272
  69. package/augment-extensions/coding-standards/powershell/examples/automation-example.ps1 +173 -173
  70. package/augment-extensions/coding-standards/powershell/examples/cloud-example.ps1 +243 -243
  71. package/augment-extensions/coding-standards/powershell/examples/cross-platform-example.ps1 +297 -297
  72. package/augment-extensions/coding-standards/powershell/examples/dsc-example.ps1 +224 -224
  73. package/augment-extensions/coding-standards/powershell/examples/legacy-migration-example.ps1 +340 -340
  74. package/augment-extensions/coding-standards/powershell/examples/module-example.psm1 +255 -255
  75. package/augment-extensions/coding-standards/powershell/module.json +165 -165
  76. package/augment-extensions/coding-standards/powershell/rules/administrative-tools.md +439 -439
  77. package/augment-extensions/coding-standards/powershell/rules/automation-scripts.md +240 -240
  78. package/augment-extensions/coding-standards/powershell/rules/cloud-orchestration.md +384 -384
  79. package/augment-extensions/coding-standards/powershell/rules/configuration-schema.md +383 -383
  80. package/augment-extensions/coding-standards/powershell/rules/cross-platform-scripts.md +482 -482
  81. package/augment-extensions/coding-standards/powershell/rules/dsc-configurations.md +296 -296
  82. package/augment-extensions/coding-standards/powershell/rules/error-handling.md +314 -314
  83. package/augment-extensions/coding-standards/powershell/rules/legacy-migrations.md +466 -466
  84. package/augment-extensions/coding-standards/powershell/rules/modules-functions.md +244 -244
  85. package/augment-extensions/coding-standards/powershell/rules/naming-conventions.md +266 -266
  86. package/augment-extensions/coding-standards/powershell/rules/performance-optimization.md +209 -209
  87. package/augment-extensions/coding-standards/powershell/rules/security-practices.md +314 -314
  88. package/augment-extensions/coding-standards/powershell/rules/testing-guidelines.md +268 -268
  89. package/augment-extensions/coding-standards/powershell/rules/universal-standards.md +197 -197
  90. package/augment-extensions/coding-standards/python/README.md +48 -48
  91. package/augment-extensions/coding-standards/python/examples/best-practices.py +373 -373
  92. package/augment-extensions/coding-standards/python/module.json +30 -30
  93. package/augment-extensions/coding-standards/python/rules/async-patterns.md +884 -884
  94. package/augment-extensions/coding-standards/python/rules/best-practices.md +232 -232
  95. package/augment-extensions/coding-standards/python/rules/code-organization.md +220 -220
  96. package/augment-extensions/coding-standards/python/rules/documentation.md +831 -831
  97. package/augment-extensions/coding-standards/python/rules/error-handling.md +1008 -1008
  98. package/augment-extensions/coding-standards/python/rules/naming-conventions.md +172 -172
  99. package/augment-extensions/coding-standards/python/rules/testing.md +409 -409
  100. package/augment-extensions/coding-standards/python/rules/tooling.md +446 -446
  101. package/augment-extensions/coding-standards/python/rules/type-hints.md +253 -253
  102. package/augment-extensions/coding-standards/react/README.md +45 -45
  103. package/augment-extensions/coding-standards/react/module.json +27 -27
  104. package/augment-extensions/coding-standards/react/rules/component-patterns.md +214 -214
  105. package/augment-extensions/coding-standards/react/rules/hooks-best-practices.md +235 -235
  106. package/augment-extensions/coding-standards/react/rules/performance.md +300 -300
  107. package/augment-extensions/coding-standards/react/rules/state-management.md +265 -265
  108. package/augment-extensions/coding-standards/react/rules/typescript-react.md +271 -271
  109. package/augment-extensions/coding-standards/typescript/README.md +45 -45
  110. package/augment-extensions/coding-standards/typescript/module.json +27 -27
  111. package/augment-extensions/coding-standards/typescript/rules/naming-conventions.md +225 -225
  112. package/augment-extensions/collections/html-css-js/README.md +82 -82
  113. package/augment-extensions/collections/html-css-js/collection.json +41 -41
  114. package/augment-extensions/domain-rules/api-design/README.md +41 -41
  115. package/augment-extensions/domain-rules/api-design/module.json +27 -27
  116. package/augment-extensions/domain-rules/api-design/rules/authentication.md +263 -263
  117. package/augment-extensions/domain-rules/api-design/rules/documentation.md +395 -395
  118. package/augment-extensions/domain-rules/api-design/rules/error-handling.md +290 -290
  119. package/augment-extensions/domain-rules/api-design/rules/graphql-api.md +313 -313
  120. package/augment-extensions/domain-rules/api-design/rules/rest-api.md +214 -214
  121. package/augment-extensions/domain-rules/api-design/rules/versioning.md +268 -268
  122. package/augment-extensions/domain-rules/database/README.md +161 -161
  123. package/augment-extensions/domain-rules/database/examples/flat-database-example.md +793 -793
  124. package/augment-extensions/domain-rules/database/examples/hybrid-database-example.md +1132 -1132
  125. package/augment-extensions/domain-rules/database/examples/nosql-document-example.md +868 -868
  126. package/augment-extensions/domain-rules/database/examples/nosql-graph-example.md +805 -805
  127. package/augment-extensions/domain-rules/database/examples/relational-schema-example.md +621 -621
  128. package/augment-extensions/domain-rules/database/examples/vector-database-example.md +965 -965
  129. package/augment-extensions/domain-rules/database/module.json +28 -28
  130. package/augment-extensions/domain-rules/database/rules/flat-databases.md +624 -624
  131. package/augment-extensions/domain-rules/database/rules/nosql-databases.md +588 -588
  132. package/augment-extensions/domain-rules/database/rules/nosql-document-stores.md +856 -856
  133. package/augment-extensions/domain-rules/database/rules/nosql-graph-databases.md +778 -778
  134. package/augment-extensions/domain-rules/database/rules/nosql-key-value-stores.md +963 -963
  135. package/augment-extensions/domain-rules/database/rules/performance-optimization.md +1076 -1076
  136. package/augment-extensions/domain-rules/database/rules/relational-databases.md +697 -697
  137. package/augment-extensions/domain-rules/database/rules/relational-indexing.md +671 -671
  138. package/augment-extensions/domain-rules/database/rules/relational-query-optimization.md +607 -607
  139. package/augment-extensions/domain-rules/database/rules/relational-schema-design.md +907 -907
  140. package/augment-extensions/domain-rules/database/rules/relational-transactions.md +783 -783
  141. package/augment-extensions/domain-rules/database/rules/security-standards.md +980 -980
  142. package/augment-extensions/domain-rules/database/rules/universal-best-practices.md +485 -485
  143. package/augment-extensions/domain-rules/database/rules/vector-databases.md +521 -521
  144. package/augment-extensions/domain-rules/database/rules/vector-embeddings.md +858 -858
  145. package/augment-extensions/domain-rules/database/rules/vector-indexing.md +934 -934
  146. package/augment-extensions/domain-rules/design/color/themes/catppuccin-latte/README.md +23 -23
  147. package/augment-extensions/domain-rules/design/color/themes/catppuccin-latte/module.json +26 -26
  148. package/augment-extensions/domain-rules/design/color/themes/catppuccin-mocha/README.md +23 -23
  149. package/augment-extensions/domain-rules/design/color/themes/catppuccin-mocha/module.json +26 -26
  150. package/augment-extensions/domain-rules/design/color/themes/dracula/README.md +23 -23
  151. package/augment-extensions/domain-rules/design/color/themes/dracula/module.json +26 -26
  152. package/augment-extensions/domain-rules/design/color/themes/gruvbox-dark/README.md +23 -23
  153. package/augment-extensions/domain-rules/design/color/themes/gruvbox-dark/module.json +26 -26
  154. package/augment-extensions/domain-rules/design/color/themes/gruvbox-light/README.md +23 -23
  155. package/augment-extensions/domain-rules/design/color/themes/gruvbox-light/module.json +26 -26
  156. package/augment-extensions/domain-rules/design/color/themes/high-contrast/README.md +27 -27
  157. package/augment-extensions/domain-rules/design/color/themes/high-contrast/module.json +26 -26
  158. package/augment-extensions/domain-rules/design/color/themes/monokai/README.md +23 -23
  159. package/augment-extensions/domain-rules/design/color/themes/monokai/module.json +26 -26
  160. package/augment-extensions/domain-rules/design/color/themes/nord/README.md +23 -23
  161. package/augment-extensions/domain-rules/design/color/themes/nord/module.json +26 -26
  162. package/augment-extensions/domain-rules/design/color/themes/one-dark/README.md +23 -23
  163. package/augment-extensions/domain-rules/design/color/themes/one-dark/module.json +26 -26
  164. package/augment-extensions/domain-rules/design/color/themes/one-light/README.md +23 -23
  165. package/augment-extensions/domain-rules/design/color/themes/one-light/module.json +26 -26
  166. package/augment-extensions/domain-rules/design/color/themes/solarized-dark/README.md +23 -23
  167. package/augment-extensions/domain-rules/design/color/themes/solarized-dark/module.json +26 -26
  168. package/augment-extensions/domain-rules/design/color/themes/solarized-light/README.md +23 -23
  169. package/augment-extensions/domain-rules/design/color/themes/solarized-light/module.json +26 -26
  170. package/augment-extensions/domain-rules/design/color/themes/tokyo-night/README.md +23 -23
  171. package/augment-extensions/domain-rules/design/color/themes/tokyo-night/module.json +26 -26
  172. package/augment-extensions/domain-rules/mcp/README.md +150 -150
  173. package/augment-extensions/domain-rules/mcp/examples/compressed-example.md +522 -522
  174. package/augment-extensions/domain-rules/mcp/examples/graph-augmented-example.md +520 -520
  175. package/augment-extensions/domain-rules/mcp/examples/hybrid-example.md +570 -570
  176. package/augment-extensions/domain-rules/mcp/examples/state-based-example.md +427 -427
  177. package/augment-extensions/domain-rules/mcp/examples/token-based-example.md +435 -435
  178. package/augment-extensions/domain-rules/mcp/examples/vector-based-example.md +502 -502
  179. package/augment-extensions/domain-rules/mcp/module.json +49 -49
  180. package/augment-extensions/domain-rules/mcp/rules/compressed-mcp.md +595 -595
  181. package/augment-extensions/domain-rules/mcp/rules/configuration.md +345 -345
  182. package/augment-extensions/domain-rules/mcp/rules/graph-augmented-mcp.md +687 -687
  183. package/augment-extensions/domain-rules/mcp/rules/hybrid-mcp.md +636 -636
  184. package/augment-extensions/domain-rules/mcp/rules/state-based-mcp.md +484 -484
  185. package/augment-extensions/domain-rules/mcp/rules/testing-validation.md +360 -360
  186. package/augment-extensions/domain-rules/mcp/rules/token-based-mcp.md +393 -393
  187. package/augment-extensions/domain-rules/mcp/rules/universal-rules.md +194 -194
  188. package/augment-extensions/domain-rules/mcp/rules/vector-based-mcp.md +625 -625
  189. package/augment-extensions/domain-rules/security/README.md +41 -41
  190. package/augment-extensions/domain-rules/security/module.json +28 -28
  191. package/augment-extensions/domain-rules/security/rules/authentication-security.md +361 -361
  192. package/augment-extensions/domain-rules/security/rules/encryption.md +208 -208
  193. package/augment-extensions/domain-rules/security/rules/input-validation.md +294 -294
  194. package/augment-extensions/domain-rules/security/rules/owasp-top-10.md +339 -339
  195. package/augment-extensions/domain-rules/security/rules/secure-coding.md +293 -293
  196. package/augment-extensions/domain-rules/security/rules/web-security.md +268 -268
  197. package/augment-extensions/domain-rules/seo-sales-marketing/ANNOUNCEMENT.md +143 -0
  198. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/README.md +140 -136
  199. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/SCHEMA-VALIDATION-REPORT.md +216 -216
  200. package/augment-extensions/domain-rules/seo-sales-marketing/TEST-VALIDATION.md +129 -0
  201. package/augment-extensions/domain-rules/seo-sales-marketing/USAGE-GUIDES.md +254 -0
  202. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/brand-kit-example.yaml +292 -292
  203. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/campaign-brief-example.yaml +389 -389
  204. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/content-calendar-example.yaml +643 -643
  205. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/email-newsletter-example.md +376 -376
  206. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/landing-page-example.md +934 -934
  207. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/ppc-ad-copy-example.md +301 -301
  208. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/seo-blog-post-example.md +347 -347
  209. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/examples/social-media-campaign-example.md +606 -606
  210. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/module.json +50 -50
  211. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/affiliate-influencer-marketing.md +593 -593
  212. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/asset-management.md +418 -418
  213. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/brand-consistency.md +210 -210
  214. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/content-marketing.md +337 -337
  215. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/conversion-optimization.md +455 -455
  216. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/direct-sales.md +499 -499
  217. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/email-marketing.md +439 -439
  218. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/legal-compliance.md +227 -227
  219. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/ppc-advertising.md +569 -569
  220. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/seo-optimization.md +470 -470
  221. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/social-media-marketing.md +414 -414
  222. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/rules/universal-marketing.md +177 -177
  223. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/asset-inventory.schema.json +247 -247
  224. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/brand-kit.schema.json +326 -326
  225. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/campaign-brief.schema.json +342 -342
  226. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/color-palette.schema.json +223 -223
  227. package/augment-extensions/domain-rules/{marketing-standards/seo-sales-marketing → seo-sales-marketing}/schemas/content-template.schema.json +383 -383
  228. package/augment-extensions/domain-rules/wordpress/README.md +163 -163
  229. package/augment-extensions/domain-rules/wordpress/module.json +32 -32
  230. package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -617
  231. package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -270
  232. package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -423
  233. package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -493
  234. package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -568
  235. package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -510
  236. package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -251
  237. package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -501
  238. package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -564
  239. package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -388
  240. package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -441
  241. package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -139
  242. package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -1599
  243. package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -1727
  244. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -428
  245. package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -422
  246. package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -1623
  247. package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -1343
  248. package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -734
  249. package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -1350
  250. package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -503
  251. package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -971
  252. package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -53
  253. package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -770
  254. package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -874
  255. package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -629
  256. package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -559
  257. package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -709
  258. package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -736
  259. package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -1057
  260. package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -463
  261. package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -478
  262. package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -818
  263. package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -416
  264. package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -667
  265. package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -878
  266. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -693
  267. package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -352
  268. package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -818
  269. package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -624
  270. package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -866
  271. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -1165
  272. package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -414
  273. package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -751
  274. package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -949
  275. package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -458
  276. package/augment-extensions/examples/design-patterns/README.md +37 -37
  277. package/augment-extensions/examples/design-patterns/examples/behavioral-patterns.md +370 -370
  278. package/augment-extensions/examples/design-patterns/examples/creational-patterns.md +250 -250
  279. package/augment-extensions/examples/design-patterns/examples/structural-patterns.md +264 -264
  280. package/augment-extensions/examples/design-patterns/module.json +27 -27
  281. package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -101
  282. package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -428
  283. package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -40
  284. package/augment-extensions/examples/rest-api-plugin/README.md +98 -98
  285. package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -1299
  286. package/augment-extensions/examples/rest-api-plugin/module.json +40 -40
  287. package/augment-extensions/examples/woocommerce-extension/README.md +98 -98
  288. package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -763
  289. package/augment-extensions/examples/woocommerce-extension/module.json +40 -40
  290. package/augment-extensions/workflows/beads/README.md +135 -135
  291. package/augment-extensions/workflows/beads/examples/complete-workflow-example.md +278 -278
  292. package/augment-extensions/workflows/beads/module.json +55 -55
  293. package/augment-extensions/workflows/beads/rules/best-practices.md +398 -398
  294. package/augment-extensions/workflows/beads/rules/file-format.md +327 -327
  295. package/augment-extensions/workflows/beads/rules/manual-setup.md +315 -315
  296. package/augment-extensions/workflows/beads/rules/workflow.md +326 -326
  297. package/augment-extensions/workflows/beads-integration/IMPLEMENTATION-STATUS.md +145 -145
  298. package/augment-extensions/workflows/beads-integration/README.md +143 -143
  299. package/augment-extensions/workflows/beads-integration/config/defaults.json +32 -32
  300. package/augment-extensions/workflows/beads-integration/config/schema.json +140 -140
  301. package/augment-extensions/workflows/beads-integration/examples/basic-task-generation.md +293 -293
  302. package/augment-extensions/workflows/beads-integration/module.json +75 -75
  303. package/augment-extensions/workflows/beads-integration/rules/core-rules.md +219 -219
  304. package/augment-extensions/workflows/beads-integration/rules/effectiveness-standards.md +256 -256
  305. package/augment-extensions/workflows/beads-integration/rules/task-generation.md +607 -607
  306. package/augment-extensions/workflows/database/README.md +195 -195
  307. package/augment-extensions/workflows/database/ai-prompt-testing.md +295 -295
  308. package/augment-extensions/workflows/database/examples/migration-example.md +498 -498
  309. package/augment-extensions/workflows/database/examples/optimization-example.md +496 -496
  310. package/augment-extensions/workflows/database/examples/schema-design-example.md +444 -444
  311. package/augment-extensions/workflows/database/module.json +42 -42
  312. package/augment-extensions/workflows/database/rules/data-migration.md +249 -249
  313. package/augment-extensions/workflows/database/rules/documentation-standards.md +339 -339
  314. package/augment-extensions/workflows/database/rules/migration-workflow.md +352 -352
  315. package/augment-extensions/workflows/database/rules/optimization-workflow.md +435 -435
  316. package/augment-extensions/workflows/database/rules/schema-design-workflow.md +535 -535
  317. package/augment-extensions/workflows/database/rules/testing-patterns.md +305 -305
  318. package/augment-extensions/workflows/database/rules/workflow.md +458 -458
  319. package/augment-extensions/workflows/wordpress-plugin/README.md +232 -232
  320. package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -839
  321. package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -854
  322. package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -540
  323. package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -1083
  324. package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -669
  325. package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -597
  326. package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -925
  327. package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -752
  328. package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -773
  329. package/augment-extensions/workflows/wordpress-plugin/module.json +49 -49
  330. package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -942
  331. package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -702
  332. package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -728
  333. package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -775
  334. package/augment-extensions/writing-standards/screenplay/README.md +339 -300
  335. package/augment-extensions/writing-standards/screenplay/_templates/README.md +121 -121
  336. package/augment-extensions/writing-standards/screenplay/_templates/genre-template.md +153 -153
  337. package/augment-extensions/writing-standards/screenplay/_templates/style-template.md +243 -243
  338. package/augment-extensions/writing-standards/screenplay/_templates/theme-template.md +213 -213
  339. package/augment-extensions/writing-standards/screenplay/examples/aaa-hollywood-scene.fountain +164 -164
  340. package/augment-extensions/writing-standards/screenplay/examples/beat-sheet-example.yaml +95 -95
  341. package/augment-extensions/writing-standards/screenplay/examples/character-profile-example.yaml +116 -116
  342. package/augment-extensions/writing-standards/screenplay/examples/commercial-30sec.fountain +151 -151
  343. package/augment-extensions/writing-standards/screenplay/examples/independent-monologue.fountain +67 -67
  344. package/augment-extensions/writing-standards/screenplay/examples/news-segment.fountain +142 -142
  345. package/augment-extensions/writing-standards/screenplay/examples/plot-outline-example.yaml +184 -184
  346. package/augment-extensions/writing-standards/screenplay/examples/tv-episode-teaser.fountain +204 -204
  347. package/augment-extensions/writing-standards/screenplay/genres/README.md +181 -181
  348. package/augment-extensions/writing-standards/screenplay/genres/examples/.gitkeep +2 -2
  349. package/augment-extensions/writing-standards/screenplay/genres/module.json +70 -70
  350. package/augment-extensions/writing-standards/screenplay/genres/rules/.gitkeep +2 -2
  351. package/augment-extensions/writing-standards/screenplay/genres/rules/action.md +399 -399
  352. package/augment-extensions/writing-standards/screenplay/genres/rules/adventure.md +407 -407
  353. package/augment-extensions/writing-standards/screenplay/genres/rules/animation.md +293 -293
  354. package/augment-extensions/writing-standards/screenplay/genres/rules/biographical.md +293 -293
  355. package/augment-extensions/writing-standards/screenplay/genres/rules/comedy.md +401 -401
  356. package/augment-extensions/writing-standards/screenplay/genres/rules/documentary.md +293 -293
  357. package/augment-extensions/writing-standards/screenplay/genres/rules/drama.md +409 -409
  358. package/augment-extensions/writing-standards/screenplay/genres/rules/fantasy.md +293 -293
  359. package/augment-extensions/writing-standards/screenplay/genres/rules/historical.md +293 -293
  360. package/augment-extensions/writing-standards/screenplay/genres/rules/horror.md +268 -268
  361. package/augment-extensions/writing-standards/screenplay/genres/rules/musical.md +294 -294
  362. package/augment-extensions/writing-standards/screenplay/genres/rules/mystery.md +293 -293
  363. package/augment-extensions/writing-standards/screenplay/genres/rules/noir.md +294 -294
  364. package/augment-extensions/writing-standards/screenplay/genres/rules/romance.md +293 -293
  365. package/augment-extensions/writing-standards/screenplay/genres/rules/sci-fi.md +289 -289
  366. package/augment-extensions/writing-standards/screenplay/genres/rules/superhero.md +293 -293
  367. package/augment-extensions/writing-standards/screenplay/genres/rules/thriller.md +294 -294
  368. package/augment-extensions/writing-standards/screenplay/genres/rules/western.md +293 -293
  369. package/augment-extensions/writing-standards/screenplay/module.json +124 -124
  370. package/augment-extensions/writing-standards/screenplay/rules/aaa-hollywood-films.md +339 -339
  371. package/augment-extensions/writing-standards/screenplay/rules/ai-integration-testing.md +329 -329
  372. package/augment-extensions/writing-standards/screenplay/rules/character-development.md +169 -169
  373. package/augment-extensions/writing-standards/screenplay/rules/commercials.md +437 -437
  374. package/augment-extensions/writing-standards/screenplay/rules/dialogue-writing.md +263 -263
  375. package/augment-extensions/writing-standards/screenplay/rules/diversity-inclusion.md +261 -261
  376. package/augment-extensions/writing-standards/screenplay/rules/examples-guide.md +315 -315
  377. package/augment-extensions/writing-standards/screenplay/rules/file-organization.md +213 -0
  378. package/augment-extensions/writing-standards/screenplay/rules/formatting-validation.md +413 -413
  379. package/augment-extensions/writing-standards/screenplay/rules/fountain-format.md +372 -372
  380. package/augment-extensions/writing-standards/screenplay/rules/independent-films.md +374 -374
  381. package/augment-extensions/writing-standards/screenplay/rules/live-tv-productions.md +443 -443
  382. package/augment-extensions/writing-standards/screenplay/rules/narrative-structures.md +207 -207
  383. package/augment-extensions/writing-standards/screenplay/rules/news-broadcasts.md +444 -444
  384. package/augment-extensions/writing-standards/screenplay/rules/pacing-timing.md +331 -331
  385. package/augment-extensions/writing-standards/screenplay/rules/quality-review-checklist.md +334 -334
  386. package/augment-extensions/writing-standards/screenplay/rules/quick-reference.md +299 -299
  387. package/augment-extensions/writing-standards/screenplay/rules/screen-continuity.md +263 -263
  388. package/augment-extensions/writing-standards/screenplay/rules/streaming-content.md +412 -412
  389. package/augment-extensions/writing-standards/screenplay/rules/trope-management.md +370 -370
  390. package/augment-extensions/writing-standards/screenplay/rules/tv-series.md +374 -374
  391. package/augment-extensions/writing-standards/screenplay/rules/universal-formatting.md +339 -339
  392. package/augment-extensions/writing-standards/screenplay/rules/vscode-integration.md +277 -277
  393. package/augment-extensions/writing-standards/screenplay/rules/web-content.md +393 -393
  394. package/augment-extensions/writing-standards/screenplay/schemas/beat-sheet.json +332 -332
  395. package/augment-extensions/writing-standards/screenplay/schemas/character-profile.json +247 -247
  396. package/augment-extensions/writing-standards/screenplay/schemas/feature-selection.json +200 -200
  397. package/augment-extensions/writing-standards/screenplay/schemas/plot-outline.json +233 -233
  398. package/augment-extensions/writing-standards/screenplay/schemas/screenplay-config.json +245 -245
  399. package/augment-extensions/writing-standards/screenplay/schemas/trope-inventory.json +221 -221
  400. package/augment-extensions/writing-standards/screenplay/styles/README.md +159 -159
  401. package/augment-extensions/writing-standards/screenplay/styles/examples/.gitkeep +2 -2
  402. package/augment-extensions/writing-standards/screenplay/styles/examples/style-applications.md +1449 -1449
  403. package/augment-extensions/writing-standards/screenplay/styles/module.json +64 -64
  404. package/augment-extensions/writing-standards/screenplay/styles/rules/.gitkeep +2 -2
  405. package/augment-extensions/writing-standards/screenplay/styles/rules/dialogue-centric.md +520 -520
  406. package/augment-extensions/writing-standards/screenplay/styles/rules/ensemble.md +499 -499
  407. package/augment-extensions/writing-standards/screenplay/styles/rules/epic.md +497 -497
  408. package/augment-extensions/writing-standards/screenplay/styles/rules/experimental.md +492 -492
  409. package/augment-extensions/writing-standards/screenplay/styles/rules/flashback.md +509 -509
  410. package/augment-extensions/writing-standards/screenplay/styles/rules/linear.md +490 -490
  411. package/augment-extensions/writing-standards/screenplay/styles/rules/minimalist.md +499 -499
  412. package/augment-extensions/writing-standards/screenplay/styles/rules/non-linear.md +501 -501
  413. package/augment-extensions/writing-standards/screenplay/styles/rules/poetic.md +499 -499
  414. package/augment-extensions/writing-standards/screenplay/styles/rules/realistic.md +498 -498
  415. package/augment-extensions/writing-standards/screenplay/styles/rules/satirical.md +499 -499
  416. package/augment-extensions/writing-standards/screenplay/styles/rules/surreal.md +508 -508
  417. package/augment-extensions/writing-standards/screenplay/styles/rules/voice-over.md +500 -500
  418. package/augment-extensions/writing-standards/screenplay/themes/README.md +158 -158
  419. package/augment-extensions/writing-standards/screenplay/themes/examples/.gitkeep +2 -2
  420. package/augment-extensions/writing-standards/screenplay/themes/examples/common-mistakes-and-fixes.md +643 -643
  421. package/augment-extensions/writing-standards/screenplay/themes/examples/complete-scene-example.md +311 -311
  422. package/augment-extensions/writing-standards/screenplay/themes/examples/individual-theme-examples.md +562 -562
  423. package/augment-extensions/writing-standards/screenplay/themes/examples/multi-theme-weaving.md +538 -538
  424. package/augment-extensions/writing-standards/screenplay/themes/examples/theme-application-guide.md +432 -432
  425. package/augment-extensions/writing-standards/screenplay/themes/examples/theme-integration-across-acts.md +637 -637
  426. package/augment-extensions/writing-standards/screenplay/themes/module.json +66 -66
  427. package/augment-extensions/writing-standards/screenplay/themes/rules/.gitkeep +2 -2
  428. package/augment-extensions/writing-standards/screenplay/themes/rules/ambition.md +458 -458
  429. package/augment-extensions/writing-standards/screenplay/themes/rules/betrayal.md +490 -490
  430. package/augment-extensions/writing-standards/screenplay/themes/rules/environment.md +458 -458
  431. package/augment-extensions/writing-standards/screenplay/themes/rules/fate.md +459 -459
  432. package/augment-extensions/writing-standards/screenplay/themes/rules/friendship.md +491 -491
  433. package/augment-extensions/writing-standards/screenplay/themes/rules/growth.md +491 -491
  434. package/augment-extensions/writing-standards/screenplay/themes/rules/identity.md +490 -490
  435. package/augment-extensions/writing-standards/screenplay/themes/rules/isolation.md +464 -464
  436. package/augment-extensions/writing-standards/screenplay/themes/rules/justice.md +461 -461
  437. package/augment-extensions/writing-standards/screenplay/themes/rules/love.md +489 -489
  438. package/augment-extensions/writing-standards/screenplay/themes/rules/power.md +494 -494
  439. package/augment-extensions/writing-standards/screenplay/themes/rules/redemption.md +483 -483
  440. package/augment-extensions/writing-standards/screenplay/themes/rules/revenge.md +489 -489
  441. package/augment-extensions/writing-standards/screenplay/themes/rules/survival.md +496 -496
  442. package/augment-extensions/writing-standards/screenplay/themes/rules/technology.md +463 -463
  443. package/augment-extensions/writing-standards/screenplay/utils/__tests__/file-organization.test.ts +169 -0
  444. package/augment-extensions/writing-standards/screenplay/utils/file-organization.ts +165 -0
  445. package/cli/MODULES.md +302 -302
  446. package/cli/dist/cli.js +109 -22
  447. package/cli/dist/cli.js.map +1 -1
  448. package/cli/dist/commands/gui.d.ts.map +1 -1
  449. package/cli/dist/commands/gui.js +54 -6
  450. package/cli/dist/commands/gui.js.map +1 -1
  451. package/cli/dist/commands/init.d.ts.map +1 -1
  452. package/cli/dist/commands/init.js +76 -23
  453. package/cli/dist/commands/init.js.map +1 -1
  454. package/cli/dist/commands/self-remove.d.ts.map +1 -1
  455. package/cli/dist/commands/self-remove.js +48 -74
  456. package/cli/dist/commands/self-remove.js.map +1 -1
  457. package/cli/dist/commands/show.d.ts +11 -0
  458. package/cli/dist/commands/show.d.ts.map +1 -1
  459. package/cli/dist/commands/show.js +120 -0
  460. package/cli/dist/commands/show.js.map +1 -1
  461. package/cli/dist/commands/showCompleted.d.ts +21 -0
  462. package/cli/dist/commands/showCompleted.d.ts.map +1 -0
  463. package/cli/dist/commands/showCompleted.js +225 -0
  464. package/cli/dist/commands/showCompleted.js.map +1 -0
  465. package/cli/dist/commands/skill.js +88 -88
  466. package/cli/dist/commands/update.d.ts +2 -0
  467. package/cli/dist/commands/update.d.ts.map +1 -1
  468. package/cli/dist/commands/update.js +67 -1
  469. package/cli/dist/commands/update.js.map +1 -1
  470. package/cli/dist/utils/beadsCompletedChecker.d.ts +72 -0
  471. package/cli/dist/utils/beadsCompletedChecker.d.ts.map +1 -0
  472. package/cli/dist/utils/beadsCompletedChecker.js +198 -0
  473. package/cli/dist/utils/beadsCompletedChecker.js.map +1 -0
  474. package/cli/dist/utils/catalog-sync.js +13 -13
  475. package/cli/dist/utils/extractCommandHelp.d.ts +51 -0
  476. package/cli/dist/utils/extractCommandHelp.d.ts.map +1 -0
  477. package/cli/dist/utils/extractCommandHelp.js +250 -0
  478. package/cli/dist/utils/extractCommandHelp.js.map +1 -0
  479. package/cli/dist/utils/install-rules.js +55 -55
  480. package/cli/dist/utils/mcp-integration.js +44 -44
  481. package/cli/dist/utils/rule-install-hooks.js +8 -8
  482. package/modules.md +667 -630
  483. 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
+