4.2 KiB
4.2 KiB
name, description, applyTo
| name | description | applyTo |
|---|---|---|
| Python project rules | Python conventions for the FINN/Eiendom MCP server | **/*.py |
Python conventions
Runtime
- Python 3.12+.
- Project-local virtualenv at
.venv/(created byuv venvorpython3.12 -m venv .venv). - All commands run inside the activated venv.
- Editable install:
uv pip install -e ".[dev]"(orpip install -e ".[dev]"). - Never install packages globally; never use
sudo pip; never mutate host Python. - Add new dependencies to
pyproject.tomlin the same change that uses them.
Language
- Use Python 3.12 syntax. Prefer
X | NoneoverOptional[X],list[int]overList[int], structural pattern matching where it actually helps. - Type hints on every function signature, including private helpers.
mypy --strict finn_eiendomis the target. - Async-first for I/O. Sync code is fine for parsing, scoring, and cache access (SQLite).
- Pydantic v2 for all structured domain models, with
model_config = ConfigDict(...). No v1class Config:blocks.
Prefer
- Small, pure functions for parsing, normalization, and scoring.
- Explicit return types and explicit exceptions.
- Dependency injection for HTTP clients and DB connections in tests (pass
client/connas args; let services own the defaults). - Domain names from the PRD (
FinnAd,EiendomUnit,SimilarUnit,analyze_search,get_or_fetch_ad). dataclassfor internal value objects that don't cross the API boundary; Pydantic for anything serialized or validated.
Avoid
- Global mutable state (module-level dicts as caches, etc.). The only allowed module-level state is configuration loaded from env in
config.py. - Hardcoded URLs, credentials, paths, or magic numbers anywhere outside
config.py. httpximports anywhere exceptfinn_eiendom/http.py.sqlite3imports anywhere exceptfinn_eiendom/cache.py.BeautifulSoupimports anywhere exceptfinn_eiendom/search.pyandfinn_eiendom/ad.py.msgpackimports anywhere exceptfinn_eiendom/eiendom_no.py.- Scraping, scoring, cache, or HTTP fetching logic inside MCP tool or CLI command bodies.
- Direct network calls in unit tests — use
respxand fixtures. print()for logging — use theloggingmodule. stdio MCP server logs go to stderr only.- Bare
except:orexcept Exception: pass— catch the specific exception or let it propagate.
External fetches
All external fetches must support:
- Configurable request delay (
FINN_REQUEST_DELAY_SECONDS,EIENDOM_NO_REQUEST_DELAY_SECONDS). - Cache lookup before fetch.
- Retry on 5xx with exponential backoff (
1s, 2s, 4s). - Graceful failure that returns
Noneor empty rather than raising, when the caller can degrade. - Structured logging at INFO for success, WARNING for retry, ERROR for final failure.
Best practices
- Single responsibility per function. If a function name needs "and" to describe it, it's two functions.
- Function length: aim for under 30 lines. Past 50 lines it's a code smell — extract helpers.
- Cyclomatic complexity: if you've got more than 3 levels of nesting, the function wants splitting.
- Naming:
get_or_fetch_ad, notprocess_ad. Verbs for actions, nouns for data. Avoid abbreviations except those well-known in the domain (url,ad,nok). - DRY: if you write the same logic, regex, SQL, or format string twice, extract it. The decision table in
PRD.md§17.2 tells you where it belongs. - Comments explain WHY, not WHAT. The code already says what.
- Errors are loud: raise with actionable messages (
f"Unknown listing_status {status!r}; expected one of {VALID_STATUSES}"). The MCP boundary wraps them as{"error": True, ...}.
When uncertain about a library API
Use the context7 MCP server before writing code:
context7:resolve-library-idwith the package name → canonical library ID.context7:query-docswith that ID + focused topic.
See docs.instructions.md. Don't guess from training memory — Pydantic, FastMCP, and Typer all change.
Tooling
ruff check .— lint. Target Python 3.12. Active rules:E F I UP B SIM.ruff format .— format. Line length 100.mypy --strict finn_eiendom— type-check.pytest— run the full suite.