Reputation: 3
I want to implement a custom list. Basically, it should behave as a normal list except when using the __contains__
function. If the list is empty we consider that it contains everything.
I then want to use this custom list in pydantic models that inherit from BaseModel.
The class is implemented like so:
class ListAllIfEmpty(list):
"""
Normal list. If the list is empty we will consider it contains
everything
"""
def __contains__(self, item):
if not self:
return True
else:
return super().__contains__(item)
When I try to use it as an attribute in a BaseModel and give it a default value, I get an error about generating a schema for ListAllIfEmpty
. I get the suggestion of setting arbitrary_types_allowed
to true. If I do so, pydantic stops doing the validation of the object. Still, I would like Pydantic to do all of the checks it does with a normal python built-in list. The class that has an attribute of ListAllIfEmpty
type looks like this:
class A(BaseModel):
value1: str
value2: ListAllIfEmpty[int] = Field(default_factory=lambda: [])
And the error message is the following:
E pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for [...].ListAllIfEmpty[typing.Annotated[datetime.date, BeforeValidator(func=<function validate_date at 0x000001EB701040E0>), PlainSerializer(func=<function format_date at 0x000001EB701053A0>, return_type=PydanticUndefined, when_used='unless-none')]]. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
E
E If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
I've seen that using RootModel might help. Still, I am not fully understanding how this works from the documentation. I have also seen some people mentioning "Annotated" but I have only seen people using it to add validation information. I would also like to keep the behaviour of "ListAllIfEmpty" the same as a normal list, aka, being able to instantiate it like a = [1,2,3]
.
Upvotes: 0
Views: 330
Reputation: 46
You should make ListAllIfEmpty
inheritance RootModel
, and set root
to any type you want.
After that, you can custom your __contains__
or other method for root
.
class ListAllIfEmpty(RootModel):
"""
Normal list. If the list is empty we will consider it contains everything
"""
root: list[int] = Field(default_factory=list)
def __contains__(self, item):
if not self.root:
return True
else:
return item in self.root
def __getitem__(self, item):
return self.root[item]
def __iter__(self):
return iter(self.root)
class A(BaseModel):
value1: str
value2: ListAllIfEmpty = Field(default_factory=ListAllIfEmpty)
I test it on these operations, is this what you want?
a = A(value1="test", value2=ListAllIfEmpty())
print(1 in a.value2) # True
b = A(value1="test", value2=list())
print(1 in b.value2) # True
c = A(value1="test", value2=[1, 2, 3])
print(1 in c.value2) # True
print(4 in c.value2) # False
d = A(value1="test")
print(1 in d.value2) # True
A(value1="test", value2=[1,"2",3]) # Pass
A(value1="test", value2=[1,"bad",3]) # Fail
Upvotes: 1