Python: функция cerberus check_with - PullRequest
1 голос
/ 08 мая 2020

Я хотел бы проверить dict, где значения соответствуют следующим правилам:

  • значение должно быть либо одиночным float, либо List(float)
  • если это одиночный float, значение должно быть 1
  • , если это List(float), каждое число с плавающей запятой должно быть положительным

Вот мой код и некоторые тестовые утверждения, которые работают правильно:

import cerberus

v = cerberus.Validator()

schema1 = {
    "key1": {
        "type": ["float", "list"],
        "min": 1,
        "max": 1,
        "schema": {"type": "float", "min": 0},
    }
}
document1 = {"key1": 1}
document2 = {"key1": 5}
document3 = {"key1": "5"}
document4 = {"key1": [0.5, 0.3]}
document5 = {"key1": ["0.5", 0.3]}

assert v.validate(document1, schema1)
assert not v.validate(document2, schema1)
assert not v.validate(document3, schema1)
assert v.validate(document4, schema1)
assert not v.validate(document5, schema1)

Теперь мне нужно выполнить еще одно условие:

  • , если это List(float), sum из float должно быть равно 1

Поэтому я написал функцию check_with, как описано в документации (https://docs.python-cerberus.org/en/stable/validation-rules.html).

from cerberus import Validator

class MyValidator(Validator):
    def _check_with_sum_eq_one(self, field, value):
        """Checks if sum equals 1"""

        if sum(value) != 1:
            self._error(field, f"Sum of '{field}' must exactly equal 1")

Скорректированная схема и тестовые документы выглядят следующим образом:

v = MyValidator()

schema2 = {
    "key1": {
        "type": ["float", "list"],
        "min": 1,
        "max": 1,
        "schema": {"type": "float", "min": 0, "max": 1, "check_with": "sum_eq_one"},
    }
}

document1 = {"key1": 1}
document2 = {"key1": 5}
document3 = {"key1": "5"}
document4 = {"key1": [0.5, 0.3]}  # error
document5 = {"key1": ["0.5", 0.3]}  # error
document6 = {"key1": [0.5, 0.5]}  # error

Теперь, когда значение равно List(float), только первый элемент list будет введено в мою функцию, что приводит к TypeError: 'float' object is not iterable.
При проверке document4, field будет int=0 и value=0.5. Итак, сообщение об ошибке имеет смысл.

Мне интересно, почему весь список не передается в мою функцию? Что мне здесь не хватает?

Ответы [ 2 ]

1 голос
/ 13 мая 2020

Что, если вы попытаетесь отловить ошибку и продолжить работу только в том случае, если ошибка возникла? Например, вот так:

class MyValidator(Validator):

def _check_with_sum_eq_one(self, field, value):
    """Checks if sum equals 1"""
    if self._errors:
        self._drop_remaining_rules
    else:
        if type(value) == list and sum(value) != 1.0:
            self._error(str(value), f"Sum of '{field}' must exactly equal 1")


schema2 = {
    "key1": {
        "type": ["list", "float"],
        "min": 1,
        "max": 1,
        "schema": {"type": "float", "min": 0, "max": 1},
        "check_with": "sum_eq_one",
    }
}

v = MyValidator(schema2)

document1 = {"key1": 1}
document2 = {"key1": 5}
document3 = {"key1": "5"}
document4 = {"key1": [0.3, 0.5]}  # error
document5 = {"key1": ["0.5", 0.3]}  # error
#document6 = {"key1": [0.5, 0.5]}  # error
assert v.validate(document1)
assert not v.validate(document2)
assert not v.validate(document3)
assert v.validate(document4)
assert not v.validate(document5)
0 голосов
/ 11 мая 2020

Следующий ответ работает правильно. Однако, на мой взгляд, это слишком сложно.

Сначала настройте schema2 следующим образом:

schema2 = {
    "key1": {
        "type": ["float", "list"],
        "min": 0,
        "max": 1,
        "check_with": "sum_eq_one"
    }
}

Затем настройте _check_with_sum_eq_one следующим образом:

class MyValidator(Validator):
    def _check_with_sum_eq_one(self, field, value):
        """Checks if sum equals 1"""

        if (isinstance(value, float) or isinstance(value, int)) and value != 1:
            self._error(field, f"Sum of '{field}' must exactly equal 1")
        if isinstance(value, list):
            if all([isinstance(x, float) for x in value]):
                if sum(value) != 1:
                    self._error(field, f"Sum of '{field}' must exactly equal 1")
            else:
                self._error(field, f"All list members must be of type ['float']")

Наконец, подтвердите, что все работает, как ожидалось.

v = MyValidator()

document1 = {"key1": 1}
document2 = {"key1": 5}
document3 = {"key1": "5"}
document4 = {"key1": [0.5, 0.3]}
document5 = {"key1": ["0.5", 0.3]}
document6 = {"key1": [0.5, 0.5]}

assert v.validate(document1, schema2)
assert not v.validate(document2, schema2)
assert not v.validate(document3, schema2)
assert not v.validate(document4, schema2)
assert not v.validate(document5, schema2)
assert v.validate(document6, schema2)

Что мне здесь не нравится, так это то, что мне нужно проверять «вручную», все ли члены списка относятся к типу float (if all([isinstance(x, float) for x in value])). На мой взгляд, этот тест относится к schema2. Однако мне не удалось настроить schema2 таким образом, чтобы проверка на принадлежность к типу float предшествовала проверке check_with.

Мы будем благодарны за любые подсказки для дальнейшего упрощения этой задачи.

...