Construct Factories (generator of random values)

In randog, factory is an object that generates values at random. The rules for generation are specified when the factory is created.

If you do not care about conditions other than type, you can create a factory by simply supplying an example value to from_example. If you want to specify the conditions in detail, create a factory using the factory constructor corresponding to the type.

>>> import randog.factory

>>> # create a factory simply
>>> factory_a = randog.factory.from_example("")
>>> generated_a = factory_a.next()
>>> assert isinstance(generated_a, str)

>>> # create a factory with conditions in detail
>>> factory_b = randog.factory.randstr(length=16)
>>> generated_b = factory_b.next()
>>> assert isinstance(generated_b, str)
>>> assert len(generated_b) == 16

Elemental types

You can create a factory that generates values of the following types:

value type

factory constructor

argument for from_example

detail

NoneType

(There is no dedicated function,
but const can be used instead.)

None

bool

randbool

True or False

int

randint

a integer value

float

randfloat

a float value

str

randstr

a string value

detail

bytes

randbytes

a ‘bytes’ value

detail

bytearray

randbytearray

a bytearray value

detail

list

randlist

a list

detail

tuple

randlist (argument type=tuple)

a tuple

detail

dict

randdict

a dict

detail

decimal.Decimal

randdecimal

a Decimal value

datetime.datetime

randdatetime

a datetime value

datetime.date

randdate

a date value

datetime.time

randtime

a time value

datetime.timedelta

randtimedelta

a timedelta value

ipaddress.IPv4Address

randipv4

an IPv4Address value

detail

An enumeration

randenum

a value of the enumeration

detail

Nullable

If you want None to be a candidate for generation, use or_none.

>>> import randog.factory

>>> factory = randog.factory.from_example("")
>>> factory_nullable = factory.or_none(0.1)

>>> # a string
>>> generated_a = factory.next()
>>> # a string or None
>>> generated_b = factory_nullable.next()

Note

If you want to get a factory that always returns None, use const instead.

Note

Normally, when an or_null factory generates a value, it first randomly determines whether to return None or generate a value and return it, and then generates a value only if it is returned. However, if the argument is specified as in or_none(..., lazy_choice=True), then when the union factory generates the value, it first generates the value using the factory and then randomly decides whether to adopt it or None.

This difference affects, for example, the use of non-random factories.

Union type

Several methods can be used to determine randomly generated values from multiple types.

Note

If you want to make it nullable, i.e., union type with None, use or_none instead.

If you use from_example, you can use Example as the argument. The following example uses -1, "", and True as examples, so generated values will be integer, string, or boolean values.

>>> from randog import Example
>>> import randog.factory

>>> factory = randog.factory.from_example(Example(-1, "", True))

>>> for _ in range(10):
...     generated = factory.next()
...     assert isinstance(generated, (int, str, bool))

If you create candidate factories, you can use union. The following example creates a factory, which chooses either randint or randbool each time randomly and returns the result of the chosen factory.

>>> import randog.factory

>>> factory = randog.factory.union(
...     randog.factory.randint(0, 10),  # integer
...     randog.factory.randbool(),  # True or False
... )

>>> for _ in range(10):
...     generated = factory.next()
...     assert isinstance(generated, (int, bool))

Note

Normally, when a union factory generates a value, it first randomly determines which factory to use, and only that factory generates the value. However, if the argument is specified as in union(..., lazy_choice=True), then when the union factory generates the value, it first generates the values using all the factories and then randomly decides which of them to use.

This difference affects, for example, the use of non-random factories.

Randomly choice

If you want a factory to randomly return one of specific values, you can use randchoice.

>>> import randog.factory

>>> factory = randog.factory.randchoice("allow", "deny")

>>> for _ in range(10):
...     generated = factory.next()
...     assert generated in ["allow", "deny"]

Note

If you want to randomly generate values of a particular enumeration type, you can also use randenum. See also: Enum factory

Constance

If you want a factory that always returns the same value, you can use const.

>>> import randog.factory

>>> # same as `factory = randog.factory.randchoice("python")`
>>> factory = randog.factory.const("python")

>>> for _ in range(10):
...     generated = factory.next()
...     assert generated == "python"

Processing output

The processing of factory output can be predefined. This can be used to change the type of output.

>>> import randog

>>> # use post_process to format the random decimal value
>>> factory = (
...     randog.factory.randdecimal(0, 50000, decimal_len=2)
...                   .post_process(lambda x: f"${x:,}")
... )

>>> # examples: '$12,345.67', '$3,153.21', '$12.90', etc.
>>> generated = factory.next()
>>> assert isinstance(generated, str)
>>> assert generated[0] == "$"

If the value to be generated is a dict and you want to process the items, you can easily code it by using post_process_items instead of post_process, as in the following example.

>>> import randog

>>> # use post_process_items to format the random decimal value '["count"]'
>>> factory = (
...     randog.factory.randdict(
...         name=randog.factory.randstr(),
...         count=randog.factory.randdecimal(0, 50000, decimal_len=2),
...     ).post_process_items(count=lambda x: f"${x:,}")
... )

>>> # examples: {'name': 'sir1w94s', 'count': '$12,345.67'}, etc.
>>> generated = factory.next()
>>> assert isinstance(generated["count"], str)
>>> assert generated["count"][0] == "$"

Note

If a non-dict value is generated, conversion by post_process_items is skipped, so it can be used in combination with or_none and union.

Custom Factory

Values of type not provided by randog can also be generated in the context of randog by using functions, iterators (include generator iterators), or custom factories. Normally, you would think that you could just use that function or iterator directly, but this method is needed to generate elements when generating dict or list in randog.

>>> import itertools
>>> import random
>>> import uuid
>>> import randog.factory

>>> # define custom factory
>>> class MailAddressFactory(randog.factory.Factory[str]):
...     def _next(self):
...         return random.randint(1, 10) * "a" + "@example.com"

>>> factory = randog.factory.from_example({
...     # use iterator (https://docs.python.org/3/library/itertools.html#itertools.count)
...     "id": itertools.count(1),
...     # use function
...     "uuid": uuid.uuid4,
...     # use function
...     "name": lambda: random.randint(1, 10) * "a",
...     # use custom factory
...     "mail": MailAddressFactory(),
... })
>>> generated = factory.next()

>>> assert isinstance(generated, dict)
>>> assert generated["id"] == 1
>>> assert isinstance(generated["uuid"], uuid.UUID)
>>> assert isinstance(generated["name"], str)
>>> assert set(generated["name"]) == {"a"}
>>> assert isinstance(generated["mail"], str)
>>> assert generated["mail"].endswith("@example.com")

Note

You can also create a factory using the factory constructor: by_callable, by_iterator

Warning

A finite iterator can be used as an example, but once the iterator terminates, the factory cannot generate any more values.

Details on how to build individual factories

Special Factory

Although any factory can be created with the custom factory, some of the most commonly used factories are provided by randog.