@firstmixon

Как в питоне обеспечить контроль целосности данных?

Есть в питоне целочисленный тип Int, который имеет достаточно условные границы в типовых задачах, как обеспечить сохранение данных в БД, в случае описания целочисленных полей в виде (byte, short, word, int, long), когда я пытаюсь Insert\Update field_byte=300?
В типизированных языках этот вопрос решался бы через объявления переменной field_byte как byte и ошибка на строке field_byte=100*3
  • Вопрос задан
  • 159 просмотров
Решения вопроса 1
Vindicar
@Vindicar
RTFM!
С переменными так не прокатит. А вот с полями класса - при желании можно, если использовать property.
Базовая идея простая - пусть сеттер свойства производит валидацию.
Например, так
class Person:
    def __init__(self, name: str):
        self._name: str = ''
        self.name = name
    
    @property
    def name(self) -> str:
        return self._name
    
    @name.setter
    def name(self, value: str) -> None:
        if not value:
            raise ValueError('Must not be empty')
        self._name = value

Но писать такой бойлерплейт быстро надоест. Поэтому можем попробовать обобщить процесс.
Например, так
from typing import TypeVar, Type, Any, Annotated, Callable, Dict, get_args, get_origin

T = TypeVar('T')
ValidationRule = Callable[[Any], bool]
ValidationRules = Dict[str, ValidationRule]

class ValidatedProperty:
    """Экземпляры этого класса будут свойствами в валидируемых классах, и будут заниматься валидацией."""
    def __init__(self, name: str, storagename: str, rules: ValidationRules):
        self.name = name  # как называется свойство
        self.storagename = storagename  # где его хранить
        self.rules = rules  # какие правила применять

    def __get__(self, instance, owner=None):
        return getattr(instance, self.storagename)  # при чтении просто возвращаем свойство
    
    def __set__(self, instance, value):  # при записи валидируем
        for message, rule in self.rules.items():  
            if not rule(value):  # если правило нарушено, выкидываем исключение с сообщением
                raise ValueError(f'{instance.__class__.__name__}.{self.name}: {message}')
        setattr(instance, self.storagename, value)


def validated(klass: Type[T]) -> Type[T]:
    """Декоратор для валидируемых классов."""
    for name, annot in klass.__annotations__.items():  # проверяем список аннотаций в классе
        base = get_origin(annot) or annot
        if base is not Annotated:  # нас интересуют только те, которые помечены как Annotated
            continue
        args = get_args(annot)
        rules = [arg for arg in args if isinstance(arg, dict)]
        if not rules:  # и только если один (любой) из аргументов Annotated - словарь
            continue
        # в этом случае мы считаем, что словарь содержит правила валидации, и создаём свойство класса
        setattr(klass, name, ValidatedProperty(name, f'_{name}', rules[0]))
    return klass  # не забываем вернуть изменённый класс!


@validated
class Person:
    name: Annotated[str, {'must not be empty': lambda v: bool(v)}]
    age: Annotated[int, {'must be positive': lambda v: v > 0}]

    def __init__(self, name: str, age: int):
        self.name = name  # валидация отработает уже здесь
        self.age = age  # валидация отработает уже здесь
    
    def __repr__(self) -> str:
        return f'Person(name={self.name!r}, age={self.age!r})'

try:
    Person('John Doe', 23)  # отработает успешно
except Exception as err:
    print('Failed to create person 1')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 1 created')

try:
    Person('', 23)  # выкинет исключение
except Exception as err:
    print('Failed to create person 2')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 2 created')

try:
    Person('Jane Doe', -23)  # выкинет исключение
except Exception as err:
    print('Failed to create person 3')
    print(f'{err.__class__.__name__}: {err!s}')
else:
    print('Person 3 created')

p = Person('John Doe', 23)
try:
    p.name = ''  # выкинет исключение
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

try:
    p.age = 0  # выкинет исключение
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

try:
    p.age = 24  # отработает успешно
except Exception as err:
    print('Failed to modify person')
    print(f'{err.__class__.__name__}: {err!s}')

print(p)
print(vars(p))


Это, конечно, велосипед, и пользоваться им я бы не посоветовал. Для валидации есть библиотеки типа pydantic.
Плюс, как было указано выше, не всё и не всегда имеет смысл валидировать...

А для работы с БД лучше использовать ORM. SqlAlchemy популярна, но есть и более простые, например, peewee или ponyorm.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
phaggi
@phaggi Куратор тега Python
лужу, паяю, ЭВМы починяю
Как-то так?

my_int_var = 555555
print(isinstance(my_int_var, int))
my_int_var = 555555.5
print(isinstance(my_int_var, int))
my_int_var = '555555'
print(isinstance(my_int_var, int))
my_int_var = True
print(isinstance(my_int_var, int))
my_int_var = b'True'
print(isinstance(my_int_var, int))

Может быть только проблема с bool, поскольку оно мэппится с 1/0.
Ответ написан
Комментировать
@semenovs
QA mobile, bh
Нестандартное решение:
from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyTable(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    field_byte = Column(Integer)
    field_short = Column(Integer)
    # Другие поля

# Пример использования
value = 300

# Создание экземпляра класса
my_object = MyTable(field_byte=value)

# Вставка в базу данных
session.add(my_object)
session.commit()


В классе определены поля с явно заданными типами данных. Если вы попытаетесь присвоить значение, выходящее за допустимый диапазон, SQLAlchemy сгенерирует исключение.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы