Files
finn-mcp/.github/instructions/python.instructions.md
T
2026-05-16 06:54:17 +00:00

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 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.