Pull to refresh

Генерация фиктивных данных с Mimesis: Часть II

Reading time 5 min
Views 12K
image

Ранее мы уже публиковали статью о том, как генерировать фиктивные данные при помощи Mimesis — библиотеки для языка программирования Python. Статья, которую вы читаете является продолжением предыдущей, потому мы не будем приводить основ работы с библиотекой. Если вы пропустили статью, поленились прочитать или просто не захотели, то, вероятно, захотите сейчас, ибо эта статья предполагает, что читатель уже знаком с основами библиотеки. В этой части статьи мы будем говорить о best practice, расскажем о нескольких, на наш взгляд, полезных особенностях библиотеки.


Remarque


Прежде всего хотелось бы отметить, что Mimesis не разрабатывалась для использования с конкретной базой данных или ORM. Основная задача, которую библиотека решает — это предоставление валидных данных. По этой причине, строгих правил работы с библиотекой нет, но есть рекомендации, которые помогут держать ваше тестовое окружение в порядке и будут препятствовать росту энтропии в проекте. Рекомендации довольно просты и полностью соответствуют духу Python (если это не так, то ждем замечаний).


Структурирование


Вопреки выше сделанному заявлению о том, что библиотека не заточена на использование с конкретной базой данных или ORM, необходимость в тестовых данных чаще всего возникает как раз таки в веб-приложениях, которые выполняют некоторые операции (чаще всего CRUD) с базой данных. У нас есть кое-какие рекомендации по организации генерации тестовых данных для веб-приложений.


Функции, выполняющие генерацию данных и их запись в БД, необходимо держать рядом с моделями, а еще лучше, как статические методы модели к которой они относятся, по примеру метода _bootstrap() из предыдущей статьи. Это нужно во избежание беготни по файлам, когда меняется структура модели и необходимо добавить какое-то новое поле. Модель Patient() из предыдущей статьи хорошо демонстрирует идею:


class Patient(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True)
    phone_number = db.Column(db.String(25))
    full_name = db.Column(db.String(100))
    weight = db.Column(db.String(64))
    height = db.Column(db.String(64))
    blood_type = db.Column(db.String(64))
    age = db.Column(db.Integer)

    def __init__(self, **kwargs):
        super(Patient, self).__init__(**kwargs)

    @staticmethod
    def _bootstrap(count=500, locale='en', gender):
        from mimesis import Personal

        person = Personal(locale)

        for _ in range(count):
            patient = Patient(
                email=person.email(),
                phone_number=person.telephone(),
                full_name=person.full_name(gender=gender),
                age=person.age(minimum=18, maximum=45),
                weight=person.weight(),
                height=person.height(),
                blood_type=person.blood_type()
            )

            db.session.add(patient)
            try:
                db.session.commit()
            except IntegrityError:
                db.session.rollback()

Имейте в виду, что пример выше — это модель Flask-приложения, которое использует SQLAlchemy. Организация генераторов фиктивных данных для приложений, созданных с использованием других фреймворков происходит аналогичным образом.


Создание объектов


Если ваше приложение ожидает данные на одном конкретном языке и только на нем, то лучше всего использовать класс Generic(), предоставляющий доступ ко всем классам-провайдерам через один единственный объект, а не плодить множество экземпляров классов-провайдеров по отдельности. Используя Generic() вы избавитесь от лишних строк кода.


Верно:


>>> from mimesis import Generic

>>> generic = Generic('ru')
>>> generic.personal.username()
'sherley3354'
>>> generic.datetime.date()
'14-05-2007'

Неверно:


>>> from mimesis import Personal, Datetime, Text, Code

>>> personal = Personal('ru')
>>> datetime = Datetime('ru')
>>> text = Text('ru')
>>> code = Code('ru')

В то же время верно:


>>> from mimesis import Personal

>>> p_en = Personal('en')
>>> p_sv = Personal('sv')
>>> # ...

Т.е импортирование классов-провайдеров по отдельности имеет смысл, только, если вы ограничиваетесь лишь теми данными, которыми располагает, импортированный вами класс, в остальных случаях рекомендуется использовать Generic().


Запись данных в БД


Если вам нужно сгенерировать данные и записать их в БД, то настоятельно рекомендуем генерировать данные порциями, а не разом 600k. Необходимо помнить, что могут быть какие-то ограничения со стороны базы данных, ORM и т.д. Чем меньше порции данных, генерируемые для записи, тем быстрее запись.


Хорошо:


>>> User()._bootstrap(count=2000, locale='de')

Очень плохо:


>>> User()._bootstrap(count=600000, locale='de')

Загрузка изображений


Класс Internet() располагает несколькими методами, которые генерируют ссылки на изображения. Для тестирования вполне достаточно ссылок на изображения, расположенных на удаленных ресурсах, однако, если вы все же хотите иметь набор случайных изображений локально, то вы сможете загружать изображения, сгенерированные соответствующими методами класса Internet() при помощи функции download_image() из модуля utils:


>>> from mimesis.utils import download_image
>>> from mimesis import Internet

>>> img_url = Internet().stock_image(category='food', width=1920, height=1080)
>>> download_image(url=img_url, save_path='/some/path/')

Пользовательские провайдеры


Библиотека поддерживает большое количество данных и в большинстве случаев их будет вполне достаточно, однако для тех, кто хочет создать свои провайдеры с более специфичными данными, такая возможность поддерживается и делается это следующим образом:


>>> from mimesis import Generic
>>> generic = Generic('en')

>>> class SomeProvider():
...     class Meta:
...         name = "some_provider"
...
...     @staticmethod
...     def one():
...         return 1

>>> class Another():
...     @staticmethod
...     def bye():
...         return "Bye!"

>>> generic.add_provider(SomeProvider)
>>> generic.add_provider(Another)
>>> # ...
>>> generic.some_provider.one()
1
>>> generic.another.bye()
'Bye!'

Тут все просто и понятно без комментариев, потому уточним лишь один момент — атрибут name, класса Meta — это то название класса, через которое будет осуществляться доступ к методам пользовательского класса-провайдера. По умолчанию название класса — это имя класса в нижнем регистре.


Builtin providers


Большинство стран, где тот или иной язык является официальным, имеют данные, которые характерны только для этих стран. К примеру CPF для Бразилии, SSN для США. Такого рода данные могут причинять неудобства и нарушать порядок (или как минимум раздражать) тем, что будут присутствовать во всех объектах, независимо от выбранного языкового стандарта. Вы можете сами убедиться в сказанном, если посмотрите на пример того, как это выглядело бы (код работать не будет):


>>> from mimesis import Personal

>>> person = Personal('ru')
>>> person.ssn()
>>> person.cpf()

Думаю все согласятся с тем, что это выглядит совсем нехорошо. Мы, будучи перфекционистами, позаботились о том, чтобы бразильский CPF не беспокоил "поляка" и по этой причине классы-провайдеры, предоставляющие такого рода специфичные для конкретных локалей данные вынесены в отдельный подпакет (mimesis.builtins), чтобы сохранить общую для всех языков структуру классов и их объектов.


Так это работает:


>>> from mimesis import Generic
>>> from mimesis.builtins import BrazilSpecProvider

>>> generic = Generic('pt-br')

>>> class BrazilProvider(BrazilSpecProvider):
...
...     class Meta:
...         name = "brazil_provider"
...

>>> generic.add_provider(BrazilProvider)
>>> generic.brazil_provider.cpf()
'696.441.186-00'

Вообще, вам необязательно добавлять встроенные классы к объекту Generic(). В примере это сделано лишь для демонстрации того, в каких случаях будет уместным добавить встроенный класс-провайдер к объекту Generic(). Вы можете использовать его напрямую, как показано ниже:


>>> from mimesis.builtins import RussiaSpecProvider

>>> ru = RussiaSpecProvider()
>>> ru.patronymic(gender='female')
'Петровна'
>>> ru.patronymic(gender='male')
'Бенедиктович'

В каких данные чаще всего возникает необходимость в вашей работе? Что в библиотеке упустили и что необходимо немедленно добавить? Мы были бы очень рады услышать ваши пожелания/рекомендации/замечания.


Ссылка на проект: тут.
На документацию ссылка: тут.
На первую часть статьи: тут.


На этом у меня все, друзья. Вам удачных тестов и да пребудет с вами сила!

Tags:
Hubs:
+13
Comments 9
Comments Comments 9

Articles