Elizabeth — это библиотека для языка программирования Python, которая помогает генерировать фиктивные данные. Один из простейших примеров использования библиотеки — это заполнение баз данных для приложений на Flask или Django. На данный момент библиотека поддерживает 16 языковых стандартов и 18 классов-провайдеров, предоставляющих разного рода данные.


Возможность генерировать фиктивные, но в то же время валидные данные бывает очень полезна при разработке приложений, которые подразумевают работу с базой данных. Ручное заполнение базы данных представляется довольно-таки сложным и изнурительным процессом, но по-настоящему все усложняется в тот момент, когда требуется сгенерировать не 10-15 пользователей, а 100-150 тысяч. В этой статье я постараюсь обратить ваше внимание на инструмент, который в разы упрощает процесс начальной загрузки базы данных на этапе тестирования.


Общая информация


В представленной ниже таблице приведены классы-провайдеры.


Provider Описание
1 Address Адресные данные (названия улиц, номера домов, индексы и т.п.)
2 Business Данные для бизнеса (компании, типы компаний, копирайты и т.п.)
3 Code Коды (ISBN, EAN, IMEI и т.п.)
4 ClothingSizes Размеры одежды (международные, европейский, американские и т.п.)
5 Datetime Время (день недели, месяц, год, день рождения и т.п.)
6 Development Разработка (версия, язык программирования, стек и т.п.)
7 File Файлы (расширения, типы файлов и т.п.)
8 Food Еда (овощи, фрукты, единицы измерения и т.п.)
9 Personal Персональные данные (имя, фамилия, возраст, email и т.п.)
10 Text Текстовые данные (предложение, заголовок, текст и т.п.)
11 Transport Транспорт (модель грузового автомобиля и т.п.)
12 Network Сеть (IPv4, IPv6, MAC address и т.п.)
13 Science Научные данные (химический элемент, формул и т.п.)
14 Internet Интернет (facebook, twitter, vk и т.п.)
15 Hardware Железо (resolution, cpu, graphics etc.)*
16 Numbers Числовые данные (floats, primes, digit etc.)*
17 Path Пути (корневая директория, рабочая директория и т.п.)
18 Generic Все в одном (когда используется только одна локаль)

На момент написания этой статьи библиотека поддерживает 16 языковых стандартов, которые перечислены в таблице, что приведена ниже:


ISO Код Название Самоназвание
1 da Датский Dansk
2 de Немецкий Deutsch
3 en Английский English
4 es Испанский Espanol
5 fa Персидский ?????
6 fi Финский Suomi
7 fr Французский Francais
8 is Исландский Islenska
9 it Итальянский Italiano
10 nl Нидерландский Nederlands
11 no Норвежский Norsk
12 pl Польский Polski
13 pt Португальский Portugues
14 pt-br Бразильский Португальский Portugues Brasileiro
15 ru Русский Русский
16 sv Шведский Svenska

Установка


Установка Elizabeth производится как обычно, т.е посредством пакетного менеджера pip:


?  ~ pip install elizabeth

Генерация


Изначально я планировал показать генерацию данных на примере небольшого Flask-приложения (блога), но решил отказаться от этой идеи, ибо не все знакомы с Flask. По этой причине я буду показывать все на чистом Python. В случае, если вы захотите перенести все в свой проект, то вам нужно всего лишь определить статический метод в теле вашей модели и вызывать его в тот момент, когда нужно выполнить начальную загрузку БД, как показано ниже.


Модель будет выглядеть как-то так:


# ...

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))

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

    @staticmethod
    def _bootstrap(count=2000):
        from elizabeth import Personal

        person = Personal('en')
        for _ in range(count):
            patient = Patient(email=person.email(),
                              phone_number=person.telephone(),
                              full_name=person.full_name(gender='female'),
                              weight=person.weight(),
                              height=person.height(),
                              blood_type=person.blood_type()
                              )

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

А вот так будет выглядеть сам процесс генерации:


>>> patient = Patient() # Создать экземпляр класса-модели.
>>> patient._bootstrap(count=40000) # Сгенерировать 40к записей.

Примеры


Хотелось бы подчеркнуть, что я буду приводить в примерах только базовые возможности библиотеки и, в основном, обойдусь несколькими классами-провайдерами, ибо их там слишком много, чтобы рассказывать обо всех. Если вы заинтересуетесь, то можете найти ссылки на проект и на документацию в самом конце статьи и самостоятельно изучить все.


Библиотека устроена довольно просто и все, что вам необходимо для того, чтобы начать работать с данными — это создать экземпляр класса-провайдера, как показано в примере ниже.


>>> from elizabeth import Personal

>>> user = Personal('is')

>>> for _ in range(0, 9):
        user.full_name(gender='male')

Karl Brynjulfsson
Rognvald Ei?sson
Vesteinn Rikhar?sson
Fri?leifur Granason
Fjarki Arngar?sson
Hafsteinn ?rymsson
Sivar Kakalason
Grimnir Unason
Gymir ?or?sson

Примеры


Код ниже создаст 50к email адресов.


>>> user = Personal('ru')
>>> emails = [user.email(gender='female') for _ in range(0, 50000)]

Вывод (сокращенный):


[
lvana6108@gmail.com', 'demetrice3816@live.com', 
'alayna7278@yahoo.com', 'maida5494@outlook.com', ...
]

Сгенерировать имя, день рождения и группу крови:


>>> from elizabeth import Generic

>>> g = Generic('pl') # согласно стандарту ISO 639-1, pl - это код Польши.

>>> for _ in range(0, 5):
        name = g.personal.full_name()
        birthday = g.datetime.birthday(readable=True)
        blood = g.personal.blood_type()
        name + '-' + birthday +  '/' + blood

Ibolya Smolik - Listopad 11, 1997 / B?
Lonislawa Podsiadlo - Styczen 20, 1989 / AB+
Loritta Boguski - Pazdziernik 11, 1990 / A?
Laurentyna Kowalczyk - Lipiec 16, 1993 / B?
Gelazyna Wawrzyniak - Pazdziernik 12, 2000 / B?

Можете генерировать и пользователей разного пола:


>>> from random import choice

>>> user = Personal('en')

>>> for _ in range(0, 20):
        title = user.title(type_='academic')
        name = user.full_name(choice(['female', 'male']))
        '{0}, {1}'.format(name, title)

Sid Powell, M.Des
Vanita Christensen, M.D.
Vania Grant, DPhil
Chester Valenzuela, MSc
Georgeann Sanders, D.Ed.
Dong Clay, DPhil
Lala Vang, DPhil
Alden Nieves, M.A.
Deloise Erickson, B.A.
Willow Cox, M.D.
Belia Lowery, Prof.
Shirley Thompson, M.Des
Tracey Dickson, PhD
Alexis Newton, D.Ed.
Leland Gay, D.Ed.
Noel Joseph, MMath
Oda Ryan, B.A.
Alan Sharp, B.A.
Bernardina Solomon, Prof.
Paul Caldwell, M.D.

Сгенерировать фиктивных держателей карты Visa:


user = Personal('en')

def get_card(sex='female'):
    owner = {
        'owner': user.full_name(sex),
        'exp_date': user.credit_card_expiration_date(maximum=21),
        'number': user.credit_card_number(card_type='visa')
    }
    return owner

for _ in range(0, 10):
    print(get_card())

{'exp_date': '02/20', 'owner': 'Laverna Morrison', 'card_number': '4920 3598 2121 3328'}
{'exp_date': '11/19', 'owner': 'Melany Martinez', 'card_number': '4980 9423 5464 1201'}
{'exp_date': '01/19', 'owner': 'Cleora Mcfarland', 'card_number': '4085 8037 5801 9703'}
{'exp_date': '06/21', 'owner': 'Masako Nielsen', 'card_number': '4987 3977 4806 4598'}
{'exp_date': '02/18', 'owner': 'Ji Reid', 'card_number': '4371 5151 1188 6594'}
{'exp_date': '12/19', 'owner': 'Kacie Huff', 'card_number': '4053 2310 5641 6849'}
{'exp_date': '05/19', 'owner': 'Violette Hurst', 'card_number': '4333 5939 9634 8805'}
{'exp_date': '07/16', 'owner': 'Shenika Moreno', 'card_number': '4509 3525 9097 4027'}
{'exp_date': '09/20', 'owner': 'Gianna Thompson', 'card_number': '4637 4384 3001 5237'}
{'exp_date': '05/19', 'owner': 'Pa Mccoy', 'card_number': '4290 9075 7914 2028'}

Сгенерировать модель грузового автомобиля (если сайт ориентирован на грузоперевозки или на иную деятельность, связанную с транспортом):


>>> trans = Transport()

>>> for _ in range(0, 5):
        trans.truck()

Seddon-2537 IM
Karrier-7799 UN
Minerva-5567 YC
Hyundai-2808 XR
LIAZ-7174 RM

Ну или можно указать маску модели:


>>> for _ in range(0, 5):
        trans.truck(model_mask="##@") # ## - числа, @ - буквы

Henschel-16G
Bean-44D
Unic-82S
Ford-05Q
Kalmar-58C

А вот так можно сгенерировать текст:


>>> text = Text('ru')
>>> print(text.text(quantity=5)) # quantity - показатель количества предложений.

'Язык включает в себя средства порождения параллельных легковесных процессов и их взаимодействия через обмен асинхронными сообщениями в соответствии с моделью акторов. Python поддерживает несколько парадигм программирования, в том числе структурное, объектно-ориентированное, функциональное, императивное и аспектно-ориентированное. Например, определение функции, которое использует сопоставление с образцом, для выбора одного из вариантов вычисления или извлечения элемента данных из составной структуры, напоминает уравнение. Сопоставление с образцом распространено даже на битовые строки, что упрощает реализацию телекоммуникационных протоколов. Haskell — стандартизированный чистый функциональный язык программирования общего назначения.'

Получить алфавит текущей локали можно так:


>>> text = Text('fa')
>>> text.alphabet()

['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?']

Можно получить список слов:


>>> text = Text('pt-br')
>>> text.words(quantity=50)

['poder', 'de', 'maior', 'so', 'cima', 'Singularidades', 'mudou', 'ficara', 'nao', 'vossas', 'prata', 'chorar', 'c', 'Equilibrio', 'drogas', 'paul', 'simplesmente', 'querido', 'vendo', 'material', 'apartamento', 'postar', 'basico', 'com', 'papa', 'cor', 'simbolo', 'ocupado', 'armas', 'vidro', 'roda', 'droga', 'serve', 'menina', 'cor', 'semente', 'solucao', 'controle', 'desapareceu', 'livros', 'voltar', 'melhores', 'Menos', 'quantas', 'pensar', 'estas', 'jogar', 'personagem', 'personagem', 'cabelo']

Можно сгенерировать название улицы:


>>> address = Address('ru')
>>> street = address.address()

'ул. Хабаровская 651'

Или получить субъект РФ:


>>> subject = address.state()
>>> subject

'Кировская область'

Сгенерировать координаты:


>>> for _ in range(15):
        address.coordinates()

{'latitude': -28.362892454682246, 'longitude': 11.512065821275826}
{'latitude': 88.6508382607752, 'longitude': 106.65769171105143}
{'latitude': 37.83559264558106, 'longitude': 152.45098344872065}
{'latitude': -13.876545618251413, 'longitude': -52.192814272082956}
{'latitude': 66.24928144869963, 'longitude': -128.7755343668871}
{'latitude': 38.294693362179856, 'longitude': -179.1208031527886}
{'latitude': -52.987692529476554, 'longitude': -59.705094559802475}
{'latitude': -33.69813204096576, 'longitude': 72.54315315052486}
{'latitude': 83.91496334322997, 'longitude': 105.58095303015153}
{'latitude': -83.45909366537177, 'longitude': -13.56788050796456}
{'latitude': 20.41176475353275, 'longitude': 151.7257568261699}
{'latitude': 63.52570687889795, 'longitude': 158.04375817974056}
{'latitude': -78.51081206017261, 'longitude': 47.50148838136573}
{'latitude': -81.94335735575271, 'longitude': -84.44100935967519}
{'latitude': -80.30372095039073, 'longitude': 21.819487429596222}

На самом деле возможностей очень много и потому кружится голова, когда пытаешься придумать что-то конкретное, но я почти уверен, что заинтересованные люди найдут много интересного для себя.


Github: lk-geimfari/elizabeth
Read the Docs: elizabeth


P.S Чуть позже я все-таки попытаюсь обновить статью и добавить пример Flask-приложения, о котором упоминалось ранее в статье.


Спасибо за внимание и удачных тестов!

Поделиться с друзьями
-->

Комментарии (33)


  1. kivsiak
    21.12.2016 14:46

    Полезная штука. Хочу такую же как плагин для sketch3. Ну или самому попереть справочники и портировать на праздниках


  1. kmz161
    21.12.2016 15:21

    Возникла ошибка при установке библиотеки
    SyntaxError: Non-ASCII character '\xe2' in file elizabeth\core\interdata.py on line 720, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details


    1. likid_ri
      21.12.2016 15:23

      Скажите, пожалуйста, какая у вас ОС и какой Python использовали?


      1. kmz161
        21.12.2016 15:32

        Проблема идентичная как на windows 10, так и на Ubuntu 16.04. На обоих ОС python 2.7.12


        1. likid_ri
          21.12.2016 15:34

          Все верно. Возможно вы не обратили внимания на бейджики в ридми. Библиотека работает только на Python 3.


          1. kmz161
            21.12.2016 15:35

            Спасибо. Действительно не увидел этого. Жаль, что на 2.7 не поддерживается


            1. likid_ri
              21.12.2016 16:01

              Версия 2.7 обошлась бы загрязнением кода, из-за необходимости поддерживать обратную совместимость, и возникновением зависимостей, потому пришлось пойти на такую жертву.


            1. dimmount
              21.12.2016 18:54

              на 2.7 есть faker


              1. likid_ri
                21.12.2016 18:54

                Да, верно.


  1. kmz161
    21.12.2016 15:31

    Проблема идентичная как на windows 10, так и на Ubuntu 16.04. На обоих ОС python 2.7.12.
    Извиняюсь, ошибся веткой


  1. tytar
    21.12.2016 15:34

    Скажите, а в чем профит вашей реализации от того же faker?


    1. likid_ri
      21.12.2016 15:56
      +1

      Какого-то глубокого сравнения я не производил, но могу сказать, что данных больше, провайдеров больше. Данные для русского языка достаточно точны и валидны. Скорость работы выше. Я, конечно, не производил сравнения скорости генерации в в боевых условиях (т.е с бд), но даже в обычной генерации данных `elizabeth` работает в разы быстрее, чем `faker`.

      Небольшой пример:
      Ниже приведен скрины работы кода, который генерирует 250к имен (Ф.И).

      image


      1. saluev
        21.12.2016 16:46
        +1

        По ссылке старый пакет. Вот актуальный.


        1. likid_ri
          21.12.2016 16:48
          +1

          Да, именно с ним я и сравнивал.


  1. polarnik
    21.12.2016 15:39

    Привет, спасибо. Как-то создал синтетический мир из нескольких тысяч организаций и сотрудников для тестирования сервиса электронного документооборота. Нужны были ИНН, КПП, ОГРН, СНИЛС, ФИО, наименования, города, улицы, индексы, ...


    Подборку исходных данных по ФИО и наименованиям, частично, вот тут отразил:



    1. likid_ri
      21.12.2016 15:58

      Пожалуйста! Мы по возможности стараемся добавлять только данные, которые годятся для всех языков. А ИНН и все такое можно генерировать другими классами-провайдерами, которые с легкостью с этим справляются.


  1. saluev
    21.12.2016 16:44
    +3

    А разные группы крови появляются с теми же вероятностями, что и в реальном мире? :)


    1. likid_ri
      21.12.2016 16:46
      +2

      :D Нет, к сожаление нет, но такую особенность прикрутить можно. Хорошая идея!


  1. JeStoneDev
    22.12.2016 02:07

    Win10

    Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from elizabeth import Personal
    >>> user = Personal('is')
    >>> for _ in range(0, 9):
    ...     print(user.full_name(gender='male'))
    ...
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
      File "C:\Users\mainj\AppData\Local\Programs\Python\Python35-32\lib\encodings\cp437.py", line 19, in encode
        return codecs.charmap_encode(input,self.errors,encoding_map)[0]
    UnicodeEncodeError: 'charmap' codec can't encode character '\xf0' in position 5: character maps to <undefined>
    

    Нетекстовые данные (и текстовые на английском) нормально генерируются.


    1. likid_ri
      22.12.2016 08:32

      Я подозревал, что на Windows проблемы могут возникнуть. Откройте, пожалуйста, issue, чтобы контрибьюторы, у которых Windows могли это исправить.

      На Linux:

      Скрин
      image


    1. likid_ri
      22.12.2016 08:37
      +1

      Все, я открыл: #70


    1. likid_ri
      23.12.2016 00:05
      +1

      Посмотрите, пожалуйста. Нашлось решение вашей проблемы: #70


      1. JeStoneDev
        23.12.2016 03:16
        +1

        Да, это помогло. Спасибо


  1. jam31
    22.12.2016 02:50

    Игнорируя второй питон вы оставляете за бортом заметную часть разработчиков.
    You know it.


    1. likid_ri
      22.12.2016 08:25

      Да, вы правы, но ради чистоты и отсутствия зависимостей приходится идти на такую жертву.


  1. PTM
    22.12.2016 08:47
    +1

    Интересно…
    как насчет того, чтобы в текст добавить универсальный генератор речей?
    _https://dezinfo.net/images2/image/09.2009/ukot/1001.jpg


    1. likid_ri
      22.12.2016 09:05

      Да, думали над этим. Пока будет только текст, но когда иностранных контрибьюторов наберется — откажемся от текста в файлах и напишем генераторы.


  1. suharik
    27.12.2016 12:39

    А как оно с падежами? Сгенерить массив из тысяч имен и фамилий это одно, а просклонять? Иными словами, как превратить ['Иван Петрович', 'Василий Байпассович', 'Феофан Илларионович'] в ['выставка картин Иван Петровича', 'концерт Василия Байпассовича', 'арест Феофана Илларионовича'] и есть ли вообще в Питоне средства для подобных трансформаций?


    1. likid_ri
      27.12.2016 13:34

      Библиотека не подразумевает, что имена и фамилии будут использоваться в одном контексте. Ф.И для одних задач, Текст — для других. Обеспечить такого рода тонкости для одного языка — это одно, а для 16 — другое. Каждый язык имеет свои тонкости. Потому проще генерировать текст из готовых Предложений.


      1. suharik
        27.12.2016 14:17
        +1

        Хорошо, а если абстрагироваться от текста и просто просклонять, можно ли получить на выходе ['Ивана Петровича'] вместо ['Иван Петрович']? Или не те задачи она решает?


        1. likid_ri
          27.12.2016 14:22

          Вы сможете сплитить строку и, в зависимости от окончания (метод endswith()), добавлять то чего, требуют правила русского языка. Другими словами, подобного рода вещи делегированы на пользователя. Задача же библиотеки — это дать вам «Ивана Петровича», а уж как его склонять — это уже дело ваше.


          1. suharik
            27.12.2016 15:12
            +1

            Понял, спасибо. Хороший инструмент, стоит освоить.


            1. likid_ri
              27.12.2016 15:16

              Спасибо за интерес!