-
Notifications
You must be signed in to change notification settings - Fork 26
Fastapi conversion #2071
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Fastapi conversion #2071
Changes from 141 commits
94d7227
3c79379
4d5c64f
8ebb3f6
98d867f
6558bc7
04da662
b84f81f
a3624de
f31519b
4de7efd
48f19a6
4e44d03
c44dc0e
da8d2e5
b130b33
c3004a0
a5b5849
74ac09f
6baffad
d761d3a
bd2dbbb
9271c5b
0ac8204
b8bbb4d
617afad
22ad07d
a4d2330
5808483
2998c02
41dd24b
95d3398
8053215
1d30533
97f01bd
a2d31fb
191c4c0
96832e8
25bdc30
6392fb2
960ab66
de54f47
78f32b2
ee75871
dbd664d
71aaf9d
573aeb5
0f84dd4
8c0060b
d7b5b8b
d3496ab
4333511
2a2d03e
7a8181c
0c45e9d
38b2b14
f21e781
eb2ddf9
fb176b4
9d83970
792f83a
870456a
ba08993
c7d7bcf
5991392
eac368f
d77cf4b
7a5dbf8
9a96716
4d860f9
7435e7d
ca962a4
4bb92e2
8276016
3725643
610ca6d
9ab2a6a
d63edcb
2bce5ed
d3c8bf0
6d6b4ed
24fa834
8f965d0
c979f3d
ddb8b7f
fa456ad
93d60e0
b6278e0
be8b7fd
b1977f8
f199f9f
dc8e53b
2572d1d
7277a99
dd91fbc
b6c9753
8cb3422
3fb7e01
c844e0a
4f7b57d
b776d2d
73e5acf
724698a
c126522
75d4bdb
a934bdd
79ef63f
93e6bf4
946e2e7
210b0e1
cb9b506
39c032f
3e21a4d
f415323
e92977f
7e359ea
0413425
d89e40c
86ba711
8e83e65
26d0398
12340a6
381353a
ddcd41f
b41a420
0264d35
a69e64d
bfb32c9
d4129b4
937be7d
453845c
2249c68
6906556
6604e55
79fd99c
d2ca35c
5f75cf3
d79ac6f
fa4886c
a5d543b
24d7b5d
e9e1074
0ebd99c
53ed677
f7fbff0
e48eb19
2077a49
fa292e1
7d617ef
8c162ed
8cc2c23
3acff74
48dce53
0280950
23494a1
0862484
0521e7c
507c11e
afbbee1
19df929
09c6a25
4a48ef7
cb90bc7
1c7a420
57d906a
7992612
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| MPCONTRIBS_ENVIRONMENT=dev | ||
|
|
||
| MPCONTRIBS_MONGO__URI=mongodb+srv://<db_username>:<db_password>@host.hash.mongodb.net/?appName=database-name | ||
| MPCONTRIBS_MONGO__DB_NAME=database-name | ||
|
|
||
| MPCONTRIBS_REDIS_ADDRESS=redis-address # placeholder; not used yet | ||
| MPCONTRIBS_REDIS_URL=redis-url | ||
|
|
||
| MPCONTRIBS_MAIL_DEFAULT_SENDER=mail-default-sender # placeholder; not used yet | ||
|
|
||
| MPCONTRIBS_VERSION=version |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| src/mpcontribs_api/old |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| This is a complete rewrite of the MPContribs API server. | ||
| The core of the package is FastAPI, Pydantic, and Beanie. | ||
| Developer experience and process efficiency are top priorities. | ||
|
|
||
| ## Commands | ||
|
|
||
| ```bash | ||
| # Format, fix, and lint (preferred) | ||
| just fmt | ||
|
|
||
| # Run tests | ||
| uv run pytest | ||
|
|
||
| # Run tests by marker | ||
| uv run pytest -m base | ||
| uv run pytest -m extra | ||
|
|
||
| # Run tests in parallel | ||
| uv run pytest -n auto | ||
|
|
||
| # Type check | ||
| uv run basedpyright | ||
| ``` | ||
|
|
||
| ## Environment | ||
|
|
||
| Copy `.env.example` to `.env`. Key variables (all prefixed `MPCONTRIBS_`): | ||
|
|
||
| - `MONGO__URI` — MongoDB Atlas URI | ||
| - `MONGO__DB_NAME` — database name | ||
| - `KONG__GATEWAY_SECRET` — shared secret for verifying requests came through Kong | ||
| - `ENVIRONMENT` — `dev` or `prod` (controls log format and debug mode) | ||
|
|
||
| ## Architecture | ||
|
|
||
| ### Domain structure | ||
|
|
||
| Each resource lives in `src/mpcontribs_api/domains/<resource>/` with four files: | ||
|
|
||
| - `models.py` — Beanie `Document` subclass (DB model) + Pydantic output/input/patch/filter models | ||
| - `repository.py` — `MongoDb<Resource>Repository` encapsulating all database access | ||
| - `router.py` — `APIRouter` with CRUD endpoint handlers | ||
| - `dependencies.py` — `<Resource>Dep` type alias (`Annotated[MongoDb<Resource>Repository, Depends(...)]`) | ||
|
|
||
| Routers are registered in `src/mpcontribs_api/api/v1/router.py`. | ||
|
|
||
| ### Authentication and authorization | ||
|
|
||
| Kong injects user identity via headers; `auth.py` parses them into a frozen `User` model. The dependency chain is: | ||
|
|
||
| - `UserDep` — any caller (anonymous or authenticated) | ||
| - `AuthedDep` — requires authenticated user (raises 401 otherwise) | ||
| - `require_role(role)` — factory returning a dependency that requires a specific group membership | ||
|
|
||
| All database access goes through a repository instantiated with the current `User`. The repository's `_scope` dict is injected into every MongoDB query automatically: | ||
|
|
||
| - **Admins** (members of `mongo.admin_group`): no filter applied | ||
| - **Authenticated users**: see public+approved data, own resources (`owner == username`), and group resources | ||
| - **Anonymous**: public + approved only | ||
|
|
||
| `verify_gateway()` in `dependencies.py` validates the `x-gateway-secret` header to ensure Kong was the actual caller. | ||
|
|
||
| ### Repository pattern | ||
|
|
||
| Repositories take a `User` at construction time and expose typed async methods (`get_*`, `insert_*`, `patch_*`, `upsert_*`, `delete_*`). They never leak the scope logic to routers — routers only call repository methods. | ||
|
|
||
| ### Sparse field selection | ||
|
|
||
| The `_fields` query parameter (handled in `projection.py`) lets callers request specific fields (comma-separated, dotted for nesting). `SparseFieldsModel` dynamically creates a trimmed Pydantic model via `create_model()` and converts field paths to MongoDB projection syntax. Results are cached with `@lru_cache`. | ||
|
|
||
| ### Cursor-based pagination | ||
|
|
||
| `Page[T]` in `pagination.py` contains `items` and a `next_cursor` (base64-encoded last item ID). Pagination is forward-only and stateless. Default page size is 20; max is 100. | ||
|
|
||
| ### Exception hierarchy | ||
|
|
||
| `exceptions.py` defines `AppError` subclasses (`NotFoundError`, `ConflictError`, `ValidationError`, `AuthenticationError`, `PermissionError`). All carry `status_code`, `error_code`, `message`, and a `context` dict. Handlers in `app.py` convert them to a uniform JSON shape; internal context is logged but not sent to clients. | ||
|
|
||
| ### Observability | ||
|
|
||
| `logging.py` configures structlog with per-request context vars (request_id, method, path, consumer_id) bound by the middleware in `middleware.py`. OpenTelemetry traces are exported via OTLP/gRPC and trace/span IDs are injected into every log line. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,58 +1,42 @@ | ||
| # NOTE: base image must be updated to Python 3.14 to satisfy requires-python in pyproject.toml | ||
| FROM materialsproject/devops:python-3.1113.4 AS base | ||
| RUN apt-get update && apt-get install -y --no-install-recommends supervisor libopenblas-dev libpq-dev vim && apt-get clean | ||
| RUN apt-get update && apt-get install -y --no-install-recommends supervisor libopenblas-dev vim && apt-get clean | ||
| WORKDIR /app | ||
|
|
||
| FROM base AS builder | ||
| RUN apt-get update && apt-get install -y --no-install-recommends gcc git g++ libsnappy-dev wget liblapack-dev && apt-get clean | ||
| ENV PIP_FLAGS "--no-cache-dir --compile" | ||
| COPY requirements/deployment.txt ./requirements.txt | ||
| RUN pip install $PIP_FLAGS -r requirements.txt | ||
| COPY pyproject.toml . | ||
| COPY mpcontribs mpcontribs | ||
| RUN apt-get update && apt-get install -y --no-install-recommends gcc git g++ wget liblapack-dev && apt-get clean | ||
| RUN pip install uv | ||
| COPY pyproject.toml uv.lock ./ | ||
| COPY src src | ||
| ARG CONTRIBS_VERSION | ||
| RUN SETUPTOOLS_SCM_PRETEND_VERSION=${CONTRIBS_VERSION} pip install $PIP_FLAGS --no-deps . | ||
| #ENV SETUPTOOLS_SCM_PRETEND_VERSION 0.0.0 | ||
| #COPY marshmallow-mongoengine marshmallow-mongoengine | ||
| #RUN cd marshmallow-mongoengine && pip install $PIP_FLAGS --no-deps -e . | ||
| #COPY mimerender mimerender | ||
| #RUN cd mimerender && pip install $PIP_FLAGS --no-deps -e . | ||
| #COPY flask-mongorest flask-mongorest | ||
| #RUN cd flask-mongorest && pip install $PIP_FLAGS --no-deps -e . | ||
| #COPY AtlasQ AtlasQ | ||
| #RUN cd AtlasQ && pip install $PIP_FLAGS --no-deps -e . | ||
| RUN SETUPTOOLS_SCM_PRETEND_VERSION=${CONTRIBS_VERSION} uv sync --frozen --no-dev | ||
| RUN wget -q https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \ | ||
| chmod +x wait-for-it.sh && mv wait-for-it.sh /usr/local/bin/ && \ | ||
| wget -q https://github.com/materialsproject/MPContribs/blob/master/mpcontribs-api/mpcontribs/api/contributions/formulae.json.gz?raw=true \ | ||
| -O mpcontribs/api/contributions/formulae.json.gz | ||
|
Comment on lines
-25
to
-26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reason for removing the formulae file?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file seemed to just function as an id-to-formula translation. I made formulas required when POSTing. If we want that moved back to optional and filled in automatically, I think I'd prefer actively fetching the formula over keeping it in a data file that we track in git. That would mean an extra query to mongo, and reaching into the nextgen database, but I feel that it is more reactive and resilient that way. |
||
| chmod +x wait-for-it.sh && mv wait-for-it.sh /usr/local/bin/ | ||
|
|
||
| FROM base | ||
| ARG BUILDARCH | ||
| ENV BUILDARCH=x86_64 | ||
| COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages | ||
| COPY --from=builder /usr/local/bin /usr/local/bin | ||
| COPY --from=builder /usr/lib/$BUILDARCH-linux-gnu/libsnappy* /usr/lib/$BUILDARCH-linux-gnu/ | ||
| COPY --from=builder /app/.venv /app/.venv | ||
| COPY --from=builder /usr/local/bin/wait-for-it.sh /usr/local/bin/ | ||
|
|
||
| COPY --from=builder /app/mpcontribs/api /app/mpcontribs/api | ||
| WORKDIR /app | ||
| RUN mkdir -p /var/log/supervisor | ||
|
|
||
| COPY supervisord supervisord | ||
| COPY scripts scripts | ||
| COPY main.py . | ||
| COPY maintenance.py . | ||
| COPY docker-entrypoint.sh . | ||
| COPY gunicorn.conf.py . | ||
| RUN chmod +x main.py scripts/start.sh scripts/start_rq.sh docker-entrypoint.sh | ||
| RUN chmod +x main.py scripts/start.sh docker-entrypoint.sh | ||
|
|
||
| ARG VERSION | ||
| ENV DD_SERVICE=contribs-apis \ | ||
| ENV PATH="/app/.venv/bin:$PATH" \ | ||
| DD_SERVICE=contribs-apis \ | ||
| DD_ENV=prod \ | ||
| DD_VERSION=$VERSION \ | ||
| DD_TRACE_HOST=localhost:8126 \ | ||
| DD_TRACE_OTEL_ENABLED=false \ | ||
| DD_MAIN_PACKAGE=mpcontribs-api | ||
|
|
||
| LABEL com.datadoghq.ad.logs='[{"source": "gunicorn", "service": "contribs-apis"}]' | ||
| LABEL com.datadoghq.ad.logs='[{"source": "uvicorn", "service": "contribs-apis"}]' | ||
| EXPOSE 10000 10002 10003 10005 20000 | ||
| ENTRYPOINT ["/app/docker-entrypoint.sh"] | ||
| CMD ["/usr/bin/supervisord", "-c", "supervisord.conf"] | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fastapi uses uvicorn |
This file was deleted.
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Convenient command runner |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Run the dev server (auto-reloads on file changes) | ||
| dev: | ||
| uv run fastapi dev src/mpcontribs_api/app.py | ||
|
|
||
| # Format, fix, and lint all source code | ||
| fmt: | ||
| uv run ruff format | ||
| -uv run ruff check --fix | ||
| -uv run ruff check --fix --unsafe-fixes | ||
| -uv run pydocstringformatter --write | ||
|
|
||
| test suite="all": | ||
| #!/usr/bin/env bash | ||
| case "{{suite}}" in | ||
| all|a) uv run pytest tests/ ;; | ||
| integration|i) uv run pytest tests/integration/ -m "not db" ;; | ||
| unit|u) uv run pytest tests/unit/ ;; | ||
| db|d) uv run pytest tests/integration/db/ -m db ;; | ||
| *) echo "unknown suite '{{suite}}' — use: all/a, integration/i, unit/u, db/d" && exit 1 ;; | ||
| esac |
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ruff formatting |
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not using MongoEngine. |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch from pip building to uv.
!! Needs further verification