Skip to content

Latest commit

 

History

History
100 lines (79 loc) · 5.09 KB

File metadata and controls

100 lines (79 loc) · 5.09 KB

CLAUDE.md — developing netcup-api (core library + netcup CLI)

Python client library + Typer CLI for the netcup SCP REST API. Package: netcup_api. Console script: netcup. This is production tooling — keep it solid and spec-driven.

The one principle that governs everything

Never hard-code the API surface. Drive it from the OpenAPI spec.

The bundled netcup_api/openapi.json is the single source of truth. spec.py parses it into an Endpoint registry; the CLI and the MCP server both consume that registry. When netcup changes the API, the fix is netcup spec updatenot editing endpoint lists.

Consequences for any change you make:

  • New endpoints are already reachable via netcup call <operationId> / netcup api …. Only add a curated command when it materially improves ergonomics for a common task.
  • Curated commands MUST route through NetcupClient.request/.call — never open their own httpx client, never rebuild URLs or auth headers.
  • Don't duplicate schema knowledge in code. If you need a column or field, read it from the spec (netcup describe <op>), don't guess.

Architecture

netcup_api/
├── config.py    Hosts, OIDC URLs, token TTLs, config-dir + spec-path resolution. All
│                tunable via NETCUP_* env vars. No hostname is hard-coded anywhere else.
├── auth.py      TokenManager: device flow, token cache, refresh, revoke, userinfo.
│                Persists ONLY the offline refresh token (0600). Access token cached
│                separately with expiry; refreshed automatically (incl. 401 retry).
├── spec.py      OpenAPI registry: Endpoint/Param dataclasses, deterministic operationId
│                generation (_make_operation_id), $ref resolution, lookups, load()/update().
├── client.py    NetcupClient: generic request()/call() + thin high-level helpers. PATCH
│                defaults to application/merge-patch+json. Raises NetcupAPIError on >=400.
├── cli.py       Typer app. Curated command groups + the generic layer
│                (endpoints/describe/call/api) + spec management.
└── openapi.json Bundled spec (shipped in the wheel via force-include).

Layering is strict: cli.py → client.py → {auth.py, spec.py} → config.py. Never invert it.

How to add a curated CLI command (the right way)

  1. Add a helper to NetcupClient that calls self.request(...) or self.call(...). Path params that are the user's userId are auto-filled — use self.user_id().
  2. Add the Typer command in cli.py, wrapping the call in run(lambda: ...) so errors become clean error: … + exit 1 (never a traceback).
  3. Output: use emit() for objects, emit_table(rows, cols, title) for lists. Respect the global --json/-j flag (the emit helpers already do). Get column names from the schema, not from memory.
  4. Add/extend an offline test in tests/.

Auth & security invariants (do not regress)

  • Only the refresh token is persisted; the access token cache is disposable.
  • Files in the config dir are mode 0600, the dir 0700.
  • Never log, print, or commit tokens or ~/.config/netcup-api/ contents.
  • The 401 path refreshes once then retries; never loop on refresh.
  • The library never does interactive login on its own except via TokenManager.login() (only the netcup auth login command calls it).

Spec maintenance

  • netcup spec update downloads the live spec to ~/.config/netcup-api/openapi.json (takes precedence over the bundled copy). To refresh the bundled copy for a release, download it and overwrite netcup_api/openapi.json, then run the tests.
  • operationIds are derived from method+path and are stable as long as paths are stable. If netcup renames a path, dependent curated commands/tests may need updating — the tests named after operationIds will catch it.

Dev workflow

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check .          # lint (CI gate)
pytest -q             # offline tests — no network, no auth (CI gate)
python -m build       # wheel/sdist; CI asserts openapi.json is bundled
  • Tests must stay offline and credential-free. Mock or avoid network; never require a real login in CI.
  • Python ≥ 3.10. Line length 100 (ruff). Keep imports clean (ruff F401 is enforced).
  • Match the existing style: type hints, from __future__ import annotations, small focused functions, docstrings on public classes/commands.

Release (when publishing)

  • Bump version in pyproject.toml and netcup_api.__version__ together.
  • netcup version reports both the CLI version and the bundled spec version — keep them sane.
  • netcup-mcp depends on this package; don't break the public surface re-exported from netcup_api/__init__.py (NetcupClient, TokenManager, Spec, Endpoint, load_spec) without coordinating a bump there.

Don't

  • Don't add a second HTTP path or auth scheme. One client, one auth flow.
  • Don't hard-code server IDs, IPs, hostnames, or endpoint URLs.
  • Don't print secrets or weaken file permissions.
  • Don't make tests hit the network.