简体   繁体   中英

Passing additional arguments to _normalise_coerse methods in cerberus

I have some code see EOM; it's by no means final but is the best way (so far) I've seen/conceived for validating multiple date formats in a somewhat performant way.

I'm wondering if there is a means to pass an additional argument to this kind of function (_normalise_coerce), it would be nice if the date format string could be defined in the schema. something like

{
   "a_date":{
      "type": "datetime",
      "coerce": "to_datetime",
      "coerce_args": "%m/%d/%Y %H:%M"
   }
}

Vs making a code change in the function to support an additional date format. I've looked through the docs and not found anything striking. Fairly good chance I'm looking at this all wrong but figured asking the experts was the best approach. I think defining within the schema is the cleanest solution to the problem, but I'm all eyes and ears for facts, thoughts and opinions.

Some context:

  • Performance is essential as this could be running against millions of rows in AWS lambdas (and Cerbie (my nickname for cerberus) isn't exactly a spring chicken:P ).
  • None of the schemas will be native python dicts as they're all defined in JSON/YAML, so it all needs to be string friendly.
  • Not using the built-in coercion as the python types cannot be parsed from strings
  • I don't need the datetime object, so regex is a possibility, just less explicit and less futureproof.
  • If this is all wrong and I'm grossly incompetent, please be gentle (づ。◕‿‿◕。)づ
def _normalize_coerce_to_datetime(self, value: Union(str, datetime, None)) -> Union(datetime, str, None):
        '''
        Casts valid datetime strings to the datetime python type.

        :param value: (str, datetime, None): python datetime, datetime string
        :return: datetime, string, None. python datetime, 
            invalid datetime string or None if the value is empty or None
        '''
        datetime_formats = ['%m/%d/%Y %H:%M']

        if isinstance(value, datetime):
            return value

        if value and not value.isspace():
            for format in datetime_formats:
                try:
                    return datetime.strptime(value, format)
                except ValueError:
                    date_time = value
            return date_time
        else:
            return None

I have attempted to do this myself and have not found a way to pass additional arguments to a custom normalize_coerce rule. If you want to extend the Cerberus library to include custom validators then you can include arguments and then access these through the constraints in the custom validator. The below is an example that I have used for a conditional to default coercer, but as I needed to specify the condition and both the value to check against and the value to return I couldn't find a way to do this with the normalize_coerce and hence applied inside a validate rule and edited the self.document, as seen by the code.

Schema:
        {
       "columns":{
          "Customer ID":{
             "type":"number",
             "conditional_to_default":{
                "condition":"greater_than",
                "value_to_check_against":100,
                "value_to_return":22
             }
          }
       }
    }




def _validate_conditional_to_default(self, constraint, field, value):
    """
    Test the values and transform if conditions are met.
    :param constraint: Dictionary with the args needed for the conditional check.
    :param field: Field name.
    :param value: Field value.
    :return: the new document value if applicable, or keep the existing document value if not
    """

    value_to_check_against = constraint["value_to_check_against"]
    value_to_return = constraint["value_to_return"]
    rule_name = 'conditional_to_default'

    condition_mapping_dict = {"greater_than": operator.gt, "less_than": operator.lt, "equal_to": operator.eq,
                              "less_than_or_equal_to": operator.le,
                              "greater_than_or_equal_to": operator.ge}

    if constraint["condition"] in condition_mapping_dict:
        if condition_mapping_dict[constraint["condition"]](value, value_to_check_against):
            self.document[field] = value_to_return
            return self.document
        else:
            return self.document
    if constraint["condition"] not in condition_mapping_dict:
        custom_errors_list = []
        custom_error = cerberus.errors.ValidationError(document_path=(field, ), schema_path=(field, rule_name),
                                                       code=0x03, rule=rule_name, constraint="Condition must be "
                                                                                             "one of: "
                                                                                             "{condition_vals}"
                                                       .format(condition_vals=list(condition_mapping_dict.keys())),
                                                       value=value, info=())
        custom_errors_list.append(custom_error)
        self._error(custom_errors_list)
        return self.document

This is probably the wrong way to do it, but I hope the above gives you some inspiration and gets you a bit further. Equally I'm following this to see if anyone else has found a way to pass arguments to the _normlize_coerce function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM