RSG - Random Struct Generator
Rsg is a tool that helps creating randomly generated nested data structures, from
simple built-in structures as lists, dictionaries and tuples, to custom composite class
hierarchies.
In its current state, this library is merely a toy project with a few basic utilities,
created with the intent of experimenting with python decorators and language inspection.
It was originally a function that generated random yaml files for testing purposes,
that quickly grew to become a generic subpackage that had no dependencies and could be
exported as a separate python package.
Basics
Generators are subclasses of Rsg
with special "generator" methods:
class RsgFooBar(Rsg):
@generator("foo")
def generate_foo(self):
return "foo"
@generator("bar")
def generate_bar(self):
return "bar"
This class genertes either "foo"
or "bar"
with equal chance:
rsg = RsgFooBar()
[print(next(rsg)) for _ in range(10)]
You can set a custom chance for each generator method, by setting the keyword agument
named <METHOD_NAME>_chance
in the object constructor. Probabilities are normalized
automatically, so you don't need to ensure they sum up to one.
rsg = RsgFooBar(foo_chance=0.8, bar_chance=0.2)
[print(next(rsg)) for _ in range(10)]
Chances values all default to 1.0, you can set a custom default value when decorating
a generator method. Default values are used only when a chance value is not specified
in the object constructor.
@generator("foo", default_chance=0.2)
Parameters
What if a generator method needs parameters? In this example we want to make a generator
of random powers of a number (a parameter), with the maximum exponent bounded by anohter
parameter.
To do so, just add any amount of parameters to a generator method, you can use both
positional and keyword arguments, without the need to implement a custom constructor
for your class.
class RsgPowers(Rsg):
@generator("power")
def generate_power(self, base: int, max_exp: int = 5):
exp = randint(0, max_exp)
return base**exp
To set the declared parameters, just pass them to the class constructor, as if you had
actually implemented it!
rsg = RsgPowers(base=2, max_exp=10)
[print(next(rsg)) for _ in range(10)]
In the previous example, the parameter max_exp
has a default value of 5
, this means
that you don't have to explicitly set it in the object constructor. On the other hand,
the base
parameter doesn't have a default value, and so it must be set explicitly.
Composite Structures
A generator method can be of two types:
- Leaf - Objects with no children, like the ones used so far.
- Composite - Objects with randomly generated children, which in turn can have children recursively.
Let's make a generator of lists-of-lists-of-lists-of-l...
class RsgList(Rsg):
@generator("list")
def generate_list(self, children: Iterable[Any]) -> list:
return list(children)
To make it recursive, you just need to add an argument named children
to the method.
This argument will contain an iterable of N randomly generated children objects.
The children objects are generated with the same generator used for the parent object.
Recusion is governed by the following parameters:
min_depth
- The minimum number of recursions before a leaf object can be generated.
Ignored if no composite generators are available.
max_depth
- The maximum number of recursions. Must be >= min_depth
.
min_breadth
- The minimum number of children per recursion step. If no leaf
generators are available, on the last recursion step min_breadth
is forced to 0.
max_breadth
- The maximum number of children per recursion step.
rsg = RsgList(min_depth=1, max_depth=3, min_breadth=2, max_breadth=4)
print(next(rsg))
Mixin
You can combine any Rsg
by simply inheriting from them:
class RsgMixed(RsgFooBar, RsgList, RsgPowers):
pass
You can still set all parameters of the base classes, remember to set the required ones,
or otherwise you will get an AttributeError
!
rsg = RsgMixed(
base=3,
foo_chance=0.1,
bar_chance=0.1,
power_chance=0.4,
list_chance=0.4,
min_depth=2,
max_depth=3,
min_breadth=2,
max_breadth=4,
)
print(next(rsg))
Built-in Generators
Rsg provides generators for the following built-in types:
int
- rsg.core.RsgInt
generates uniform integer values.
float
- rsg.core.RsgFloat
generates uniform float values.
str
- rsg.core.RsgStr
generates random strings containing a uniform random number
of letters, digits and punctuations.
list
- rsg.core.RsgList
generates nested lists.
tuple
- rsg.core.RsgTuple
generates nested tuples.
dict
- rsg.core.RsgDict
generates nested dicts with valid python identifiers
as keys.
In addition, rsg.core.RsgBase
combines all previous generators into one:
from rsg.core import RsgBase
rsg = RsgBase(min_depth=1, max_depth=4, min_breadth=1, max_breadth=3)
print(next(rsg))