Mpc api - #32
Conversation
Introduces MPCClient class with methods for all 10 MPC public APIs: identify, get_orbit, get_observations, get_observatory, get_mpecs, check_near_duplicates, get_submission_status, submit_xml/psv, request_action_code, and get_neocp_observations. Includes optional pandas DataFrame output, custom exception hierarchy, and 60 mocked tests. Updates all tutorial notebooks with mpc_api usage examples and adds a Getting Started notebook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each notebook opening markdown cell now mentions the mpc_api package as an alternative, with a link to the detailed examples lower in the same notebook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@federicaspoto : I've asked @fiftymillionfeltcapfanscantbewrong to review this PR, but I'm just flagging you on it so that you are aware of it (e.g. for newsletter/similar). |
| def test_get_neocp_observations_df(client): | ||
| responses.get( | ||
| NEOCP_URL, | ||
| json=[{ | ||
| "ADES_DF": [ | ||
| {"trksub": "P21Eetc", "obstime": "2025-02-10", "ra": 10.0, "dec": 20.0, "stn": "F51"}, | ||
| {"trksub": "P21Eetc", "obstime": "2025-02-11", "ra": 11.0, "dec": 21.0, "stn": "G96"}, | ||
| ] | ||
| }], | ||
| ) | ||
|
|
||
| df = client.get_neocp_observations_df("P21Eetc") | ||
| assert isinstance(df, pd.DataFrame) | ||
| assert len(df) == 2 |
There was a problem hiding this comment.
I haven't tested this, but in principle this should not work unless the tracklet is currently on the NEOCP. This doesn't seem to be the expected behavior though.
There was a problem hiding this comment.
I'm not quite sure what you mean here, but I suspect it is lack of clarify in the tests regarding what @responses.activate does (it mocks API responses).
As such, I have added more tests, and then split the tests into 3 main sections:
- tests that actually, for real, call the API (but are allowed to fail/skip if the API is unavailable)
- N.B. I'm allowing these to pass if the API is unavailable because it is not strictly the job of
mpc_apito be the api, and also, I don't want the CI to get screwed-up due to (e.g.) downtime inmpcweb6
- tests that mock the API response, so that we are purely testing this
mpc_api.MPCClient - tests that are independent of the API (such as error handling within
mpc_api)
I note that I have done the same for all of the various test files in tests/test_*.py, and added a bunch of comments to make clear what is going on.
…d tutorial notebook - Rename mpc_api/ to mpc-api/ and move source to src/ layout for packaging - Update pyproject.toml hatch build targets, add lxml to test deps - Update GitHub Actions workflows for new paths - Add real API tests to all test files with per-endpoint skip logic - Add shared check_api fixture in conftest.py for graceful API-unavailable skipping - Add updated mpc_tutorial_mpcapi.ipynb notebook with install cell and all API demos Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Publishes to TestPyPI on every PR that touches mpc-api/, with a unique .devN version suffix per run. Also supports manual dispatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update release workflow to trigger on push to main (in addition to manual releases). Checks PyPI for the current version and skips publish if it already exists, so only version bumps cause a release. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
OK, @federicaspoto , @fiftymillionfeltcapfanscantbewrong , I have addressed the previous comments regarding I have also verified that the version currently released to I have clarified the README I have also added various
|
…rapped & added to mpc-api
…output Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hey Matt, I think this project is really cool, but I have concerns about the maintainability of it. This PR introduces validation steps that duplicate the validation that we perform in the APIs, and each endpoint requires a special mixin class. Yes, an LLM could produce these quickly, but we need to be able to read it. Perhaps a simpler solution would be to provide a generic, pip-installable utility? Calls could be structured like: from mpc_api import call_mpc_api
endpoint = 'mpecs'
payload = {
...
}
method = 'GET'
# 'POST' should be the default method; we need to move to consistently using that, anyway.
call_mpc_api(endpoint, payload, method=method)
def call_mpc_api(endpoint: str, payload: dict, method: Literal['GET', 'POST'], retries: int = 5) -> dict:
# call the API, attempting retries
# If the Pydantic validation fails, which some endpoints report, it's already formatted in JSON
# If successful, just return what the API does.Irrespective of this suggestion, this package should attempt retries intelligently; currently it seems like it only attempts requests once? Perhaps it's also a philosophical question: If we provide APIs for the public to use, with attendant documentation, should we obfuscate them behind a package such as this? Users will ultimately be subject to rate-limiting imposed by the API, which may give them the wrong impression of this package. |
|
OK, a few early comments. First: we should keep in mind that distributing and maintaining a real (not halfassed) API client library is really valuable, but also a lot of work. Documentation, testing, examples, keeping up-to-date with internal changes, support, avoiding compatibility breaks, etc. If we're going to provide a package like this and not work ourselves into an annoyingly fragmented ecosystem, it needs to become the thing that we use every single time anyone asks for or needs example code to use MPC APIs. I'm all for that, but we'd need really embrace "eating our own dogfood" as much as possible. Doing a decent job of this would demand time and attention from multiple MPC staff. Nomenclature: these kinds of libraries are usually called "clients", à la the MPCClient type. I would prefer that this package be named Documentation: the package needs documentation. Example notebooks are a component of this but there is a lot else that is needed. Fortunately I just spent a year developing a scientific software documentation checklist. Docs should probably be built with Sphinx (or mkdocs) and wired up to publish to readthedocs.org. The mixin pattern might be a bit of a hassle for autogenerated API docs. Typing: in terms of the actual implementation, this package fails to do what I think is the most useful thing for a language-specific client library, which is to (consistently) map the JSON HTTP API responses into data structures useful in the target language. In my experience the most useful pattern for a library like this is to rigorously define Python dataclasses representing all of the request and response types, using something like Pydantic to map to/from JSON. Among other things, this enables you to write docstrings on the classes and their fields that turn into published documentation that actually rigorously describe their semantics, and get Intellisense in a modern editor. Low-level API invocation methods pretty much only transmute between the Python types and the JSON API request, and then you can build smarter and higher-level workflows based on those low-level primitives. Throughout, function signatures should have thorough type annotations. There should probably be linting automation to enforce this in CI. Scope: Thinking about the above leads to a question of library scope. If we have APIs that return data in obs80 format, we want to provide something low-level that does just that, but it would also likely be helpful to provide something a bit more "opinionated" that can parse the obs80 out of its bespoke textual format. So then we have to start thinking about providing public obs80 parsing code, or depending on a blessed external obs80 library, etc. This is probably especially relevant here since we have a lot of bespoke data formats flying around. Examples of docs describing similar client libraries that I've built: |
Rename directory mpc-api/ to mpc-client/, rename package from mpc_api to mpc_client, update GitHub workflows, and fix all mpc-api/mpc_api references across README, pyproject.toml, tutorial notebooks, and docs-public tutorials. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add mkdocs site (Material theme, mkdocstrings, mkdocs-jupyter) with: - index.md: synopsis, install, contact, license, acknowledgments - api-reference.md: auto-generated from docstrings - contributing.md: contribution guidelines - Tutorial notebook symlink - CITATION.cff for citation metadata - .readthedocs.yml for ReadTheDocs publishing - docs extras in pyproject.toml Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SubmissionResponse, ActionCodeResponse, NearDuplicateMatch, and ObservationsResult response models so all API methods return objects with attribute access. Add ObscodeRequest validation. Clean up unused imports. Export all response models from __init__.py. Update both tutorial notebooks and docs to demonstrate attribute access instead of dict-style access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ests Add one-line docstrings to all undocumented test functions across 12 files. Add dict-style access assertions alongside attribute access in mocked tests to verify DictCompatModel backward compatibility. Add inline comments on all responses.get/post calls to clarify they register fake responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ict-style access Response models now inherit from plain BaseModel (no DictCompatModel), so json.dumps() on model instances and bracket/get access need updating: - Use .model_dump() before json.dumps() for serialization - Use attribute access (result.OBS80, orbit.designation_data.permid) instead of bracket access - Fix variable name typo (v -> value) in wamo notebook Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nse models DictCompatModel added dict-style access (model["key"], model.get()) to response models for backward compatibility, but mpc-client has never been released so there are no existing users to support. This simplifies the codebase by having all response models inherit directly from Pydantic BaseModel. - Delete DictCompatModel class from _requests.py - Update all 9 module files to import BaseModel instead of DictCompatModel - Fix internal dict-style access in _mpecs.py, _neocp.py, _observations.py - Update all tests to use attribute access instead of bracket access - Pin mkdocs<2 in pyproject.toml to avoid compatibility issues Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add .github/workflows/mpc_client_lint.yml running ruff check + format - Add [tool.ruff] config to pyproject.toml (line-length=100, E/F/W/I rules) - Add ruff to test extras - Fix all lint errors: remove unused imports, sort imports, shorten long lines - Auto-format all source and test files with ruff format Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@pkgw : Thanks for your various thoughts and suggestions. I've made numerous updates, much of which I describe below.
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ansion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s] syntax Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pip does not support path/[extras] syntax; use cd + .[extras] instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aths Commands run in an unknown working directory; use the RTD env var to locate the checkout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RTD ships pip 24.0 which does not support path[extras]; pip 25+ does. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… bracket parsing issues Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pip 24.0 cannot do editable installs with hatchling; need pip 25+. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editable installs fail on RTD pip; regular install is fine for docs builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…utput Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verified locally: mkdocs-jupyter 0.24.8 + nbconvert 7.17.0 builds successfully. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- .readthedocs.yaml: keep mpc-api version (builds mpc-client docs) - CLAUDE.md: accept main's version - mpc_tutorial_api_mpecs.ipynb: accept main's updated version Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PRs validate that the package builds correctly; publishing only runs after merge (or manual workflow_dispatch) to avoid trusted publisher errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces an mpc_api package that is intended to be a pip-installable package on PyPi.
mpc_apiprovides a singleMPCClientclass with methods for all 10 MPC public APIs:Includes optional pandas DataFrame output, custom exception hierarchy, and 60 mocked tests.
This PR also
I have verified that the code works and that the main demo notebook,
mpc_tutorial_mpcapi.ipynb, also works and provides simple-but-cogent explanations of the individual apis, as well as linking out to the tutorials & documentation for the APIs.NB: