mirror of
https://github.com/duthaho/claudekit.git
synced 2026-06-10 20:24:57 +03:00
feat: improved the Claude Kit as a plugin
This commit is contained in:
@@ -0,0 +1,697 @@
|
||||
# Languages — Python Patterns
|
||||
|
||||
|
||||
# Python
|
||||
|
||||
## When to Use
|
||||
|
||||
- Working with Python files (.py)
|
||||
- Writing Python scripts or applications
|
||||
- Using Python frameworks (Django, FastAPI, Flask)
|
||||
- Data processing and automation
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
- JavaScript or TypeScript-only projects with no Python components
|
||||
- Non-Python environments where another language skill is more appropriate
|
||||
|
||||
---
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### 1. Type Hints
|
||||
|
||||
Use type hints on all public functions and module-level variables. Python 3.10+ syntax is preferred (use `X | Y` instead of `Union[X, Y]`).
|
||||
|
||||
#### Basic Types
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}"
|
||||
|
||||
def process(count: int, factor: float = 1.0) -> float:
|
||||
return count * factor
|
||||
|
||||
def is_valid(data: bytes | None) -> bool:
|
||||
return data is not None and len(data) > 0
|
||||
```
|
||||
|
||||
#### Optional and Union
|
||||
|
||||
```python
|
||||
# Python 3.10+ syntax (preferred)
|
||||
def find_user(user_id: int) -> User | None:
|
||||
...
|
||||
|
||||
# Pre-3.10 fallback
|
||||
from typing import Optional, Union
|
||||
|
||||
def find_user(user_id: int) -> Optional[User]:
|
||||
...
|
||||
|
||||
def parse_input(value: Union[str, int]) -> str:
|
||||
return str(value)
|
||||
```
|
||||
|
||||
#### Generic Collections
|
||||
|
||||
```python
|
||||
# Python 3.9+ built-in generics (preferred)
|
||||
def process_items(items: list[str]) -> dict[str, int]:
|
||||
return {item: len(item) for item in items}
|
||||
|
||||
def merge_configs(base: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]:
|
||||
return {**base, **overrides}
|
||||
|
||||
# Nested generics
|
||||
def group_by_key(pairs: list[tuple[str, int]]) -> dict[str, list[int]]:
|
||||
result: dict[str, list[int]] = {}
|
||||
for key, value in pairs:
|
||||
result.setdefault(key, []).append(value)
|
||||
return result
|
||||
```
|
||||
|
||||
#### Protocol for Structural Subtyping
|
||||
|
||||
```python
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
@runtime_checkable
|
||||
class Renderable(Protocol):
|
||||
def render(self) -> str: ...
|
||||
|
||||
class HtmlWidget:
|
||||
def render(self) -> str:
|
||||
return "<div>widget</div>"
|
||||
|
||||
def display(item: Renderable) -> None:
|
||||
print(item.render())
|
||||
|
||||
# HtmlWidget satisfies Renderable without inheriting from it
|
||||
display(HtmlWidget()) # works
|
||||
```
|
||||
|
||||
#### TypeVar for Generic Functions
|
||||
|
||||
```python
|
||||
from typing import TypeVar, Sequence
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def first(items: Sequence[T]) -> T:
|
||||
return items[0]
|
||||
|
||||
# Bounded TypeVar
|
||||
Numeric = TypeVar("Numeric", int, float)
|
||||
|
||||
def clamp(value: Numeric, low: Numeric, high: Numeric) -> Numeric:
|
||||
return max(low, min(high, value))
|
||||
```
|
||||
|
||||
#### @overload for Multiple Signatures
|
||||
|
||||
```python
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def parse(raw: str) -> dict[str, Any]: ...
|
||||
@overload
|
||||
def parse(raw: bytes) -> dict[str, Any]: ...
|
||||
@overload
|
||||
def parse(raw: str, as_list: bool) -> list[Any]: ...
|
||||
|
||||
def parse(raw: str | bytes, as_list: bool = False) -> dict[str, Any] | list[Any]:
|
||||
data = raw if isinstance(raw, str) else raw.decode()
|
||||
parsed = json.loads(data)
|
||||
return list(parsed) if as_list else parsed
|
||||
```
|
||||
|
||||
#### TypeAlias and TypeGuard
|
||||
|
||||
```python
|
||||
from typing import TypeAlias, TypeGuard
|
||||
|
||||
# TypeAlias for complex types
|
||||
JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]
|
||||
Headers: TypeAlias = dict[str, str]
|
||||
|
||||
# TypeGuard for narrowing
|
||||
def is_string_list(val: list[Any]) -> TypeGuard[list[str]]:
|
||||
return all(isinstance(item, str) for item in val)
|
||||
|
||||
def process(items: list[Any]) -> None:
|
||||
if is_string_list(items):
|
||||
# items is now list[str] inside this branch
|
||||
print(", ".join(items))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Dataclasses & Pydantic
|
||||
|
||||
#### @dataclass with Options
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
email: str
|
||||
name: str
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
tags: list[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
self.email = self.email.strip().lower()
|
||||
```
|
||||
|
||||
#### Frozen and Slots
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Coordinate:
|
||||
"""Immutable, memory-efficient value object."""
|
||||
x: float
|
||||
y: float
|
||||
|
||||
@property
|
||||
def magnitude(self) -> float:
|
||||
return (self.x ** 2 + self.y ** 2) ** 0.5
|
||||
```
|
||||
|
||||
#### Pydantic BaseModel
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator, computed_field, model_validator
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
model_config = {"str_strip_whitespace": True, "frozen": False}
|
||||
|
||||
email: EmailStr
|
||||
name: str = Field(min_length=1, max_length=100)
|
||||
password: str = Field(min_length=8)
|
||||
age: int = Field(ge=0, le=150)
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def name_must_not_be_blank(cls, v: str) -> str:
|
||||
if not v.strip():
|
||||
raise ValueError("Name must not be blank")
|
||||
return v.title()
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return f"{self.name} <{self.email}>"
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_consistency(self) -> "UserCreate":
|
||||
if "admin" in self.name.lower() and self.age < 18:
|
||||
raise ValueError("Admins must be 18+")
|
||||
return self
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Async Patterns
|
||||
|
||||
#### Basic async/await
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
async def fetch_json(url: str) -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
```
|
||||
|
||||
#### asyncio.gather for Parallel Work
|
||||
|
||||
```python
|
||||
async def fetch_all(urls: list[str]) -> list[dict]:
|
||||
return await asyncio.gather(*[fetch_json(url) for url in urls])
|
||||
```
|
||||
|
||||
#### asyncio.TaskGroup (Python 3.11+)
|
||||
|
||||
```python
|
||||
async def fetch_all_safe(urls: list[str]) -> list[dict]:
|
||||
results: list[dict] = []
|
||||
async with asyncio.TaskGroup() as tg:
|
||||
for url in urls:
|
||||
tg.create_task(fetch_and_append(url, results))
|
||||
return results
|
||||
|
||||
async def fetch_and_append(url: str, results: list[dict]) -> None:
|
||||
data = await fetch_json(url)
|
||||
results.append(data)
|
||||
```
|
||||
|
||||
#### Async Generators
|
||||
|
||||
```python
|
||||
async def paginate(url: str) -> AsyncIterator[dict]:
|
||||
page = 1
|
||||
while True:
|
||||
data = await fetch_json(f"{url}?page={page}")
|
||||
if not data["items"]:
|
||||
break
|
||||
for item in data["items"]:
|
||||
yield item
|
||||
page += 1
|
||||
|
||||
# Usage
|
||||
async for item in paginate("/api/users"):
|
||||
process(item)
|
||||
```
|
||||
|
||||
#### Async Context Managers
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def db_transaction(pool):
|
||||
conn = await pool.acquire()
|
||||
tx = await conn.begin()
|
||||
try:
|
||||
yield conn
|
||||
await tx.commit()
|
||||
except Exception:
|
||||
await tx.rollback()
|
||||
raise
|
||||
finally:
|
||||
await pool.release(conn)
|
||||
```
|
||||
|
||||
#### Semaphores for Concurrency Limiting
|
||||
|
||||
```python
|
||||
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10) -> list[dict]:
|
||||
semaphore = asyncio.Semaphore(max_concurrent)
|
||||
|
||||
async def limited_fetch(url: str) -> dict:
|
||||
async with semaphore:
|
||||
return await fetch_json(url)
|
||||
|
||||
return await asyncio.gather(*[limited_fetch(url) for url in urls])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Decorators
|
||||
|
||||
#### Function Decorator with functools.wraps
|
||||
|
||||
```python
|
||||
import functools
|
||||
import time
|
||||
|
||||
def timing(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
start = time.perf_counter()
|
||||
result = func(*args, **kwargs)
|
||||
elapsed = time.perf_counter() - start
|
||||
print(f"{func.__name__} took {elapsed:.4f}s")
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
@timing
|
||||
def slow_operation():
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
#### Decorator with Arguments
|
||||
|
||||
```python
|
||||
def retry(max_attempts: int = 3, delay: float = 1.0):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
last_error: Exception | None = None
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
if attempt < max_attempts - 1:
|
||||
await asyncio.sleep(delay * (2 ** attempt))
|
||||
raise last_error
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@retry(max_attempts=5, delay=0.5)
|
||||
async def unreliable_call(url: str) -> dict:
|
||||
return await fetch_json(url)
|
||||
```
|
||||
|
||||
#### Class Decorator
|
||||
|
||||
```python
|
||||
def singleton(cls):
|
||||
instances: dict[type, Any] = {}
|
||||
|
||||
@functools.wraps(cls)
|
||||
def get_instance(*args, **kwargs):
|
||||
if cls not in instances:
|
||||
instances[cls] = cls(*args, **kwargs)
|
||||
return instances[cls]
|
||||
|
||||
return get_instance
|
||||
|
||||
@singleton
|
||||
class AppConfig:
|
||||
def __init__(self):
|
||||
self.settings = load_settings()
|
||||
```
|
||||
|
||||
#### Caching Decorator
|
||||
|
||||
```python
|
||||
from functools import lru_cache, cache
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def fibonacci(n: int) -> int:
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n - 1) + fibonacci(n - 2)
|
||||
|
||||
# Python 3.9+ unbounded cache
|
||||
@cache
|
||||
def load_config(path: str) -> dict:
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Context Managers
|
||||
|
||||
#### Basic @contextmanager
|
||||
|
||||
```python
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def managed_connection(dsn: str):
|
||||
conn = connect(dsn)
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
with managed_connection("postgres://...") as conn:
|
||||
conn.execute("SELECT 1")
|
||||
```
|
||||
|
||||
#### Temporary File Context Manager
|
||||
|
||||
```python
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
@contextmanager
|
||||
def temp_directory():
|
||||
dirpath = tempfile.mkdtemp()
|
||||
try:
|
||||
yield dirpath
|
||||
finally:
|
||||
shutil.rmtree(dirpath)
|
||||
|
||||
with temp_directory() as tmpdir:
|
||||
filepath = os.path.join(tmpdir, "data.json")
|
||||
write_json(filepath, data)
|
||||
```
|
||||
|
||||
#### Lock Context Manager
|
||||
|
||||
```python
|
||||
import threading
|
||||
|
||||
@contextmanager
|
||||
def timed_lock(lock: threading.Lock, timeout: float = 5.0):
|
||||
acquired = lock.acquire(timeout=timeout)
|
||||
if not acquired:
|
||||
raise TimeoutError("Could not acquire lock")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
lock.release()
|
||||
```
|
||||
|
||||
#### Async Context Manager
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def http_session():
|
||||
session = aiohttp.ClientSession()
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Pattern Matching
|
||||
|
||||
#### Basic match/case
|
||||
|
||||
```python
|
||||
def handle_command(command: str) -> str:
|
||||
match command.split():
|
||||
case ["quit"]:
|
||||
return "Goodbye"
|
||||
case ["hello", name]:
|
||||
return f"Hello, {name}"
|
||||
case ["add", *items]:
|
||||
return f"Adding {len(items)} items"
|
||||
case _:
|
||||
return "Unknown command"
|
||||
```
|
||||
|
||||
#### Structural Patterns
|
||||
|
||||
```python
|
||||
def process_event(event: dict) -> None:
|
||||
match event:
|
||||
case {"type": "click", "x": int(x), "y": int(y)}:
|
||||
handle_click(x, y)
|
||||
case {"type": "keypress", "key": str(key)} if len(key) == 1:
|
||||
handle_keypress(key)
|
||||
case {"type": "resize", "width": w, "height": h}:
|
||||
handle_resize(w, h)
|
||||
```
|
||||
|
||||
#### Guard Clauses and OR Patterns
|
||||
|
||||
```python
|
||||
def classify_status(code: int) -> str:
|
||||
match code:
|
||||
case 200 | 201 | 204:
|
||||
return "success"
|
||||
case code if 300 <= code < 400:
|
||||
return "redirect"
|
||||
case 401 | 403:
|
||||
return "auth_error"
|
||||
case code if 400 <= code < 500:
|
||||
return "client_error"
|
||||
case code if 500 <= code < 600:
|
||||
return "server_error"
|
||||
case _:
|
||||
return "unknown"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Error Handling
|
||||
|
||||
#### Custom Exception Hierarchies
|
||||
|
||||
```python
|
||||
class AppError(Exception):
|
||||
"""Base exception for the application."""
|
||||
def __init__(self, message: str, code: str | None = None):
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
class NotFoundError(AppError):
|
||||
"""Resource was not found."""
|
||||
def __init__(self, resource: str, resource_id: str):
|
||||
super().__init__(f"{resource} {resource_id} not found", code="NOT_FOUND")
|
||||
self.resource = resource
|
||||
self.resource_id = resource_id
|
||||
|
||||
class ValidationError(AppError):
|
||||
"""Input validation failed."""
|
||||
def __init__(self, errors: list[str]):
|
||||
super().__init__(f"Validation failed: {'; '.join(errors)}", code="VALIDATION")
|
||||
self.errors = errors
|
||||
```
|
||||
|
||||
#### ExceptionGroup (Python 3.11+)
|
||||
|
||||
```python
|
||||
async def process_batch(items: list[dict]) -> list[dict]:
|
||||
results = []
|
||||
errors = []
|
||||
for item in items:
|
||||
try:
|
||||
results.append(await process(item))
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
if errors:
|
||||
raise ExceptionGroup("Batch processing errors", errors)
|
||||
return results
|
||||
|
||||
# Handling with except*
|
||||
try:
|
||||
await process_batch(items)
|
||||
except* ValueError as eg:
|
||||
print(f"Validation errors: {len(eg.exceptions)}")
|
||||
except* ConnectionError as eg:
|
||||
print(f"Connection errors: {len(eg.exceptions)}")
|
||||
```
|
||||
|
||||
#### Exception Chaining
|
||||
|
||||
```python
|
||||
def load_config(path: str) -> dict:
|
||||
try:
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError as e:
|
||||
raise AppError(f"Config file missing: {path}") from e
|
||||
except json.JSONDecodeError as e:
|
||||
raise AppError(f"Invalid JSON in {path}") from e
|
||||
```
|
||||
|
||||
#### contextlib.suppress
|
||||
|
||||
```python
|
||||
from contextlib import suppress
|
||||
|
||||
# Instead of try/except/pass
|
||||
with suppress(FileNotFoundError):
|
||||
os.remove("temp_file.txt")
|
||||
|
||||
# Instead of:
|
||||
# try:
|
||||
# os.remove("temp_file.txt")
|
||||
# except FileNotFoundError:
|
||||
# pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use type hints on all public functions** -- they serve as documentation, enable IDE autocompletion, and allow static analysis with mypy or pyright.
|
||||
|
||||
2. **Prefer dataclasses or Pydantic for structured data** -- avoid passing raw dicts around. Use `@dataclass` for internal data, Pydantic `BaseModel` for external boundaries (API input/output, config files).
|
||||
|
||||
3. **Use context managers for resource management** -- database connections, file handles, locks, and temporary resources should always be wrapped in `with` statements to guarantee cleanup.
|
||||
|
||||
4. **Prefer `asyncio.TaskGroup` over bare `gather`** -- TaskGroup (3.11+) provides proper error handling by cancelling sibling tasks when one fails, avoiding orphaned coroutines.
|
||||
|
||||
5. **Follow PEP 8 and use a formatter** -- use `ruff format` or `black` for consistent formatting, and `ruff check` for linting. Configure in `pyproject.toml`.
|
||||
|
||||
6. **Write small, composable functions** -- each function should do one thing. Prefer returning values over mutating state. Limit functions to ~20 lines when practical.
|
||||
|
||||
7. **Use `__all__` in public modules** -- explicitly declare the public API of a module to prevent accidental imports of internal helpers.
|
||||
|
||||
8. **Use `pathlib.Path` over `os.path`** -- pathlib provides a cleaner, object-oriented API for file system operations and works cross-platform.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Mutable default arguments** -- default values are shared across calls. Use `None` and initialize inside the function body.
|
||||
```python
|
||||
# BAD
|
||||
def add_item(item: str, items: list[str] = []) -> list[str]: ...
|
||||
|
||||
# GOOD
|
||||
def add_item(item: str, items: list[str] | None = None) -> list[str]:
|
||||
if items is None:
|
||||
items = []
|
||||
items.append(item)
|
||||
return items
|
||||
```
|
||||
|
||||
2. **Blocking calls inside async functions** -- calling `time.sleep()`, `requests.get()`, or CPU-heavy code in an async function blocks the entire event loop. Use `asyncio.to_thread()` or `asyncio.sleep()`.
|
||||
```python
|
||||
# BAD
|
||||
async def fetch():
|
||||
return requests.get(url) # blocks event loop
|
||||
|
||||
# GOOD
|
||||
async def fetch():
|
||||
return await asyncio.to_thread(requests.get, url)
|
||||
```
|
||||
|
||||
3. **Catching bare `Exception`** -- always be specific about which exceptions you catch. Bare `except:` or `except Exception:` hides bugs.
|
||||
```python
|
||||
# BAD
|
||||
try:
|
||||
result = compute()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# GOOD
|
||||
try:
|
||||
result = compute()
|
||||
except (ValueError, TypeError) as e:
|
||||
logger.warning("Computation failed: %s", e)
|
||||
result = default_value
|
||||
```
|
||||
|
||||
4. **Using `is` for value comparison** -- `is` checks identity, not equality. Only use `is` for `None`, `True`, `False`, and sentinel objects.
|
||||
```python
|
||||
# BAD
|
||||
if x is 42: ...
|
||||
|
||||
# GOOD
|
||||
if x == 42: ...
|
||||
if x is None: ...
|
||||
```
|
||||
|
||||
5. **Forgetting to close resources** -- file handles, database connections, and HTTP sessions leak if not closed. Always use context managers.
|
||||
```python
|
||||
# BAD
|
||||
f = open("data.txt")
|
||||
data = f.read()
|
||||
|
||||
# GOOD
|
||||
with open("data.txt") as f:
|
||||
data = f.read()
|
||||
```
|
||||
|
||||
6. **Circular imports** -- restructure code to avoid circular dependencies. Move shared types into a separate module, use `TYPE_CHECKING` for type-only imports, or use lazy imports.
|
||||
```python
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import User # only imported during type checking
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `typescript` -- TypeScript language patterns for polyglot projects
|
||||
- `fastapi` -- FastAPI web framework built on Python
|
||||
- `django` -- Django web framework for Python
|
||||
- `pytest` -- Python testing with pytest
|
||||
- `error-handling` -- Python error handling and exception hierarchies
|
||||
Reference in New Issue
Block a user