From ceac2e73265f19b86ea189c49cb51a527c81b35a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 11:04:14 +0000 Subject: [PATCH 1/2] deps(deps): bump pandas-stubs from 2.3.0.250703 to 2.3.3.260113 Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.3.0.250703 to 2.3.3.260113. - [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md) - [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.3.0.250703...v2.3.3.260113) --- updated-dependencies: - dependency-name: pandas-stubs dependency-version: 2.3.3.260113 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 3e0839e2..133a3c80 100644 --- a/uv.lock +++ b/uv.lock @@ -2267,16 +2267,16 @@ wheels = [ [[package]] name = "pandas-stubs" -version = "2.3.0.250703" +version = "2.3.3.260113" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "types-pytz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/df/c1c51c5cec087b8f4d04669308b700e9648745a77cdd0c8c5e16520703ca/pandas_stubs-2.3.0.250703.tar.gz", hash = "sha256:fb6a8478327b16ed65c46b1541de74f5c5947f3601850caf3e885e0140584717", size = 103910, upload-time = "2025-07-02T17:49:11.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/5d/be23854a73fda69f1dbdda7bc10fbd6f930bd1fa87aaec389f00c901c1e8/pandas_stubs-2.3.3.260113.tar.gz", hash = "sha256:076e3724bcaa73de78932b012ec64b3010463d377fa63116f4e6850643d93800", size = 116131, upload-time = "2026-01-13T22:30:16.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/cb/09d5f9bf7c8659af134ae0ffc1a349038a5d0ff93e45aedc225bde2872a3/pandas_stubs-2.3.0.250703-py3-none-any.whl", hash = "sha256:a9265fc69909f0f7a9cabc5f596d86c9d531499fed86b7838fd3278285d76b81", size = 154719, upload-time = "2025-07-02T17:49:10.697Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/df1fe324248424f77b89371116dab5243db7f052c32cc9fe7442ad9c5f75/pandas_stubs-2.3.3.260113-py3-none-any.whl", hash = "sha256:ec070b5c576e1badf12544ae50385872f0631fc35d99d00dc598c2954ec564d3", size = 168246, upload-time = "2026-01-13T22:30:15.244Z" }, ] [[package]] From bccf95a63acc7ca5e5488f5d3cd485fc1d119133 Mon Sep 17 00:00:00 2001 From: Benjamin Gaidioz Date: Fri, 22 May 2026 17:05:48 +0200 Subject: [PATCH 2/2] Fix pandas-stubs mypy regression in validator output handling (#278) ## Summary - replace `replace({pd.NaT: None})` with typed-safe pandas null normalization in validator output paths - keep DataFrame and Series handling behavior the same while avoiding the stricter `pandas-stubs` replacement-map typing error - add regression tests for `NaT` in DataFrame and Series outputs ## Why PR #271 bumps `pandas-stubs` and makes `uv run mypy .` fail in CI on `src/mxcp/sdk/validator/converters.py` because the `replace({pd.NaT: None})` pattern is typed more strictly. ## Testing - `uv run ruff check .` - `uv run black --check --diff .` - `uv run mypy .` - `env -u OP_SERVICE_ACCOUNT_TOKEN uv run pytest --cov=src/mxcp --cov-report=xml --cov-report=term-missing` ## Notes - The full local pytest run only needed `OP_SERVICE_ACCOUNT_TOKEN` unset because one existing config test assumes that variable is absent. --- src/mxcp/sdk/validator/converters.py | 18 ++++++++++--- tests/sdk/validator/test_core.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/mxcp/sdk/validator/converters.py b/src/mxcp/sdk/validator/converters.py index 781a83b2..5408c064 100644 --- a/src/mxcp/sdk/validator/converters.py +++ b/src/mxcp/sdk/validator/converters.py @@ -20,6 +20,16 @@ class ValidationError(ValueError): class TypeConverter: """Utility class for type conversion and validation.""" + @staticmethod + def _normalize_pandas_dataframe_nulls(value: pd.DataFrame) -> pd.DataFrame: + """Convert pandas null sentinels into Python None for output handling.""" + return value.astype(object).where(value.notna(), None) + + @staticmethod + def _normalize_pandas_series_nulls(value: pd.Series) -> pd.Series: + """Convert pandas null sentinels into Python None for output handling.""" + return value.astype(object).where(value.notna(), None) + @staticmethod def python_type_to_schema_type(python_type: str) -> str: """Map Python type names to schema types.""" @@ -238,13 +248,13 @@ def validate_output(value: Any, schema: TypeSchemaModel) -> None: f"DataFrame rows must be objects, but schema expects array of {schema.items.type}" ) # Convert DataFrame to list of dicts for validation - value = value.replace({pd.NaT: None}).to_dict("records") + value = TypeConverter._normalize_pandas_dataframe_nulls(value).to_dict("records") # Handle Series - they validate as arrays if isinstance(value, pd.Series): if return_type != "array": raise ValidationError(f"Expected {return_type}, got Series (which is array)") - value = value.replace({pd.NaT: None}).tolist() + value = TypeConverter._normalize_pandas_series_nulls(value).tolist() if return_type == "string": if ( @@ -355,10 +365,10 @@ def serialize_for_output(obj: Any) -> Any: return [TypeConverter.serialize_for_output(item) for item in items] elif isinstance(obj, pd.DataFrame): # Convert DataFrame to list of dicts - return obj.replace({pd.NaT: None}).to_dict("records") + return TypeConverter._normalize_pandas_dataframe_nulls(obj).to_dict("records") elif isinstance(obj, pd.Series): # Convert Series to list - return obj.replace({pd.NaT: None}).tolist() + return TypeConverter._normalize_pandas_series_nulls(obj).tolist() elif isinstance(obj, pd.Timestamp | datetime | date | time): return obj.isoformat() elif isinstance(obj, type(pd.NaT)): diff --git a/tests/sdk/validator/test_core.py b/tests/sdk/validator/test_core.py index 84ea5b24..377f899e 100644 --- a/tests/sdk/validator/test_core.py +++ b/tests/sdk/validator/test_core.py @@ -255,6 +255,35 @@ def test_dataframe_output(self): result = validator.validate_output(df) assert result == [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}] + def test_dataframe_output_with_nat(self): + """Test DataFrame output validation converts NaT values to None.""" + schema = { + "output": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "created_at": {"type": "string", "format": "date-time"}, + }, + }, + } + } + + validator = TypeValidator.from_dict(schema) + df = pd.DataFrame( + [ + {"id": 1, "created_at": pd.Timestamp("2024-01-01T00:00:00")}, + {"id": 2, "created_at": pd.NaT}, + ] + ) + + result = validator.validate_output(df) + assert result == [ + {"id": 1, "created_at": pd.Timestamp("2024-01-01T00:00:00")}, + {"id": 2, "created_at": None}, + ] + def test_series_output(self): """Test Series output validation.""" schema = {"output": {"type": "array", "items": {"type": "number"}}} @@ -266,6 +295,16 @@ def test_series_output(self): result = validator.validate_output(series) assert result == [1.0, 2.0, 3.0] + def test_series_output_with_nat(self): + """Test Series output validation converts NaT values to None.""" + schema = {"output": {"type": "array", "items": {"type": "string", "format": "date-time"}}} + + validator = TypeValidator.from_dict(schema) + series = pd.Series([pd.Timestamp("2024-01-01T00:00:00"), pd.NaT]) + + result = validator.validate_output(series) + assert result == [pd.Timestamp("2024-01-01T00:00:00"), None] + def test_sensitive_field_masking(self): """Test sensitive field masking.""" schema = {