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/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 bf4c60f625..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, ) @@ -428,14 +429,22 @@ 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: + 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 + + +_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..fdb7fb5f6b 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 set — narrows type for pyright config = CompanyMemoryConfig(backend="mem0") with (