Compare commits

...
Author SHA1 Message Date
James Brunton f651bc1014 Add Pyrefly config and fix type errors 2026-05-14 15:09:04 +01:00
14 changed files with 96 additions and 55 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ tasks:
desc: "Run type checking"
deps: [install]
cmds:
- uv run pyright . --warnings
- uv run pyrefly check
test:
desc: "Run tests"
+24 -14
View File
@@ -23,7 +23,7 @@ dev = [
"anyio>=4.0.0",
"datamodel-code-generator[ruff]>=0.26.0",
"pytest>=8.0.0",
"pyright>=1.1.408",
"pyrefly==1.0.0",
"referencing>=0.35.0",
"ruff>=0.14.10",
]
@@ -51,21 +51,11 @@ select = [
"W",
"RUF100",
"UP",
"PYI", # flake8-pyi: flags deprecated typing constructs
"FA", # flake8-future-annotations: flags missing future annotations imports
"BLE", # flake8-blind-except: flags bare `except Exception`
"PYI", # flake8-pyi: flags deprecated typing constructs
"FA", # flake8-future-annotations: flags missing future annotations imports
"BLE", # flake8-blind-except: flags bare `except Exception`
]
[tool.pyright]
pythonVersion = "3.13"
reportImportCycles = "warning"
reportUnnecessaryCast = "warning"
reportUnnecessaryTypeIgnoreComment = "warning"
reportUnusedImport = "warning"
reportUnknownParameterType = "warning"
reportDeprecated = "warning"
[tool.pytest.ini_options]
testpaths = ["tests"]
# ``tests`` is on the path so test modules can import shared helpers (e.g.
@@ -74,3 +64,23 @@ pythonpath = ["src", "tests"]
# Use importlib import mode so test directories don't need __init__.py files
# and duplicate basenames (e.g. multiple test_routes.py) collect cleanly.
addopts = "--import-mode=importlib"
[tool.pyrefly]
python-version = "3.13.0"
search-path = [
"src",
"tests",
]
# Disallow `type: ignore` comments to encourage accurate error codes to be used in ignore comments
# Pyrefly won't flag `type: ignore [wrong-code]`, but it will flag `pyrefly: ignore [wrong-code]`
enabled-ignores = ["pyrefly"]
min-severity = "warn" # TODO: Change settings below to `warn` - https://github.com/facebook/pyrefly/issues/3370
[tool.pyrefly.errors]
deprecated = "error"
implicit-any = "error"
redundant-cast = "error"
unused-ignore = "error"
# TODO: Add equivalents to:
# - reportImportCycles
# - reportUnusedImport
+1 -1
View File
@@ -478,6 +478,7 @@ class MathAuditorAgent:
logger.info("[math-auditor-agent] extracting figures from page %d (%d chars)", folio.page, len(text))
prompt = f"Page {folio.page + 1} text:\n{text}"
figures: list[ExtractedFigure] = []
try:
result = await self._figure_extractor.run(prompt)
figures = result.output.figures
@@ -487,7 +488,6 @@ class MathAuditorAgent:
folio.page,
exc_info=True,
)
figures = []
logger.debug(
"TOOL (extract_figures)\nArgs: %s\nResult: %s",
+12 -2
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import logging
from collections.abc import Iterable
from typing import Literal, overload
from typing import Literal, Protocol, overload
from pydantic import Field
from pydantic_ai import Agent
@@ -72,6 +72,16 @@ class PdfEditSelectionAgent:
return result.output
class ParameterSelector(Protocol):
async def select(
self,
request: PdfEditRequest,
operation_plan: list[ToolEndpoint],
operation_index: int,
generated_steps: list[ToolOperationStep],
) -> ParamToolModel: ...
class PdfEditParameterSelector:
def __init__(self, runtime: AppRuntime) -> None:
self.runtime = runtime
@@ -141,7 +151,7 @@ class PdfEditParameterSelector:
class PdfEditAgent:
def __init__(self, runtime: AppRuntime) -> None:
self.runtime = runtime
self.parameter_selector = PdfEditParameterSelector(runtime)
self.parameter_selector: ParameterSelector = PdfEditParameterSelector(runtime)
async def orchestrate(self, request: OrchestratorRequest) -> PdfEditResponse:
"""Entry point for the orchestrator delegate — adapts the orchestrator's
+1 -1
View File
@@ -131,7 +131,7 @@ def _enable_http_debug(formatter: logging.Formatter) -> None:
lg.setLevel(level)
# Idempotent: avoid stacking handlers on settings reload.
if not any(getattr(h, "_stirling_http_debug", False) for h in lg.handlers):
handler._stirling_http_debug = True # type: ignore[attr-defined]
handler._stirling_http_debug = True # pyrefly: ignore [missing-attribute]
lg.addHandler(handler)
lg.propagate = False
+1 -1
View File
@@ -69,7 +69,7 @@ def _transform_output_choices(choices: list[Any]) -> list[Any]:
for choice in choices:
if not isinstance(choice, dict) or "parts" not in choice:
continue
tool_calls = []
tool_calls: list[dict[str, Any]] = []
for part in choice.get("parts", []):
if isinstance(part, dict) and part.get("type") == "tool_call":
tool_calls.append(
@@ -50,7 +50,7 @@ async def test_delegate_pdf_review_wires_prompt_to_tool_step(runtime: AppRuntime
"stirling.agents.pdf_review.MathIntentClassifier.classify",
new=AsyncMock(return_value=False),
):
response = await orchestrator.delegate_pdf_review(ctx) # type: ignore[arg-type]
response = await orchestrator.delegate_pdf_review(ctx) # pyrefly: ignore [bad-argument-type]
assert isinstance(response, EditPlanResponse)
assert len(response.steps) == 1
+15 -3
View File
@@ -54,7 +54,7 @@ class StubEmbedder:
@pytest.fixture
def runtime_with_stub_docs(runtime: AppRuntime) -> AppRuntime:
stub = DocumentService(
embedder=StubEmbedder(), # type: ignore[arg-type]
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
store=SqliteVecStore.ephemeral(),
default_top_k=runtime.settings.rag_default_top_k,
)
@@ -198,13 +198,25 @@ async def test_read_full_document_budget_hides_tool_when_exhausted(
sentinel: object = object()
# Budget intact -> prepare returns the tool.
assert await capability._prepare_read_full_document(None, sentinel) is sentinel # type: ignore[arg-type]
assert (
await capability._prepare_read_full_document(
None, # pyrefly: ignore [bad-argument-type]
sentinel, # pyrefly: ignore [bad-argument-type]
)
is sentinel
)
# Spend the budget.
await capability._read_full_document("anything")
# Budget spent -> prepare returns None.
assert await capability._prepare_read_full_document(None, sentinel) is None # type: ignore[arg-type]
assert (
await capability._prepare_read_full_document(
None, # pyrefly: ignore [bad-argument-type]
sentinel, # pyrefly: ignore [bad-argument-type]
)
is None
)
@pytest.mark.anyio
+3 -4
View File
@@ -35,11 +35,10 @@ def test_folio_manifest_round_trip() -> None:
assert reloaded == manifest
def test_folio_manifest_round_bounds() -> None:
@pytest.mark.parametrize("round_value", [0, 4])
def test_folio_manifest_round_bounds(round_value: int) -> None:
with pytest.raises(ValidationError):
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=0)
with pytest.raises(ValidationError):
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=4)
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=round_value)
# ---------------------------------------------------------------------------
+1 -2
View File
@@ -12,6 +12,7 @@ from collections.abc import Iterator
from decimal import Decimal
import pytest
from conftest import build_app_settings
from fastapi.testclient import TestClient
from stirling.api import app
@@ -34,8 +35,6 @@ from stirling.contracts.ledger import (
class StubSettingsProvider:
def __call__(self) -> AppSettings:
from conftest import build_app_settings
return build_app_settings()
+20 -4
View File
@@ -152,7 +152,11 @@ class StubEmbeddingService:
def documents() -> DocumentService:
"""Each DocumentService test gets its own fresh ephemeral store to avoid dimension conflicts."""
store = SqliteVecStore.ephemeral()
return DocumentService(embedder=StubEmbeddingService(), store=store, default_top_k=3) # type: ignore[arg-type]
return DocumentService(
embedder=StubEmbeddingService(), # pyrefly: ignore [bad-argument-type]
store=store,
default_top_k=3,
)
def _pages(text: str) -> list[PageText]:
@@ -265,7 +269,7 @@ async def _invoke_search_knowledge(capability: RagCapability, query: str, max_re
toolset = capability.toolset
assert isinstance(toolset, FunctionToolset)
tool = toolset.tools["search_knowledge"]
return await tool.function(query=query, max_results=max_results) # type: ignore[call-arg]
return await tool.function(query=query, max_results=max_results)
class TestRagCapability:
@@ -345,12 +349,24 @@ class TestRagCapability:
cap = RagCapability(documents, max_searches=2)
tool_def = _dummy_tool_def()
assert await cap._prepare_search_knowledge(None, tool_def) is tool_def # type: ignore[arg-type]
assert (
await cap._prepare_search_knowledge(
None, # pyrefly: ignore [bad-argument-type]
tool_def, # pyrefly: ignore [bad-argument-type]
)
is tool_def
)
await _invoke_search_knowledge(cap, "content")
await _invoke_search_knowledge(cap, "content")
assert await cap._prepare_search_knowledge(None, tool_def) is None # type: ignore[arg-type]
assert (
await cap._prepare_search_knowledge(
None, # pyrefly: ignore [bad-argument-type]
tool_def, # pyrefly: ignore [bad-argument-type]
)
is None
)
def _dummy_tool_def() -> object:
+1 -1
View File
@@ -45,7 +45,7 @@ class StubEmbedder:
def _build_service() -> DocumentService:
return DocumentService(
embedder=StubEmbedder(), # type: ignore[arg-type]
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
store=SqliteVecStore.ephemeral(),
default_top_k=3,
)
+1 -1
View File
@@ -68,7 +68,7 @@ class StubPdfQuestionAgent(PdfQuestionAgent):
def runtime_with_stub_rag(runtime: AppRuntime) -> AppRuntime:
"""A runtime whose document service uses a stub embedder + ephemeral store."""
stub = DocumentService(
embedder=StubEmbedder(), # type: ignore[arg-type]
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
store=SqliteVecStore.ephemeral(),
default_top_k=runtime.settings.rag_default_top_k,
)
+14 -19
View File
@@ -621,7 +621,7 @@ dependencies = [
dev = [
{ name = "anyio" },
{ name = "datamodel-code-generator", extra = ["ruff"] },
{ name = "pyright" },
{ name = "pyrefly" },
{ name = "pytest" },
{ name = "referencing" },
{ name = "ruff" },
@@ -647,7 +647,7 @@ requires-dist = [
dev = [
{ name = "anyio", specifier = ">=4.0.0" },
{ name = "datamodel-code-generator", extras = ["ruff"], specifier = ">=0.26.0" },
{ name = "pyright", specifier = ">=1.1.408" },
{ name = "pyrefly", specifier = "==1.0.0" },
{ name = "pytest", specifier = ">=8.0.0" },
{ name = "referencing", specifier = ">=0.35.0" },
{ name = "ruff", specifier = ">=0.14.10" },
@@ -1671,15 +1671,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/13/04/eaac430d0e6bf21265ae989427d37e94be5e41dc216879f1fbb6c5339942/nexus_rpc-1.2.0-py3-none-any.whl", hash = "sha256:977876f3af811ad1a09b2961d3d1ac9233bda43ff0febbb0c9906483b9d9f8a3", size = 28166, upload-time = "2025-11-17T19:17:05.64Z" },
]
[[package]]
name = "nodeenv"
version = "1.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
]
[[package]]
name = "numpy"
version = "2.4.4"
@@ -2500,16 +2491,20 @@ wheels = [
]
[[package]]
name = "pyright"
version = "1.1.408"
name = "pyrefly"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9f/3a/9045b0097ac58979c7c30a4fa0e673db942d4adbc7b6d439bd54ae58c441/pyrefly-1.0.0.tar.gz", hash = "sha256:5c2b810ffcebd84be71de5df1223651edee951653a66935c6f091e957c452455", size = 5677995, upload-time = "2026-05-12T20:12:46.812Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" },
{ url = "https://files.pythonhosted.org/packages/f4/c6/90788819bac9c61dd7bacba53b79f3c12d47ccbe5e51b3d6d89f2387e1d2/pyrefly-1.0.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e355a0908555348ed4b9585ef25c76ff566673e345c866c325f1633f44d890b6", size = 13122950, upload-time = "2026-05-12T20:12:20.711Z" },
{ url = "https://files.pythonhosted.org/packages/82/91/a3cf2a1e87d336eaa804a1e6fc93266faf6dc2a97eecdbc7eae289628022/pyrefly-1.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7038efc3a40f8294edee339895633cf22db268c0d434cdbcbefc34f78a9ecc3", size = 12599494, upload-time = "2026-05-12T20:12:23.495Z" },
{ url = "https://files.pythonhosted.org/packages/cd/ab/74d1e11e737e99b1c003ecc5d7d2e846c4ea1f328966bfdbbd0ac63fad0a/pyrefly-1.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da331ca515ed1c08791da2b5f664cf9c1294c48fd802133262e7d5d51e0f4416", size = 12995507, upload-time = "2026-05-12T20:12:25.951Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ac/2df0899f8464c97e5d995f994c97c5cb5b0f58610432aa90d26d924e1db5/pyrefly-1.0.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c74219d8f3e63cdaa5501a0b21d1c9d37011820f9606728d0ed06f09ae86a878", size = 13947693, upload-time = "2026-05-12T20:12:29.188Z" },
{ url = "https://files.pythonhosted.org/packages/6b/3e/b247c24321e36f04b7d51f9ccf3df93e5009e4b29939524b36ec2e17dc2a/pyrefly-1.0.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0d05543b1bb6ee6d64149eb5d6b2fb15aa72d3962d6a97abca0afaca8b0c131", size = 13925803, upload-time = "2026-05-12T20:12:31.904Z" },
{ url = "https://files.pythonhosted.org/packages/61/16/cfa2d61a4aa1e1f7bca48bb37acd01c6a09db4864b16a54f9587092765ff/pyrefly-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1382d5b1fcdb49a4de9f34d112d2bddf290a78ff93ee8149492ad5f1077ddffc", size = 13470398, upload-time = "2026-05-12T20:12:35.302Z" },
{ url = "https://files.pythonhosted.org/packages/cb/2b/6372c7dddb326223e24a46b17efd0d4bd7b4fe22c821e523157577eed2d2/pyrefly-1.0.0-py3-none-win32.whl", hash = "sha256:aa8b5d0e47080e3202a2547b39f7a5a61d2c781c712b3b67884f745ca2c759d2", size = 12222643, upload-time = "2026-05-12T20:12:38.618Z" },
{ url = "https://files.pythonhosted.org/packages/be/ad/1d23be700b6b2ddaeb362360c7145917a8edbbf7240ae428d40541772fce/pyrefly-1.0.0-py3-none-win_amd64.whl", hash = "sha256:c8abcb0f2082e83c890375128f9cff4aa4d3f210b85eea7b3046c1ae764e77f5", size = 13146369, upload-time = "2026-05-12T20:12:41.423Z" },
{ url = "https://files.pythonhosted.org/packages/8c/38/16589134f3012fd097a10dcc85771555f1a5fb76e04b682597180743af30/pyrefly-1.0.0-py3-none-win_arm64.whl", hash = "sha256:d150fa9e40e8392832be81c3bcfc0497c146674ce4d0f8e04e1ec29e775ffb8c", size = 12538326, upload-time = "2026-05-12T20:12:43.996Z" },
]
[[package]]