Skip to content

Commit 048ad79

Browse files
skearnesclaude
andauthored
Migrate tooling to ruff, ty, and uv (#173)
* Migrate tooling to ruff, ty, and uv Mirrors the migration done in ord-schema (#780, #783): replaces black / isort / pylint / pytype with ruff + ty, switches dependency management to uv, consolidates setup.py into pyproject.toml, and adds pre-commit. Tooling - pyproject.toml: full [project] block + [dependency-groups] dev; [tool.uv] package = false; [tool.ruff] / [tool.ty.src] config matching ord-schema. Drop [tool.black]/[tool.isort]/[tool.pytype]. Drop setup.py and format.sh. - Add .pre-commit-config.yaml (addlicense + ruff-check + ruff-format + ty). - Generate uv.lock; bump numpy<2 -> numpy>=1.26.4 to enable cp313/cp314. CI - New checks.yml replaces cleanup.yml: ruff check + ruff format --check + ty + addlicense + clang-format. - run_tests.yml uses astral-sh/setup-uv@v5 + uv sync --frozen; matrix bumped from 3.10-3.12 to 3.11-3.14 (matches ord-schema). Source - Strip 8 # pylint: disable and 1 # pytype: disable comments from non-editor files. - Exclude ord_interface/editor/ and ord_interface/interface.py from ruff/ty (slated for removal in #N). - Fix all 19 ty diagnostics: * Type psycopg connection/cursor as dict[str, Any] rows (introduces DictCursor alias in queries.py). * Fix QueryResult.__eq__ Liskov violation (other: object + isinstance). * Annotate kwargs: dict[str, Any] in queries_test.test_similarity_query. * Generalize _pbtxt to accept any protobuf Message (callers pass both Reaction and ProductCompound). * cast(Chem.Mol, ...) around mol_from_compound (return type widens to a tuple when return_identifier=True). * Annotate yielding fixture as Iterator[Reaction]. * One # ty: ignore[invalid-await] on redis.asyncio client.ping() (stub returns Awaitable[bool] | bool). Dockerfile - Replace setup.py egg_info + pip install dance with uv sync --frozen --no-dev; drop the final pip install . (package=false). * Clean up tests workflow Rename run_tests.yml to tests.yml, restrict push trigger to main, and drop the unused CACHE_TARGET env var. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix CI failures on modernize-tooling - Bump protobuf cap from <5 to <6 so resolution picks 5.29.6, which ships cp38-abi3 wheels and runs on Python 3.14. The 4.x series has no 3.14 wheels, so import fails with "Metaclasses with custom tp_new are not supported." Matches ord-schema's constraint. - test_app: invoke run_tests.sh via "uv run" so the venv's python and pytest are on PATH. The script's bare "python client/build_database.py" was hitting the system interpreter, which doesn't have ord_interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test_app: set PYTHONPATH so bare python finds ord_interface The pyproject sets [tool.uv] package = false and has no [build-system], so uv sync only installs dependencies, not the ord_interface package itself. Pytest's rootdir handling masked this in test_ord_interface, but the test_app job runs build_test_database.sh which calls "python client/build_database.py" directly and hits ModuleNotFoundError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: pass as_utf8=True to text_format.MessageToBytes protobuf 5.x's MessageToBytes encodes the string form as ASCII by default and raises UnicodeEncodeError when the message contains non-ASCII content. as_utf8=True keeps non-ASCII bytes through to the output, matching the prior 4.x behavior in practice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Make ord-interface installable; align codecov with ord-schema - Add [build-system] (setuptools+wheel) and [tool.setuptools.packages.find] scoped to ord_interface*; drop [tool.uv] package = false. uv sync now installs ord-interface as an editable wheel into the venv, so "import ord_interface" works from anywhere instead of requiring the repo root on sys.path. - tests.yml: drop the test_app PYTHONPATH workaround that compensated for the project not being installed. - tests.yml: add fail_ci_if_error to codecov-action so fork PRs and dependabot/* branches don't fail the job on tokenless uploads. Mirrors ord-schema's run_tests.yml. - Dockerfile: split uv sync into a deps-only pass before COPYing sources (--no-install-project) and a final sync after to install the project itself, preserving layer caching for deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Tighten ty annotations; move testing-postgresql to dev group Apply review feedback: - filters.py: cast(Chem.Mol | None, ...) instead of cast(Chem.Mol, ...) to preserve the meaning of the subsequent `if mol:` null-check. - search.py: replace `# ty: ignore[invalid-await]` on `redis.asyncio.Redis.ping()` with `cast(Awaitable[bool], ...)`, scoping the workaround to the actual problem (stub union return type). - pyproject.toml/uv.lock: move `testing-postgresql` from runtime dependencies to the `dev` group; lazy-import `Postgresql` in `api/main.py` inside the `ORD_INTERFACE_TESTING=TRUE` branch since that's the only path that instantiates it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8731895 commit 048ad79

19 files changed

Lines changed: 2580 additions & 245 deletions
Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020 Open Reaction Database Project Authors
1+
# Copyright 2026 Open Reaction Database Project Authors
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,52 +12,48 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
name: Lint
15+
name: Checks
1616

17-
on: [ pull_request, push ]
17+
on:
18+
pull_request:
19+
push:
20+
branches:
21+
- main
1822

1923
jobs:
2024
check_licenses:
2125
runs-on: ubuntu-latest
2226
steps:
23-
- uses: actions/checkout@v3
24-
- uses: actions/setup-go@v3
27+
- uses: actions/checkout@v4
28+
- uses: actions/setup-go@v5
2529
with:
2630
go-version: '>=1.16'
27-
- name: Install dependencies
31+
- name: Install addlicense
2832
run: go install github.com/google/addlicense@latest
2933
- name: addlicense
3034
run: addlicense -check -c "Open Reaction Database Project Authors" -l apache .
3135

3236
check_python:
3337
runs-on: ubuntu-latest
3438
steps:
35-
- uses: actions/checkout@v3
36-
- uses: actions/setup-python@v4
39+
- uses: actions/checkout@v4
40+
- uses: astral-sh/setup-uv@v5
3741
with:
38-
python-version: '3.10'
39-
- name: Install ord-interface
40-
run: |
41-
python -m pip install --upgrade pip
42-
python -m pip install wheel
43-
python -m pip install .[tests]
44-
- name: Run black
45-
run: |
46-
black --check .
47-
- name: Run isort
48-
run: |
49-
isort --check .
50-
- name: Run pylint
51-
run: |
52-
pylint ord_interface *.py
53-
- name: Run pytype
54-
run: |
55-
pytype -j auto
42+
enable-cache: true
43+
python-version: "3.12"
44+
- name: Install dependencies
45+
run: uv sync --frozen
46+
- name: ruff check
47+
run: uv run ruff check ord_interface
48+
- name: ruff format
49+
run: uv run ruff format --check ord_interface
50+
- name: ty
51+
run: uv run ty check ord_interface
5652

5753
check_javascript:
5854
runs-on: ubuntu-latest
5955
steps:
60-
- uses: actions/checkout@v3
56+
- uses: actions/checkout@v4
6157
- name: clang-format
6258
run: |
6359
sudo apt update && sudo apt install clang-format --yes
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020 Open Reaction Database Project Authors
1+
# Copyright 2026 Open Reaction Database Project Authors
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,21 +12,20 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
name: CI
16-
17-
on: [ pull_request, push ]
18-
19-
env:
20-
# See https://dev.to/dtinth/caching-docker-builds-in-github-actions-which-approach-is-the-fastest-a-research-18ei.
21-
CACHE_TARGET: "docker.pkg.github.com/open-reaction-database/ord-interface/interface-cache"
15+
name: Tests
2216

17+
on:
18+
pull_request:
19+
push:
20+
branches:
21+
- main
2322

2423
jobs:
2524
test_ord_interface:
2625
strategy:
2726
matrix:
2827
os: ["ubuntu-latest", "macos-14"]
29-
python-version: ["3.10", "3.11", "3.12"]
28+
python-version: ["3.11", "3.12", "3.13", "3.14"]
3029
runs-on: ${{ matrix.os }}
3130
env:
3231
PGDATA: $GITHUB_WORKSPACE/rdkit-postgres
@@ -41,51 +40,54 @@ jobs:
4140
# NOTE(skearnes): conda is only used for postgres (not python).
4241
conda install -c conda-forge rdkit-postgresql
4342
initdb
44-
- uses: actions/setup-python@v4
43+
- uses: astral-sh/setup-uv@v5
4544
with:
45+
enable-cache: true
4646
python-version: ${{ matrix.python-version }}
4747
- name: actions-setup-redis
4848
uses: shogo82148/actions-setup-redis@v1.35.0
4949
- name: Install ord_interface
50-
run: |
51-
python -m pip install --upgrade pip
52-
python -m pip install wheel
53-
python -m pip install .[tests]
50+
run: uv sync --frozen
5451
- name: Run tests
5552
shell: bash -l {0}
5653
run: |
57-
coverage erase
58-
pytest -vv --cov=ord_interface --durations=20 --ignore=ord_interface/editor
59-
coverage xml
60-
- uses: codecov/codecov-action@v1
54+
uv run coverage erase
55+
uv run pytest -vv --cov=ord_interface --durations=20 --ignore=ord_interface/editor
56+
uv run coverage xml
57+
# Codecov uses CODECOV_TOKEN when set. Do not fail the job when uploads cannot authenticate:
58+
# fork PRs (tokenless), Dependabot PRs (no repo secrets + protected dependabot/* branches).
59+
# Pushes to main and normal same-repo PRs stay strict.
60+
- uses: codecov/codecov-action@v5
61+
with:
62+
files: coverage.xml
63+
fail_ci_if_error: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && !startsWith(github.head_ref, 'dependabot/')) }}
64+
token: ${{ secrets.CODECOV_TOKEN }}
6165

6266
test_app:
6367
strategy:
6468
matrix:
65-
python-version: ["3.10", "3.11", "3.12"]
69+
python-version: ["3.11", "3.12", "3.13", "3.14"]
6670
# NOTE(skearnes): Docker is not supported on macOS GitLab runners.
6771
# NOTE(skearnes): ubuntu-latest AppArmor doesn't play nicely with puppeteer.
6872
runs-on: ubuntu-22.04
6973
steps:
7074
- uses: actions/checkout@v4
71-
- uses: actions/setup-python@v4
75+
- uses: astral-sh/setup-uv@v5
7276
with:
77+
enable-cache: true
7378
python-version: ${{ matrix.python-version }}
74-
- uses: actions/setup-node@v3
79+
- uses: actions/setup-node@v4
7580
- name: actions-setup-redis
7681
uses: shogo82148/actions-setup-redis@v1.35.0
7782
- name: Set up Docker Buildx
7883
uses: docker/setup-buildx-action@v3
7984
- name: Install ord-interface
80-
run: |
81-
python -m pip install --upgrade pip
82-
python -m pip install wheel
83-
python -m pip install .[tests]
85+
run: uv sync --frozen
8486
- name: Install non-python test dependencies
8587
run: |
8688
cd "${GITHUB_WORKSPACE}/ord_interface"
8789
npm install puppeteer
8890
- name: Run tests
8991
run: |
9092
cd "${GITHUB_WORKSPACE}/ord_interface"
91-
./run_tests.sh
93+
uv run ./run_tests.sh

.pre-commit-config.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2026 Open Reaction Database Project Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
repos:
16+
- repo: https://github.com/google/addlicense
17+
rev: v1.2.0
18+
hooks:
19+
- id: addlicense
20+
args:
21+
- -c
22+
- Open Reaction Database Project Authors
23+
- -l
24+
- apache
25+
26+
- repo: https://github.com/astral-sh/ruff-pre-commit
27+
rev: v0.15.10
28+
hooks:
29+
- id: ruff-check
30+
args: [--fix]
31+
- id: ruff-format
32+
33+
- repo: local
34+
hooks:
35+
- id: ty-check
36+
name: ty check (ord_interface)
37+
entry: uv run ty check ord_interface
38+
language: system
39+
pass_filenames: false

format.sh

Lines changed: 0 additions & 41 deletions
This file was deleted.

ord_interface/Dockerfile

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,12 @@ RUN wget https://github.com/open-reaction-database/ord-schema/archive/refs/tags/
6161

6262
# Install dependencies.
6363
WORKDIR /app/ord-interface
64-
COPY setup.py README.md ./
65-
RUN pip install --upgrade pip \
66-
&& pip install gunicorn wheel
67-
# https://stackoverflow.com/a/60511098.
68-
RUN python setup.py egg_info \
69-
&& pip install $(grep -v '^\[' *.egg-info/requires.txt) \
70-
&& rm -rf *.egg-info/
64+
RUN pip install --no-cache-dir uv
65+
ENV UV_PROJECT_ENVIRONMENT=/usr/local \
66+
UV_COMPILE_BYTECODE=1 \
67+
UV_LINK_MODE=copy
68+
COPY pyproject.toml uv.lock README.md ./
69+
RUN uv sync --frozen --no-dev --no-install-project
7170

7271
# Copy the local state.
7372
COPY ord_interface/editor/js/ ord_interface/editor/js/
@@ -90,7 +89,7 @@ COPY ord_interface/about.html ord_interface/
9089
COPY ord_interface/interface.py ord_interface/
9190
COPY ord_interface/api/ ord_interface/api/
9291
COPY ord_interface/visualization/ ord_interface/visualization/
93-
RUN pip install .
92+
RUN uv sync --frozen --no-dev
9493

9594
# Build and launch the interface.
9695
COPY ord_interface/start_app.sh .

ord_interface/api/conftest.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
"""Pytest fixtures."""
1616

1717
import os
18+
from collections.abc import AsyncIterator, Iterator
1819
from contextlib import ExitStack
19-
from typing import AsyncIterator, Iterator
20+
from typing import Any
2021
from unittest.mock import patch
2122

2223
import psycopg
@@ -43,8 +44,8 @@ def test_postgres_fixture() -> Iterator[Postgresql]:
4344

4445

4546
@pytest_asyncio.fixture
46-
async def test_cursor(test_postgres) -> AsyncIterator[AsyncCursor]:
47-
async with await psycopg.AsyncConnection.connect(
47+
async def test_cursor(test_postgres) -> AsyncIterator[AsyncCursor[dict[str, Any]]]:
48+
async with await psycopg.AsyncConnection[dict[str, Any]].connect(
4849
test_postgres.url(), row_factory=dict_row, options="-c search_path=public,ord"
4950
) as connection:
5051
await connection.set_read_only(True)
@@ -54,9 +55,12 @@ async def test_cursor(test_postgres) -> AsyncIterator[AsyncCursor]:
5455

5556
@pytest.fixture(scope="session")
5657
def test_client(test_postgres) -> Iterator[TestClient]:
57-
with TestClient(app) as client, RedisServer() as redis_server, patch.dict(
58-
os.environ, {"REDIS_PORT": str(redis_server.dsn()["port"])}
59-
), ExitStack() as stack:
58+
with (
59+
TestClient(app) as client,
60+
RedisServer() as redis_server,
61+
patch.dict(os.environ, {"REDIS_PORT": str(redis_server.dsn()["port"])}),
62+
ExitStack() as stack,
63+
):
6064
# NOTE(skearnes): Set ORD_INTERFACE_POSTGRES to use that database instead of a testing.postgresql instance.
6165
# To force the use of testing.postgresl, set ORD_INTERFACE_TESTING=TRUE.
6266
if os.environ.get("ORD_INTERFACE_TESTING", "FALSE") == "FALSE" and not os.environ.get("ORD_INTERFACE_POSTGRES"):

ord_interface/api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from contextlib import asynccontextmanager
2020

2121
from fastapi import FastAPI
22-
from testing.postgresql import Postgresql
2322

2423
from ord_interface.api import search, view
2524
from ord_interface.api.testing import setup_test_postgres
@@ -30,6 +29,9 @@ async def lifespan(*args, **kwargs):
3029
"""FastAPI lifespan setup; see https://fastapi.tiangolo.com/advanced/events/#lifespan."""
3130
del args, kwargs # Unused.
3231
if os.getenv("ORD_INTERFACE_TESTING", "FALSE") == "TRUE":
32+
# testing-postgresql is a dev dep; only required when ORD_INTERFACE_TESTING=TRUE.
33+
from testing.postgresql import Postgresql
34+
3335
with Postgresql() as postgres:
3436
url = re.sub("postgresql://", "postgresql+psycopg://", postgres.url())
3537
setup_test_postgres(url)

0 commit comments

Comments
 (0)