Skip to content

Extract shared API response models into a side-effect-free module #148

Description

@marcosfrenkel

Problem

The models that describe the Node API's responses live in the modules that serve them. Importing one therefore costs the whole serving stack — FastAPI, every router, and the pqn_hardware driver imports they pull in — even when the caller only wants a two-field schema.

Measured on this branch:

Module Holds Import cost
pqn_node/core/config.py GamesAvailability 62ms, and builds the Settings singleton as a side effect (reads whatever config.toml is in the working directory)
pqn_node/api/main.py NodeConfig 176ms — includes all eight routers
pqn_node/api/routes/health.py HealthStatus, ComponentStatus, DeviceStatus 154ms
pqn_node/api/routes/chsh.py ChshResult 157ms

There is no module a consumer can import a response schema from for free.

Two consequences are already in the tree:

  1. daily_report.py:22-24 imports three models out of route modulesChshResult from api/routes/chsh.py, ComponentStatus and HealthStatus from api/routes/health.py. It works because daily_report runs on a Node, where the serving stack is loaded anyway.

  2. pqn_whobot has started duplicating instead. pqn_whobot/node_client.py:NodeConfigResponse restates api/main.py:NodeConfig rather than importing it, and the two are already drifting (follower_node_address is a required nullable key on one and defaulted on the other). Whobot may not run on a Node at all, so paying 176ms and loading hardware drivers to get two fields is not a reasonable trade.

This gets worse, not better: the Whobot Daily Digest (WHOBOT.md Phase 5) is a port of daily_report.py, so it needs exactly HealthStatus and ChshResult — the two in route modules. Importing a FastAPI router into a Slack bot is the point at which duplication starts to look like the cheaper option, which is how schemas silently fork.

Proposal

Add a module — pqn_node/api/models.py or similar — that contains only pydantic models and imports nothing but pydantic. Move the shared schemas there and re-export them from where they live today, so no existing import breaks and the OpenAPI schema is unchanged.

Known models to move:

Model Currently in Wanted by
NodeConfig api/main.py:18 pqn_whobot — Node Info action, registry name resolution
HealthStatus api/routes/health.py:52 daily_report.py, Whobot Phase 5 digest
ComponentStatus api/routes/health.py:40 as above (HealthStatus is built from it)
DeviceStatus api/routes/health.py:46 as above
ChshResult api/routes/chsh.py:26 daily_report.py, Whobot Phase 5 digest
GamesAvailability core/config.py:59 pqn_whobot — Change Game availability action

GamesAvailability is the debatable one. It is genuinely a configuration model as well as a response model, so moving it out of core/config.py may be wrong. Worth deciding explicitly rather than by default — the reason it is on the list is that importing it today drags in the Settings singleton build.

Not solved by this

Sharing the module does not make NodeConfig usable by Whobot as-is. The server guarantees node_name (Settings.node_name: str, core/config.py:66) so its response model requires it; a client must treat it as optional, because a Node running older code answers without one and is out of date rather than unreachable — a distinction pqn_whobot deliberately reports (see registry.py). Either the shared model relaxes that field, or the client keeps a lenient variant and says why. There is a FIXME on pqn_whobot/node_client.py:NodeConfigResponse tracking that half.

Done when

  • A models module exists that imports nothing but pydantic, verified by an import-cost check or a test.
  • The models above live there and are re-exported from their current homes; the OpenAPI schema is unchanged.
  • daily_report.py imports from it rather than from route modules.
  • A decision is recorded on GamesAvailability and on NodeConfig's optional-vs-required node_name.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions