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 |
---|---|---|---|
|
(There is no dedicated function,
but const can be used instead.)
|
|
|
|
|
||
|
a integer value |
||
|
a float value |
||
|
a string value |
||
|
a ‘bytes’ value |
||
|
a bytearray value |
||
|
a list |
||
|
randlist (argument |
a tuple |
|
|
a dict |
||
a Decimal value |
|||
a datetime value |
|||
a date value |
|||
a time value |
|||
a timedelta value |
|||
an IPv4Address value |
|||
An enumeration |
a value of the enumeration |
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] == "$"
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.