| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| from typing import Any | |
| import pytest | |
| from pydantic import BaseModel, ValidationError | |
| from hamilton.function_modifiers import check_output | |
| from hamilton.node import Node | |
| from hamilton.plugins import h_pydantic | |
| from hamilton.plugins.pydantic_extensions import PydanticModelValidator | |
| def test_basic_pydantic_validator_passes(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| validator = PydanticModelValidator(model=DummyModel, importance="warn") | |
| validation_result = validator.validate({"value": 15.0}) | |
| assert validation_result.passes | |
| def test_basic_pydantic_check_output_passes(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| @check_output(model=DummyModel, importance="warn") | |
| def dummy() -> dict[str, float]: | |
| return {"value": 15.0} | |
| node = Node.from_fn(dummy) | |
| validators = check_output(model=DummyModel).get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result_success = validator.validate(node()) | |
| assert result_success.passes | |
| def test_basic_pydantic_validator_fails(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| validator = PydanticModelValidator(model=DummyModel, importance="warn") | |
| validation_result = validator.validate({"value": "15.0"}) | |
| assert not validation_result.passes | |
| assert "value" in validation_result.diagnostics["model_errors"][0]["loc"] | |
| def test_basic_pydantic_check_output_fails(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| @check_output(model=DummyModel, importance="warn") | |
| def dummy() -> dict[str, float]: | |
| return {"value": "fifteen"} # type: ignore | |
| node = Node.from_fn(dummy) | |
| validators = check_output(model=DummyModel).get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result = validator.validate(node()) | |
| assert not result.passes | |
| def test_pydantic_validator_is_strict(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| validator = PydanticModelValidator(model=DummyModel, importance="warn") | |
| validation_result = validator.validate({"value": "15"}) | |
| assert not validation_result.passes | |
| def test_complex_pydantic_validator_passes(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| data = { | |
| "name": "hamilton", | |
| "owner": {"name": "DAGWorks-Inc"}, | |
| "versions": [{"name": "0.1.0", "id": 1}, {"name": "0.2.0", "id": 2}], | |
| } | |
| validator = PydanticModelValidator(model=Repo, importance="warn") | |
| validation_result = validator.validate(data) | |
| assert validation_result.passes | |
| def test_complex_pydantic_validator_fails(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| data = { | |
| "name": "hamilton", | |
| "owner": {"name": "DAGWorks-Inc"}, | |
| "versions": [{"name": "0.1.0", "id": 1}, {"name": "0.2.0", "id": "2"}], | |
| } | |
| validator = PydanticModelValidator(model=Repo, importance="warn") | |
| validation_result = validator.validate(data) | |
| assert not validation_result.passes | |
| def test_complex_pydantic_check_output_passes(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| @check_output(model=Repo, importance="warn") | |
| def dummy() -> dict[str, Any]: | |
| return { | |
| "name": "hamilton", | |
| "owner": {"name": "DAGWorks-Inc"}, | |
| "versions": [{"name": "0.1.0", "id": 1}, {"name": "0.2.0", "id": 2}], | |
| } | |
| node = Node.from_fn(dummy) | |
| validators = check_output(model=Repo).get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result_success = validator.validate(node()) | |
| assert result_success.passes | |
| def test_complex_pydantic_check_output_fails(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| @check_output(model=Repo, importance="warn") | |
| def dummy() -> dict[str, Any]: | |
| return { | |
| "name": "hamilton", | |
| "owner": {"name": "DAGWorks-Inc"}, | |
| "versions": [ | |
| {"name": "0.1.0", "id": "one"}, # id should be an int | |
| {"name": "0.2.0", "id": "two"}, # id should be an int | |
| ], | |
| } | |
| node = Node.from_fn(dummy) | |
| validators = check_output(model=Repo).get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result = validator.validate(node()) | |
| assert not result.passes | |
| def test_basic_pydantic_plugin_check_output_passes(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| def dummy() -> DummyModel: | |
| return DummyModel(value=15.0) | |
| node = Node.from_fn(dummy) | |
| validators = h_pydantic.check_output().get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result_success = validator.validate(node()) | |
| assert result_success.passes | |
| def test_basic_pydantic_plugin_check_output_fails(): | |
| class DummyModel(BaseModel): | |
| value: float | |
| def dummy() -> DummyModel: | |
| return DummyModel(value="fifteen") # type: ignore | |
| node = Node.from_fn(dummy) | |
| validators = h_pydantic.check_output().get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| with pytest.raises(ValidationError): | |
| result = validator.validate(node()) | |
| assert not result.passes | |
| def test_complex_pydantic_plugin_check_output_passes(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| def dummy() -> Repo: | |
| return Repo( | |
| name="hamilton", | |
| owner=Owner(name="DAGWorks-Inc"), | |
| versions=[Version(name="0.1.0", id=1), Version(name="0.2.0", id=2)], | |
| ) | |
| node = Node.from_fn(dummy) | |
| validators = h_pydantic.check_output().get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| result_success = validator.validate(node()) | |
| assert result_success.passes | |
| def test_complex_pydantic_plugin_check_output_fails(): | |
| class Owner(BaseModel): | |
| name: str | |
| class Version(BaseModel): | |
| name: str | |
| id: int | |
| class Repo(BaseModel): | |
| name: str | |
| owner: Owner | |
| versions: list[Version] | |
| def dummy() -> Repo: | |
| return Repo( | |
| name="hamilton", | |
| owner=Owner(name="DAGWorks-Inc"), | |
| versions=[ | |
| Version(name="0.1.0", id=1), | |
| Version(name="0.2.0", id="two"), # type: ignore | |
| ], | |
| ) | |
| node = Node.from_fn(dummy) | |
| validators = h_pydantic.check_output().get_validators(node) | |
| assert len(validators) == 1 | |
| validator = validators[0] | |
| with pytest.raises(ValidationError): | |
| result = validator.validate(node()) | |
| assert not result.passes |