erk 0.4.5__py3-none-any.whl

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 (331) hide show
  1. erk/__init__.py +12 -0
  2. erk/__main__.py +6 -0
  3. erk/agent_docs/__init__.py +5 -0
  4. erk/agent_docs/models.py +123 -0
  5. erk/agent_docs/operations.py +666 -0
  6. erk/artifacts/__init__.py +5 -0
  7. erk/artifacts/artifact_health.py +623 -0
  8. erk/artifacts/detection.py +16 -0
  9. erk/artifacts/discovery.py +343 -0
  10. erk/artifacts/models.py +63 -0
  11. erk/artifacts/staleness.py +56 -0
  12. erk/artifacts/state.py +100 -0
  13. erk/artifacts/sync.py +624 -0
  14. erk/cli/__init__.py +0 -0
  15. erk/cli/activation.py +132 -0
  16. erk/cli/alias.py +53 -0
  17. erk/cli/cli.py +221 -0
  18. erk/cli/commands/__init__.py +0 -0
  19. erk/cli/commands/admin.py +153 -0
  20. erk/cli/commands/artifact/__init__.py +1 -0
  21. erk/cli/commands/artifact/check.py +260 -0
  22. erk/cli/commands/artifact/group.py +31 -0
  23. erk/cli/commands/artifact/list_cmd.py +89 -0
  24. erk/cli/commands/artifact/show.py +62 -0
  25. erk/cli/commands/artifact/sync_cmd.py +39 -0
  26. erk/cli/commands/branch/__init__.py +26 -0
  27. erk/cli/commands/branch/assign_cmd.py +152 -0
  28. erk/cli/commands/branch/checkout_cmd.py +357 -0
  29. erk/cli/commands/branch/create_cmd.py +161 -0
  30. erk/cli/commands/branch/list_cmd.py +82 -0
  31. erk/cli/commands/branch/unassign_cmd.py +197 -0
  32. erk/cli/commands/cc/__init__.py +15 -0
  33. erk/cli/commands/cc/jsonl_cmd.py +20 -0
  34. erk/cli/commands/cc/session/AGENTS.md +30 -0
  35. erk/cli/commands/cc/session/CLAUDE.md +1 -0
  36. erk/cli/commands/cc/session/__init__.py +15 -0
  37. erk/cli/commands/cc/session/list_cmd.py +167 -0
  38. erk/cli/commands/cc/session/show_cmd.py +175 -0
  39. erk/cli/commands/completion.py +89 -0
  40. erk/cli/commands/completions.py +165 -0
  41. erk/cli/commands/config.py +327 -0
  42. erk/cli/commands/docs/__init__.py +1 -0
  43. erk/cli/commands/docs/group.py +16 -0
  44. erk/cli/commands/docs/sync.py +121 -0
  45. erk/cli/commands/docs/validate.py +102 -0
  46. erk/cli/commands/doctor.py +243 -0
  47. erk/cli/commands/down.py +171 -0
  48. erk/cli/commands/exec/__init__.py +1 -0
  49. erk/cli/commands/exec/group.py +164 -0
  50. erk/cli/commands/exec/scripts/AGENTS.md +79 -0
  51. erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
  52. erk/cli/commands/exec/scripts/__init__.py +5 -0
  53. erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
  54. erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
  55. erk/cli/commands/exec/scripts/check_impl.py +152 -0
  56. erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
  57. erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
  58. erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
  59. erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
  60. erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
  61. erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
  62. erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
  63. erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
  64. erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
  65. erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
  66. erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
  67. erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
  68. erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
  69. erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
  70. erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
  71. erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
  72. erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
  73. erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
  74. erk/cli/commands/exec/scripts/impl_init.py +158 -0
  75. erk/cli/commands/exec/scripts/impl_signal.py +375 -0
  76. erk/cli/commands/exec/scripts/impl_verify.py +49 -0
  77. erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
  78. erk/cli/commands/exec/scripts/list_sessions.py +296 -0
  79. erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
  80. erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
  81. erk/cli/commands/exec/scripts/marker.py +163 -0
  82. erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
  83. erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
  84. erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
  85. erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
  86. erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
  87. erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
  88. erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
  89. erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
  90. erk/cli/commands/exec/scripts/quick_submit.py +32 -0
  91. erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
  92. erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
  93. erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
  94. erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
  95. erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
  96. erk/cli/commands/exec/scripts/slot_objective.py +102 -0
  97. erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
  98. erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
  99. erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
  100. erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
  101. erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
  102. erk/cli/commands/implement.py +695 -0
  103. erk/cli/commands/implement_shared.py +649 -0
  104. erk/cli/commands/info/__init__.py +14 -0
  105. erk/cli/commands/info/release_notes_cmd.py +128 -0
  106. erk/cli/commands/init.py +801 -0
  107. erk/cli/commands/land_cmd.py +690 -0
  108. erk/cli/commands/log_cmd.py +137 -0
  109. erk/cli/commands/md/__init__.py +5 -0
  110. erk/cli/commands/md/check.py +118 -0
  111. erk/cli/commands/md/group.py +14 -0
  112. erk/cli/commands/navigation_helpers.py +430 -0
  113. erk/cli/commands/objective/__init__.py +16 -0
  114. erk/cli/commands/objective/list_cmd.py +47 -0
  115. erk/cli/commands/objective_helpers.py +132 -0
  116. erk/cli/commands/plan/__init__.py +32 -0
  117. erk/cli/commands/plan/check_cmd.py +174 -0
  118. erk/cli/commands/plan/close_cmd.py +69 -0
  119. erk/cli/commands/plan/create_cmd.py +120 -0
  120. erk/cli/commands/plan/docs/__init__.py +18 -0
  121. erk/cli/commands/plan/docs/extract_cmd.py +53 -0
  122. erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
  123. erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
  124. erk/cli/commands/plan/extraction/__init__.py +16 -0
  125. erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
  126. erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
  127. erk/cli/commands/plan/get.py +71 -0
  128. erk/cli/commands/plan/list_cmd.py +754 -0
  129. erk/cli/commands/plan/log_cmd.py +440 -0
  130. erk/cli/commands/plan/start_cmd.py +459 -0
  131. erk/cli/commands/planner/__init__.py +40 -0
  132. erk/cli/commands/planner/configure_cmd.py +73 -0
  133. erk/cli/commands/planner/connect_cmd.py +96 -0
  134. erk/cli/commands/planner/create_cmd.py +148 -0
  135. erk/cli/commands/planner/list_cmd.py +51 -0
  136. erk/cli/commands/planner/register_cmd.py +105 -0
  137. erk/cli/commands/planner/set_default_cmd.py +23 -0
  138. erk/cli/commands/planner/unregister_cmd.py +43 -0
  139. erk/cli/commands/pr/__init__.py +23 -0
  140. erk/cli/commands/pr/check_cmd.py +112 -0
  141. erk/cli/commands/pr/checkout_cmd.py +165 -0
  142. erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
  143. erk/cli/commands/pr/parse_pr_reference.py +10 -0
  144. erk/cli/commands/pr/submit_cmd.py +360 -0
  145. erk/cli/commands/pr/sync_cmd.py +181 -0
  146. erk/cli/commands/prepare_cwd_recovery.py +60 -0
  147. erk/cli/commands/project/__init__.py +16 -0
  148. erk/cli/commands/project/init_cmd.py +91 -0
  149. erk/cli/commands/run/__init__.py +17 -0
  150. erk/cli/commands/run/list_cmd.py +189 -0
  151. erk/cli/commands/run/logs_cmd.py +54 -0
  152. erk/cli/commands/run/shared.py +19 -0
  153. erk/cli/commands/shell_integration.py +29 -0
  154. erk/cli/commands/slot/__init__.py +23 -0
  155. erk/cli/commands/slot/check_cmd.py +277 -0
  156. erk/cli/commands/slot/common.py +314 -0
  157. erk/cli/commands/slot/init_pool_cmd.py +157 -0
  158. erk/cli/commands/slot/list_cmd.py +228 -0
  159. erk/cli/commands/slot/repair_cmd.py +190 -0
  160. erk/cli/commands/stack/__init__.py +23 -0
  161. erk/cli/commands/stack/consolidate_cmd.py +470 -0
  162. erk/cli/commands/stack/list_cmd.py +79 -0
  163. erk/cli/commands/stack/move_cmd.py +309 -0
  164. erk/cli/commands/stack/split_old/README.md +64 -0
  165. erk/cli/commands/stack/split_old/__init__.py +5 -0
  166. erk/cli/commands/stack/split_old/command.py +233 -0
  167. erk/cli/commands/stack/split_old/display.py +116 -0
  168. erk/cli/commands/stack/split_old/plan.py +216 -0
  169. erk/cli/commands/status.py +58 -0
  170. erk/cli/commands/submit.py +768 -0
  171. erk/cli/commands/up.py +154 -0
  172. erk/cli/commands/upgrade.py +82 -0
  173. erk/cli/commands/wt/__init__.py +29 -0
  174. erk/cli/commands/wt/checkout_cmd.py +110 -0
  175. erk/cli/commands/wt/create_cmd.py +998 -0
  176. erk/cli/commands/wt/current_cmd.py +35 -0
  177. erk/cli/commands/wt/delete_cmd.py +573 -0
  178. erk/cli/commands/wt/list_cmd.py +332 -0
  179. erk/cli/commands/wt/rename_cmd.py +66 -0
  180. erk/cli/config.py +242 -0
  181. erk/cli/constants.py +29 -0
  182. erk/cli/core.py +65 -0
  183. erk/cli/debug.py +9 -0
  184. erk/cli/ensure-conversion-tasks.md +288 -0
  185. erk/cli/ensure.py +628 -0
  186. erk/cli/github_parsing.py +96 -0
  187. erk/cli/graphite.py +81 -0
  188. erk/cli/graphite_command.py +80 -0
  189. erk/cli/help_formatter.py +345 -0
  190. erk/cli/output.py +361 -0
  191. erk/cli/presets/dagster.toml +12 -0
  192. erk/cli/presets/generic.toml +12 -0
  193. erk/cli/prompt_hooks_templates/README.md +68 -0
  194. erk/cli/script_output.py +32 -0
  195. erk/cli/shell_integration/bash_wrapper.sh +32 -0
  196. erk/cli/shell_integration/fish_wrapper.fish +39 -0
  197. erk/cli/shell_integration/handler.py +338 -0
  198. erk/cli/shell_integration/zsh_wrapper.sh +32 -0
  199. erk/cli/shell_utils.py +171 -0
  200. erk/cli/subprocess_utils.py +92 -0
  201. erk/cli/uvx_detection.py +59 -0
  202. erk/core/__init__.py +0 -0
  203. erk/core/claude_executor.py +511 -0
  204. erk/core/claude_settings.py +317 -0
  205. erk/core/command_log.py +406 -0
  206. erk/core/commit_message_generator.py +234 -0
  207. erk/core/completion.py +10 -0
  208. erk/core/consolidation_utils.py +177 -0
  209. erk/core/context.py +570 -0
  210. erk/core/display/__init__.py +4 -0
  211. erk/core/display/abc.py +24 -0
  212. erk/core/display/real.py +30 -0
  213. erk/core/display_utils.py +526 -0
  214. erk/core/file_utils.py +87 -0
  215. erk/core/health_checks.py +1315 -0
  216. erk/core/health_checks_dogfooder/__init__.py +85 -0
  217. erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
  218. erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
  219. erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
  220. erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
  221. erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
  222. erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
  223. erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
  224. erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
  225. erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
  226. erk/core/implementation_queue/__init__.py +1 -0
  227. erk/core/implementation_queue/github/__init__.py +8 -0
  228. erk/core/implementation_queue/github/abc.py +7 -0
  229. erk/core/implementation_queue/github/noop.py +38 -0
  230. erk/core/implementation_queue/github/printing.py +43 -0
  231. erk/core/implementation_queue/github/real.py +119 -0
  232. erk/core/init_utils.py +227 -0
  233. erk/core/output_filter.py +338 -0
  234. erk/core/plan_store/__init__.py +6 -0
  235. erk/core/planner/__init__.py +1 -0
  236. erk/core/planner/registry_abc.py +8 -0
  237. erk/core/planner/registry_fake.py +129 -0
  238. erk/core/planner/registry_real.py +195 -0
  239. erk/core/planner/types.py +7 -0
  240. erk/core/pr_utils.py +30 -0
  241. erk/core/release_notes.py +263 -0
  242. erk/core/repo_discovery.py +126 -0
  243. erk/core/script_writer.py +41 -0
  244. erk/core/services/__init__.py +1 -0
  245. erk/core/services/plan_list_service.py +94 -0
  246. erk/core/shell.py +51 -0
  247. erk/core/user_feedback.py +11 -0
  248. erk/core/version_check.py +55 -0
  249. erk/core/workflow_display.py +75 -0
  250. erk/core/worktree_pool.py +190 -0
  251. erk/core/worktree_utils.py +300 -0
  252. erk/data/CHANGELOG.md +438 -0
  253. erk/data/__init__.py +1 -0
  254. erk/data/claude/agents/devrun.md +180 -0
  255. erk/data/claude/commands/erk/__init__.py +0 -0
  256. erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
  257. erk/data/claude/commands/erk/fix-conflicts.md +25 -0
  258. erk/data/claude/commands/erk/git-pr-push.md +345 -0
  259. erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
  260. erk/data/claude/commands/erk/land.md +193 -0
  261. erk/data/claude/commands/erk/objective-create.md +370 -0
  262. erk/data/claude/commands/erk/objective-list.md +34 -0
  263. erk/data/claude/commands/erk/objective-next-plan.md +220 -0
  264. erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
  265. erk/data/claude/commands/erk/plan-implement.md +202 -0
  266. erk/data/claude/commands/erk/plan-save.md +45 -0
  267. erk/data/claude/commands/erk/plan-submit.md +39 -0
  268. erk/data/claude/commands/erk/pr-address.md +367 -0
  269. erk/data/claude/commands/erk/pr-submit.md +58 -0
  270. erk/data/claude/skills/dignified-python/SKILL.md +48 -0
  271. erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
  272. erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
  273. erk/data/claude/skills/dignified-python/subprocess.md +99 -0
  274. erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
  275. erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
  276. erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
  277. erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
  278. erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
  279. erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
  280. erk/data/claude/skills/learned-docs/SKILL.md +362 -0
  281. erk/data/github/actions/setup-claude-erk/action.yml +11 -0
  282. erk/data/github/prompts/dignified-python-review.md +125 -0
  283. erk/data/github/workflows/dignified-python-review.yml +61 -0
  284. erk/data/github/workflows/erk-impl.yml +251 -0
  285. erk/hooks/__init__.py +1 -0
  286. erk/hooks/decorators.py +319 -0
  287. erk/status/__init__.py +8 -0
  288. erk/status/collectors/__init__.py +9 -0
  289. erk/status/collectors/base.py +52 -0
  290. erk/status/collectors/git.py +76 -0
  291. erk/status/collectors/github.py +81 -0
  292. erk/status/collectors/graphite.py +80 -0
  293. erk/status/collectors/impl.py +145 -0
  294. erk/status/models/__init__.py +4 -0
  295. erk/status/models/status_data.py +404 -0
  296. erk/status/orchestrator.py +169 -0
  297. erk/status/renderers/__init__.py +5 -0
  298. erk/status/renderers/simple.py +322 -0
  299. erk/tui/AGENTS.md +193 -0
  300. erk/tui/CLAUDE.md +1 -0
  301. erk/tui/__init__.py +1 -0
  302. erk/tui/app.py +1404 -0
  303. erk/tui/commands/__init__.py +1 -0
  304. erk/tui/commands/executor.py +66 -0
  305. erk/tui/commands/provider.py +165 -0
  306. erk/tui/commands/real_executor.py +63 -0
  307. erk/tui/commands/registry.py +121 -0
  308. erk/tui/commands/types.py +36 -0
  309. erk/tui/data/__init__.py +1 -0
  310. erk/tui/data/provider.py +492 -0
  311. erk/tui/data/types.py +104 -0
  312. erk/tui/filtering/__init__.py +1 -0
  313. erk/tui/filtering/logic.py +43 -0
  314. erk/tui/filtering/types.py +55 -0
  315. erk/tui/jsonl_viewer/__init__.py +1 -0
  316. erk/tui/jsonl_viewer/app.py +61 -0
  317. erk/tui/jsonl_viewer/models.py +208 -0
  318. erk/tui/jsonl_viewer/widgets.py +204 -0
  319. erk/tui/sorting/__init__.py +6 -0
  320. erk/tui/sorting/logic.py +55 -0
  321. erk/tui/sorting/types.py +68 -0
  322. erk/tui/styles/dash.tcss +95 -0
  323. erk/tui/widgets/__init__.py +1 -0
  324. erk/tui/widgets/command_output.py +112 -0
  325. erk/tui/widgets/plan_table.py +276 -0
  326. erk/tui/widgets/status_bar.py +116 -0
  327. erk-0.4.5.dist-info/METADATA +376 -0
  328. erk-0.4.5.dist-info/RECORD +331 -0
  329. erk-0.4.5.dist-info/WHEEL +4 -0
  330. erk-0.4.5.dist-info/entry_points.txt +2 -0
  331. erk-0.4.5.dist-info/licenses/LICENSE.md +3 -0
@@ -0,0 +1,536 @@
1
+ ---
2
+ ---
3
+
4
+ # Type Annotations - Python 3.11
5
+
6
+ This document provides complete, canonical type annotation guidance for Python 3.11.
7
+
8
+ ## Overview
9
+
10
+ Python 3.11 builds on 3.10's type syntax with the addition of the `Self` type (PEP 673), making method chaining and builder patterns significantly cleaner. All modern syntax from 3.10 continues to work.
11
+
12
+ **What's new in 3.11:**
13
+
14
+ - `Self` type for self-returning methods (PEP 673)
15
+ - Variadic generics with TypeVarTuple (PEP 646)
16
+ - Significantly improved error messages
17
+
18
+ **Available from 3.10:**
19
+
20
+ - Built-in generic types: `list[T]`, `dict[K, V]`, etc. (PEP 585)
21
+ - Union types with `|` operator (PEP 604)
22
+ - Optional with `X | None`
23
+
24
+ **What you need from typing module:**
25
+
26
+ - `Self` for self-returning methods (NEW)
27
+ - `TypeVar` for generic functions/classes
28
+ - `Generic` for generic classes
29
+ - `Protocol` for structural typing (rare - prefer ABC)
30
+ - `TYPE_CHECKING` for conditional imports
31
+ - `Any` (use sparingly)
32
+
33
+ ## Complete Type Annotation Syntax for Python 3.11
34
+
35
+ ### Basic Collection Types
36
+
37
+ ✅ **PREFERRED** - Use built-in generic types:
38
+
39
+ ```python
40
+ names: list[str] = []
41
+ mapping: dict[str, int] = {}
42
+ unique_ids: set[str] = set()
43
+ coordinates: tuple[int, int] = (0, 0)
44
+ ```
45
+
46
+ ❌ **WRONG** - Don't use typing module equivalents:
47
+
48
+ ```python
49
+ from typing import List, Dict, Set, Tuple # Don't do this
50
+ names: List[str] = []
51
+ ```
52
+
53
+ ### Union Types
54
+
55
+ ✅ **PREFERRED** - Use `|` operator:
56
+
57
+ ```python
58
+ def process(value: str | int) -> str:
59
+ return str(value)
60
+
61
+ def find_config(name: str) -> dict[str, str] | dict[str, int]:
62
+ ...
63
+
64
+ # Multiple unions
65
+ def parse(input: str | int | float) -> str:
66
+ return str(input)
67
+ ```
68
+
69
+ ❌ **WRONG** - Don't use `typing.Union`:
70
+
71
+ ```python
72
+ from typing import Union
73
+ def process(value: Union[str, int]) -> str: # Don't do this
74
+ ...
75
+ ```
76
+
77
+ ### Optional Types
78
+
79
+ ✅ **PREFERRED** - Use `X | None`:
80
+
81
+ ```python
82
+ def find_user(id: str) -> User | None:
83
+ """Returns user or None if not found."""
84
+ if id in users:
85
+ return users[id]
86
+ return None
87
+ ```
88
+
89
+ ❌ **WRONG** - Don't use `typing.Optional`:
90
+
91
+ ```python
92
+ from typing import Optional
93
+ def find_user(id: str) -> Optional[User]: # Don't do this
94
+ ...
95
+ ```
96
+
97
+ ### Self Type for Self-Returning Methods (NEW in 3.11)
98
+
99
+ ✅ **PREFERRED** - Use Self for methods that return the instance:
100
+
101
+ ```python
102
+ from typing import Self
103
+
104
+ class Builder:
105
+ def set_name(self, name: str) -> Self:
106
+ self.name = name
107
+ return self
108
+
109
+ def set_value(self, value: int) -> Self:
110
+ self.value = value
111
+ return self
112
+
113
+ # Usage with type safety
114
+ builder = Builder().set_name("app").set_value(42)
115
+ ```
116
+
117
+ ❌ **WRONG** - Don't use bound TypeVar anymore:
118
+
119
+ ```python
120
+ from typing import TypeVar
121
+
122
+ T = TypeVar("T", bound="Builder")
123
+
124
+ class Builder:
125
+ def set_name(self: T, name: str) -> T: # Don't do this
126
+ ...
127
+ ```
128
+
129
+ **When to use Self:**
130
+
131
+ - Methods that return `self`
132
+ - Builder pattern methods
133
+ - Fluent interfaces with method chaining
134
+ - Factory classmethods
135
+
136
+ **Self in classmethod:**
137
+
138
+ ```python
139
+ from typing import Self
140
+
141
+ class Config:
142
+ def __init__(self, data: dict[str, str]) -> None:
143
+ self.data = data
144
+
145
+ @classmethod
146
+ def from_file(cls, path: str) -> Self:
147
+ """Load config from file."""
148
+ import json
149
+ with open(path, encoding="utf-8") as f:
150
+ data = json.load(f)
151
+ return cls(data)
152
+ ```
153
+
154
+ ### Generic Functions with TypeVar
155
+
156
+ ✅ **PREFERRED** - Use TypeVar for generic functions:
157
+
158
+ ```python
159
+ from typing import TypeVar
160
+
161
+ T = TypeVar("T")
162
+
163
+ def first(items: list[T]) -> T | None:
164
+ """Return first item or None if empty."""
165
+ if not items:
166
+ return None
167
+ return items[0]
168
+
169
+ def identity(value: T) -> T:
170
+ return value
171
+ ```
172
+
173
+ **Note**: Python 3.12 introduces better syntax (PEP 695) for this pattern.
174
+
175
+ ### Generic Classes
176
+
177
+ ✅ **PREFERRED** - Use Generic with TypeVar:
178
+
179
+ ```python
180
+ from typing import Generic, TypeVar
181
+
182
+ T = TypeVar("T")
183
+
184
+ class Stack(Generic[T]):
185
+ """A generic stack data structure."""
186
+
187
+ def __init__(self) -> None:
188
+ self._items: list[T] = []
189
+
190
+ def push(self, item: T) -> Self: # Can combine with Self!
191
+ self._items.append(item)
192
+ return self
193
+
194
+ def pop(self) -> T | None:
195
+ if not self._items:
196
+ return None
197
+ return self._items.pop()
198
+
199
+ # Usage
200
+ int_stack = Stack[int]()
201
+ int_stack.push(42).push(43) # Method chaining works!
202
+ ```
203
+
204
+ **Note**: Python 3.12 introduces cleaner syntax for generic classes.
205
+
206
+ ### Constrained and Bounded TypeVars
207
+
208
+ ✅ **Use TypeVar constraints when needed**:
209
+
210
+ ```python
211
+ from typing import TypeVar
212
+
213
+ # Constrained to specific types
214
+ Numeric = TypeVar("Numeric", int, float)
215
+
216
+ def add(a: Numeric, b: Numeric) -> Numeric:
217
+ return a + b
218
+
219
+ # Bounded to base class
220
+ T = TypeVar("T", bound=BaseClass)
221
+
222
+ def process(obj: T) -> T:
223
+ return obj
224
+ ```
225
+
226
+ ### Callable Types
227
+
228
+ ✅ **PREFERRED** - Use `collections.abc.Callable`:
229
+
230
+ ```python
231
+ from collections.abc import Callable
232
+
233
+ # Function that takes int, returns str
234
+ processor: Callable[[int], str] = str
235
+
236
+ # Function with no args, returns None
237
+ callback: Callable[[], None] = lambda: None
238
+
239
+ # Function with multiple args
240
+ validator: Callable[[str, int], bool] = lambda s, i: len(s) > i
241
+ ```
242
+
243
+ ### Type Aliases
244
+
245
+ ✅ **Use simple assignment for type aliases**:
246
+
247
+ ```python
248
+ # Simple alias
249
+ UserId = str
250
+ Config = dict[str, str | int | bool]
251
+
252
+ # Complex nested type
253
+ JsonValue = dict[str, "JsonValue"] | list["JsonValue"] | str | int | float | bool | None
254
+
255
+ def load_config() -> Config:
256
+ return {"host": "localhost", "port": 8080}
257
+ ```
258
+
259
+ **Note**: Python 3.12 introduces `type` statement for better alias support.
260
+
261
+ ### When from **future** import annotations is Needed
262
+
263
+ Use `from __future__ import annotations` when you encounter:
264
+
265
+ **Forward references** (class referencing itself):
266
+
267
+ ```python
268
+ from __future__ import annotations
269
+
270
+ class Node:
271
+ def __init__(self, value: int, parent: Node | None = None):
272
+ self.value = value
273
+ self.parent = parent
274
+ ```
275
+
276
+ **Circular type imports**:
277
+
278
+ ```python
279
+ # a.py
280
+ from __future__ import annotations
281
+ from typing import TYPE_CHECKING
282
+
283
+ if TYPE_CHECKING:
284
+ from b import B
285
+
286
+ class A:
287
+ def method(self) -> B:
288
+ ...
289
+ ```
290
+
291
+ **Complex recursive types**:
292
+
293
+ ```python
294
+ from __future__ import annotations
295
+
296
+ JsonValue = dict[str, JsonValue] | list[JsonValue] | str | int | float | bool | None
297
+ ```
298
+
299
+ ### Interfaces: ABC vs Protocol
300
+
301
+ ✅ **PREFERRED** - Use ABC for interfaces:
302
+
303
+ ```python
304
+ from abc import ABC, abstractmethod
305
+
306
+ class Repository(ABC):
307
+ @abstractmethod
308
+ def get(self, id: str) -> User | None:
309
+ """Get user by ID."""
310
+
311
+ @abstractmethod
312
+ def save(self, user: User) -> None:
313
+ """Save user."""
314
+ ```
315
+
316
+ 🟡 **VALID** - Use Protocol only for structural typing:
317
+
318
+ ```python
319
+ from typing import Protocol
320
+
321
+ class Drawable(Protocol):
322
+ def draw(self) -> None: ...
323
+
324
+ # Any object with draw() method matches
325
+ def render(obj: Drawable) -> None:
326
+ obj.draw()
327
+ ```
328
+
329
+ **Dignified Python prefers ABC** because it makes inheritance and intent explicit.
330
+
331
+ ## Complete Examples
332
+
333
+ ### Builder Pattern with Self
334
+
335
+ ```python
336
+ from typing import Self
337
+
338
+ class QueryBuilder:
339
+ """SQL query builder with fluent interface."""
340
+
341
+ def __init__(self) -> None:
342
+ self._select: list[str] = ["*"]
343
+ self._from: str | None = None
344
+ self._where: list[str] = []
345
+ self._limit: int | None = None
346
+
347
+ def select(self, *columns: str) -> Self:
348
+ """Specify columns to select."""
349
+ self._select = list(columns)
350
+ return self
351
+
352
+ def from_table(self, table: str) -> Self:
353
+ """Specify table to query."""
354
+ self._from = table
355
+ return self
356
+
357
+ def where(self, condition: str) -> Self:
358
+ """Add WHERE condition."""
359
+ self._where.append(condition)
360
+ return self
361
+
362
+ def limit(self, n: int) -> Self:
363
+ """Set LIMIT."""
364
+ self._limit = n
365
+ return self
366
+
367
+ def build(self) -> str:
368
+ """Build final SQL query."""
369
+ if not self._from:
370
+ raise ValueError("FROM table not specified")
371
+
372
+ parts = [f"SELECT {', '.join(self._select)}"]
373
+ parts.append(f"FROM {self._from}")
374
+
375
+ if self._where:
376
+ parts.append(f"WHERE {' AND '.join(self._where)}")
377
+
378
+ if self._limit:
379
+ parts.append(f"LIMIT {self._limit}")
380
+
381
+ return " ".join(parts)
382
+
383
+ # Usage with type-safe method chaining
384
+ query = (
385
+ QueryBuilder()
386
+ .select("id", "name", "email")
387
+ .from_table("users")
388
+ .where("active = true")
389
+ .where("age > 18")
390
+ .limit(10)
391
+ .build()
392
+ )
393
+ ```
394
+
395
+ ### Factory Methods with Self
396
+
397
+ ```python
398
+ from typing import Self
399
+ from pathlib import Path
400
+ import json
401
+
402
+ class Config:
403
+ """Application configuration with multiple factory methods."""
404
+
405
+ def __init__(self, data: dict[str, str | int]) -> None:
406
+ self.data = data
407
+
408
+ @classmethod
409
+ def from_json(cls, path: Path) -> Self:
410
+ """Load configuration from JSON file."""
411
+ if not path.exists():
412
+ raise FileNotFoundError(f"Config not found: {path}")
413
+
414
+ with path.open(encoding="utf-8") as f:
415
+ data = json.load(f)
416
+ return cls(data)
417
+
418
+ @classmethod
419
+ def from_env(cls) -> Self:
420
+ """Load configuration from environment variables."""
421
+ import os
422
+ data = {
423
+ k.lower(): v
424
+ for k, v in os.environ.items()
425
+ if k.startswith("APP_")
426
+ }
427
+ return cls(data)
428
+
429
+ @classmethod
430
+ def default(cls) -> Self:
431
+ """Create default configuration."""
432
+ return cls({"host": "localhost", "port": 8080})
433
+
434
+ def with_override(self, key: str, value: str | int) -> Self:
435
+ """Return new config with overridden value."""
436
+ new_data = self.data.copy()
437
+ new_data[key] = value
438
+ return type(self)(new_data)
439
+
440
+ # All factory methods return correct type
441
+ config = Config.from_json(Path("config.json"))
442
+ dev_config = config.with_override("debug", True)
443
+ ```
444
+
445
+ ## Type Checking Rules
446
+
447
+ ### What to Type
448
+
449
+ ✅ **MUST type**:
450
+
451
+ - All public function parameters (except `self`, `cls`)
452
+ - All public function return values
453
+ - All class attributes (public and private)
454
+ - Module-level constants
455
+
456
+ 🟡 **SHOULD type**:
457
+
458
+ - Internal function signatures
459
+ - Complex local variables
460
+
461
+ 🟢 **MAY skip**:
462
+
463
+ - Simple local variables where type is obvious (`count = 0`)
464
+ - Lambda parameters in short inline lambdas
465
+ - Loop variables in short comprehensions
466
+
467
+ ### Running Type Checker
468
+
469
+ ```bash
470
+ uv run ty check
471
+ ```
472
+
473
+ All code should pass type checking without errors.
474
+
475
+ ### Type Checking Configuration
476
+
477
+ Configure ty in `pyproject.toml`:
478
+
479
+ ```toml
480
+ [tool.ty.environment]
481
+ python-version = "3.11"
482
+ ```
483
+
484
+ ## Common Patterns
485
+
486
+ ### Checking for None
487
+
488
+ ✅ **CORRECT** - Check before use:
489
+
490
+ ```python
491
+ def process_user(user: User | None) -> str:
492
+ if user is None:
493
+ return "No user"
494
+ return user.name
495
+ ```
496
+
497
+ ### Dict.get() with Type Safety
498
+
499
+ ✅ **CORRECT** - Handle None case:
500
+
501
+ ```python
502
+ def get_port(config: dict[str, int]) -> int:
503
+ port = config.get("port")
504
+ if port is None:
505
+ return 8080
506
+ return port
507
+ ```
508
+
509
+ ### List Operations
510
+
511
+ ✅ **CORRECT** - Check before accessing:
512
+
513
+ ```python
514
+ def first_or_default(items: list[str], default: str) -> str:
515
+ if not items:
516
+ return default
517
+ return items[0]
518
+ ```
519
+
520
+ ## Migration from Python 3.10
521
+
522
+ If upgrading from Python 3.10:
523
+
524
+ 1. **Replace bound TypeVar with Self** for self-returning methods:
525
+ - Old: `T = TypeVar("T", bound="ClassName")`
526
+ - New: `from typing import Self` and use `-> Self`
527
+
528
+ 2. **Enjoy improved error messages** (no code changes needed)
529
+
530
+ 3. **All existing 3.10 syntax continues to work**
531
+
532
+ ## References
533
+
534
+ - [PEP 673: Self Type](https://peps.python.org/pep-0673/)
535
+ - [PEP 646: Variadic Generics](https://peps.python.org/pep-0646/)
536
+ - [Python 3.11 What's New](https://docs.python.org/3.11/whatsnew/3.11.html)