英文:
Excluding fields on a pydantic model when it is the nested child of another model
问题
I have a pydantic model that I want to dynamically exclude fields on.
I can do this by overriding the dict
function on the model so it can take my custom flag, e.g.:
class MyModel(BaseModel):
field: str
def dict(self, **kwargs):
if ('exclude_special_fields' in kwargs):
super().dict(exclude={"field": True}, **kwargs)
super().dict(**kwargs)
However, this does not work if my model is a child of another model that has .dict
called on it:
class AnotherModel(BaseModel):
models: List[MyModel]
AnotherModel(models=[...]).dict(exclude_special_fields=True) # does not work
This is because when MyModel.dict()
is called, it isn't called with the same arguments as the parent.
I could write a dict
override on the parent model too to specify an exclude for any child components (e.g. exclude={"models": {"__all__": {"field": True}}}
), but in my real-world example, I have many parent models that use this one sub-model, and I don't want to have to write an override for each one.
Is there any way I can ensure the child model knows when to exclude fields?
Extra context
Extra context not completely important to the question, but the reason I want to do this is to exclude certain fields on a model if it's ever returned from an API call.
英文:
I have a pydantic model that I want to dynamically exclude fields on.
I can do this by overriding the dict
function on the model so it can take my custom flag, e.g.:
class MyModel(BaseModel):
field: str
def dict(self, **kwargs):
if ('exclude_special_fields' in kwargs):
super().dict(exclude={"field": True}, **kwargs)
super().dict(**kwargs)
However, this does not work if my model is a child of another model that has .dict
called on it:
class AnotherModel(BaseModel):
models: List[MyModel]
AnotherModel(models=[...]).dict(exclude_special_fields=True) # does not work
This is because when MyModel.dict()
is called, it isn't called with the same arguments as the parent.
I could write a dict
override on the parent model too to specify an exclude for any child components (e.g. exclude={"models": {"__all__": {"field": True}}}
), but in my real world example, I have many parent models that use this one sub-model, and I don't want to have to write an override for each one.
Is there anyway I can ensure the child model knows when to exclude fields?
Extra context
Extra context not completely important to the question, but the reason I want to do this is to exclude certain fields on a model if it's ever returned from an API call.
答案1
得分: 3
After looking through the source code, I don't see any way that this would be easily possible with the specialized kwarg supplied. The dict
function is recursive and doesn't support arbitrary arguments.
Now, I was able to hack something together but... it is awful. This was performed using pydantic==1.10.7
.
I was thinking we could apply a special flag value in the exclude arguments to provide something to trigger the exclusion logic off of. This became trickier than I expected because it requires knowing the full structure of the object and exactly how to index the excluded fields. There is also some odd normalization happening on lists, which is causing the value provided to be mutated as it makes its way down the function call.
This is the best I could get (WARNING not tested thoroughly). We create a dictionary that returns itself on every lookup, that exposed __all__
as an exclude field. This will allow our key to be passed to each and every model and be passed on to child objects to evaluate as well.
EXCLUDED_SPECIAL_FIELDS = "exclude_special_fields"
class _ExclusionDict(dict):
def __init__(self):
super().__init__({"__all__": {EXCLUDED_SPECIAL_FIELDS: True}})
def get(self, key):
return self
ExcludeSpecial = _ExclusionDict()
class SpecialExclusionBaseModel(BaseModel):
_special_exclusions: set[str]
def dict(self, **kwargs):
exclusions = getattr(self.__class__, "_special_exclusions", None)
exclude = kwargs.get("exclude")
if exclusions and exclude and EXCLUDED_SPECIAL_FIELDS in exclude:
return {
k: v
for k, v in super().dict(**kwargs).items()
if k not in exclusions
}
return super().dict(**kwargs)
With this base class, we can provide a class field called _special_exclusions
to indicate which fields we want excluded when the ExcludeSpecial
instance is provided as the exclude
kw argument.
On some initial testing, this seems to work with nested hierarchies, including dicts and lists. There are probably bugs here that need to be worked out, but hopefully, this is a good jumping-off point for others.
class MyModel(SpecialExclusionBaseModel):
_special_exclusions = {"field"}
field: str
class AnotherModel(BaseModel):
models: list[MyModel]
class AnotherAnotherModel(BaseModel):
models: dict[str, AnotherModel]
model = MyModel(field=1)
another = AnotherAnotherModel(models={"test": AnotherModel(models=[model])})
print(another.dict(exclude=ExcludeSpecial))
{'models': {'test': {'models': [{}]}}}
英文:
After looking through the source code, I don't see any way that this would be easily possible with the specialized kwarg supplied. The dict
function is recursive and doesn't support arbitrary arguments.
Now, I was able to hack something together but... it is awful. This was performed using pydantic==1.10.7
.
I was thinking we could apply a special flag value in the exclude arguments to provide something to trigger the exclusion logic off of. This became trickier than I expected, because it requires knowing the full structure of the object and exactly how to index the excluded fields. There is also some odd normalization happening on lists, which is causing the value provided to be mutated as it makes its way down the function call.
This is the best I could get (WARNING not tested thoroughly). We create a dictionary that returns itself on every lookup, that exposed __all__
as an exclude field. This will allow our key to be passed to each and every model, and be passed on to child objects to evaluate as well.
EXCLUDED_SPECIAL_FIELDS = "exclude_special_fields"
class _ExclusionDict(dict):
def __init__(self):
super().__init__({"__all__": {EXCLUDED_SPECIAL_FIELDS: True}})
def get(self, key):
return self
ExcludeSpecial = _ExclusionDict()
class SpecialExclusionBaseModel(BaseModel):
_special_exclusions: set[str]
def dict(self, **kwargs):
exclusions = getattr(self.__class__, "_special_exclusions", None)
exclude = kwargs.get("exclude")
if exclusions and exclude and EXCLUDED_SPECIAL_FIELDS in exclude:
return {
k: v
for k, v in super().dict(**kwargs).items()
if k not in exclusions
}
return super().dict(**kwargs)
With this base class, we can provide a class field called _special_exclusions
to indicate which fields we want excluded when the ExcludeSpecial
instance is provided as the exclude
kw argument.
On some initial testing, this seems to work with nested hierarchies including dicts and lists. There are probably bugs here that need to be worked out, but hopefully this is a good jumping off point for others.
class MyModel(SpecialExclusionBaseModel):
_special_exclusions = {"field"}
field: str
class AnotherModel(BaseModel):
models: list[MyModel]
class AnotherAnotherModel(BaseModel):
models: dict[str, AnotherModel]
model = MyModel(field=1)
another = AnotherAnotherModel(models={"test": AnotherModel(models=[model])})
print(another.dict(exclude=ExcludeSpecial))
{'models': {'test': {'models': [{}]}}}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论