Generating mock data with Elizabeth: Part II

We have already published how to generate mock data with the help of a Python library — Elizabeth. The article you are reading now is the continuation of the previous one, therefore, we will not be going over the basics again. In case you missed out on the first article or you felt lazy at the time, you might want to go back to it now since this article assumes you are familiar with the library. Here we are going to speak about best practices and a number of most useful features of the library.

Note

First of all we would like to point out that Elizabeth wasn’t developed to be used with a certain database or ORM. The main problem the library solves is generating valid data. Consequently, while there are no rigid rules of working with the library, here are a few recommendations that will help you keep your testing environment in order and will avert growth of entropy within your project. Recommendations are quite simple and are fully in tune with the Python spirit (if you disagree, feel free to let us know).

Structuring

Despite the previous note that the library isn’t to be used with a certain database or ORM, the need for test data usually occurs in web-apps that perform certain operations (mostly CRUD) with a database. We have some advice on organizing test data generation for web-apps.

Functions responsible for data generation and importing it to the database should be kept close to the models, or even better as statistical methods of the model they are related to, as in the example of _bootstrap() method from the previous article. This is necessary to avoid running around files when the model structure changes and you need to add a new filed. Model Patient() from the previous article illustrates the idea:

Keep in mind that the example above is a model of a Flask-app used by SQLAlchemy. Organizing mock data generators for such apps with the use of different frameworks is done in the same way.

Creating objects

If your app requires data in one particular language, it’s preferable to use class Generic(), giving access to all class providers through a single object, rather than through multiple separate class providers. Using Generic() will allow you to get rid of several extra lines of code.

Correct:

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

Incorrect:

>>> from elizabeth import Personal, Datetime, Text, Code
>>> personal = Personal('ru')
>>> datetime = Datetime('ru')
>>> text = Text('ru')
>>> code = Code('ru')

Still correct:

>>> from elizabeth import Personal
>>> p_en = Personal('en')
>>> p_sv = Personal('sv')
>>> # …

It means that importing class providers separately makes sense only if you limit yourself to the data available through the class you imported, otherwise it’s better to use Generic().

Inserting data into database

If you need to generate data and import it into a database we strongly recommend generating data in chunks rather than 600k at once. Keep in mind the possible limitations of databases, ORM, etc. The smaller the generated data chunks are, the faster the process will go.

Good:

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

Very bad:

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

Importing images

Class Internet() boasts of several methods which generate image links (more details here). Links to images locate on remote servers would be enough, however, if you still want to have a number of random images locally, you can download images generated by the respective class Internet() methods with the help of function download_image() from model utils:

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

User providers

The library supports a vast amount of data and in most cases this would be enough. For those who want to create their own providers with more specific data. This can be done like this:

>>> 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!'

Starting with version 0.4.0 you can also add multiple providers:

>>> generic.add_providers(SomeProvider, Another)
>>> generic.some_provider.one()
1
>>> generic.another.bye()
'Bye!'

Everything is pretty easy and self-explanatory here, therefore, we will only clarify one moment — attribute name, class Meta is the name of a class through which access to methods of user-class providers is carried out. By default class name is the name of the class in the lower register.

Built-in providers

Most countries, where only one language is official, have data typical only for these particular countries. For example, CPF for Brazil (pt-br), SSN for USA (en). This kind of data can cause discomfort and meddle with the order (or at least annoy) by being present in all the objects regardless of the chosen language standard. You can see that for yourselves by looking at the example (the code won’t run):

>>> from elizabeth import Personal
>>> person = Personal('ru')
>>> person.ssn()
>>> person.cpf()

We bet everyone would agree that this does not look too good. Perfectionists, as we are, have taken care of this in a way that some specific regional provider would not bother other providers for other regions. For this reason, class providers with locally-specific data are separated into a special sub-package (elizabeth.builtins) for keeping a common class structure for all languages and their objects.

Here’s how it works:

>>> from elizabeth import Generic
>>> from elizabeth.builtins import BrazilSpecProvider
>>> generic = Generic('pt-br')
>>> generic.add_provider(BrazilProvider)
>>> generic.brazil_provider.cpf()
'696.441.186-00'

If you want to change default name of built-in provider, just change value of attribute name, class Meta of the builtin provider:

>>> BrazilSpecProvider.Meta.name = 'brasil'
>>> generic.add_provider(BrazilSpecProvider)
>>> generic.brasil.cpf()
'019.775.929-70'

Or just inherit the class and override the value of attribute name of class Meta of the provider (in our case this is BrazilSpecProvider()) :

>>> class Brasil(BrazilSpecProvider):
...
... class Meta:
... name = "brasil"
...
>>> generic.add_provider(Brasil)
>>> generic.brasil.cnpj()
'55.806.487/7994-45'

Generally, you don’t need to add built-it classes to the object Generic(). It was done in the example with the single purpose of demonstrating in which cases you should add a built-in class provider to the object Generic(). You can use it directly, as shown below:

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

Which type of data do you usually need in your work? What is the library missing? We will be very happy to hear your suggestions and comments.

Useful links

Link to the first part of this article.

Link to the project:

Also don’t forget to subscribe to our blog.

That’s all for now. Good luck with testing and may the force be with you.