python-skills 1.0.0__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.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Generation: Error Handling
|
|
2
|
+
|
|
3
|
+
**Purpose**: Consistent, safe error handling patterns for code generation.
|
|
4
|
+
|
|
5
|
+
**When to use**: All code generation. Error handling is critical for correctness.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Exception Hierarchy
|
|
12
|
+
```
|
|
13
|
+
BaseException
|
|
14
|
+
├── KeyboardInterrupt
|
|
15
|
+
├── SystemExit
|
|
16
|
+
├── GeneratorExit
|
|
17
|
+
└── Exception
|
|
18
|
+
├── StopIteration
|
|
19
|
+
├── StopAsyncIteration
|
|
20
|
+
├── ArithmeticError
|
|
21
|
+
│ ├── ZeroDivisionError
|
|
22
|
+
│ └── ...
|
|
23
|
+
├── AssertionError
|
|
24
|
+
├── AttributeError
|
|
25
|
+
├── BufferError
|
|
26
|
+
├── EOFError
|
|
27
|
+
├── ImportError
|
|
28
|
+
│ └── ModuleNotFoundError
|
|
29
|
+
├── LookupError
|
|
30
|
+
│ ├── IndexError
|
|
31
|
+
│ └── KeyError
|
|
32
|
+
├── MemoryError
|
|
33
|
+
├── NameError
|
|
34
|
+
│ └── UnboundLocalError
|
|
35
|
+
├── OSError
|
|
36
|
+
│ ├── BlockingIOError
|
|
37
|
+
│ ├── ChildProcessError
|
|
38
|
+
│ ├── ConnectionError
|
|
39
|
+
│ ├── FileExistsError
|
|
40
|
+
│ ├── FileNotFoundError
|
|
41
|
+
│ ├── InterruptedError
|
|
42
|
+
│ ├── IsADirectoryError
|
|
43
|
+
│ ├── NotADirectoryError
|
|
44
|
+
│ ├── PermissionError
|
|
45
|
+
│ ├── ProcessLookupError
|
|
46
|
+
│ └── TimeoutError
|
|
47
|
+
├── ReferenceError
|
|
48
|
+
├── RuntimeError
|
|
49
|
+
│ ├── NotImplementedError
|
|
50
|
+
│ └── RecursionError
|
|
51
|
+
├── SyntaxError
|
|
52
|
+
│ └── IndentationError
|
|
53
|
+
├── SystemError
|
|
54
|
+
├── TypeError
|
|
55
|
+
├── ValueError
|
|
56
|
+
│ └── UnicodeError
|
|
57
|
+
└── Warning
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Catch Specific Exceptions
|
|
61
|
+
```python
|
|
62
|
+
# GOOD — specific
|
|
63
|
+
try:
|
|
64
|
+
data = json.loads(text)
|
|
65
|
+
except json.JSONDecodeError as e:
|
|
66
|
+
logger.error("Invalid JSON", extra={"error": str(e), "pos": e.pos})
|
|
67
|
+
raise ValueError("Invalid JSON input") from e
|
|
68
|
+
|
|
69
|
+
# GOOD — tuple of related
|
|
70
|
+
try:
|
|
71
|
+
response = requests.get(url, timeout=5)
|
|
72
|
+
except (requests.Timeout, requests.ConnectionError) as e:
|
|
73
|
+
logger.warning("Request failed", extra={"url": url, "error": str(e)})
|
|
74
|
+
raise ServiceUnavailable("Service unreachable") from e
|
|
75
|
+
|
|
76
|
+
# BAD — bare except
|
|
77
|
+
try:
|
|
78
|
+
...
|
|
79
|
+
except:
|
|
80
|
+
... # Catches KeyboardInterrupt, SystemExit!
|
|
81
|
+
|
|
82
|
+
# BAD — broad Exception
|
|
83
|
+
try:
|
|
84
|
+
...
|
|
85
|
+
except Exception:
|
|
86
|
+
... # Swallows everything, hard to debug
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Exception Chaining
|
|
90
|
+
```python
|
|
91
|
+
# Explicit chaining (preserves original traceback)
|
|
92
|
+
try:
|
|
93
|
+
risky()
|
|
94
|
+
except OriginalError as e:
|
|
95
|
+
raise NewError("Context") from e
|
|
96
|
+
|
|
97
|
+
# Implicit chaining (automatic when raising in except)
|
|
98
|
+
try:
|
|
99
|
+
risky()
|
|
100
|
+
except OriginalError:
|
|
101
|
+
raise NewError("Context") # __context__ set automatically
|
|
102
|
+
|
|
103
|
+
# Suppress chaining
|
|
104
|
+
try:
|
|
105
|
+
risky()
|
|
106
|
+
except OriginalError:
|
|
107
|
+
raise NewError("Context") from None
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Exceptions
|
|
111
|
+
```python
|
|
112
|
+
# Domain-specific exceptions
|
|
113
|
+
class DomainError(Exception):
|
|
114
|
+
"""Base for domain errors."""
|
|
115
|
+
def __init__(self, message: str, code: str, details: dict | None = None):
|
|
116
|
+
super().__init__(message)
|
|
117
|
+
self.code = code
|
|
118
|
+
self.details = details or {}
|
|
119
|
+
|
|
120
|
+
class ValidationError(DomainError):
|
|
121
|
+
def __init__(self, field: str, message: str):
|
|
122
|
+
super().__init__(message, "VALIDATION_ERROR", {"field": field})
|
|
123
|
+
|
|
124
|
+
class NotFoundError(DomainError):
|
|
125
|
+
def __init__(self, resource: str, id: str):
|
|
126
|
+
super().__init__(f"{resource} not found: {id}", "NOT_FOUND", {"resource": resource, "id": id})
|
|
127
|
+
|
|
128
|
+
class ConflictError(DomainError):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# Usage
|
|
132
|
+
raise ValidationError("email", "Invalid format")
|
|
133
|
+
raise NotFoundError("User", "123")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Error Categories (For Handling Strategy)
|
|
137
|
+
|
|
138
|
+
| Category | Examples | Handling |
|
|
139
|
+
|----------|----------|----------|
|
|
140
|
+
| Expected operational failure | `NotFoundError`, `ValidationError`, `ConflictError` | Handle explicitly, return error result |
|
|
141
|
+
| Programming bug | `AssertionError`, `TypeError`, `IndexError` | Let crash, fix code |
|
|
142
|
+
| Invalid external input | `ValueError`, `JSONDecodeError` | Validate early, return 400 |
|
|
143
|
+
| Configuration failure | `KeyError` (missing config), `ImproperlyConfigured` | Fail fast at startup |
|
|
144
|
+
| System failure | `OSError`, `MemoryError`, `ConnectionError` | Retry, circuit breaker, degrade |
|
|
145
|
+
|
|
146
|
+
### Result Pattern (Alternative to Exceptions)
|
|
147
|
+
```python
|
|
148
|
+
from dataclasses import dataclass
|
|
149
|
+
from typing import Generic, TypeVar
|
|
150
|
+
|
|
151
|
+
T = TypeVar("T")
|
|
152
|
+
E = TypeVar("E", bound=Exception)
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True)
|
|
155
|
+
class Result(Generic[T, E]):
|
|
156
|
+
value: T | None = None
|
|
157
|
+
error: E | None = None
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def is_ok(self) -> bool:
|
|
161
|
+
return self.error is None
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def is_err(self) -> bool:
|
|
165
|
+
return self.error is not None
|
|
166
|
+
|
|
167
|
+
def unwrap(self) -> T:
|
|
168
|
+
if self.error:
|
|
169
|
+
raise self.error
|
|
170
|
+
return self.value
|
|
171
|
+
|
|
172
|
+
def map[U](self, func: Callable[[T], U]) -> "Result[U, E]":
|
|
173
|
+
if self.error:
|
|
174
|
+
return Result(error=self.error)
|
|
175
|
+
try:
|
|
176
|
+
return Result(value=func(self.value))
|
|
177
|
+
except Exception as e:
|
|
178
|
+
return Result(error=e)
|
|
179
|
+
|
|
180
|
+
# Usage
|
|
181
|
+
def parse_int(s: str) -> Result[int, ValueError]:
|
|
182
|
+
try:
|
|
183
|
+
return Result(value=int(s))
|
|
184
|
+
except ValueError as e:
|
|
185
|
+
return Result(error=e)
|
|
186
|
+
|
|
187
|
+
result = parse_int("42")
|
|
188
|
+
if result.is_ok:
|
|
189
|
+
print(result.value)
|
|
190
|
+
else:
|
|
191
|
+
print(f"Error: {result.error}")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Decision Rules
|
|
197
|
+
|
|
198
|
+
| Situation | Pattern |
|
|
199
|
+
|-----------|---------|
|
|
200
|
+
| Expected failure (validation, not found) | Custom exception or `Result` |
|
|
201
|
+
| Programming bug | `assert` or let built-in raise |
|
|
202
|
+
| External API failure | Custom exception wrapping original |
|
|
203
|
+
| Retryable failure | Catch specific, retry with backoff |
|
|
204
|
+
| Cleanup needed | `try/finally` or context manager |
|
|
205
|
+
| Multiple independent failures | `ExceptionGroup` (3.11+) |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Preferred Patterns
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# Validation at boundaries
|
|
213
|
+
def create_user(data: dict) -> User:
|
|
214
|
+
# Validate input early
|
|
215
|
+
email = data.get("email")
|
|
216
|
+
if not email or "@" not in email:
|
|
217
|
+
raise ValidationError("email", "Invalid email format")
|
|
218
|
+
|
|
219
|
+
# Business logic
|
|
220
|
+
if user_repo.exists(email):
|
|
221
|
+
raise ConflictError("User already exists")
|
|
222
|
+
|
|
223
|
+
return user_repo.save(User(email=email))
|
|
224
|
+
|
|
225
|
+
# Graceful degradation
|
|
226
|
+
async def get_user_profile(user_id: str) -> UserProfile:
|
|
227
|
+
try:
|
|
228
|
+
return await user_service.get_profile(user_id)
|
|
229
|
+
except UserService.Unavailable:
|
|
230
|
+
# Fallback to cache
|
|
231
|
+
return await cache.get_profile(user_id)
|
|
232
|
+
except UserService.NotFound:
|
|
233
|
+
raise NotFoundError("User", user_id)
|
|
234
|
+
|
|
235
|
+
# Structured error responses (API)
|
|
236
|
+
def handle_error(e: Exception) -> ErrorResponse:
|
|
237
|
+
if isinstance(e, ValidationError):
|
|
238
|
+
return ErrorResponse(400, e.code, e.details)
|
|
239
|
+
if isinstance(e, NotFoundError):
|
|
240
|
+
return ErrorResponse(404, e.code, e.details)
|
|
241
|
+
if isinstance(e, ConflictError):
|
|
242
|
+
return ErrorResponse(409, e.code, e.details)
|
|
243
|
+
logger.exception("Unhandled error")
|
|
244
|
+
return ErrorResponse(500, "INTERNAL_ERROR", {})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Avoid
|
|
250
|
+
|
|
251
|
+
- Bare `except:` or `except Exception:`
|
|
252
|
+
- Swallowing exceptions silently
|
|
253
|
+
- Using exceptions for control flow (except EAFP)
|
|
254
|
+
- Custom exceptions without `__init__` for context
|
|
255
|
+
- Raising generic `Exception` or `RuntimeError`
|
|
256
|
+
- Losing original exception context (use `from e`)
|
|
257
|
+
- Catching `KeyboardInterrupt` / `SystemExit` (unless shutting down)
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Validation Considerations
|
|
262
|
+
|
|
263
|
+
- Test error paths explicitly
|
|
264
|
+
- Verify exception messages don't leak secrets
|
|
265
|
+
- Check logging includes context for debugging
|
|
266
|
+
- Ensure `Result` pattern doesn't hide errors
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Related Skills
|
|
271
|
+
|
|
272
|
+
- `generation/workflow.md`
|
|
273
|
+
- `generation/async_concurrency.md` (async errors)
|
|
274
|
+
- `security/input_validation.md`
|
|
275
|
+
- `testing/edge_cases.md`
|
|
276
|
+
- `quality/functions.md`
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Generation: Protocols and Generics
|
|
2
|
+
|
|
3
|
+
**Purpose**: Advanced typing with protocols, generics, and variance.
|
|
4
|
+
|
|
5
|
+
**When to use**: Designing flexible, decoupled interfaces and reusable components.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Protocols (Structural Subtyping)
|
|
12
|
+
```python
|
|
13
|
+
from typing import Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
# Basic protocol
|
|
16
|
+
class Serializable(Protocol):
|
|
17
|
+
def to_json(self) -> str: ...
|
|
18
|
+
def to_bytes(self) -> bytes: ...
|
|
19
|
+
|
|
20
|
+
# With generics
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
class Repository(Protocol[T]):
|
|
24
|
+
def get(self, id: str) -> T: ...
|
|
25
|
+
def save(self, entity: T) -> None: ...
|
|
26
|
+
def delete(self, id: str) -> bool: ...
|
|
27
|
+
|
|
28
|
+
# Runtime checkable (for isinstance)
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class Configurable(Protocol):
|
|
31
|
+
def configure(self, settings: dict) -> None: ...
|
|
32
|
+
|
|
33
|
+
# Multiple protocols
|
|
34
|
+
class Service(Repository[User], Configurable, Protocol):
|
|
35
|
+
def start(self) -> None: ...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Protocol vs ABC
|
|
39
|
+
| Feature | Protocol | ABC |
|
|
40
|
+
|---------|----------|-----|
|
|
41
|
+
| Inheritance required | No | Yes |
|
|
42
|
+
| Runtime isinstance | Only `@runtime_checkable` | Yes |
|
|
43
|
+
| Multiple implementations | Easy | Requires inheritance |
|
|
44
|
+
| Third-party classes | Works if structure matches | Must subclass |
|
|
45
|
+
| Method enforcement | Static only | Static + runtime |
|
|
46
|
+
|
|
47
|
+
**Prefer Protocol** for interfaces unless runtime checks needed.
|
|
48
|
+
|
|
49
|
+
### Generic Classes
|
|
50
|
+
```python
|
|
51
|
+
from typing import Generic, TypeVar
|
|
52
|
+
|
|
53
|
+
T = TypeVar("T")
|
|
54
|
+
K = TypeVar("K")
|
|
55
|
+
V = TypeVar("V")
|
|
56
|
+
|
|
57
|
+
class Box(Generic[T]):
|
|
58
|
+
def __init__(self, value: T) -> None:
|
|
59
|
+
self._value = value
|
|
60
|
+
|
|
61
|
+
def get(self) -> T:
|
|
62
|
+
return self._value
|
|
63
|
+
|
|
64
|
+
def map[U](self, func: Callable[[T], U]) -> Box[U]:
|
|
65
|
+
return Box(func(self._value))
|
|
66
|
+
|
|
67
|
+
# Multiple type vars
|
|
68
|
+
class Pair(Generic[T, U]):
|
|
69
|
+
def __init__(self, first: T, second: U) -> None:
|
|
70
|
+
self.first = first
|
|
71
|
+
self.second = second
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Variance (Covariance/Contravariance)
|
|
75
|
+
```python
|
|
76
|
+
from typing import TypeVar, Generic, Protocol
|
|
77
|
+
|
|
78
|
+
# Covariant (output only) — use +T
|
|
79
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
80
|
+
|
|
81
|
+
class Producer(Generic[T_co]):
|
|
82
|
+
def produce(self) -> T_co: ...
|
|
83
|
+
|
|
84
|
+
# Contravariant (input only) — use -T
|
|
85
|
+
T_contra = TypeVar("T_contra", contravariant=True)
|
|
86
|
+
|
|
87
|
+
class Consumer(Generic[T_contra]):
|
|
88
|
+
def consume(self, item: T_contra) -> None: ...
|
|
89
|
+
|
|
90
|
+
# Invariant (both) — default
|
|
91
|
+
T = TypeVar("T")
|
|
92
|
+
|
|
93
|
+
class Processor(Generic[T]):
|
|
94
|
+
def process(self, item: T) -> T: ...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Variance Rules
|
|
98
|
+
| Position | Variance |
|
|
99
|
+
|----------|----------|
|
|
100
|
+
| Return type | Covariant (+) |
|
|
101
|
+
| Argument type | Contravariant (-) |
|
|
102
|
+
| Mutable attribute | Invariant |
|
|
103
|
+
| Read-only property | Covariant |
|
|
104
|
+
|
|
105
|
+
### Protocol Variance
|
|
106
|
+
```python
|
|
107
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
108
|
+
|
|
109
|
+
class Readable(Protocol[T_co]):
|
|
110
|
+
def read(self) -> T_co: ... # Covariant OK
|
|
111
|
+
|
|
112
|
+
T_contra = TypeVar("T_contra", contravariant=True)
|
|
113
|
+
|
|
114
|
+
class Writable(Protocol[T_contra]):
|
|
115
|
+
def write(self, data: T_contra) -> None: ... # Contravariant OK
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Constrained TypeVars
|
|
119
|
+
```python
|
|
120
|
+
# Only these types allowed
|
|
121
|
+
T = TypeVar("T", int, float, str)
|
|
122
|
+
|
|
123
|
+
def process(x: T) -> T: # Only int, float, str accepted
|
|
124
|
+
return x
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Bound TypeVars
|
|
128
|
+
```python
|
|
129
|
+
# Must be subclass of bound
|
|
130
|
+
T = TypeVar("T", bound="BaseEntity")
|
|
131
|
+
|
|
132
|
+
class BaseEntity:
|
|
133
|
+
id: int
|
|
134
|
+
|
|
135
|
+
def save(entity: T) -> T: # Must be BaseEntity subclass
|
|
136
|
+
return entity
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Higher-Kinded Types (Simulated)
|
|
140
|
+
```python
|
|
141
|
+
from typing import TypeVar, Generic, Callable
|
|
142
|
+
|
|
143
|
+
# Functor-like
|
|
144
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
145
|
+
|
|
146
|
+
def map_func[F](func: Callable[[A], B], fa: F[A]) -> F[B]: # Not directly expressible
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
# Use protocols for typeclass-like patterns
|
|
150
|
+
class Functor(Protocol[T]):
|
|
151
|
+
def map[U](self, func: Callable[[T], U]) -> "Functor[U]": ...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Type Parameters (Python 3.12+)
|
|
155
|
+
```python
|
|
156
|
+
# New syntax
|
|
157
|
+
class Box[T]:
|
|
158
|
+
def __init__(self, value: T) -> None:
|
|
159
|
+
self.value = value
|
|
160
|
+
|
|
161
|
+
def get(self) -> T:
|
|
162
|
+
return self.value
|
|
163
|
+
|
|
164
|
+
def identity[T](x: T) -> T:
|
|
165
|
+
return x
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Decision Rules
|
|
171
|
+
|
|
172
|
+
| Need | Pattern |
|
|
173
|
+
|------|---------|
|
|
174
|
+
| Interface for duck typing | `Protocol` |
|
|
175
|
+
| Interface needing `isinstance` | `@runtime_checkable Protocol` |
|
|
176
|
+
| Reusable container | `Generic[T]` |
|
|
177
|
+
| Read-only collection | `Generic[+T]` (covariant) |
|
|
178
|
+
| Callback handler | `Generic[-T]` (contravariant) |
|
|
179
|
+
| Limited type set | `TypeVar("T", A, B, C)` |
|
|
180
|
+
| Subtype constraint | `TypeVar("T", bound=Base)` |
|
|
181
|
+
| Self-returning methods | `Self` (3.11+) |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Preferred Patterns
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Repository pattern with protocol
|
|
189
|
+
class UserRepo(Protocol):
|
|
190
|
+
def get(self, id: UserID) -> User | None: ...
|
|
191
|
+
def list(self, filter: UserFilter) -> list[User]: ...
|
|
192
|
+
def save(self, user: User) -> User: ...
|
|
193
|
+
|
|
194
|
+
# Generic service
|
|
195
|
+
class CrudService(Generic[T]):
|
|
196
|
+
def __init__(self, repo: Repository[T]) -> None:
|
|
197
|
+
self.repo = repo
|
|
198
|
+
|
|
199
|
+
def get_or_create(self, id: str, factory: Callable[[], T]) -> T:
|
|
200
|
+
if (existing := self.repo.get(id)) is not None:
|
|
201
|
+
return existing
|
|
202
|
+
new = factory()
|
|
203
|
+
self.repo.save(new)
|
|
204
|
+
return new
|
|
205
|
+
|
|
206
|
+
# Covariant read-only view
|
|
207
|
+
class ReadOnlyList(Generic[T_co]):
|
|
208
|
+
def __init__(self, items: list[T_co]) -> None:
|
|
209
|
+
self._items = tuple(items)
|
|
210
|
+
|
|
211
|
+
def __getitem__(self, i: int) -> T_co:
|
|
212
|
+
return self._items[i]
|
|
213
|
+
|
|
214
|
+
def __iter__(self) -> Iterator[T_co]:
|
|
215
|
+
return iter(self._items)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Avoid
|
|
221
|
+
|
|
222
|
+
- Overusing generics (YAGNI)
|
|
223
|
+
- Invariant generics when covariant/contravariant works
|
|
224
|
+
- `@runtime_checkable` on large protocols (performance)
|
|
225
|
+
- Complex variance without clear need
|
|
226
|
+
- Protocols with too many methods (split them)
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Validation Considerations
|
|
231
|
+
|
|
232
|
+
- `mypy --strict` catches variance errors
|
|
233
|
+
- Protocol conformance checked structurally
|
|
234
|
+
- `isinstance(obj, Protocol)` only works with `@runtime_checkable`
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Related Skills
|
|
239
|
+
|
|
240
|
+
- `generation/type_hints.md`
|
|
241
|
+
- `core/oop.md` (ABCs)
|
|
242
|
+
- `quality/abstractions.md`
|
|
243
|
+
- `engineering/dependency_management.md`
|