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,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)
|