Init repository
This commit is contained in:
60
.gitignore
vendored
Normal file
60
.gitignore
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Environment files (do not commit secrets)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Pytest
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Ruff
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# AI Skills
|
||||||
|
|
||||||
|
This repository contains skills and a superpower used by AI agents when working on this codebase.
|
||||||
|
|
||||||
|
All documents are written in English. They describe technology choices, project structure, architecture, and coding conventions.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
| File | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| [`skills/python-development/SKILL.md`](skills/python-development/SKILL.md) | Superpower | High-level philosophy, when to apply, and skill routing map |
|
||||||
|
| [`skills/python-development-stack/SKILL.md`](skills/python-development-stack/SKILL.md) | Skill | Technology stack: Python, FastAPI, SQLModel, facet, typer, loguru, uv |
|
||||||
|
| [`skills/python-development-layout/SKILL.md`](skills/python-development-layout/SKILL.md) | Skill | File and folder organization, service and adapter package layout |
|
||||||
|
| [`skills/python-development-architecture/SKILL.md`](skills/python-development-architecture/SKILL.md) | Skill | Ports and Adapters, Monolith-over-Microservices, facet hierarchy |
|
||||||
|
| [`skills/python-development-code-style/SKILL.md`](skills/python-development-code-style/SKILL.md) | Skill | Imports, `__init__.py`, wildcard bans, block grouping |
|
||||||
|
| [`skills/python-development-settings/SKILL.md`](skills/python-development-settings/SKILL.md) | Skill | Pydantic Settings, BaseModel, YAML/JSON loading, validators |
|
||||||
|
| [`skills/python-development-cli/SKILL.md`](skills/python-development-cli/SKILL.md) | Skill | Typer composition, callback boot, deferred imports, nesting |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
When an agent starts working on a task, it should load the superpower first.
|
||||||
|
The superpower contains a decision tree that points to the relevant granular skills.
|
||||||
|
|
||||||
|
## Customizing
|
||||||
|
|
||||||
|
Edit the files in `skills/` to match your actual preferences, frameworks, and team conventions.
|
||||||
13
skills/python-development-architecture/SKILL.md
Normal file
13
skills/python-development-architecture/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: python-development-architecture
|
||||||
|
description: Architectural patterns, service boundaries, API design, resilience, and observability. Use when designing systems, reviewing integration patterns, or investigating production issues.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Follow Ports and Adapters (Hexagonal Architecture). Define abstract interfaces (ports) at the package level in `interfaces.py` or `base.py`. Place concrete implementations (adapters) in dedicated sub-packages named after the infrastructure type, e.g. `database/`, `filesystem/`, `memory/`.
|
||||||
|
- When a port has multiple adapter implementations, provide a factory module (conventionally `fabric.py` in the port's package) that selects and instantiates the concrete implementation based on a type discriminator in settings. The rest of the codebase imports only the port; it must never import a concrete adapter directly.
|
||||||
|
- Follow the Monolith-over-Microservices approach. Each top-level package inside the main project folder is a bounded context and a candidate for future extraction into a standalone service.
|
||||||
|
- A package becomes an independent runnable service when it contains a `service.py` module with an async service class. The class participates in the hierarchical service tree managed by the service orchestration library.
|
||||||
|
- The root service of the application aggregates child services through a declarative `dependencies` property. The orchestration library handles the full async lifecycle of the tree: concurrent start, run, and graceful shutdown in reverse dependency order.
|
||||||
|
- Define a single central composition root (e.g. a resolver or registry class). It reads configuration, lazily instantiates adapters and services, caches resolved instances, and injects them into consumers. Domain code must never construct its own dependencies.
|
||||||
|
- Package business logic into capability modules that expose operations to higher layers. A capability module declares what it needs (required capabilities) and what it can do (provided capabilities) through class-level declarations. The execution context and accessors are injected by the calling layer at runtime based on these declarations.
|
||||||
100
skills/python-development-cli/SKILL.md
Normal file
100
skills/python-development-cli/SKILL.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
name: python-development-cli
|
||||||
|
description: CLI design patterns using typer. Use when building, nesting, or extending command-line interfaces.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Every package that exposes CLI commands contains a `cli.py` module with a `get_cli() -> typer.Typer` factory function. Do not create the Typer instance at import time.
|
||||||
|
- The root package CLI (`cli.py` in the root package) is the composition point. It calls `cli.add_typer(get_subpackage_cli(), name="subpackage")` to mount sub-package CLI trees as subcommands.
|
||||||
|
- The root CLI defines a single `callback` function that runs before any command. It is responsible for shared bootstrap: load settings, configure logging, and store the settings object in `ctx.obj["settings"]` so every sub-command can access it.
|
||||||
|
|
||||||
|
Example root CLI `cli.py`:
|
||||||
|
```python
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .service import ApplicationService
|
||||||
|
from .settings import RootSettings
|
||||||
|
|
||||||
|
|
||||||
|
def callback(
|
||||||
|
ctx: typer.Context,
|
||||||
|
env_path: pathlib.Path | None = typer.Option(
|
||||||
|
None, "--env", "-e",
|
||||||
|
help="Environment variables file location",
|
||||||
|
),
|
||||||
|
config_path: pathlib.Path | None = typer.Option(
|
||||||
|
None, "--config", "-c",
|
||||||
|
help="Config file location",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
ctx.obj = {}
|
||||||
|
settings = RootSettings.load(
|
||||||
|
env_prefix="APP__",
|
||||||
|
env_file=env_path,
|
||||||
|
config_file=config_path,
|
||||||
|
)
|
||||||
|
ctx.obj["settings"] = settings
|
||||||
|
logger.configure(handlers=[{"sink": sys.stderr, "level": settings.logging.level}])
|
||||||
|
|
||||||
|
|
||||||
|
def run(ctx: typer.Context):
|
||||||
|
settings: RootSettings = ctx.obj["settings"]
|
||||||
|
service = ApplicationService(settings=settings)
|
||||||
|
asyncio.run(service.run())
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli() -> typer.Typer:
|
||||||
|
from .agents import get_cli as get_agents_cli
|
||||||
|
from .cron import get_cli as get_cron_cli
|
||||||
|
|
||||||
|
cli = typer.Typer()
|
||||||
|
cli.callback()(callback)
|
||||||
|
cli.command(name="run")(run)
|
||||||
|
cli.add_typer(get_agents_cli(), name="agents")
|
||||||
|
cli.add_typer(get_cron_cli(), name="cron")
|
||||||
|
return cli
|
||||||
|
```
|
||||||
|
|
||||||
|
- Sub-package CLI commands receive `ctx: typer.Context` as the first parameter and read settings via `ctx.obj["settings"]`.
|
||||||
|
|
||||||
|
Example sub-package `cli.py`:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
|
||||||
|
def run(
|
||||||
|
ctx: typer.Context,
|
||||||
|
name: str | None = typer.Argument(default=None),
|
||||||
|
debug: bool = typer.Option(False, "-d", "--debug"),
|
||||||
|
):
|
||||||
|
# Deferred imports — heavy project modules are imported inside the command body
|
||||||
|
from mypackage.resolver import DependencyResolver
|
||||||
|
from mypackage.settings import RootSettings
|
||||||
|
from mypackage.channels.tui import TUIChannel, TUIChannelSettings
|
||||||
|
|
||||||
|
settings: RootSettings = ctx.obj["settings"]
|
||||||
|
resolver = DependencyResolver(settings=settings)
|
||||||
|
channel = TUIChannel(settings=TUIChannelSettings(debug=debug), ...)
|
||||||
|
asyncio.run(channel.run())
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli() -> typer.Typer:
|
||||||
|
cli = typer.Typer()
|
||||||
|
cli.command(name="run")(run)
|
||||||
|
return cli
|
||||||
|
```
|
||||||
|
|
||||||
|
- Each CLI command is a plain Python function decorated with `cli.command(name="...")`. Keep command functions small: parse arguments, create a resolver or service from settings, and run async code with `asyncio.run()`. No business logic inside the command function.
|
||||||
|
- The root package `__main__.py` calls `get_cli()` and runs it. Example:
|
||||||
|
```python
|
||||||
|
from .cli import get_cli
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli = get_cli()
|
||||||
|
cli()
|
||||||
|
```
|
||||||
56
skills/python-development-code-style/SKILL.md
Normal file
56
skills/python-development-code-style/SKILL.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: python-development-code-style
|
||||||
|
description: Code conventions, typing, testing habits, and engineering principles. Use when writing, reviewing, or refactoring Python code.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Follow PEP 8 for all Python code. Use `ruff` for automatic formatting and linting.
|
||||||
|
- Every package `__init__.py` must declare the public API explicitly with `__all__`. `__all__` is always a tuple, even when it contains a single element. Use inline comment group headers to separate imports by source module.
|
||||||
|
- Inside `__init__.py` import only from sibling modules. Do not place executable code.
|
||||||
|
- Group related names together in `__all__` and mirror the grouping with comment headers. Example:
|
||||||
|
```python
|
||||||
|
from .base import BaseService, ServiceMixin
|
||||||
|
from .dto import ServiceInput, ServiceOutput
|
||||||
|
from .exceptions import ServiceError, ValidationError
|
||||||
|
from .fabric import get_service
|
||||||
|
from .settings import ServiceSettings
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
# base
|
||||||
|
"BaseService",
|
||||||
|
"ServiceMixin",
|
||||||
|
# dto
|
||||||
|
"ServiceInput",
|
||||||
|
"ServiceOutput",
|
||||||
|
# exceptions
|
||||||
|
"ServiceError",
|
||||||
|
"ValidationError",
|
||||||
|
# fabric
|
||||||
|
"get_service",
|
||||||
|
# settings
|
||||||
|
"ServiceSettings",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
- Wildcard imports (`from module import *`) are forbidden in every module, including `__init__.py`. Always import explicit names.
|
||||||
|
- Within a package, import sibling modules using relative imports (`from .module import Name`). When importing from a different top-level package or from a parent package that is not a direct sibling, use an absolute import (`from project_name.module import Name`).
|
||||||
|
- Group imports in every module into three blocks separated by a blank line:
|
||||||
|
1. Standard library (`import pathlib`, `from typing import Self`, etc.).
|
||||||
|
2. Third-party packages (`import yaml`, `from pydantic import Field`, etc.).
|
||||||
|
3. Project-local imports (`from .services import MyService`, `from microclaw.settings import Settings`, etc.).
|
||||||
|
- Within each block, place `from ... import ...` lines first, then `import ...` lines. Sort all lines alphabetically. Example:
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
from .services import MyService
|
||||||
|
from microclaw.settings import Settings
|
||||||
|
```
|
||||||
|
- Functions and methods must not return tuples of multiple values. Returning a tuple is allowed only in exceptional cases (e.g. unpacking a well-known pair such as `(key, value)`). When a function needs to return more than one piece of data, define a Pydantic `BaseModel` for the result or reuse an existing model. This makes the return type self-documenting, enables IDE autocomplete, and keeps field names stable during refactoring.
|
||||||
|
- Do not use `from __future__ import annotations`. Use explicit forward-reference strings (e.g. `"MyClass"`) when necessary.
|
||||||
|
- Do not use `if TYPE_CHECKING:` blocks. Import the types you need at the top level. Deferred imports inside `TYPE_CHECKING` hide dependencies and break runtime introspection.
|
||||||
|
- Do not abbreviate variable or parameter names. Write them out in full so the intent is obvious without context. Examples of forbidden abbreviations: `idx` (use `index`), `dep` (use `dependency`), `cfg` (use `config` or `configuration`), `msg` (use `message`), `err` (use `error`), `resp` (use `response`). The same rule applies to class attributes, function parameters, and local variables.
|
||||||
114
skills/python-development-fastapi/SKILL.md
Normal file
114
skills/python-development-fastapi/SKILL.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
name: python-development-fastapi
|
||||||
|
description: Patterns for building a FastAPI HTTP service inside the facet service tree. Use when creating REST endpoints, routers, or the api/rest service package.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- The FastAPI service lives in a sub-package named `api/rest/` inside the root package. It is a regular facet service: it has `service.py`, `settings.py`, and `__init__.py`.
|
||||||
|
- Provide a custom `UvicornServer` that disables signal handlers so the parent facet orchestrator controls startup and shutdown. Example:
|
||||||
|
```python
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
class UvicornServer(uvicorn.Server):
|
||||||
|
def install_signal_handlers(self):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
- The service class inherits from `facet.AsyncioServiceMixin`. Its constructor accepts the settings instance declared in the same package's `settings.py`. Keep the constructor minimal; heavy initialization happens in `start()`.
|
||||||
|
- The `start()` method builds the FastAPI application asynchronously, configures uvicorn, and adds the server coroutine to the facet task list with `self.add_task(server.serve())`.
|
||||||
|
- Extract app construction into an `async` `_build_app()` method so router registration and dependency wiring can use `await`.
|
||||||
|
- Register routers in `_setup_app()` or `_build_app()` using `app.include_router(...)`. Group related endpoints under routers with a `prefix`.
|
||||||
|
- The settings class inherits from `pydantic.BaseModel` (not `BaseSettings`). It declares the minimum required for the HTTP layer: `host`, `port`, and references to other subsystem settings when the REST layer wires them directly.
|
||||||
|
|
||||||
|
### Minimal complete example
|
||||||
|
|
||||||
|
`api/rest/settings.py`:
|
||||||
|
```python
|
||||||
|
from pydantic import BaseModel, conint
|
||||||
|
|
||||||
|
|
||||||
|
class RESTAPISettings(BaseModel):
|
||||||
|
host: str = "127.0.0.1"
|
||||||
|
port: conint(ge=1, le=65535) = 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
`api/rest/service.py`:
|
||||||
|
```python
|
||||||
|
import facet
|
||||||
|
import fastapi
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from . import handlers, users
|
||||||
|
from .settings import RESTAPISettings
|
||||||
|
|
||||||
|
|
||||||
|
class UvicornServer(uvicorn.Server):
|
||||||
|
def install_signal_handlers(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RESTAPIService(facet.AsyncioServiceMixin):
|
||||||
|
def __init__(self, settings: RESTAPISettings):
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
app = await self._build_app()
|
||||||
|
config = uvicorn.Config(
|
||||||
|
app=app,
|
||||||
|
host=self._settings.host,
|
||||||
|
port=self._settings.port,
|
||||||
|
)
|
||||||
|
server = UvicornServer(config)
|
||||||
|
self.add_task(server.serve())
|
||||||
|
|
||||||
|
async def _build_app(self) -> fastapi.FastAPI:
|
||||||
|
app = fastapi.FastAPI()
|
||||||
|
await self._setup_app(app)
|
||||||
|
return app
|
||||||
|
|
||||||
|
async def _setup_app(self, app: fastapi.FastAPI):
|
||||||
|
app.get("/health")(handlers.health)
|
||||||
|
app.include_router(users.get_users, prefix="/users")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Router and handler structure
|
||||||
|
|
||||||
|
- Endpoints must be RESTful. The URL path structure maps directly to the package structure inside `api/rest/`.
|
||||||
|
- Each URL segment becomes a sub-package. For example `/users/{id}` lives in `api/rest/users/`.
|
||||||
|
- Every handler sub-package contains at minimum:
|
||||||
|
- `router.py` — defines `get_router() -> fastapi.APIRouter` and wires each route to a handler function.
|
||||||
|
- `handlers.py` — plain async functions that implement the endpoint logic.
|
||||||
|
- `schemas.py` — Pydantic request and response models shared by the handlers in this package.
|
||||||
|
- `__init__.py` — exports only `get_router`.
|
||||||
|
- Optional but common additional files:
|
||||||
|
- `dependencies.py` — FastAPI dependency functions reused across handlers in the package.
|
||||||
|
- `exceptions.py` — custom HTTP exceptions for this domain.
|
||||||
|
|
||||||
|
Example `api/rest/users/router.py`:
|
||||||
|
```python
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
from . import handlers
|
||||||
|
|
||||||
|
|
||||||
|
def get_router() -> fastapi.APIRouter:
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
router.add_api_route(path="/", methods=["GET"], endpoint=handlers.list_users)
|
||||||
|
router.add_api_route(path="/", methods=["POST"], endpoint=handlers.create_user)
|
||||||
|
router.add_api_route(path="/{id}", methods=["GET"], endpoint=handlers.get_user)
|
||||||
|
router.add_api_route(path="/{id}", methods=["PUT"], endpoint=handlers.update_user)
|
||||||
|
router.add_api_route(path="/{id}", methods=["DELETE"], endpoint=handlers.delete_user)
|
||||||
|
return router
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `api/rest/users/__init__.py`:
|
||||||
|
```python
|
||||||
|
from .router import get_router
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
# router
|
||||||
|
"get_router",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Do not construct `fastapi.APIRouter` instances at import time outside `get_router()`. The router is created only when `get_router()` is called during `_setup_app()`.
|
||||||
|
```
|
||||||
45
skills/python-development-layout/SKILL.md
Normal file
45
skills/python-development-layout/SKILL.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
name: python-development-layout
|
||||||
|
description: Folder structure and file organization for Python services. Use when creating a new service, reorganizing modules, or reviewing project structure.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Every repository must contain a `README.md`.
|
||||||
|
- Every repository must contain a `.gitignore` that excludes `.venv/`, `*.pyc`, and `__pycache__/`.
|
||||||
|
- Every repository must contain a `.dockerignore` with content similar to `.gitignore`.
|
||||||
|
- Every repository must contain a `Dockerfile` for containerized deployment.
|
||||||
|
- Every repository must contain a `pyproject.toml` created automatically by `uv init`.
|
||||||
|
- The Python package source folder lives in the repository root (e.g. `my_package/`), not under a `src/` directory.
|
||||||
|
- `pyproject.toml` must explicitly name the root package folder so the build system knows where the code lives.
|
||||||
|
- All tool configuration (tests, linters, formatters, type checkers, coverage, etc.) must live inside `pyproject.toml` under the `[tool.*]` tables. Do not use separate dot-files such as `.pylintrc`, `.flake8`, `setup.cfg`, or `tox.ini` for tool settings.
|
||||||
|
- The repository root may also contain a `tests/` folder and a `docs/` folder alongside the package folder.
|
||||||
|
- Inside the root package every sub-package is either a service package, an adapter package, or a library package. The structure of each must follow the conventions below.
|
||||||
|
- The root package must contain a `__main__.py` module. It is the executable entry point when the package is run with `python -m package_name`. Keep it minimal: import only from sibling modules (relative imports), call the factory that builds the CLI or the root service, and invoke it under `if __name__ == "__main__":`. No business logic lives here. Example:
|
||||||
|
```python
|
||||||
|
from .cli import get_cli
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli = get_cli()
|
||||||
|
cli()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service package layout
|
||||||
|
A package that exposes a runnable service must contain at minimum:
|
||||||
|
- `service.py` — the service class. It inherits from the async service mixin and implements `start`/`stop` lifecycle methods. The constructor accepts exactly one settings instance — the class declared in the same package's `settings.py`. Example:
|
||||||
|
```python
|
||||||
|
import facet
|
||||||
|
|
||||||
|
from .settings import MyServiceSettings
|
||||||
|
|
||||||
|
class MyService(facet.AsyncioServiceMixin):
|
||||||
|
def __init__(self, settings: MyServiceSettings):
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
# service startup logic
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
- `settings.py` — a Pydantic Settings class that declares the configuration schema for the service.
|
||||||
|
- `interfaces.py` or `base.py` — abstract ports (interfaces) that the service depends on. Consumers import from here, never from adapters.
|
||||||
|
- `fabric.py` — a factory module that instantiates the service and its collaborators. It receives settings and returns fully constructed objects.
|
||||||
|
- `__init__.py` — public API of the package. Exports only the types and functions intended for external use.
|
||||||
38
skills/python-development-settings/SKILL.md
Normal file
38
skills/python-development-settings/SKILL.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: python-development-settings
|
||||||
|
description: Application configuration and settings management. Use when defining, loading, validating, or extending application settings.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- The root settings class must inherit from `pydantic_settings.BaseSettings`. This enables automatic loading from environment variables and `.env` files.
|
||||||
|
- Every nested settings class (for individual services, adapters, or components) must inherit from `pydantic.BaseModel`, not from `BaseSettings`. Only the root class needs the env-loading behavior.
|
||||||
|
- The root settings class exposes one field per top-level service or subsystem. Provide sensible defaults as plain values for immutable types (str, int, bool, None) and use `Field(default_factory=...)` only when the default is a mutable object (dict, list, or model instance). Never use mutable literals as class-attribute defaults in Pydantic v2. Provide defaults so the application starts without a config file when reasonable.
|
||||||
|
- A settings class can accept either a settings object or a string key referencing another entry in the same config. The resolver (or a `@model_validator`) replaces string references with resolved instances after initial parsing. This is how services declare their dependencies declaratively.
|
||||||
|
- Use `@model_validator(mode="after")` on the root settings class to validate cross-references between subsystems, resolve string keys to actual instances, and enforce consistency across the configuration graph.
|
||||||
|
- Support loading from YAML config files with custom tags. Register `!include` for file inclusion and `!env` for environment variable interpolation inside YAML. The YAML loader builder must live as a `@staticmethod` inside the root settings class. Example:
|
||||||
|
```python
|
||||||
|
@staticmethod
|
||||||
|
def get_yaml_loader(base_path: pathlib.Path) -> yaml.BaseLoader:
|
||||||
|
loader = type("Loader", (yaml.SafeLoader,), {})
|
||||||
|
loader.add_constructor(
|
||||||
|
"!include",
|
||||||
|
yaml_include.Constructor(base_dir=str(base_path)),
|
||||||
|
)
|
||||||
|
loader.add_constructor("!env", construct_env_tag)
|
||||||
|
return loader
|
||||||
|
```
|
||||||
|
- Support loading from JSON config files using standard `json.load`. JSON does not support custom tags; use string interpolation or env var resolution handled by `BaseSettings` after load.
|
||||||
|
- Provide a single `load()` classmethod on the root settings class that reads the config file (YAML or JSON), then passes the parsed dict into the `BaseSettings` constructor along with optional `_env_prefix` and `_env_file`. Example:
|
||||||
|
```python
|
||||||
|
@classmethod
|
||||||
|
def load(cls, config_file: pathlib.Path | None = None) -> Self:
|
||||||
|
data = {}
|
||||||
|
if config_file is not None:
|
||||||
|
if config_file.suffix in (".yaml", ".yml"):
|
||||||
|
data.update(yaml.load(config_file.read_text(), Loader=get_loader()))
|
||||||
|
elif config_file.suffix == ".json":
|
||||||
|
data.update(json.loads(config_file.read_text()))
|
||||||
|
return cls(**data)
|
||||||
|
```
|
||||||
|
- Keep each subsystem's settings in its own `settings.py` module inside the subsystem package. Import and compose them into the root settings class. Do not inline subsystem schemas into the root file.
|
||||||
|
- Use `pydantic.Field(default_factory=...)` for mutable default values (dicts, lists, models). Never use mutable literals as class-attribute defaults in Pydantic v2.
|
||||||
16
skills/python-development-stack/SKILL.md
Normal file
16
skills/python-development-stack/SKILL.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: python-development-stack
|
||||||
|
description: Technology stack decisions for Python services. Use when adding dependencies, choosing infrastructure, bootstrapping projects, or reviewing tool choices.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Use `uv` as the package manager for all Python dependency operations.
|
||||||
|
- Use `FastAPI` for REST API and HTTP services.
|
||||||
|
- Use `SQLModel` and `SQLAlchemy` for database access. Async drivers: `asyncpg` for PostgreSQL, `aiosqlite` for SQLite.
|
||||||
|
- Use `alembic` for database migrations.
|
||||||
|
- Use `facet` for hierarchical async service composition with dependency management and graceful shutdown.
|
||||||
|
- Use `typer` for CLI interfaces.
|
||||||
|
- Use `pydantic` for DTOs and data validation; use `pydantic-settings` for application configuration.
|
||||||
|
- Use `loguru` for logging.
|
||||||
|
- Use `pyyaml` for YAML parsing; use `pyyaml-include` for `!include` tags and `pyyaml-env-tag` for `!env` interpolation inside YAML config files.
|
||||||
|
|
||||||
20
skills/python-development/SKILL.md
Normal file
20
skills/python-development/SKILL.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: python-development
|
||||||
|
description: Superpower for building robust, maintainable Python backend services. Use whenever you create, modify, review, or debug service code in this repository.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skill Map
|
||||||
|
|
||||||
|
| Situation | Primary Skill |
|
||||||
|
|---|---|
|
||||||
|
| Choosing frameworks, libraries, DBs, or infrastructure components | [`python-development-stack`](./python-development-stack/SKILL.md) |
|
||||||
|
| Bootstrapping a new service or reviewing folder structure | [`python-development-layout`](./python-development-layout/SKILL.md) |
|
||||||
|
| Designing service boundaries, APIs, data flow, or resilience patterns | [`python-development-architecture`](./python-development-architecture/SKILL.md) |
|
||||||
|
| Writing or reviewing code, tests, configs, or observability instrumentation | [`python-development-code-style`](./python-development-code-style/SKILL.md) |
|
||||||
|
| Defining, loading, validating, or extending application settings | [`python-development-settings`](./python-development-settings/SKILL.md) |
|
||||||
|
| Building, nesting, or extending command-line interfaces | [`python-development-cli`](./python-development-cli/SKILL.md) |
|
||||||
|
| Creating a FastAPI REST service inside the facet tree | [`python-development-fastapi`](./python-development-fastapi/SKILL.md) |
|
||||||
|
| Debugging production issues (logs, traces, metrics) | [`python-development-architecture`](../python-development-architecture/SKILL.md) + [`python-development-code-style`](../python-development-code-style/SKILL.md) |
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Python interpreter versions are managed with `pyenv`.
|
||||||
Reference in New Issue
Block a user