Skip to content

Mpc api - #32

Open
matthewjohnpayne wants to merge 42 commits into
mainfrom
mpc-api
Open

Mpc api#32
matthewjohnpayne wants to merge 42 commits into
mainfrom
mpc-api

Conversation

@matthewjohnpayne

@matthewjohnpayne matthewjohnpayne commented Feb 14, 2026

Copy link
Copy Markdown
Collaborator

Introduces an mpc_api package that is intended to be a pip-installable package on PyPi.
mpc_api provides a single 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.

This PR also

  • updates all api tutorial notebooks with mpc_api usage examples
  • adds a Getting Started notebook for the mpc_api package

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:

  • As per my comment in PR31 , It's probably reasonably obvious that I created this using claude-code.
  • It's unclear to me how we should be reviewing such code going forward: It works, but it was all produced in a few hours, and reviewers will have a hard time digesting such a large volume of code.

matthewjohnpayne and others added 4 commits February 14, 2026 15:12
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>
@matthewjohnpayne

Copy link
Copy Markdown
Collaborator Author

@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).

Comment thread .github/workflows/mpc_api_pytest.yml Outdated
Comment thread mpc_api/tests/test_neocp.py Outdated
Comment on lines +36 to +49
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

@federicaspoto federicaspoto Feb 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@matthewjohnpayne matthewjohnpayne Feb 28, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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_api to be the api, and also, I don't want the CI to get screwed-up due to (e.g.) downtime in mpcweb6
  1. tests that mock the API response, so that we are purely testing this mpc_api.MPCClient
  2. 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.

matthewjohnpayne and others added 10 commits February 28, 2026 13:35
…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>
@matthewjohnpayne

matthewjohnpayne commented Feb 28, 2026

Copy link
Copy Markdown
Collaborator Author

OK, @federicaspoto , @fiftymillionfeltcapfanscantbewrong , I have addressed the previous comments regarding mpc_api.

I have also verified that the version currently released to TestPyPI works when downloaded.

I have clarified the README

I have also added various .github/workflows/ to ensure that

  1. the code will be released to TestPyPI on every push to a PR involving mpc_api, and
  2. the code will be released to PyPI on every merge to main of a PR involving mpc_api.

matthewjohnpayne and others added 2 commits February 28, 2026 14:31
@fiftymillionfeltcapfanscantbewrong

Copy link
Copy Markdown
Collaborator

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.

@pkgw

pkgw commented Mar 18, 2026

Copy link
Copy Markdown
Collaborator

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 mpc_client or mpc_api_client rather than mpc_api, which to me makes it sound like something that would implement the server side of the API. It would be a hassle to change this, but we'll be stuck with whatever name we adopt for a very long time.

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:

matthewjohnpayne and others added 4 commits March 22, 2026 19:19
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>
matthewjohnpayne and others added 4 commits March 23, 2026 15:33
…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>
@matthewjohnpayne

matthewjohnpayne commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator Author

@pkgw : Thanks for your various thoughts and suggestions.

I've made numerous updates, much of which I describe below.

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.

  • I agree!
  • I'd like this package to become the standard place that we point users too, so I'm all in favor of putting in the required effort to build, document & maintain it.

Nomenclature: these kinds of libraries are usually called "clients", à la the MPCClient type. I would prefer that this package be named mpc_client or mpc_api_client rather than mpc_api, which to me makes it sound like something that would implement the server side of the API. It would be a hassle to change this, but we'll be stuck with whatever name we adopt for a very long time.

  • I've changed it to mpc_client.

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.

  • I believe that I have now implemented pydantic validation on the request side, as well as consistent class+attribute structures for the results side.
  • I note that I have opted to put all code (both request & result classes) for a given API (e.g. cnd) into a single mpc-client script (e.g. _cnd.py). I could have opted to (e.g.) put all request structures for all apis into a single _requests.py file, but I felt like the _cnd.py pattern would be easier for folks to follow when adding additional future APIs into the package.

Throughout, function signatures should have thorough type annotations. There should probably be linting automation to enforce this in CI.

  • I have added .github/workflows/mpc_client_lint.yml to the repo (and fixed various problems).
  • I am not at all experienced in using/deploying this stuff, so I'd appreciate any suggestions for improving it.

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.

  • I agree that we should probably do this. In many respects I'm resistant to it, as I ultimately want to stop users submitting in this format, but logically, just because I might prevent submission in a format doesn't mean that format doesn't exist!
  • We have a python package (named obs80) in mpc-software that will parse obs80 strings into python classes/namedtuples. It is not perfect, but it would provide a solid base for a piece of public obs80 parsing code.
  • Alternatively, one could use the ADES package to convert to XML and thence on to python dicts/classes.

matthewjohnpayne and others added 18 commits March 23, 2026 21:06
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>
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.

4 participants