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,662 @@
1
+ ---
2
+ ---
3
+
4
+ # Type Annotations - Python 3.12
5
+
6
+ This document provides complete, canonical type annotation guidance for Python 3.12.
7
+
8
+ ## Overview
9
+
10
+ Python 3.12 introduces PEP 695, a major syntactic improvement for generic types. The new type parameter syntax makes generic functions and classes significantly more readable. All syntax from 3.10 and 3.11 continues to work.
11
+
12
+ **What's new in 3.12:**
13
+
14
+ - PEP 695 type parameter syntax: `def func[T](x: T) -> T`
15
+ - `type` statement for better type aliases
16
+ - Cleaner generic class syntax
17
+
18
+ **Available from 3.11:**
19
+
20
+ - `Self` type for self-returning methods
21
+
22
+ **Available from 3.10:**
23
+
24
+ - Built-in generic types: `list[T]`, `dict[K, V]`, etc.
25
+ - Union types with `|` operator
26
+ - Optional with `X | None`
27
+
28
+ **What you need from typing module:**
29
+
30
+ - `Self` for self-returning methods
31
+ - `TypeVar` only for constrained/bounded generics
32
+ - `Protocol` for structural typing (rare - prefer ABC)
33
+ - `TYPE_CHECKING` for conditional imports
34
+ - `Any` (use sparingly)
35
+
36
+ ## Complete Type Annotation Syntax for Python 3.12
37
+
38
+ ### Basic Collection Types
39
+
40
+ ✅ **PREFERRED** - Use built-in generic types:
41
+
42
+ ```python
43
+ names: list[str] = []
44
+ mapping: dict[str, int] = {}
45
+ unique_ids: set[str] = set()
46
+ coordinates: tuple[int, int] = (0, 0)
47
+ ```
48
+
49
+ ❌ **WRONG** - Don't use typing module equivalents:
50
+
51
+ ```python
52
+ from typing import List, Dict, Set, Tuple # Don't do this
53
+ names: List[str] = []
54
+ ```
55
+
56
+ ### Union Types
57
+
58
+ ✅ **PREFERRED** - Use `|` operator:
59
+
60
+ ```python
61
+ def process(value: str | int) -> str:
62
+ return str(value)
63
+
64
+ def find_config(name: str) -> dict[str, str] | dict[str, int]:
65
+ ...
66
+
67
+ # Multiple unions
68
+ def parse(input: str | int | float) -> str:
69
+ return str(input)
70
+ ```
71
+
72
+ ❌ **WRONG** - Don't use `typing.Union`:
73
+
74
+ ```python
75
+ from typing import Union
76
+ def process(value: Union[str, int]) -> str: # Don't do this
77
+ ...
78
+ ```
79
+
80
+ ### Optional Types
81
+
82
+ ✅ **PREFERRED** - Use `X | None`:
83
+
84
+ ```python
85
+ def find_user(id: str) -> User | None:
86
+ """Returns user or None if not found."""
87
+ if id in users:
88
+ return users[id]
89
+ return None
90
+ ```
91
+
92
+ ❌ **WRONG** - Don't use `typing.Optional`:
93
+
94
+ ```python
95
+ from typing import Optional
96
+ def find_user(id: str) -> Optional[User]: # Don't do this
97
+ ...
98
+ ```
99
+
100
+ ### Self Type for Self-Returning Methods
101
+
102
+ ✅ **PREFERRED** - Use Self for methods that return the instance:
103
+
104
+ ```python
105
+ from typing import Self
106
+
107
+ class Builder:
108
+ def set_name(self, name: str) -> Self:
109
+ self.name = name
110
+ return self
111
+
112
+ def set_value(self, value: int) -> Self:
113
+ self.value = value
114
+ return self
115
+ ```
116
+
117
+ ### Generic Functions with PEP 695 (NEW in 3.12)
118
+
119
+ ✅ **PREFERRED** - Use PEP 695 type parameter syntax:
120
+
121
+ ```python
122
+ def first[T](items: list[T]) -> T | None:
123
+ """Return first item or None if empty."""
124
+ if not items:
125
+ return None
126
+ return items[0]
127
+
128
+ def identity[T](value: T) -> T:
129
+ """Return value unchanged."""
130
+ return value
131
+
132
+ # Multiple type parameters
133
+ def zip_dicts[K, V](keys: list[K], values: list[V]) -> dict[K, V]:
134
+ """Create dict from separate key and value lists."""
135
+ return dict(zip(keys, values))
136
+ ```
137
+
138
+ 🟡 **VALID** - TypeVar still works:
139
+
140
+ ```python
141
+ from typing import TypeVar
142
+
143
+ T = TypeVar("T")
144
+
145
+ def first(items: list[T]) -> T | None:
146
+ if not items:
147
+ return None
148
+ return items[0]
149
+ ```
150
+
151
+ **Note**: Prefer PEP 695 syntax for simple generics. TypeVar is still needed for constraints/bounds.
152
+
153
+ ### Generic Classes with PEP 695 (NEW in 3.12)
154
+
155
+ ✅ **PREFERRED** - Use PEP 695 class syntax:
156
+
157
+ ```python
158
+ class Stack[T]:
159
+ """A generic stack data structure."""
160
+
161
+ def __init__(self) -> None:
162
+ self._items: list[T] = []
163
+
164
+ def push(self, item: T) -> Self:
165
+ self._items.append(item)
166
+ return self
167
+
168
+ def pop(self) -> T | None:
169
+ if not self._items:
170
+ return None
171
+ return self._items.pop()
172
+
173
+ # Usage
174
+ int_stack = Stack[int]()
175
+ int_stack.push(42).push(43)
176
+ ```
177
+
178
+ 🟡 **VALID** - Generic with TypeVar still works:
179
+
180
+ ```python
181
+ from typing import Generic, TypeVar
182
+
183
+ T = TypeVar("T")
184
+
185
+ class Stack(Generic[T]):
186
+ def __init__(self) -> None:
187
+ self._items: list[T] = []
188
+ # ... rest of implementation
189
+ ```
190
+
191
+ **Note**: PEP 695 is cleaner - no imports needed, type parameter scope is local to class.
192
+
193
+ ### Type Parameter Bounds
194
+
195
+ ✅ **Use bounds with PEP 695**:
196
+
197
+ ```python
198
+ class Comparable:
199
+ def compare(self, other: object) -> int:
200
+ ...
201
+
202
+ def max_value[T: Comparable](items: list[T]) -> T:
203
+ """Get maximum value from comparable items."""
204
+ return max(items, key=lambda x: x)
205
+ ```
206
+
207
+ ### Constrained TypeVars (Still Use TypeVar)
208
+
209
+ ✅ **Use TypeVar for specific type constraints**:
210
+
211
+ ```python
212
+ from typing import TypeVar
213
+
214
+ # Constrained to specific types - must use TypeVar
215
+ Numeric = TypeVar("Numeric", int, float)
216
+
217
+ def add(a: Numeric, b: Numeric) -> Numeric:
218
+ return a + b
219
+ ```
220
+
221
+ ❌ **WRONG** - PEP 695 doesn't support constraints:
222
+
223
+ ```python
224
+ # This doesn't constrain to int|float
225
+ def add[Numeric](a: Numeric, b: Numeric) -> Numeric:
226
+ return a + b
227
+ ```
228
+
229
+ ### Type Aliases with type Statement (NEW in 3.12)
230
+
231
+ ✅ **PREFERRED** - Use `type` statement:
232
+
233
+ ```python
234
+ # Simple alias
235
+ type UserId = str
236
+ type Config = dict[str, str | int | bool]
237
+
238
+ # Generic type alias
239
+ type Result[T] = tuple[T, str | None]
240
+
241
+ def process(value: str) -> Result[int]:
242
+ try:
243
+ return (int(value), None)
244
+ except ValueError as e:
245
+ return (0, str(e))
246
+ ```
247
+
248
+ 🟡 **VALID** - Simple assignment still works:
249
+
250
+ ```python
251
+ UserId = str # Still valid
252
+ Config = dict[str, str | int | bool] # Still valid
253
+ ```
254
+
255
+ **Note**: `type` statement is more explicit and works better with generics.
256
+
257
+ ### Callable Types
258
+
259
+ ✅ **PREFERRED** - Use `collections.abc.Callable`:
260
+
261
+ ```python
262
+ from collections.abc import Callable
263
+
264
+ # Function that takes int, returns str
265
+ processor: Callable[[int], str] = str
266
+
267
+ # Function with no args, returns None
268
+ callback: Callable[[], None] = lambda: None
269
+
270
+ # Function with multiple args
271
+ validator: Callable[[str, int], bool] = lambda s, i: len(s) > i
272
+ ```
273
+
274
+ ### When from **future** import annotations is Needed
275
+
276
+ Use `from __future__ import annotations` when you encounter:
277
+
278
+ **Forward references** (class referencing itself):
279
+
280
+ ```python
281
+ from __future__ import annotations
282
+
283
+ class Node:
284
+ def __init__(self, value: int, parent: Node | None = None):
285
+ self.value = value
286
+ self.parent = parent
287
+ ```
288
+
289
+ **Circular type imports**:
290
+
291
+ ```python
292
+ # a.py
293
+ from __future__ import annotations
294
+ from typing import TYPE_CHECKING
295
+
296
+ if TYPE_CHECKING:
297
+ from b import B
298
+
299
+ class A:
300
+ def method(self) -> B:
301
+ ...
302
+ ```
303
+
304
+ **Complex recursive types**:
305
+
306
+ ```python
307
+ from __future__ import annotations
308
+
309
+ type JsonValue = dict[str, JsonValue] | list[JsonValue] | str | int | float | bool | None
310
+ ```
311
+
312
+ ### Interfaces: ABC vs Protocol
313
+
314
+ ✅ **PREFERRED** - Use ABC for interfaces:
315
+
316
+ ```python
317
+ from abc import ABC, abstractmethod
318
+
319
+ class Repository(ABC):
320
+ @abstractmethod
321
+ def get(self, id: str) -> User | None:
322
+ """Get user by ID."""
323
+
324
+ @abstractmethod
325
+ def save(self, user: User) -> None:
326
+ """Save user."""
327
+ ```
328
+
329
+ 🟡 **VALID** - Use Protocol only for structural typing:
330
+
331
+ ```python
332
+ from typing import Protocol
333
+
334
+ class Drawable(Protocol):
335
+ def draw(self) -> None: ...
336
+
337
+ def render(obj: Drawable) -> None:
338
+ obj.draw()
339
+ ```
340
+
341
+ **Dignified Python prefers ABC** because it makes inheritance and intent explicit.
342
+
343
+ ## Complete Examples
344
+
345
+ ### Generic Stack with PEP 695
346
+
347
+ ```python
348
+ from typing import Self
349
+
350
+ class Stack[T]:
351
+ """Type-safe stack with PEP 695 syntax."""
352
+
353
+ def __init__(self) -> None:
354
+ self._items: list[T] = []
355
+
356
+ def push(self, item: T) -> Self:
357
+ """Push item and return self for chaining."""
358
+ self._items.append(item)
359
+ return self
360
+
361
+ def pop(self) -> T | None:
362
+ """Pop item or return None if empty."""
363
+ if not self._items:
364
+ return None
365
+ return self._items.pop()
366
+
367
+ def peek(self) -> T | None:
368
+ """Peek at top item without removing."""
369
+ if not self._items:
370
+ return None
371
+ return self._items[-1]
372
+
373
+ def is_empty(self) -> bool:
374
+ """Check if stack is empty."""
375
+ return len(self._items) == 0
376
+
377
+ # Usage
378
+ numbers = Stack[int]()
379
+ numbers.push(1).push(2).push(3)
380
+ top = numbers.pop() # Type checker knows this is int | None
381
+ ```
382
+
383
+ ### Generic Repository with PEP 695
384
+
385
+ ```python
386
+ from abc import ABC, abstractmethod
387
+ from typing import Self
388
+
389
+ class Repository[T]:
390
+ """Abstract repository with generic type parameter."""
391
+
392
+ @abstractmethod
393
+ def get(self, id: str) -> T | None:
394
+ """Get entity by ID."""
395
+
396
+ @abstractmethod
397
+ def save(self, entity: T) -> Self:
398
+ """Save entity, return self for chaining."""
399
+
400
+ @abstractmethod
401
+ def delete(self, id: str) -> bool:
402
+ """Delete entity, return success."""
403
+
404
+ def get_or_fail(self, id: str) -> T:
405
+ """Get entity or raise error."""
406
+ entity = self.get(id)
407
+ if entity is None:
408
+ raise ValueError(f"Entity not found: {id}")
409
+ return entity
410
+
411
+ class InMemoryRepository[T](Repository[T]):
412
+ """In-memory repository implementation."""
413
+
414
+ def __init__(self) -> None:
415
+ self._storage: dict[str, T] = {}
416
+
417
+ def get(self, id: str) -> T | None:
418
+ return self._storage.get(id)
419
+
420
+ def save(self, entity: T) -> Self:
421
+ # Assume entity has 'id' attribute
422
+ entity_id = str(getattr(entity, "id", id(entity)))
423
+ self._storage[entity_id] = entity
424
+ return self
425
+
426
+ def delete(self, id: str) -> bool:
427
+ if id in self._storage:
428
+ del self._storage[id]
429
+ return True
430
+ return False
431
+
432
+ # Usage
433
+ from dataclasses import dataclass
434
+
435
+ @dataclass
436
+ class User:
437
+ id: str
438
+ name: str
439
+
440
+ repo = InMemoryRepository[User]()
441
+ repo.save(User("1", "Alice")).save(User("2", "Bob"))
442
+ user = repo.get("1") # Type: User | None
443
+ ```
444
+
445
+ ### Type Aliases with type Statement
446
+
447
+ ```python
448
+ # Simple aliases
449
+ type UserId = str
450
+ type ErrorMessage = str
451
+
452
+ # Complex nested types
453
+ type JsonValue = dict[str, JsonValue] | list[JsonValue] | str | int | float | bool | None
454
+
455
+ # Generic type aliases
456
+ type Result[T] = tuple[T, ErrorMessage | None]
457
+ type AsyncResult[T] = tuple[T | None, ErrorMessage | None]
458
+
459
+ def parse_int(value: str) -> Result[int]:
460
+ """Parse string to int, return result with optional error."""
461
+ try:
462
+ return (int(value), None)
463
+ except ValueError as e:
464
+ return (0, str(e))
465
+
466
+ def fetch_user(id: UserId) -> AsyncResult[dict[str, str]]:
467
+ """Fetch user data asynchronously."""
468
+ # Implementation...
469
+ return ({"id": id, "name": "Alice"}, None)
470
+ ```
471
+
472
+ ### Builder Pattern with Self and PEP 695
473
+
474
+ ```python
475
+ from typing import Self
476
+
477
+ class QueryBuilder[T]:
478
+ """Generic query builder with fluent interface."""
479
+
480
+ def __init__(self, result_type: type[T]) -> None:
481
+ self._result_type = result_type
482
+ self._filters: list[str] = []
483
+ self._limit: int | None = None
484
+
485
+ def filter(self, condition: str) -> Self:
486
+ """Add filter condition."""
487
+ self._filters.append(condition)
488
+ return self
489
+
490
+ def limit(self, n: int) -> Self:
491
+ """Set result limit."""
492
+ self._limit = n
493
+ return self
494
+
495
+ def build(self) -> str:
496
+ """Build query string."""
497
+ query = " AND ".join(self._filters)
498
+ if self._limit:
499
+ query += f" LIMIT {self._limit}"
500
+ return query
501
+
502
+ # Usage
503
+ @dataclass
504
+ class User:
505
+ name: str
506
+ age: int
507
+
508
+ builder = QueryBuilder[User](User)
509
+ query = (
510
+ builder
511
+ .filter("active = true")
512
+ .filter("age > 18")
513
+ .limit(10)
514
+ .build()
515
+ )
516
+ ```
517
+
518
+ ### Generic Function Utilities
519
+
520
+ ```python
521
+ def map_list[T, U](items: list[T], func: Callable[[T], U]) -> list[U]:
522
+ """Map function over list items."""
523
+ from collections.abc import Callable
524
+ return [func(item) for item in items]
525
+
526
+ def filter_list[T](items: list[T], predicate: Callable[[T], bool]) -> list[T]:
527
+ """Filter list by predicate."""
528
+ from collections.abc import Callable
529
+ return [item for item in items if predicate(item)]
530
+
531
+ def reduce_list[T, U](
532
+ items: list[T],
533
+ func: Callable[[U, T], U],
534
+ initial: U,
535
+ ) -> U:
536
+ """Reduce list to single value."""
537
+ from collections.abc import Callable
538
+ result = initial
539
+ for item in items:
540
+ result = func(result, item)
541
+ return result
542
+
543
+ # Usage
544
+ numbers = [1, 2, 3, 4, 5]
545
+ doubled = map_list(numbers, lambda x: x * 2) # list[int]
546
+ evens = filter_list(numbers, lambda x: x % 2 == 0) # list[int]
547
+ sum_val = reduce_list(numbers, lambda acc, x: acc + x, 0) # int
548
+ ```
549
+
550
+ ## Type Checking Rules
551
+
552
+ ### What to Type
553
+
554
+ ✅ **MUST type**:
555
+
556
+ - All public function parameters (except `self`, `cls`)
557
+ - All public function return values
558
+ - All class attributes (public and private)
559
+ - Module-level constants
560
+
561
+ 🟡 **SHOULD type**:
562
+
563
+ - Internal function signatures
564
+ - Complex local variables
565
+
566
+ 🟢 **MAY skip**:
567
+
568
+ - Simple local variables where type is obvious (`count = 0`)
569
+ - Lambda parameters in short inline lambdas
570
+ - Loop variables in short comprehensions
571
+
572
+ ### Running Type Checker
573
+
574
+ ```bash
575
+ uv run ty check
576
+ ```
577
+
578
+ All code should pass type checking without errors.
579
+
580
+ ### Type Checking Configuration
581
+
582
+ Configure ty in `pyproject.toml`:
583
+
584
+ ```toml
585
+ [tool.ty.environment]
586
+ python-version = "3.12"
587
+ ```
588
+
589
+ ## Common Patterns
590
+
591
+ ### Checking for None
592
+
593
+ ✅ **CORRECT** - Check before use:
594
+
595
+ ```python
596
+ def process_user(user: User | None) -> str:
597
+ if user is None:
598
+ return "No user"
599
+ return user.name
600
+ ```
601
+
602
+ ### Dict.get() with Type Safety
603
+
604
+ ✅ **CORRECT** - Handle None case:
605
+
606
+ ```python
607
+ def get_port(config: dict[str, int]) -> int:
608
+ port = config.get("port")
609
+ if port is None:
610
+ return 8080
611
+ return port
612
+ ```
613
+
614
+ ### List Operations
615
+
616
+ ✅ **CORRECT** - Check before accessing:
617
+
618
+ ```python
619
+ def first_or_default[T](items: list[T], default: T) -> T:
620
+ if not items:
621
+ return default
622
+ return items[0]
623
+ ```
624
+
625
+ ## When to Use PEP 695 vs TypeVar
626
+
627
+ **Use PEP 695 for**:
628
+
629
+ - Simple generic functions (no constraints/bounds)
630
+ - Simple generic classes
631
+ - Most common generic use cases
632
+ - New code
633
+
634
+ **Still use TypeVar for**:
635
+
636
+ - Constrained type variables: `TypeVar("T", str, bytes)`
637
+ - Bound type variables with complex bounds
638
+ - Covariant/contravariant type variables
639
+ - Reusing same TypeVar across multiple functions
640
+
641
+ ## Migration from Python 3.11
642
+
643
+ If upgrading from Python 3.11:
644
+
645
+ 1. **Consider migrating to PEP 695 syntax**:
646
+ - `TypeVar` + `def func(x: T) -> T` → `def func[T](x: T) -> T`
647
+ - `Generic[T]` + `class C(Generic[T])` → `class C[T]`
648
+
649
+ 2. **Consider using `type` statement for aliases**:
650
+ - `Config = dict[str, str]` → `type Config = dict[str, str]`
651
+
652
+ 3. **Keep TypeVar for constraints**:
653
+ - `TypeVar` with constraints still needed
654
+
655
+ 4. **All existing 3.11 syntax continues to work**:
656
+ - `Self` type still preferred
657
+ - Union with `|` still preferred
658
+
659
+ ## References
660
+
661
+ - [PEP 695: Type Parameter Syntax](https://peps.python.org/pep-0695/)
662
+ - [Python 3.12 What's New - Type Hints](https://docs.python.org/3.12/whatsnew/3.12.html)