ci: add ruff linting and mypy type checking

Add ruff and mypy to dev dependencies with configuration in pyproject.toml.
Add a lint CI job that runs ruff check, ruff format --check, and mypy.
Auto-fix import sorting and formatting across the codebase. Exclude
alembic/versions from linting (auto-generated migrations). Ignore B008
(FastAPI Depends pattern) and RUF001 (unicode in user-facing strings).

21 ruff lint errors and 50 mypy errors remain for manual review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Justin Visser 2026-02-26 22:11:41 +01:00
parent 399276ea69
commit bf04c77aa9
137 changed files with 3066 additions and 1900 deletions

View file

@ -1,6 +1,6 @@
import pytest
from app.services.domains.openalex.types import OpenAlexWork
def test_parse_openalex_work_from_api_dict() -> None:
raw_api_response = {
"id": "https://openalex.org/W2741809807",
@ -13,28 +13,19 @@ def test_parse_openalex_work_from_api_dict() -> None:
"doi": "https://doi.org/10.1038/s41586-020-0315-z",
"mag": "2741809807",
"pmid": "https://pubmed.ncbi.nlm.nih.gov/32040050",
"pmcid": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7325852"
},
"open_access": {
"is_oa": True,
"oa_url": "https://example.com/pdf"
"pmcid": "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7325852",
},
"open_access": {"is_oa": True, "oa_url": "https://example.com/pdf"},
"authorships": [
{
"author_position": "first",
"author": {
"id": "https://openalex.org/A1969205032",
"display_name": "Giuseppe Carleo"
}
"author": {"id": "https://openalex.org/A1969205032", "display_name": "Giuseppe Carleo"},
},
{
"author_position": "middle",
"author": {
"id": "https://openalex.org/A4356881717",
"display_name": "Ignacio Cirac"
}
}
]
"author": {"id": "https://openalex.org/A4356881717", "display_name": "Ignacio Cirac"},
},
],
}
work = OpenAlexWork.from_api_dict(raw_api_response)
@ -52,6 +43,7 @@ def test_parse_openalex_work_from_api_dict() -> None:
assert work.authors[0].display_name == "Giuseppe Carleo"
assert work.authors[1].display_name == "Ignacio Cirac"
def test_parse_openalex_work_empty() -> None:
work = OpenAlexWork.from_api_dict({"id": "W123"})
assert work.openalex_id == "W123"

View file

@ -1,54 +1,62 @@
import pytest
from app.services.domains.openalex.types import OpenAlexWork
from app.services.domains.openalex.matching import find_best_match
from app.services.domains.openalex.types import OpenAlexWork
def test_find_best_match_exact_title():
cand1 = OpenAlexWork.from_api_dict({"id": "W1", "title": "Exact Title of the Paper"})
cand2 = OpenAlexWork.from_api_dict({"id": "W2", "title": "Totally Different Paper"})
match = find_best_match("Exact Title of the Paper", 2020, "Author A", [cand1, cand2])
assert match is not None
assert match.openalex_id == "W1"
def test_find_best_match_fuzzy_title():
# Only differences are punctuation or minor phrasing (e.g., matching a preprint title vs published)
cand1 = OpenAlexWork.from_api_dict({"id": "W1", "title": "Fuzzier Title: A Study on OpenAlex"})
cand2 = OpenAlexWork.from_api_dict({"id": "W2", "title": "Some completely unrelated work"})
match = find_best_match("Fuzzier Title A Study on OpenAlex", 2021, "Author B", [cand1, cand2])
assert match is not None
assert match.openalex_id == "W1"
def test_find_best_match_rejects_low_score():
cand1 = OpenAlexWork.from_api_dict({"id": "W1", "title": "Cats in hats"})
match = find_best_match("Dogs with logs", 2020, "Author A", [cand1])
assert match is None
def test_find_best_match_year_tiebreaker():
# Both titles are very similar, one has exact year.
cand1 = OpenAlexWork.from_api_dict({"id": "W1", "title": "The exact same title", "publication_year": 2018})
cand2 = OpenAlexWork.from_api_dict({"id": "W2", "title": "The exact same title", "publication_year": 2020})
match = find_best_match("The exact same title", 2020, "Author A", [cand1, cand2])
assert match is not None
assert match.openalex_id == "W2"
def test_find_best_match_author_tiebreaker():
# Titles and years match exactly. Author overlap decides it.
cand1 = OpenAlexWork.from_api_dict({
"id": "W1",
"title": "A popular title",
"publication_year": 2020,
"authorships": [{"author": {"display_name": "Smith, J"}}]
})
cand2 = OpenAlexWork.from_api_dict({
"id": "W2",
"title": "A popular title",
"publication_year": 2020,
"authorships": [{"author": {"display_name": "Doe, J"}}]
})
cand1 = OpenAlexWork.from_api_dict(
{
"id": "W1",
"title": "A popular title",
"publication_year": 2020,
"authorships": [{"author": {"display_name": "Smith, J"}}],
}
)
cand2 = OpenAlexWork.from_api_dict(
{
"id": "W2",
"title": "A popular title",
"publication_year": 2020,
"authorships": [{"author": {"display_name": "Doe, J"}}],
}
)
# Target authors contains "Doe"
match = find_best_match("A popular title", 2020, "A Einstein, J Doe", [cand1, cand2])
assert match is not None