5
votes

Pydantic: Rendre le champ Aucun dans le validateur basé sur la valeur d'un autre champ

J'utilise le BaseModel pydantic avec un validateur comme celui-ci:

from datetime import date
from typing import List, Optional
from pydantic import BaseModel, BaseConfig, validator

class Model(BaseModel):
    class Config(BaseConfig):
        allow_population_by_alias = True
        fields = {
            "some_date": {
                "alias": "some_list"
            }
        }
    some_date: Optional[date]
    some_list: List[date]

    @validator("some_date", pre=True, always=True)
    def validate_date(cls, value):
        if len(value) < 2: # here value is some_list
            return None
        return value[0] # return the first value - let's assume it's a date string

# This reproduces the problem
m = Model(some_list=['2019-01-03'])

Je voudrais calculer la valeur de some_date basé sur la valeur de some_list et le rendre None si une certaine condition est remplie.

Mon JSON ne contient jamais le champ some_date , il est toujours renseigné en fonction de some_list d'où pre = True, always = True . Cependant, le validateur par défaut pour some_date fonctionnera après mon personnalisé, qui échouera si validate_date renvoie None . / p>

Existe-t-il un moyen de créer un tel champ qui n'est calculé que par un autre et qui peut encore être Optionnel?


3 commentaires

@normanius mon mauvais, j'ai oublié que les champs allow ... et étaient dans une classe Config interne


btw j'essaye avec python 3.6.1 et pydantic 0.16.1


Les nouvelles versions de pydantic (à partir de 0.20) gèrent beaucoup mieux votre cas d'utilisation.


3 Réponses :


0
votes

Mise à jour : comme d'autres l'ont souligné, cela peut être fait maintenant avec les versions plus récentes (> = 0.20). Consultez cette réponse . (Note latérale: même le code de l'OP fonctionne maintenant, mais le faire sans alias est encore mieux.)


D'après la documentation de lecture et la source de pydantic, j'ai tendance à dire que le mécanisme de validation de pydantic a actuellement prise en charge très limitée des transformations de type ( liste -> date , liste -> NoneType ) dans les fonctions de validation.

prenez du recul, cependant, votre approche utilisant un alias et le drapeau allow_population_by_alias semble un peu surchargé. some_date n'est nécessaire que comme raccourci pour some_list [0] if len (some_list)> = 2 else None , mais il n'est jamais défini indépendamment de some_list code >. Si c'est vraiment le cas, pourquoi ne pas opter pour l'option suivante?

class Model(BaseModel):
    some_list: List[date] = ...

    @property 
    def some_date(self):
        return None if len(self.some_list) < 2 else self.some_list[0]


1 commentaires

Je me demandais si mon approche était possible du tout, mais vous avez raison, ce problème ne devrait pas être résolu comme ça



1
votes

Vous devriez pouvoir utiliser des valeurs conformément aux docs pydantic

vous pouvez également ajouter n'importe quel sous-ensemble des arguments suivants au signature (les noms doivent correspondre):

valeurs: un dict contenant les mappage nom-valeur de tous les champs précédemment validés

config: le configuration du modèle

Champ

: le champ en cours de validation

** kwargs: s'il est fourni, cela inclura les arguments ci-dessus non explicitement listés dans la signature

@validator()
def set_value_to_zero(cls, v, values):
    # look up other value in values, set v accordingly.


0 commentaires

3
votes

Si vous souhaitez pouvoir modifier dynamiquement un champ en fonction d'un autre, vous pouvez utiliser l'argument values ​​. Il contient tous les champs précédents, et attention: l ' ordre compte . Vous pouvez le faire en utilisant un validateur ou un root_validator .

Avec un validator

>>> class Model(BaseModel):
        some_list: List[date]
        some_date: Optional[date]
    
        @root_validator
        def validate_date(cls, values):
            if not len(values["some_list"]) < 2:
                values["some_date"] = values["some_list"][0]
            return values

>>> Model(some_list=['2019-01-03', '2020-01-03', '2021-01-03'])
Model(some_list=[datetime.date(2019, 1, 3), datetime.date(2020, 1, 3), datetime.date(2021, 1, 3)],
      some_date=datetime.date(2019, 1, 3))


0 commentaires