Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing data to default_factory in with_default_schema #1582

Open
RolandSaur opened this issue Dec 16, 2024 · 5 comments
Open

Passing data to default_factory in with_default_schema #1582

RolandSaur opened this issue Dec 16, 2024 · 5 comments

Comments

@RolandSaur
Copy link

RolandSaur commented Dec 16, 2024

I am trying to replace a model with a default model in case validation fails. But I would like to customize the default model based on the error message.

For this I am using with_default_schema and am trying to pass data to default_factory.

My Question: How to pass the validation error to the default_factory of with_default_schema?

Minimal Example:
The Minimal Example validates an Item and calls error_handler because of a validation error. This part works. However from the docs it is not clear how to pass anything to the error_handler. context is simply None.

from pydantic import BaseModel, Field, model_validator
from pydantic_core import SchemaValidator, core_schema
from typing import Any

class Item(BaseModel):
    value: float = Field(ge=0)
    weight: float = Field(ge=0)


def error_handler(context: dict[str, Any]) -> Item:
    return Item(value=0, weight=0)

default_item_schema = core_schema.with_default_schema(
    schema=Item.__pydantic_core_schema__,
    default_factory=error_handler,
    default_factory_takes_data=True,
    on_error="default",
)
item_validator = SchemaValidator(default_item_schema)

default_item = item_validator.validate_python({"value": -5, "weight": 4})

System:

  • pydantic version 2.10.3
  • python version 3.12.3
  • OS: Kubuntu 24.04
@davidhewitt
Copy link
Contributor

The data that is passed to the default_factory is currently the data that is passed to the containing model. So if your with_default_schema was inside a model itself (probably works with a root model) then I think you should get non-none data.

@RolandSaur
Copy link
Author

RolandSaur commented Dec 19, 2024

Thanks for the quick response: I tried but it still says context=None
(I hope I understood correctly and sorry if I didn't)

So if your with_default_schema was inside a model itself (probably works with a root model) then I think you should get non-none data.
I tried to do that by making a new Model (DefaultItem), adding the default_item_schema under __pydantic_core_schema__ creating a DefaultItem with invalid data.

The example now looks like this:

from pydantic_core import SchemaValidator, core_schema
from typing import Any
from pydantic import BaseModel, Field


class Item(BaseModel):
    value: float = Field(ge=0)
    weight: float = Field(ge=0)


def error_handler(context: dict[str, Any]) -> Item:
    import pdb

    pdb.set_trace()
    return Item(value=0, weight=0)


# to replace with default value: https://docs.pydantic.dev/latest/api/pydantic_core_schema/#pydantic_core.core_schema.with_default_schema
default_item_schema = core_schema.with_default_schema(
    schema=Item.__pydantic_core_schema__,
    default_factory=error_handler,
    default_factory_takes_data=True,
    on_error="default",
)
schema_validator = SchemaValidator(default_item_schema)


class DefaultItem(Item):
    __pydantic_core_schema__ = default_item_schema
    __pydantic_validator__ = schema_validator


default_item = DefaultItem(value=-4, weight=7)

When I go up the stack to see where error_handler was called from, I get this line here

validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)

This line is in BaseModel and has access to the input data. So somewhere between this line and error_handler things get lost.

@Viicos
Copy link
Member

Viicos commented Dec 27, 2024

@RolandSaur, can you please provide an example of what you're trying to achieve exactly, before explaining it with core_schema stuff?

@RolandSaur
Copy link
Author

RolandSaur commented Jan 3, 2025

@Viicos Thanks for pointing this out. It helped me structure my thoughts.
My immeadiate problem is logging error messages (there might be a much simpler solution).

Immediate problem: Log information when there is a validation error.
The code example below does it based on a separate function with try-except structure.

from pydantic import BaseModel, Field, ValidationError
import logging

logger = logging.getLogger(__name__)


class Item(BaseModel):
    value: float = Field(ge=0)
    weight: float = Field(ge=0)


def create_item(value: float, weight: float) -> Item:
    try:
        new_item = Item(value=value, weight=weight)
        return new_item
    except ValidationError as err:
        for error in err.errors():
            logger.warning(
                f"Error at {error['loc']} with input {error['input']}:  {error['msg']}"
            )
        return Item(value=1.0, weight=1.0)


new_item = create_item(1, -4)

But I would like to do the logging directly in the Item definition:
It ensures that ValidationErrors are logged no matter how/where the Item was created.

@Viicos
Copy link
Member

Viicos commented Jan 6, 2025

Does this example cover your use case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants