Skip to content

feat(dispatcher): catalog-driven MEOS-function dispatcher (step 4 foundation)#6

Open
estebanzimanyi wants to merge 2 commits into
MobilityDB:masterfrom
estebanzimanyi:feat/step4-dispatcher-foundation
Open

feat(dispatcher): catalog-driven MEOS-function dispatcher (step 4 foundation)#6
estebanzimanyi wants to merge 2 commits into
MobilityDB:masterfrom
estebanzimanyi:feat/step4-dispatcher-foundation

Conversation

@estebanzimanyi
Copy link
Copy Markdown
Member

Stacks on #4. Step 4 of docs/MEOS_API_INGESTION_PLAN.md — the catalog-driven dispatcher that the 5 'REPLACE' resource modules will delegate to in follow-up PRs.

What it ships

mobilityapi/dispatcher.py:

  • Loads vendor/meos-api/meos-idl.json at construction; honours an explicit catalog_path= for tests.
  • Filters to network.exposable functions only when enrichment fields are present; otherwise treats every function as exposable.
  • FunctionSignature dataclass: name / category / params / return_type / decode_per_param / encode_return / description, all populated from the catalog (enriched or bare).
  • dispatch(function_name, params) -> Any:
    • validates the parameter set against the catalog signature
    • resolves the MEOS function via an injected resolver callable
    • invokes it with validated keyword args
    • returns the result; the caller owns encoding to JSON / WKB

tests/test_dispatcher.py — 12 tests, all passing locally (python3 -m pytest tests/test_dispatcher.py -v):

  • catalog load (default path, explicit path, FileNotFoundError)
  • FunctionSignature.from_catalog_entry (basic fields, enriched wire metadata, fallback when wire absent, non-exposable filtering)
  • dispatch contract (resolver invocation, unknown function, missing param, unexpected param, default stub resolver raises NotImplementedError)
  • integration sanity (the 5 MovFeat dispatch candidates named in the ingestion plan are present in the vendored catalog)

Resolver pattern

Production wiring lands when the first endpoint migrates:

import pymeos
d = Dispatcher(resolver=lambda name: getattr(pymeos.functions, name))

For tests, a stub registry suffices:

d = Dispatcher(resolver=_stub_registry(tpoint_speed=fake_tpoint_speed))

What this PR does NOT change

  • Existing hand-written endpoint modules in resource/* are unchanged. The plan's 5 REPLACE candidates migrate to the dispatcher one PR at a time:
    • resource/temporal_geom_seq/ (Create, Retrieve) → temporal_from_mfjson / temporal_as_mfjson
    • resource/temporal_geom_query/velocity.pytpoint_speed
    • resource/temporal_geom_query/acceleration.pytemporal_derivative
    • resource/temporal_geom_query/distance.pytpoint_cumulative_length
    • resource/temporal_properties/temporal_as_mfjson
  • PyMEOS is not yet a dependency (the dispatcher is resolver-agnostic; the production resolver lands with the first endpoint migration).
  • OGC API – Moving Features path mapping is in MEOS-API #13. Step 5 of the ingestion plan consumes that mapping; this PR is the dispatch core shared between step 4 and step 5.

Dependency chain

Stacks on / depends on Status
MobilityAPI #4 (vendor MEOS-API artefacts) OPEN, green
MobilityAPI #3 (ingestion plan) OPEN, green
MEOS-API #13 (movfeat OpenAPI projection) OPEN — needed for step 5, not for this PR

When #4 lands, this PR's diff narrows to just the new mobilityapi/ + tests/ files.

Vendor MobilityAPI's read-only copy of MEOS-API's published catalog +
projection artefacts under `vendor/meos-api/`, plus a Makefile target
`make vendor-meos-api` that regenerates them from upstream.

Files added:

  vendor/meos-api/
    PROVENANCE.json              -- per-artefact source URLs + regenerate cmd
    README.md                    -- refresh procedure
    meos-idl.json                -- 3546 fns / 70 structs / 16 enums
                                    (generated by MEOS-API run.py over
                                    MobilityDB master meos/include headers)
    meos-coverage.json           -- structural worklist (from open PR MobilityDB#4)
    meos-object-model-parity.json -- 29-pair portable-parity (from open PR #10)

The Makefile target clones MEOS-API + MobilityDB shallowly, installs
libclang, runs MEOS-API's `run.py` against MobilityDB's MEOS headers,
and copies the produced JSON artefacts into `vendor/meos-api/`.

Two of the four artefacts (`meos-coverage.json`,
`meos-object-model-parity.json`) currently come from open MEOS-API PR
branches because their generators are not yet on master; PROVENANCE.json
makes that explicit. The Makefile gracefully skips them if absent.

Step 2 of `docs/MEOS_API_INGESTION_PLAN.md`. The drift gate workflow
that fails on stale artefacts is step 3 (separate stacked PR).
…ndation)

Step 4 of docs/MEOS_API_INGESTION_PLAN.md — the catalog-driven
dispatcher that the 5 'REPLACE' resource modules will delegate to in
follow-up PRs.

mobilityapi/dispatcher.py:

- Loads vendor/meos-api/meos-idl.json at construction; honours an
  explicit catalog_path= for tests.
- Filters to network.exposable functions only when enrichment fields
  are present; otherwise treats every function as exposable.
- FunctionSignature dataclass: name / category / params / return_type
  / decode_per_param / encode_return / description, all populated
  from the catalog (enriched or bare).
- dispatch(function_name, params) -> Any:
  * validates the parameter set against the catalog signature
    (missing or unexpected names raise TypeError)
  * resolves the MEOS function via an injected resolver callable
    (production: getattr(pymeos.functions, name); tests: stub
    registry)
  * invokes it with the validated keyword args
  * returns the result; the caller owns encoding to JSON / WKB

tests/test_dispatcher.py (12 tests, all passing locally):

- catalog load (default path, explicit path, FileNotFoundError)
- FunctionSignature.from_catalog_entry (basic fields, enriched wire
  metadata, fallback when wire absent, non-exposable filtering)
- dispatch contract (resolver invocation, unknown function, missing
  param, unexpected param, default stub resolver)
- integration sanity (the 5 MovFeat dispatch candidates named in the
  ingestion plan are present in the vendored catalog)

What this PR does NOT change:

- Existing hand-written endpoint modules in resource/* remain
  unchanged. The plan's 5 REPLACE candidates migrate to the
  dispatcher module-by-module in follow-up PRs:
  temporal_geom_seq/, temporal_geom_query/{velocity,acceleration,
  distance}, temporal_properties/.
- PyMEOS is not yet a dependency (the dispatcher is resolver-
  agnostic; the production resolver lands when the first endpoint
  migrates).

Stacks on MobilityDB#4 (vendor MEOS-API artefacts) so vendor/meos-api/meos-idl.json
is in the tree.
@estebanzimanyi estebanzimanyi force-pushed the feat/step4-dispatcher-foundation branch from 9db7952 to 6bf5621 Compare May 20, 2026 19:00
estebanzimanyi added a commit to estebanzimanyi/MobilityAPI that referenced this pull request May 21, 2026
Adds mobilityapi/app.py + two routers that expose the catalog-driven
Dispatcher (PR MobilityDB#6) and WireCodec (PR MobilityDB#7) as HTTP endpoints:

  - GET  /catalog          -> list dispatcher-exposable functions
  - GET  /catalog/{name}   -> full signature for one function
  - POST /functions/{name} -> invoke with JSON body, decode/encode
                              opaque MEOS types via the WireCodec

The app is built by create_app(dispatcher, codec) — both dependencies
are explicit; no global singletons. Production wires both to PyMEOS;
tests pass stubs.

The POST /functions/{name} flow:
  1. Decode opaque-type params via codec.decode(encoding, wire_value).
  2. Dispatch via Dispatcher.dispatch(name, params).
  3. Encode the result via codec.encode(encoding, value) if the catalog
     marks the return as serialised.

Error mapping:
  - Unknown function           -> 404
  - Missing / extra parameters -> 400
  - Param encoding has no codec decoder -> 400
  - Result encoding has no codec encoder -> 500

tests/test_app.py: 11 HTTP-level tests against a tiny in-test catalog
covering scalar, serialised-in / scalar-out, and serialised-in /
serialised-out shapes, plus the four error paths. All 38 framework
tests pass.
estebanzimanyi added a commit to estebanzimanyi/MobilityAPI that referenced this pull request May 21, 2026
Adds mobilityapi/app.py + two routers that expose the catalog-driven
Dispatcher (PR MobilityDB#6) and WireCodec (PR MobilityDB#7) as HTTP endpoints:

  - GET  /catalog          -> list dispatcher-exposable functions
  - GET  /catalog/{name}   -> full signature for one function
  - POST /functions/{name} -> invoke with JSON body, decode/encode
                              opaque MEOS types via the WireCodec

The app is built by create_app(dispatcher, codec) — both dependencies
are explicit; no global singletons. Production wires both to PyMEOS;
tests pass stubs.

The POST /functions/{name} flow:
  1. Decode opaque-type params via codec.decode(encoding, wire_value).
  2. Dispatch via Dispatcher.dispatch(name, params).
  3. Encode the result via codec.encode(encoding, value) if the catalog
     marks the return as serialised.

Error mapping:
  - Unknown function           -> 404
  - Missing / extra parameters -> 400
  - Param encoding has no codec decoder -> 400
  - Result encoding has no codec encoder -> 500

tests/test_app.py: 11 HTTP-level tests against a tiny in-test catalog
covering scalar, serialised-in / scalar-out, and serialised-in /
serialised-out shapes, plus the four error paths. All 38 framework
tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant