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.
- erk/__init__.py +12 -0
- erk/__main__.py +6 -0
- erk/agent_docs/__init__.py +5 -0
- erk/agent_docs/models.py +123 -0
- erk/agent_docs/operations.py +666 -0
- erk/artifacts/__init__.py +5 -0
- erk/artifacts/artifact_health.py +623 -0
- erk/artifacts/detection.py +16 -0
- erk/artifacts/discovery.py +343 -0
- erk/artifacts/models.py +63 -0
- erk/artifacts/staleness.py +56 -0
- erk/artifacts/state.py +100 -0
- erk/artifacts/sync.py +624 -0
- erk/cli/__init__.py +0 -0
- erk/cli/activation.py +132 -0
- erk/cli/alias.py +53 -0
- erk/cli/cli.py +221 -0
- erk/cli/commands/__init__.py +0 -0
- erk/cli/commands/admin.py +153 -0
- erk/cli/commands/artifact/__init__.py +1 -0
- erk/cli/commands/artifact/check.py +260 -0
- erk/cli/commands/artifact/group.py +31 -0
- erk/cli/commands/artifact/list_cmd.py +89 -0
- erk/cli/commands/artifact/show.py +62 -0
- erk/cli/commands/artifact/sync_cmd.py +39 -0
- erk/cli/commands/branch/__init__.py +26 -0
- erk/cli/commands/branch/assign_cmd.py +152 -0
- erk/cli/commands/branch/checkout_cmd.py +357 -0
- erk/cli/commands/branch/create_cmd.py +161 -0
- erk/cli/commands/branch/list_cmd.py +82 -0
- erk/cli/commands/branch/unassign_cmd.py +197 -0
- erk/cli/commands/cc/__init__.py +15 -0
- erk/cli/commands/cc/jsonl_cmd.py +20 -0
- erk/cli/commands/cc/session/AGENTS.md +30 -0
- erk/cli/commands/cc/session/CLAUDE.md +1 -0
- erk/cli/commands/cc/session/__init__.py +15 -0
- erk/cli/commands/cc/session/list_cmd.py +167 -0
- erk/cli/commands/cc/session/show_cmd.py +175 -0
- erk/cli/commands/completion.py +89 -0
- erk/cli/commands/completions.py +165 -0
- erk/cli/commands/config.py +327 -0
- erk/cli/commands/docs/__init__.py +1 -0
- erk/cli/commands/docs/group.py +16 -0
- erk/cli/commands/docs/sync.py +121 -0
- erk/cli/commands/docs/validate.py +102 -0
- erk/cli/commands/doctor.py +243 -0
- erk/cli/commands/down.py +171 -0
- erk/cli/commands/exec/__init__.py +1 -0
- erk/cli/commands/exec/group.py +164 -0
- erk/cli/commands/exec/scripts/AGENTS.md +79 -0
- erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
- erk/cli/commands/exec/scripts/__init__.py +5 -0
- erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
- erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
- erk/cli/commands/exec/scripts/check_impl.py +152 -0
- erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
- erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
- erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
- erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
- erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
- erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
- erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
- erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
- erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
- erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
- erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
- erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
- erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
- erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
- erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
- erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
- erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
- erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
- erk/cli/commands/exec/scripts/impl_init.py +158 -0
- erk/cli/commands/exec/scripts/impl_signal.py +375 -0
- erk/cli/commands/exec/scripts/impl_verify.py +49 -0
- erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
- erk/cli/commands/exec/scripts/list_sessions.py +296 -0
- erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
- erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
- erk/cli/commands/exec/scripts/marker.py +163 -0
- erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
- erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
- erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
- erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
- erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
- erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
- erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
- erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
- erk/cli/commands/exec/scripts/quick_submit.py +32 -0
- erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
- erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
- erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
- erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
- erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
- erk/cli/commands/exec/scripts/slot_objective.py +102 -0
- erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
- erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
- erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
- erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
- erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
- erk/cli/commands/implement.py +695 -0
- erk/cli/commands/implement_shared.py +649 -0
- erk/cli/commands/info/__init__.py +14 -0
- erk/cli/commands/info/release_notes_cmd.py +128 -0
- erk/cli/commands/init.py +801 -0
- erk/cli/commands/land_cmd.py +690 -0
- erk/cli/commands/log_cmd.py +137 -0
- erk/cli/commands/md/__init__.py +5 -0
- erk/cli/commands/md/check.py +118 -0
- erk/cli/commands/md/group.py +14 -0
- erk/cli/commands/navigation_helpers.py +430 -0
- erk/cli/commands/objective/__init__.py +16 -0
- erk/cli/commands/objective/list_cmd.py +47 -0
- erk/cli/commands/objective_helpers.py +132 -0
- erk/cli/commands/plan/__init__.py +32 -0
- erk/cli/commands/plan/check_cmd.py +174 -0
- erk/cli/commands/plan/close_cmd.py +69 -0
- erk/cli/commands/plan/create_cmd.py +120 -0
- erk/cli/commands/plan/docs/__init__.py +18 -0
- erk/cli/commands/plan/docs/extract_cmd.py +53 -0
- erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
- erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
- erk/cli/commands/plan/extraction/__init__.py +16 -0
- erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
- erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
- erk/cli/commands/plan/get.py +71 -0
- erk/cli/commands/plan/list_cmd.py +754 -0
- erk/cli/commands/plan/log_cmd.py +440 -0
- erk/cli/commands/plan/start_cmd.py +459 -0
- erk/cli/commands/planner/__init__.py +40 -0
- erk/cli/commands/planner/configure_cmd.py +73 -0
- erk/cli/commands/planner/connect_cmd.py +96 -0
- erk/cli/commands/planner/create_cmd.py +148 -0
- erk/cli/commands/planner/list_cmd.py +51 -0
- erk/cli/commands/planner/register_cmd.py +105 -0
- erk/cli/commands/planner/set_default_cmd.py +23 -0
- erk/cli/commands/planner/unregister_cmd.py +43 -0
- erk/cli/commands/pr/__init__.py +23 -0
- erk/cli/commands/pr/check_cmd.py +112 -0
- erk/cli/commands/pr/checkout_cmd.py +165 -0
- erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
- erk/cli/commands/pr/parse_pr_reference.py +10 -0
- erk/cli/commands/pr/submit_cmd.py +360 -0
- erk/cli/commands/pr/sync_cmd.py +181 -0
- erk/cli/commands/prepare_cwd_recovery.py +60 -0
- erk/cli/commands/project/__init__.py +16 -0
- erk/cli/commands/project/init_cmd.py +91 -0
- erk/cli/commands/run/__init__.py +17 -0
- erk/cli/commands/run/list_cmd.py +189 -0
- erk/cli/commands/run/logs_cmd.py +54 -0
- erk/cli/commands/run/shared.py +19 -0
- erk/cli/commands/shell_integration.py +29 -0
- erk/cli/commands/slot/__init__.py +23 -0
- erk/cli/commands/slot/check_cmd.py +277 -0
- erk/cli/commands/slot/common.py +314 -0
- erk/cli/commands/slot/init_pool_cmd.py +157 -0
- erk/cli/commands/slot/list_cmd.py +228 -0
- erk/cli/commands/slot/repair_cmd.py +190 -0
- erk/cli/commands/stack/__init__.py +23 -0
- erk/cli/commands/stack/consolidate_cmd.py +470 -0
- erk/cli/commands/stack/list_cmd.py +79 -0
- erk/cli/commands/stack/move_cmd.py +309 -0
- erk/cli/commands/stack/split_old/README.md +64 -0
- erk/cli/commands/stack/split_old/__init__.py +5 -0
- erk/cli/commands/stack/split_old/command.py +233 -0
- erk/cli/commands/stack/split_old/display.py +116 -0
- erk/cli/commands/stack/split_old/plan.py +216 -0
- erk/cli/commands/status.py +58 -0
- erk/cli/commands/submit.py +768 -0
- erk/cli/commands/up.py +154 -0
- erk/cli/commands/upgrade.py +82 -0
- erk/cli/commands/wt/__init__.py +29 -0
- erk/cli/commands/wt/checkout_cmd.py +110 -0
- erk/cli/commands/wt/create_cmd.py +998 -0
- erk/cli/commands/wt/current_cmd.py +35 -0
- erk/cli/commands/wt/delete_cmd.py +573 -0
- erk/cli/commands/wt/list_cmd.py +332 -0
- erk/cli/commands/wt/rename_cmd.py +66 -0
- erk/cli/config.py +242 -0
- erk/cli/constants.py +29 -0
- erk/cli/core.py +65 -0
- erk/cli/debug.py +9 -0
- erk/cli/ensure-conversion-tasks.md +288 -0
- erk/cli/ensure.py +628 -0
- erk/cli/github_parsing.py +96 -0
- erk/cli/graphite.py +81 -0
- erk/cli/graphite_command.py +80 -0
- erk/cli/help_formatter.py +345 -0
- erk/cli/output.py +361 -0
- erk/cli/presets/dagster.toml +12 -0
- erk/cli/presets/generic.toml +12 -0
- erk/cli/prompt_hooks_templates/README.md +68 -0
- erk/cli/script_output.py +32 -0
- erk/cli/shell_integration/bash_wrapper.sh +32 -0
- erk/cli/shell_integration/fish_wrapper.fish +39 -0
- erk/cli/shell_integration/handler.py +338 -0
- erk/cli/shell_integration/zsh_wrapper.sh +32 -0
- erk/cli/shell_utils.py +171 -0
- erk/cli/subprocess_utils.py +92 -0
- erk/cli/uvx_detection.py +59 -0
- erk/core/__init__.py +0 -0
- erk/core/claude_executor.py +511 -0
- erk/core/claude_settings.py +317 -0
- erk/core/command_log.py +406 -0
- erk/core/commit_message_generator.py +234 -0
- erk/core/completion.py +10 -0
- erk/core/consolidation_utils.py +177 -0
- erk/core/context.py +570 -0
- erk/core/display/__init__.py +4 -0
- erk/core/display/abc.py +24 -0
- erk/core/display/real.py +30 -0
- erk/core/display_utils.py +526 -0
- erk/core/file_utils.py +87 -0
- erk/core/health_checks.py +1315 -0
- erk/core/health_checks_dogfooder/__init__.py +85 -0
- erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
- erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
- erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
- erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
- erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
- erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
- erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
- erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
- erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
- erk/core/implementation_queue/__init__.py +1 -0
- erk/core/implementation_queue/github/__init__.py +8 -0
- erk/core/implementation_queue/github/abc.py +7 -0
- erk/core/implementation_queue/github/noop.py +38 -0
- erk/core/implementation_queue/github/printing.py +43 -0
- erk/core/implementation_queue/github/real.py +119 -0
- erk/core/init_utils.py +227 -0
- erk/core/output_filter.py +338 -0
- erk/core/plan_store/__init__.py +6 -0
- erk/core/planner/__init__.py +1 -0
- erk/core/planner/registry_abc.py +8 -0
- erk/core/planner/registry_fake.py +129 -0
- erk/core/planner/registry_real.py +195 -0
- erk/core/planner/types.py +7 -0
- erk/core/pr_utils.py +30 -0
- erk/core/release_notes.py +263 -0
- erk/core/repo_discovery.py +126 -0
- erk/core/script_writer.py +41 -0
- erk/core/services/__init__.py +1 -0
- erk/core/services/plan_list_service.py +94 -0
- erk/core/shell.py +51 -0
- erk/core/user_feedback.py +11 -0
- erk/core/version_check.py +55 -0
- erk/core/workflow_display.py +75 -0
- erk/core/worktree_pool.py +190 -0
- erk/core/worktree_utils.py +300 -0
- erk/data/CHANGELOG.md +438 -0
- erk/data/__init__.py +1 -0
- erk/data/claude/agents/devrun.md +180 -0
- erk/data/claude/commands/erk/__init__.py +0 -0
- erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
- erk/data/claude/commands/erk/fix-conflicts.md +25 -0
- erk/data/claude/commands/erk/git-pr-push.md +345 -0
- erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
- erk/data/claude/commands/erk/land.md +193 -0
- erk/data/claude/commands/erk/objective-create.md +370 -0
- erk/data/claude/commands/erk/objective-list.md +34 -0
- erk/data/claude/commands/erk/objective-next-plan.md +220 -0
- erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
- erk/data/claude/commands/erk/plan-implement.md +202 -0
- erk/data/claude/commands/erk/plan-save.md +45 -0
- erk/data/claude/commands/erk/plan-submit.md +39 -0
- erk/data/claude/commands/erk/pr-address.md +367 -0
- erk/data/claude/commands/erk/pr-submit.md +58 -0
- erk/data/claude/skills/dignified-python/SKILL.md +48 -0
- erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
- erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
- erk/data/claude/skills/dignified-python/subprocess.md +99 -0
- erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
- erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
- erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
- erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
- erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
- erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
- erk/data/claude/skills/learned-docs/SKILL.md +362 -0
- erk/data/github/actions/setup-claude-erk/action.yml +11 -0
- erk/data/github/prompts/dignified-python-review.md +125 -0
- erk/data/github/workflows/dignified-python-review.yml +61 -0
- erk/data/github/workflows/erk-impl.yml +251 -0
- erk/hooks/__init__.py +1 -0
- erk/hooks/decorators.py +319 -0
- erk/status/__init__.py +8 -0
- erk/status/collectors/__init__.py +9 -0
- erk/status/collectors/base.py +52 -0
- erk/status/collectors/git.py +76 -0
- erk/status/collectors/github.py +81 -0
- erk/status/collectors/graphite.py +80 -0
- erk/status/collectors/impl.py +145 -0
- erk/status/models/__init__.py +4 -0
- erk/status/models/status_data.py +404 -0
- erk/status/orchestrator.py +169 -0
- erk/status/renderers/__init__.py +5 -0
- erk/status/renderers/simple.py +322 -0
- erk/tui/AGENTS.md +193 -0
- erk/tui/CLAUDE.md +1 -0
- erk/tui/__init__.py +1 -0
- erk/tui/app.py +1404 -0
- erk/tui/commands/__init__.py +1 -0
- erk/tui/commands/executor.py +66 -0
- erk/tui/commands/provider.py +165 -0
- erk/tui/commands/real_executor.py +63 -0
- erk/tui/commands/registry.py +121 -0
- erk/tui/commands/types.py +36 -0
- erk/tui/data/__init__.py +1 -0
- erk/tui/data/provider.py +492 -0
- erk/tui/data/types.py +104 -0
- erk/tui/filtering/__init__.py +1 -0
- erk/tui/filtering/logic.py +43 -0
- erk/tui/filtering/types.py +55 -0
- erk/tui/jsonl_viewer/__init__.py +1 -0
- erk/tui/jsonl_viewer/app.py +61 -0
- erk/tui/jsonl_viewer/models.py +208 -0
- erk/tui/jsonl_viewer/widgets.py +204 -0
- erk/tui/sorting/__init__.py +6 -0
- erk/tui/sorting/logic.py +55 -0
- erk/tui/sorting/types.py +68 -0
- erk/tui/styles/dash.tcss +95 -0
- erk/tui/widgets/__init__.py +1 -0
- erk/tui/widgets/command_output.py +112 -0
- erk/tui/widgets/plan_table.py +276 -0
- erk/tui/widgets/status_bar.py +116 -0
- erk-0.4.5.dist-info/METADATA +376 -0
- erk-0.4.5.dist-info/RECORD +331 -0
- erk-0.4.5.dist-info/WHEEL +4 -0
- erk-0.4.5.dist-info/entry_points.txt +2 -0
- 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)
|