initial
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: Python project rules
|
||||
description: Python conventions for the FINN/Eiendom MCP server
|
||||
applyTo: "**/*.py"
|
||||
---
|
||||
|
||||
# Python conventions
|
||||
|
||||
## Runtime
|
||||
|
||||
* Python **3.12+**.
|
||||
* Project-local virtualenv at `.venv/` (created by `uv venv` or `python3.12 -m venv .venv`).
|
||||
* All commands run inside the activated venv.
|
||||
* Editable install: `uv pip install -e ".[dev]"` (or `pip install -e ".[dev]"`).
|
||||
* Never install packages globally; never use `sudo pip`; never mutate host Python.
|
||||
* Add new dependencies to `pyproject.toml` in the same change that uses them.
|
||||
|
||||
## Language
|
||||
|
||||
* Use Python 3.12 syntax. Prefer `X | None` over `Optional[X]`, `list[int]` over `List[int]`, structural pattern matching where it actually helps.
|
||||
* **Type hints on every function signature**, including private helpers. `mypy --strict finn_eiendom` is 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 v1 `class 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` / `conn` as args; let services own the defaults).
|
||||
* Domain names from the PRD (`FinnAd`, `EiendomUnit`, `SimilarUnit`, `analyze_search`, `get_or_fetch_ad`).
|
||||
* `dataclass` for 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`.
|
||||
* `httpx` imports anywhere except `finn_eiendom/http.py`.
|
||||
* `sqlite3` imports anywhere except `finn_eiendom/cache.py`.
|
||||
* `BeautifulSoup` imports anywhere except `finn_eiendom/search.py` and `finn_eiendom/ad.py`.
|
||||
* `msgpack` imports anywhere except `finn_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 `respx` and fixtures.
|
||||
* `print()` for logging — use the `logging` module. stdio MCP server logs go to **stderr only**.
|
||||
* Bare `except:` or `except 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 `None` or 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`, not `process_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:
|
||||
|
||||
1. `context7:resolve-library-id` with the package name → canonical library ID.
|
||||
2. `context7:query-docs` with 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.
|
||||
Reference in New Issue
Block a user