From 38d1ca2af61b3c3e7858377b492baa6ecc669f09 Mon Sep 17 00:00:00 2001 From: Aurelio <19254254+Aureliolo@users.noreply.github.com> Date: Sun, 15 Mar 2026 17:32:10 +0100 Subject: [PATCH 1/2] chore: add pyrightconfig.json and fix all pyright errors Add pyright configuration with standard mode and suppressed noise rules (unknown types from Pydantic/structlog/pytest). Fix all 17 actionable pyright findings: - middleware: pyright-specific ignore for ASGI send() type mismatch - loader: use getattr for YAMLError.problem_mark (no type stubs) - mem0 adapter: pyright ignore for _client attr assignment - org/store: initialize escaped variable before conditional use - presets: extract validation loop into function to avoid unbound vars - mcp/__init__: add TYPE_CHECKING imports for lazy-loaded symbols - docker_sandbox: explicit aiodocker.containers import + pyright ignores - test_factory: initialize side_effect with None + assert narrowing --- pyrightconfig.json | 16 ++++++++++++++++ src/synthorg/api/middleware.py | 2 +- src/synthorg/config/loader.py | 7 ++++--- src/synthorg/memory/backends/mem0/adapter.py | 2 +- src/synthorg/memory/org/store.py | 1 + src/synthorg/templates/presets.py | 19 +++++++++++-------- src/synthorg/tools/mcp/__init__.py | 9 +++++++++ src/synthorg/tools/sandbox/docker_sandbox.py | 9 +++++---- tests/unit/memory/test_factory.py | 4 +++- 9 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 pyrightconfig.json diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000000..a3c77051a1 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,16 @@ +{ + "include": ["src/", "tests/"], + "exclude": [".venv/", "build/", "dist/", ".pytest_cache/", ".hypothesis/"], + "pythonVersion": "3.14", + "venvPath": ".", + "venv": ".venv", + "typeCheckingMode": "standard", + "reportUnknownArgumentType": false, + "reportUnknownMemberType": false, + "reportUnknownVariableType": false, + "reportUnknownParameterType": false, + "reportUnknownLambdaType": false, + "reportCallIssue": false, + "reportPrivateUsage": false, + "reportMissingTypeStubs": false +} diff --git a/src/synthorg/api/middleware.py b/src/synthorg/api/middleware.py index 703eab22ee..62590bc07e 100644 --- a/src/synthorg/api/middleware.py +++ b/src/synthorg/api/middleware.py @@ -153,7 +153,7 @@ async def capture_send(message: Any) -> None: and message.get("type") == "http.response.start" ): status_code = message.get("status", 500) - await original_send(message) + await original_send(message) # pyright: ignore[reportArgumentType] try: await self.app(scope, receive, capture_send) diff --git a/src/synthorg/config/loader.py b/src/synthorg/config/loader.py index c3aa592994..bc6ae7a369 100644 --- a/src/synthorg/config/loader.py +++ b/src/synthorg/config/loader.py @@ -116,9 +116,10 @@ def _parse_yaml_string( except yaml.YAMLError as exc: line: int | None = None col: int | None = None - if hasattr(exc, "problem_mark") and exc.problem_mark is not None: - line = exc.problem_mark.line + 1 - col = exc.problem_mark.column + 1 + mark = getattr(exc, "problem_mark", None) + if mark is not None: + line = mark.line + 1 + col = mark.column + 1 msg = f"YAML syntax error in {source_name}: {exc}" logger.warning( CONFIG_PARSE_FAILED, diff --git a/src/synthorg/memory/backends/mem0/adapter.py b/src/synthorg/memory/backends/mem0/adapter.py index c4c6d68c1e..66a993231c 100644 --- a/src/synthorg/memory/backends/mem0/adapter.py +++ b/src/synthorg/memory/backends/mem0/adapter.py @@ -174,7 +174,7 @@ async def connect(self) -> None: ) msg = f"Failed to connect to Mem0: {exc}" raise MemoryConnectionError(msg) from exc - self._client = client + self._client = client # pyright: ignore[reportAttributeAccessIssue] self._connected = True logger.info(MEMORY_BACKEND_CONNECTED, backend="mem0") diff --git a/src/synthorg/memory/org/store.py b/src/synthorg/memory/org/store.py index 776d009b80..b0007a0078 100644 --- a/src/synthorg/memory/org/store.py +++ b/src/synthorg/memory/org/store.py @@ -421,6 +421,7 @@ async def query( clauses.append(f"category IN ({placeholders})") params.extend(c.value for c in categories) + escaped = "" if text is not None: escaped = text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") clauses.append("content LIKE ? ESCAPE '\\'") diff --git a/src/synthorg/templates/presets.py b/src/synthorg/templates/presets.py index bf4c60f625..f3474b689b 100644 --- a/src/synthorg/templates/presets.py +++ b/src/synthorg/templates/presets.py @@ -428,14 +428,17 @@ def get_personality_preset(name: str) -> dict[str, Any]: # Validate all presets at import time to catch key typos immediately. -for _preset_name, _preset_dict in PERSONALITY_PRESETS.items(): - try: - PersonalityConfig(**_preset_dict) - except (ValidationError, TypeError) as _exc: - msg = f"Invalid personality preset {_preset_name!r}: {_exc}" - raise ValueError(msg) from _exc -if PERSONALITY_PRESETS: - del _preset_name, _preset_dict +def _validate_presets() -> None: + for name, preset in PERSONALITY_PRESETS.items(): + try: + PersonalityConfig(**preset) + except (ValidationError, TypeError) as exc: + msg = f"Invalid personality preset {name!r}: {exc}" + raise ValueError(msg) from exc + + +_validate_presets() +del _validate_presets def generate_auto_name(role: str, *, seed: int | None = None) -> str: diff --git a/src/synthorg/tools/mcp/__init__.py b/src/synthorg/tools/mcp/__init__.py index ce4917d3a3..3879f88b5f 100644 --- a/src/synthorg/tools/mcp/__init__.py +++ b/src/synthorg/tools/mcp/__init__.py @@ -5,6 +5,8 @@ no dependency on the tool base classes. """ +from typing import TYPE_CHECKING + from .config import MCPConfig, MCPServerConfig from .errors import ( MCPConnectionError, @@ -15,6 +17,13 @@ ) from .models import MCPRawResult, MCPToolInfo +if TYPE_CHECKING: + from .bridge_tool import MCPBridgeTool + from .cache import MCPResultCache + from .client import MCPClient + from .factory import MCPToolFactory + from .result_mapper import map_call_tool_result + __all__ = [ "MCPBridgeTool", "MCPClient", diff --git a/src/synthorg/tools/sandbox/docker_sandbox.py b/src/synthorg/tools/sandbox/docker_sandbox.py index e917fe0d7b..a875fed201 100644 --- a/src/synthorg/tools/sandbox/docker_sandbox.py +++ b/src/synthorg/tools/sandbox/docker_sandbox.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Final import aiodocker +import aiodocker.containers from synthorg.observability import get_logger from synthorg.observability.events.docker import ( @@ -353,7 +354,7 @@ async def _run_container( # noqa: PLR0913 ) try: - container = await docker.containers.create(config) + container = await docker.containers.create(config) # pyright: ignore[reportAttributeAccessIssue] except Exception as exc: msg = f"Failed to create container: {exc}" logger.exception( @@ -409,7 +410,7 @@ async def _start_and_wait( Returns: A ``SandboxResult``. """ - container_obj = docker.containers.container(container_id) + container_obj = docker.containers.container(container_id) # pyright: ignore[reportAttributeAccessIssue] try: await container_obj.start() except Exception as exc: @@ -558,7 +559,7 @@ async def _stop_container( container_id: Container ID to stop. """ try: - container_obj = docker.containers.container(container_id) + container_obj = docker.containers.container(container_id) # pyright: ignore[reportAttributeAccessIssue] await container_obj.stop( t=_STOP_TIMEOUT_SECONDS, ) @@ -585,7 +586,7 @@ async def _remove_container( container_id: Container ID to remove. """ try: - container_obj = docker.containers.container(container_id) + container_obj = docker.containers.container(container_id) # pyright: ignore[reportAttributeAccessIssue] await container_obj.delete(force=True) logger.debug( DOCKER_CONTAINER_REMOVED, diff --git a/tests/unit/memory/test_factory.py b/tests/unit/memory/test_factory.py index 580905dece..a945a40d8a 100644 --- a/tests/unit/memory/test_factory.py +++ b/tests/unit/memory/test_factory.py @@ -92,10 +92,12 @@ def test_backend_init_validation_error_wraps(self) -> None: class _Dummy(BaseModel): x: int + side_effect: ValidationError | None = None try: _Dummy(x="not-an-int") # type: ignore[arg-type] except ValidationError as ve: - side_effect: ValidationError = ve + side_effect = ve + assert side_effect is not None # always fails, narrows type for pyright config = CompanyMemoryConfig(backend="mem0") with ( From c8bff28c02d7a991cb506eac7d47374176f02033 Mon Sep 17 00:00:00 2001 From: Aurelio <19254254+Aureliolo@users.noreply.github.com> Date: Sun, 15 Mar 2026 17:51:05 +0100 Subject: [PATCH 2/2] fix: address PR review feedback from CodeRabbit, Greptile, and Gemini - Add logging before raise in preset validation (CLAUDE.md requirement) - Add TEMPLATE_PERSONALITY_PRESET_INVALID event constant - Fix misleading comment in test_factory.py ("always fails" -> "always set") - Keep reportCallIssue disabled (393 false positives from Pydantic/Annotated) - PEP 758 except syntax deferred (ruff doesn't support it yet) --- src/synthorg/observability/events/template.py | 1 + src/synthorg/templates/presets.py | 6 ++++++ tests/unit/memory/test_factory.py | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/synthorg/observability/events/template.py b/src/synthorg/observability/events/template.py index ac0e1bc451..c61178025c 100644 --- a/src/synthorg/observability/events/template.py +++ b/src/synthorg/observability/events/template.py @@ -13,6 +13,7 @@ TEMPLATE_RENDER_JINJA2_ERROR: Final[str] = "template.render.jinja2_error" TEMPLATE_RENDER_YAML_ERROR: Final[str] = "template.render.yaml_error" TEMPLATE_RENDER_VALIDATION_ERROR: Final[str] = "template.render.validation_error" +TEMPLATE_PERSONALITY_PRESET_INVALID: Final[str] = "template.personality_preset.invalid" TEMPLATE_PERSONALITY_PRESET_UNKNOWN: Final[str] = "template.personality_preset.unknown" TEMPLATE_PASS1_FLOAT_FALLBACK: Final[str] = "template.pass1.float_fallback" TEMPLATE_INHERIT_RESOLVE_START: Final[str] = "template.inherit.resolve_start" diff --git a/src/synthorg/templates/presets.py b/src/synthorg/templates/presets.py index f3474b689b..cf25738f78 100644 --- a/src/synthorg/templates/presets.py +++ b/src/synthorg/templates/presets.py @@ -13,6 +13,7 @@ from synthorg.core.agent import PersonalityConfig from synthorg.observability import get_logger from synthorg.observability.events.template import ( + TEMPLATE_PERSONALITY_PRESET_INVALID, TEMPLATE_PERSONALITY_PRESET_UNKNOWN, ) @@ -433,6 +434,11 @@ def _validate_presets() -> None: try: PersonalityConfig(**preset) except (ValidationError, TypeError) as exc: + logger.warning( + TEMPLATE_PERSONALITY_PRESET_INVALID, + preset_name=name, + error=str(exc), + ) msg = f"Invalid personality preset {name!r}: {exc}" raise ValueError(msg) from exc diff --git a/tests/unit/memory/test_factory.py b/tests/unit/memory/test_factory.py index a945a40d8a..fdb7fb5f6b 100644 --- a/tests/unit/memory/test_factory.py +++ b/tests/unit/memory/test_factory.py @@ -97,7 +97,7 @@ class _Dummy(BaseModel): _Dummy(x="not-an-int") # type: ignore[arg-type] except ValidationError as ve: side_effect = ve - assert side_effect is not None # always fails, narrows type for pyright + assert side_effect is not None # always set — narrows type for pyright config = CompanyMemoryConfig(backend="mem0") with (