Skip to content

feat(resolvers,wire): pluggable resolvers + wire-layer codec - #7

Closed
estebanzimanyi wants to merge 88 commits into
MobilityDB:masterfrom
estebanzimanyi:feat/step4-resolvers-and-wire
Closed

feat(resolvers,wire): pluggable resolvers + wire-layer codec#7
estebanzimanyi wants to merge 88 commits into
MobilityDB:masterfrom
estebanzimanyi:feat/step4-resolvers-and-wire

Conversation

@estebanzimanyi

Copy link
Copy Markdown
Member

Stacks on #6 (catalog-driven dispatcher foundation). Adds the two layers a production endpoint migration needs alongside the dispatcher:

What ships

FileRole
mobilityapi/resolvers.pystub_resolver(registry) / pymeos_resolver() / default_resolver(prefer_pymeos=True) — pick the MEOS function implementation. PyMEOS is lazy-imported on first call so MobilityAPI can be installed without it.
mobilityapi/wire.pyWireCodec keyed by encoding name (mfjson / text / wkb / hexwkb) — decode HTTP wire values to PyMEOS objects, encode PyMEOS results back. stub_codec for tests; pymeos_codec() for production.
mobilityapi/__init__.pyRe-exports Dispatcher, FunctionSignature, stub_resolver, pymeos_resolver, default_resolver, WireCodec, stub_codec, pymeos_codec, and the ENCODING_* constants. Endpoint migrations import from one module.
tests/test_resolvers.py7 tests covering: stub registry hit/miss, pymeos_resolver lazy-import contract, default_resolver fallback path, prefer_pymeos=False short-circuit.
tests/test_wire.py8 tests covering: encoding constants, stub codec round-trip, missing-encoding error, has_decoder/has_encoder, pymeos_codec lazy-import contract.
.github/workflows/python.ymlPytest job extended to run all three test files together (the existing 12 dispatcher tests + 7 resolver tests + 8 wire tests = 27 unit tests).

All 15 new tests pass locally (python3 -m pytest tests/test_resolvers.py tests/test_wire.py -v).

Migration plan unlocked

With #6 + this PR in place, each of the 5 endpoint migrations becomes the same ~50-100 LoC pattern:

frommobilityapiimportDispatcher, default_resolver, pymeos_codec, ENCODING_MFJSON_DISPATCHER=Dispatcher(resolver=default_resolver())
_CODEC=pymeos_codec()
defget_velocity(self, collection_id, feature_id, geometry_id, connection, cursor):
# 1. fetch the trajectory from the DBcursor.execute("SELECT asMfjson(trajectory) FROM temporal_geometries WHERE id = %s", (geometry_id,))
traj_mfjson=cursor.fetchone()[0]
# 2. dispatch to MEOS via PyMEOStraj=_CODEC.decode(ENCODING_MFJSON, traj_mfjson)
speed=_DISPATCHER.dispatch("tpoint_speed", {"temp": traj})
# 3. encode back to MF-JSON for the HTTP responsereturnsend_json_response(self, _CODEC.encode(ENCODING_MFJSON, speed))

Production wiring

Production runs install PyMEOS via requirements.txt (not added in this PR — the migration PR that flips the first endpoint adds it). Until then, default_resolver() returns a stub that raises NotImplementedError on first dispatch with an actionable message, so MobilityAPI can be installed and unit-tested without PyMEOS.

Dependency chain

Stacks onStatus
#6 (dispatcher foundation)OPEN, green (12 dispatcher tests in CI)

Next stacked PRs

StepScope
#N+1Add pymeos>=1.4 to requirements.txt; flip the first endpoint to use the dispatcher (recommended: temporal_geom_query/velocity.pyDispatcher.dispatch("tpoint_speed", …)).
#N+2Remaining 4 endpoint migrations (acceleration.py, distance.py, temporal_geom_seq/, temporal_properties/).
#N+3 (step 5 of ingestion plan)Swap the dispatcher routes for the OGC API – Moving Features paths exposed by MEOS-API #13.

sirimeraouiand others added 23 commits March 16, 2026 12:52
MobilityAPI's working tree is the result of two distinct
contribution phases that today's repo doesn't make visible:
1. **pg_mfserv (March 2024)** — the founding OGC API – Moving
Features Python server for MobilityDB, authored at ULB.
- Maxime Schoemans (@mschoema) — initial commit, endpoint design.
- Victor Morabito (@MrMaxime1er) — main developer of the
pg_mfserv codebase: column discovery, request handling,
exception handling, route refactors.
2. **MobilityAPI (2025–)** — the current production-grade
implementation, also at ULB.
- Sirine Meraoui (@sirimeraoui) — current maintainer; structured
resource layout, tests, OGC conformance, documentation.
This commit adds three artefacts that surface the lineage:
- **README.md** — new `## History and Acknowledgements` section
before `## License` crediting all three contributors and pointing
to pg_mfserv as the archived predecessor.
- **AUTHORS.md** (new) — structured contributor list per phase.
- **CITATION.cff** (new) — machine-readable citation metadata for
Zenodo / GitHub citation widget, including a `references:` block
citing pg_mfserv as predecessor work.
License declared in CITATION.cff: PostgreSQL (matching the
MobilityDB main project's license posture).
The lineage credit is symmetric: pg_mfserv's README will gain an
archive banner pointing forward to MobilityAPI in the companion
PR on the pg_mfserv repository.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add lineage credits: pg_mfserv predecessor, AUTHORS, CITATION
…ADME rewrite
The repo is now the canonical home of MobilityAPI but was missing
several artefacts that ecosystem-grade org repos carry:
1. **LICENSE.txt** — The PostgreSQL License, matching the
MobilityDB main project's licence posture. Without this,
the repository was effectively all-rights-reserved by
default. Copyright Université libre de Bruxelles and
MobilityAPI contributors.
2. **CONTRIBUTING.md** — Development setup, test instructions
(./run.sh, ./run.sh --with-tests), code-style conventions
(PEP 8 + ruff), PR conventions. Cross-references AUTHORS.md
and the lineage section in the README.
3. **.github/workflows/python.yml** — Stub CI:
- Lint job: ruff check on PRs (warning-only at this stage so
legacy code isn't blocked; tighten when codebase has been
once-through).
- Import-smoke-test job: imports each application module so
a minimal "code is at least loadable" gate runs on PRs.
No DB-integration tests at this stage; that's a separate
GitHub Actions service-container effort once the test
harness stabilises.
4. **.github/ISSUE_TEMPLATE/{bug,feature}.md** + **PULL_REQUEST_TEMPLATE.md**
— issue / PR scaffolding. Bug template asks for environment
info; feature template prompts for OGC-spec references.
5. **README rewrite** — restructured for canonical-home framing:
- Added badges (License, Python, OGC API conformance).
- Lead with what MobilityAPI is (HTTP / OGC layer of the MEOS
ecosystem) rather than the bare introduction.
- Added a Status section pointing at issues + discussions.
- Added a "Where MobilityAPI fits" section showing peer SQL
layers (MobilityDB / MobilityDuck) and language bindings.
- Cross-link to https://libmeos.org/bindings/mobilityapi/.
- Added Contributing section pointing at CONTRIBUTING.md.
- Fixed "Pyhton" / "Developement" typos and the orphaned
##Poetry section. Final License section points at LICENSE.txt
and CITATION.cff.
- Lineage section (PR #1) preserved unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canonicalisation bundle: LICENSE, CI, contributor scaffolding, README rewrite
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 MobilityDB#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.
Stacks on the MobilityDB#6 dispatcher foundation. Adds the two layers a
production endpoint migration needs alongside the dispatcher:
mobilityapi/resolvers.py:
- stub_resolver(registry) — explicit name->callable map for tests
- pymeos_resolver() — production resolver that looks up
functions in pymeos.functions; lazy-
imports PyMEOS, raises ImportError
with an actionable message when it's
absent
- default_resolver(prefer_pymeos=True) — production-first probe
with a stub fallback that raises
NotImplementedError on first call
mobilityapi/wire.py:
- WireCodec — keyed by encoding name (mfjson, text,
wkb, hexwkb); decode wire-value to
PyMEOS obj; encode PyMEOS obj back
- stub_codec(decoders, encoders) — explicit-map constructor for tests
- pymeos_codec() — production codec bridging to PyMEOS
factory entry points
- ENCODING_{MFJSON,TEXT,WKB,HEXWKB} — canonical encoding-name
constants matching the catalog's
x-meos-{decode,encode} fields
mobilityapi/__init__.py re-exports all three names so endpoint
migrations import from one module.
15 new unit tests (tests/test_resolvers.py + tests/test_wire.py),
all passing locally. CI workflow's pytest job is extended to cover
all three test files together.
Production wiring lands when the first endpoint migrates: install
pymeos, replace getattr(pymeos.functions, name) plumbing with
default_resolver(), point WireCodec at pymeos_codec(). The
intervening migration PRs are bounded ~50-100 LoC each.
estebanzimanyi added a commit to estebanzimanyi/MobilityAPI-Python 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-Python 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.
@estebanzimanyiestebanzimanyi changed the title feat(resolvers,wire): pluggable resolvers + wire-layer codec (step 4)feat(resolvers,wire): pluggable resolvers + wire-layer codecMay 22, 2026
@estebanzimanyi
estebanzimanyi deleted the feat/step4-resolvers-and-wire branch June 1, 2026 15:29
Sign up for freeto 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.

4 participants

@estebanzimanyi@mschoema@MrMaxime1er@sirimeraoui