agentic-team-templates 0.12.1 → 0.13.1

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.
@@ -0,0 +1,240 @@
1
+ # Python Tooling
2
+
3
+ Modern Python tooling is fast, integrated, and opinionated. Use it all.
4
+
5
+ ## Project Configuration
6
+
7
+ ### pyproject.toml (Single Source of Truth)
8
+
9
+ ```toml
10
+ [project]
11
+ name = "mypackage"
12
+ version = "1.0.0"
13
+ requires-python = ">=3.12"
14
+ dependencies = [
15
+ "fastapi>=0.110",
16
+ "pydantic>=2.0",
17
+ "sqlalchemy>=2.0",
18
+ "httpx>=0.27",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "pytest>=8.0",
24
+ "pytest-asyncio>=0.23",
25
+ "pytest-cov>=5.0",
26
+ "mypy>=1.10",
27
+ "ruff>=0.4",
28
+ "pre-commit>=3.7",
29
+ ]
30
+
31
+ [project.scripts]
32
+ myapp = "mypackage.cli:main"
33
+
34
+ [build-system]
35
+ requires = ["hatchling"]
36
+ build-backend = "hatchling.build"
37
+ ```
38
+
39
+ ## uv (Package Manager)
40
+
41
+ ```bash
42
+ # uv is the modern Python package manager — fast, reliable, replaces pip/pip-tools/venv
43
+ uv venv # Create virtual environment
44
+ uv pip install -e ".[dev]" # Install with dev dependencies
45
+ uv pip compile pyproject.toml -o requirements.lock # Lock dependencies
46
+ uv run pytest # Run in the project's environment
47
+ uv tool install ruff # Install CLI tools globally
48
+ ```
49
+
50
+ ## Ruff (Linter + Formatter)
51
+
52
+ ```toml
53
+ # pyproject.toml
54
+ [tool.ruff]
55
+ target-version = "py312"
56
+ line-length = 100
57
+
58
+ [tool.ruff.lint]
59
+ select = [
60
+ "E", # pycodestyle errors
61
+ "W", # pycodestyle warnings
62
+ "F", # pyflakes
63
+ "I", # isort
64
+ "N", # pep8-naming
65
+ "UP", # pyupgrade
66
+ "B", # flake8-bugbear
67
+ "SIM", # flake8-simplify
68
+ "TCH", # flake8-type-checking
69
+ "RUF", # ruff-specific rules
70
+ "S", # flake8-bandit (security)
71
+ "DTZ", # flake8-datetimez
72
+ "PT", # flake8-pytest-style
73
+ "ERA", # eradicate (commented-out code)
74
+ "ARG", # flake8-unused-arguments
75
+ "PTH", # flake8-use-pathlib
76
+ "PERF", # perflint
77
+ ]
78
+ ignore = [
79
+ "E501", # line length handled by formatter
80
+ ]
81
+
82
+ [tool.ruff.lint.per-file-ignores]
83
+ "tests/**" = ["S101"] # Allow assert in tests
84
+
85
+ [tool.ruff.format]
86
+ quote-style = "double"
87
+ indent-style = "space"
88
+ ```
89
+
90
+ ```bash
91
+ ruff check . # Lint
92
+ ruff check --fix . # Lint with auto-fix
93
+ ruff format . # Format
94
+ ruff format --check . # Check formatting
95
+ ```
96
+
97
+ ## mypy
98
+
99
+ ```toml
100
+ [tool.mypy]
101
+ python_version = "3.12"
102
+ strict = true
103
+ warn_return_any = true
104
+ warn_unused_configs = true
105
+ ```
106
+
107
+ ```bash
108
+ mypy . # Type check everything
109
+ mypy --strict . # Strictest mode
110
+ ```
111
+
112
+ ## pytest
113
+
114
+ ```toml
115
+ [tool.pytest.ini_options]
116
+ testpaths = ["tests"]
117
+ asyncio_mode = "auto"
118
+ addopts = [
119
+ "-ra", # Show summary of all non-passing tests
120
+ "--strict-markers", # Undefined markers are errors
121
+ "--strict-config", # Warn about unknown config options
122
+ "-x", # Stop on first failure during development
123
+ ]
124
+ markers = [
125
+ "integration: marks integration tests",
126
+ "slow: marks slow tests",
127
+ ]
128
+ ```
129
+
130
+ ```bash
131
+ pytest # Run all tests
132
+ pytest tests/unit/ # Run subset
133
+ pytest -k "test_create" # Run by name pattern
134
+ pytest --cov=mypackage --cov-report=html # Coverage
135
+ pytest -m "not integration" # Skip integration tests
136
+ ```
137
+
138
+ ## Coverage
139
+
140
+ ```toml
141
+ [tool.coverage.run]
142
+ source = ["src/mypackage"]
143
+ branch = true
144
+
145
+ [tool.coverage.report]
146
+ fail_under = 80
147
+ show_missing = true
148
+ exclude_lines = [
149
+ "pragma: no cover",
150
+ "if TYPE_CHECKING:",
151
+ "if __name__ == .__main__.",
152
+ "@overload",
153
+ "raise NotImplementedError",
154
+ ]
155
+ ```
156
+
157
+ ## Pre-commit
158
+
159
+ ```yaml
160
+ # .pre-commit-config.yaml
161
+ repos:
162
+ - repo: https://github.com/astral-sh/ruff-pre-commit
163
+ rev: v0.4.0
164
+ hooks:
165
+ - id: ruff
166
+ args: [--fix]
167
+ - id: ruff-format
168
+
169
+ - repo: https://github.com/pre-commit/mirrors-mypy
170
+ rev: v1.10.0
171
+ hooks:
172
+ - id: mypy
173
+ additional_dependencies: [types-requests]
174
+ ```
175
+
176
+ ## Makefile
177
+
178
+ ```makefile
179
+ .PHONY: check test lint format typecheck
180
+
181
+ check: format lint typecheck test
182
+
183
+ format:
184
+ ruff format .
185
+
186
+ lint:
187
+ ruff check .
188
+
189
+ typecheck:
190
+ mypy .
191
+
192
+ test:
193
+ pytest --cov=src/mypackage --cov-report=term-missing
194
+
195
+ test-all:
196
+ pytest -m "" --cov=src/mypackage
197
+
198
+ clean:
199
+ find . -type d -name __pycache__ -exec rm -rf {} +
200
+ find . -type f -name "*.pyc" -delete
201
+ rm -rf .pytest_cache .mypy_cache .ruff_cache dist build *.egg-info
202
+ ```
203
+
204
+ ## CI/CD
205
+
206
+ ```yaml
207
+ # .github/workflows/ci.yml
208
+ name: CI
209
+ on: [push, pull_request]
210
+
211
+ jobs:
212
+ check:
213
+ runs-on: ubuntu-latest
214
+ strategy:
215
+ matrix:
216
+ python-version: ["3.12", "3.13"]
217
+ steps:
218
+ - uses: actions/checkout@v4
219
+ - uses: astral-sh/setup-uv@v3
220
+ - run: uv venv && uv pip install -e ".[dev]"
221
+ - run: ruff format --check .
222
+ - run: ruff check .
223
+ - run: mypy .
224
+ - run: pytest --cov --cov-report=xml
225
+
226
+ security:
227
+ runs-on: ubuntu-latest
228
+ steps:
229
+ - uses: actions/checkout@v4
230
+ - run: pip install pip-audit
231
+ - run: pip-audit .
232
+ ```
233
+
234
+ ## Dependency Hygiene
235
+
236
+ - **Pin for applications**: exact versions in lock file
237
+ - **Range for libraries**: `>=1.0,<2.0` in pyproject.toml
238
+ - **Audit regularly**: `pip-audit` for security advisories
239
+ - **Minimize dependencies**: every dep is attack surface and maintenance burden
240
+ - **Prefer stdlib**: `pathlib` over `os.path`, `dataclasses` over attrs (for simple cases), `tomllib` over `toml`
@@ -0,0 +1,203 @@
1
+ # Python Type System
2
+
3
+ Modern Python is typed Python. Type hints enable tooling, prevent bugs, and serve as living documentation. `mypy --strict` is the baseline.
4
+
5
+ ## Core Typing
6
+
7
+ ### Function Signatures
8
+
9
+ ```python
10
+ from collections.abc import Sequence, Mapping, Callable, Awaitable, Iterator
11
+ from typing import Any, TypeVar, overload
12
+
13
+ # All parameters and return types annotated
14
+ def process_items(
15
+ items: Sequence[Item],
16
+ *,
17
+ transform: Callable[[Item], Result],
18
+ max_retries: int = 3,
19
+ ) -> list[Result]:
20
+ ...
21
+
22
+ # Use None return type explicitly
23
+ def log_event(event: Event) -> None:
24
+ logger.info("event", extra={"event": event})
25
+
26
+ # Async functions
27
+ async def fetch_user(user_id: str) -> User | None:
28
+ ...
29
+ ```
30
+
31
+ ### Modern Syntax (3.10+)
32
+
33
+ ```python
34
+ # Union types with |
35
+ def parse(value: str | int) -> Data:
36
+ ...
37
+
38
+ # Optional is just T | None
39
+ def find_user(email: str) -> User | None:
40
+ ...
41
+
42
+ # Built-in generics — no need to import List, Dict, etc.
43
+ names: list[str] = []
44
+ config: dict[str, Any] = {}
45
+ coordinates: tuple[float, float] = (0.0, 0.0)
46
+ ```
47
+
48
+ ### TypeVar and Generics
49
+
50
+ ```python
51
+ from typing import TypeVar, Generic
52
+
53
+ T = TypeVar("T")
54
+
55
+ # Generic container
56
+ class Stack(Generic[T]):
57
+ def __init__(self) -> None:
58
+ self._items: list[T] = []
59
+
60
+ def push(self, item: T) -> None:
61
+ self._items.append(item)
62
+
63
+ def pop(self) -> T:
64
+ if not self._items:
65
+ raise IndexError("pop from empty stack")
66
+ return self._items.pop()
67
+
68
+ # Bounded TypeVar
69
+ from typing import SupportsFloat
70
+ N = TypeVar("N", bound=SupportsFloat)
71
+
72
+ def average(values: Sequence[N]) -> float:
73
+ return sum(float(v) for v in values) / len(values)
74
+
75
+ # Python 3.12+ syntax
76
+ def first[T](items: Sequence[T]) -> T:
77
+ return items[0]
78
+
79
+ class Stack[T]:
80
+ ...
81
+ ```
82
+
83
+ ### Protocol (Structural Subtyping)
84
+
85
+ ```python
86
+ from typing import Protocol, runtime_checkable
87
+
88
+ # Define what you need, not what you depend on
89
+ class Renderable(Protocol):
90
+ def render(self) -> str: ...
91
+
92
+ class HTMLWidget:
93
+ def render(self) -> str:
94
+ return "<div>widget</div>"
95
+
96
+ # HTMLWidget satisfies Renderable without inheriting from it
97
+ def display(item: Renderable) -> None:
98
+ print(item.render())
99
+
100
+ display(HTMLWidget()) # Works — structural match
101
+
102
+ # runtime_checkable for isinstance() checks
103
+ @runtime_checkable
104
+ class Closeable(Protocol):
105
+ def close(self) -> None: ...
106
+
107
+ if isinstance(resource, Closeable):
108
+ resource.close()
109
+ ```
110
+
111
+ ### TypedDict
112
+
113
+ ```python
114
+ from typing import TypedDict, Required, NotRequired
115
+
116
+ class UserDict(TypedDict):
117
+ id: str
118
+ email: str
119
+ name: Required[str]
120
+ bio: NotRequired[str]
121
+
122
+ # Useful for JSON responses, config dicts, and legacy code
123
+ # Prefer dataclasses/Pydantic for new code
124
+ ```
125
+
126
+ ### Literal and Final
127
+
128
+ ```python
129
+ from typing import Literal, Final
130
+
131
+ # Restrict to specific values
132
+ def set_log_level(level: Literal["debug", "info", "warn", "error"]) -> None:
133
+ ...
134
+
135
+ # Constants that shouldn't be reassigned
136
+ MAX_RETRIES: Final = 3
137
+ API_VERSION: Final[str] = "v2"
138
+ ```
139
+
140
+ ### Overloads
141
+
142
+ ```python
143
+ from typing import overload
144
+
145
+ @overload
146
+ def get(key: str, default: None = None) -> str | None: ...
147
+ @overload
148
+ def get(key: str, default: str) -> str: ...
149
+
150
+ def get(key: str, default: str | None = None) -> str | None:
151
+ value = store.get(key)
152
+ return value if value is not None else default
153
+ ```
154
+
155
+ ## mypy Configuration
156
+
157
+ ```toml
158
+ # pyproject.toml
159
+ [tool.mypy]
160
+ python_version = "3.12"
161
+ strict = true
162
+ warn_return_any = true
163
+ warn_unused_configs = true
164
+ disallow_untyped_defs = true
165
+ disallow_incomplete_defs = true
166
+ check_untyped_defs = true
167
+ no_implicit_optional = true
168
+ warn_redundant_casts = true
169
+ warn_unused_ignores = true
170
+
171
+ # Per-module overrides for third-party libs without stubs
172
+ [[tool.mypy.overrides]]
173
+ module = "some_untyped_lib.*"
174
+ ignore_missing_imports = true
175
+ ```
176
+
177
+ ## Anti-Patterns
178
+
179
+ ```python
180
+ # Never: Any as a crutch
181
+ def process(data: Any) -> Any: # This is just untyped Python with extra steps
182
+ ...
183
+
184
+ # Never: type: ignore without explanation
185
+ result = sketchy_call() # type: ignore
186
+ # Better:
187
+ result = sketchy_call() # type: ignore[no-untyped-call] # library lacks stubs
188
+
189
+ # Never: Mutable default in typed signatures
190
+ def append_to(item: str, target: list[str] = []) -> list[str]: # BUG
191
+ ...
192
+ # Fix:
193
+ def append_to(item: str, target: list[str] | None = None) -> list[str]:
194
+ if target is None:
195
+ target = []
196
+ target.append(item)
197
+ return target
198
+
199
+ # Never: cast() to lie to the type checker
200
+ from typing import cast
201
+ user = cast(User, random_dict) # This doesn't validate anything at runtime
202
+ # Use Pydantic or manual validation instead
203
+ ```
@@ -0,0 +1,231 @@
1
+ # Python Web and APIs
2
+
3
+ Patterns for building production-grade web services and APIs in Python.
4
+
5
+ ## FastAPI
6
+
7
+ ### Application Structure
8
+
9
+ ```python
10
+ from fastapi import FastAPI, Depends, HTTPException, status
11
+ from contextlib import asynccontextmanager
12
+
13
+ @asynccontextmanager
14
+ async def lifespan(app: FastAPI):
15
+ # Startup
16
+ app.state.db = await create_pool(settings.database_url)
17
+ yield
18
+ # Shutdown
19
+ await app.state.db.close()
20
+
21
+ app = FastAPI(
22
+ title="My Service",
23
+ version="1.0.0",
24
+ lifespan=lifespan,
25
+ )
26
+ ```
27
+
28
+ ### Dependency Injection
29
+
30
+ ```python
31
+ from fastapi import Depends
32
+ from typing import Annotated
33
+
34
+ async def get_db(request: Request) -> AsyncGenerator[Database, None]:
35
+ async with request.app.state.db.acquire() as conn:
36
+ yield Database(conn)
37
+
38
+ async def get_current_user(
39
+ token: Annotated[str, Depends(oauth2_scheme)],
40
+ db: Annotated[Database, Depends(get_db)],
41
+ ) -> User:
42
+ user = await db.get_user_by_token(token)
43
+ if user is None:
44
+ raise HTTPException(status_code=401, detail="Invalid token")
45
+ return user
46
+
47
+ # Type alias for common dependencies
48
+ CurrentUser = Annotated[User, Depends(get_current_user)]
49
+ DB = Annotated[Database, Depends(get_db)]
50
+
51
+ @app.get("/users/me")
52
+ async def get_me(user: CurrentUser) -> UserResponse:
53
+ return UserResponse.model_validate(user)
54
+ ```
55
+
56
+ ### Request/Response Models
57
+
58
+ ```python
59
+ from pydantic import BaseModel, Field, EmailStr, ConfigDict
60
+
61
+ class CreateUserRequest(BaseModel):
62
+ model_config = ConfigDict(strict=True)
63
+
64
+ name: str = Field(min_length=1, max_length=200)
65
+ email: EmailStr
66
+ role: Literal["admin", "user"] = "user"
67
+
68
+ class UserResponse(BaseModel):
69
+ model_config = ConfigDict(from_attributes=True)
70
+
71
+ id: str
72
+ name: str
73
+ email: str
74
+ created_at: datetime
75
+
76
+ class PaginatedResponse(BaseModel, Generic[T]):
77
+ items: list[T]
78
+ total: int
79
+ page: int
80
+ per_page: int
81
+ ```
82
+
83
+ ### Error Handling
84
+
85
+ ```python
86
+ from fastapi import Request
87
+ from fastapi.responses import JSONResponse
88
+
89
+ class AppError(Exception):
90
+ def __init__(self, message: str, status_code: int = 500) -> None:
91
+ self.message = message
92
+ self.status_code = status_code
93
+
94
+ class NotFoundError(AppError):
95
+ def __init__(self, entity: str, id: str) -> None:
96
+ super().__init__(f"{entity} not found: {id}", status_code=404)
97
+
98
+ class ValidationError(AppError):
99
+ def __init__(self, message: str) -> None:
100
+ super().__init__(message, status_code=400)
101
+
102
+ @app.exception_handler(AppError)
103
+ async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
104
+ return JSONResponse(
105
+ status_code=exc.status_code,
106
+ content={"error": exc.message},
107
+ )
108
+ ```
109
+
110
+ ### Middleware
111
+
112
+ ```python
113
+ from starlette.middleware.base import BaseHTTPMiddleware
114
+ import time
115
+
116
+ class TimingMiddleware(BaseHTTPMiddleware):
117
+ async def dispatch(self, request: Request, call_next):
118
+ start = time.monotonic()
119
+ response = await call_next(request)
120
+ duration = time.monotonic() - start
121
+ response.headers["X-Process-Time"] = f"{duration:.4f}"
122
+ return response
123
+
124
+ app.add_middleware(TimingMiddleware)
125
+ ```
126
+
127
+ ## Django Patterns
128
+
129
+ ### Fat Models, Thin Views
130
+
131
+ ```python
132
+ # Business logic lives in models and services, not views
133
+ class Order(models.Model):
134
+ status = models.CharField(max_length=20)
135
+ total = models.DecimalField(max_digits=10, decimal_places=2)
136
+
137
+ def can_cancel(self) -> bool:
138
+ return self.status in ("pending", "confirmed")
139
+
140
+ def cancel(self) -> None:
141
+ if not self.can_cancel():
142
+ raise ValueError(f"Cannot cancel order in {self.status} state")
143
+ self.status = "cancelled"
144
+ self.save()
145
+
146
+ # Views are thin — delegate to models/services
147
+ class OrderCancelView(View):
148
+ def post(self, request, order_id):
149
+ order = get_object_or_404(Order, id=order_id)
150
+ try:
151
+ order.cancel()
152
+ except ValueError as e:
153
+ return JsonResponse({"error": str(e)}, status=400)
154
+ return JsonResponse({"status": "cancelled"})
155
+ ```
156
+
157
+ ### QuerySet Optimization
158
+
159
+ ```python
160
+ # Select only needed fields
161
+ users = User.objects.only("id", "name", "email")
162
+
163
+ # Prefetch related objects to avoid N+1 queries
164
+ orders = Order.objects.select_related("user").prefetch_related("items")
165
+
166
+ # Use exists() instead of count() for boolean checks
167
+ if Order.objects.filter(user=user, status="pending").exists():
168
+ ...
169
+
170
+ # Use iterator() for large querysets
171
+ for user in User.objects.all().iterator(chunk_size=1000):
172
+ process(user)
173
+ ```
174
+
175
+ ## Configuration
176
+
177
+ ```python
178
+ from pydantic_settings import BaseSettings
179
+
180
+ class Settings(BaseSettings):
181
+ model_config = ConfigDict(env_file=".env", env_file_encoding="utf-8")
182
+
183
+ database_url: str
184
+ redis_url: str = "redis://localhost:6379"
185
+ debug: bool = False
186
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
187
+ allowed_origins: list[str] = ["http://localhost:3000"]
188
+
189
+ # Validate at import time — fail fast
190
+ settings = Settings()
191
+ ```
192
+
193
+ ## Observability
194
+
195
+ ```python
196
+ import structlog
197
+
198
+ # Structured logging
199
+ logger = structlog.get_logger()
200
+
201
+ logger.info(
202
+ "request_completed",
203
+ method=request.method,
204
+ path=request.url.path,
205
+ status=response.status_code,
206
+ duration_ms=round(duration * 1000, 2),
207
+ request_id=request.state.request_id,
208
+ )
209
+
210
+ # Never log sensitive data (passwords, tokens, PII)
211
+ # Never log at ERROR for expected conditions (404, validation failures)
212
+ ```
213
+
214
+ ## Anti-Patterns
215
+
216
+ ```python
217
+ # Never: Business logic in views/routes
218
+ @app.post("/orders")
219
+ async def create_order(data: OrderInput, db: DB) -> OrderResponse:
220
+ # Don't put 50 lines of business logic here
221
+ # Delegate to a service
222
+ return await order_service.create(data)
223
+
224
+ # Never: Raw SQL without parameterization
225
+ cursor.execute(f"SELECT * FROM users WHERE id = '{user_id}'") # SQL INJECTION
226
+ cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) # Safe
227
+
228
+ # Never: Synchronous HTTP calls in async handlers
229
+ response = requests.get(url) # Blocks the event loop
230
+ response = await httpx_client.get(url) # Non-blocking
231
+ ```