autofaker 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
autofaker/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ Provides modules for creation anonymous variables to help minimize the setup/arrange phase when writing unit tests
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ from autofaker.autodata import *
8
+ from autofaker.decorators import *
9
+ from autofaker.registry import (
10
+ register_predicate,
11
+ register_type,
12
+ registry_context,
13
+ reset_registry,
14
+ )
@@ -0,0 +1,19 @@
1
+ class Attributes:
2
+ def __init__(self, instance):
3
+ self.instance = instance
4
+
5
+ def get_members(self):
6
+ return [
7
+ attr
8
+ for attr in dir(self.instance)
9
+ if not callable(getattr(self.instance, attr)) and not attr.startswith("__")
10
+ ]
11
+
12
+ def get_attribute(self, member):
13
+ return getattr(self.instance, member)
14
+
15
+ def get_typename(self, member):
16
+ return type(self.get_attribute(member)).__name__
17
+
18
+ def set_value(self, member, value):
19
+ setattr(self.instance, member, value)
autofaker/autodata.py ADDED
@@ -0,0 +1,71 @@
1
+ """
2
+ Provides classes for anonymous object creation to help minimize
3
+ the setup/arrange phase when writing unit tests
4
+ """
5
+
6
+ import typing
7
+ from typing import List
8
+
9
+ from autofaker.dataframe import PandasDataFrameGenerator
10
+ from autofaker.generator import TypeDataGenerator
11
+
12
+
13
+ class Autodata:
14
+ """
15
+ Provides anonymous object creation functions to help minimize
16
+ the setup/arrange phase when writing unit tests
17
+ """
18
+
19
+ @staticmethod
20
+ def create(t, use_fake_data: bool = False):
21
+ """
22
+ Creates an anonymous variable of the requested type
23
+
24
+ :param type t: type
25
+ The type to generate data for
26
+
27
+ :param use_fake_data: bool
28
+ Set this to True to use Faker to generate data
29
+ otherwise False to generate anonymous data
30
+ """
31
+ return TypeDataGenerator.create(t, use_fake_data=use_fake_data).generate()
32
+
33
+ @staticmethod
34
+ def create_many(t, size: int = 3, use_fake_data: bool = False) -> List[typing.Any]:
35
+ """
36
+ Creates a list of anonymous variables of the requested
37
+ type using the specified length (default 3)
38
+
39
+ :param type t: (type)
40
+ The type to generate data for
41
+
42
+ :param size: (int)
43
+ The number of items to generate (default 3)
44
+
45
+ :param use_fake_data: (bool)
46
+ Set this to True to use Faker to generate data,
47
+ otherwise False to generate anonymous data
48
+ """
49
+ items = []
50
+ for _ in range(size):
51
+ items.append(Autodata.create(t, use_fake_data=use_fake_data))
52
+ return items
53
+
54
+ @staticmethod
55
+ def create_pandas_dataframe(t, rows: int = 3, use_fake_data: bool = False):
56
+ """
57
+ Create a Pandas DataFrame containing anonymous data
58
+ with the specified number of rows (default 3)
59
+
60
+ :param type t: object
61
+ The class that represents the DataFrame.
62
+ This can be a plain old class or a @dataclass
63
+
64
+ :param type rows: int
65
+ The number of rows to generate for the DataFrame (default 3)
66
+
67
+ :param use_fake_data: bool
68
+ Set this to True to use Faker to generate data,
69
+ otherwise False to generate anonymous data
70
+ """
71
+ return PandasDataFrameGenerator(t, rows, use_fake_data=use_fake_data).generate()
autofaker/base.py ADDED
@@ -0,0 +1,7 @@
1
+ from abc import abstractmethod
2
+
3
+
4
+ class TypeDataGeneratorBase:
5
+ @abstractmethod
6
+ def generate(self):
7
+ pass
autofaker/builtins.py ADDED
@@ -0,0 +1,105 @@
1
+ import decimal
2
+ import pathlib
3
+ import random
4
+ import uuid
5
+
6
+ from autofaker.base import TypeDataGeneratorBase
7
+
8
+
9
+ class IntegerGenerator(TypeDataGeneratorBase):
10
+ def generate(self):
11
+ return random.randint(1, 10000)
12
+
13
+
14
+ class StringGenerator(TypeDataGeneratorBase):
15
+ def generate(self):
16
+ return str(uuid.uuid4())
17
+
18
+
19
+ class FloatGenerator(TypeDataGeneratorBase):
20
+ def generate(self):
21
+ return random.uniform(0, 10000)
22
+
23
+
24
+ class BooleanGenerator(TypeDataGeneratorBase):
25
+ def generate(self):
26
+ return bool(random.getrandbits(1))
27
+
28
+
29
+ class ComplexGenerator(TypeDataGeneratorBase):
30
+ def generate(self):
31
+ return complex(IntegerGenerator().generate())
32
+
33
+
34
+ class RangeGenerator(TypeDataGeneratorBase):
35
+ def generate(self):
36
+ return range(IntegerGenerator().generate())
37
+
38
+
39
+ class BytesGenerator(TypeDataGeneratorBase):
40
+ def generate(self):
41
+ return bytes(range(random.randint(1, 256)))
42
+
43
+
44
+ class ByteArrayGenerator(TypeDataGeneratorBase):
45
+ def generate(self):
46
+ return bytearray(range(random.randint(1, 256)))
47
+
48
+
49
+ class MemoryViewGenerator(TypeDataGeneratorBase):
50
+ def generate(self):
51
+ return memoryview(BytesGenerator().generate())
52
+
53
+
54
+ class TupleGenerator(TypeDataGeneratorBase):
55
+ def generate(self):
56
+ return (
57
+ IntegerGenerator().generate(),
58
+ StringGenerator().generate(),
59
+ FloatGenerator().generate(),
60
+ )
61
+
62
+
63
+ class SetGenerator(TypeDataGeneratorBase):
64
+ def generate(self):
65
+ return {
66
+ StringGenerator().generate(),
67
+ StringGenerator().generate(),
68
+ StringGenerator().generate(),
69
+ }
70
+
71
+
72
+ class FrozenSetGenerator(TypeDataGeneratorBase):
73
+ def generate(self):
74
+ return frozenset({
75
+ StringGenerator().generate(),
76
+ StringGenerator().generate(),
77
+ StringGenerator().generate(),
78
+ })
79
+
80
+
81
+ class DictGenerator(TypeDataGeneratorBase):
82
+ def generate(self):
83
+ return {
84
+ StringGenerator().generate(): IntegerGenerator().generate(),
85
+ StringGenerator().generate(): IntegerGenerator().generate(),
86
+ StringGenerator().generate(): IntegerGenerator().generate(),
87
+ }
88
+
89
+
90
+ class DecimalGenerator(TypeDataGeneratorBase):
91
+ def generate(self):
92
+ return decimal.Decimal(str(random.uniform(0, 10000)))
93
+
94
+
95
+ class UUIDGenerator(TypeDataGeneratorBase):
96
+ def generate(self):
97
+ return uuid.uuid4()
98
+
99
+
100
+ class PathGenerator(TypeDataGeneratorBase):
101
+ def generate(self):
102
+ return pathlib.Path(
103
+ StringGenerator().generate(),
104
+ StringGenerator().generate(),
105
+ )
autofaker/dataframe.py ADDED
@@ -0,0 +1,38 @@
1
+ import dataclasses
2
+
3
+ import pandas as pd
4
+
5
+ from autofaker.attributes import Attributes
6
+ from autofaker.generator import TypeDataGenerator
7
+
8
+
9
+ class PandasDataFrameGenerator:
10
+ def __init__(self, t, rows: int = 3, use_fake_data: bool = False):
11
+ self.t = t
12
+ self.data = []
13
+ for _ in range(rows):
14
+ row = TypeDataGenerator.create(t, use_fake_data=use_fake_data).generate()
15
+ self.data.append(row)
16
+
17
+ def _get_columns(self, instance):
18
+ return [
19
+ attr
20
+ for attr in dir(instance)
21
+ if (dataclasses.is_dataclass(getattr(instance, attr))
22
+ or not callable(getattr(instance, attr)))
23
+ and not attr.startswith("__")
24
+ ]
25
+
26
+ def generate(self):
27
+ if not self.data:
28
+ sample = TypeDataGenerator.create(self.t).generate()
29
+ columns = self._get_columns(sample)
30
+ return pd.DataFrame(columns=columns)
31
+ members = self._get_columns(self.data[0])
32
+ rows = []
33
+ for d in self.data:
34
+ row = []
35
+ for member in members:
36
+ row.append(Attributes(d).get_attribute(member))
37
+ rows.append(row)
38
+ return pd.DataFrame(rows, columns=members)
autofaker/dates.py ADDED
@@ -0,0 +1,51 @@
1
+ import datetime
2
+ import random
3
+
4
+ from autofaker.base import TypeDataGeneratorBase
5
+
6
+
7
+ def is_date_type(type_name) -> bool:
8
+ return type_name in ["datetime", "date", "time", "timedelta"]
9
+
10
+
11
+ class DatetimeGenerator(TypeDataGeneratorBase):
12
+ def generate(self):
13
+ year = datetime.date.today().year
14
+ return datetime.datetime(
15
+ random.randint(year - 10, year + 10),
16
+ random.randint(1, 12),
17
+ random.randint(1, 28),
18
+ random.randint(0, 23),
19
+ random.randint(0, 59),
20
+ random.randint(0, 59),
21
+ random.randint(0, 999999),
22
+ )
23
+
24
+
25
+ class DateGenerator(TypeDataGeneratorBase):
26
+ def generate(self):
27
+ year = datetime.date.today().year
28
+ return datetime.date(
29
+ random.randint(year - 10, year + 10),
30
+ random.randint(1, 12),
31
+ random.randint(1, 28),
32
+ )
33
+
34
+
35
+ class TimeGenerator(TypeDataGeneratorBase):
36
+ def generate(self):
37
+ return datetime.time(
38
+ random.randint(0, 23),
39
+ random.randint(0, 59),
40
+ random.randint(0, 59),
41
+ random.randint(0, 999999),
42
+ )
43
+
44
+
45
+ class TimedeltaGenerator(TypeDataGeneratorBase):
46
+ def generate(self):
47
+ return datetime.timedelta(
48
+ days=random.randint(0, 365),
49
+ seconds=random.randint(0, 86399),
50
+ microseconds=random.randint(0, 999999),
51
+ )
@@ -0,0 +1,209 @@
1
+ """
2
+ Provides anonymous object creation functions to help minimize
3
+ the setup/arrange phase when writing unit tests
4
+ """
5
+
6
+ import inspect
7
+ import unittest
8
+ from typing import List
9
+
10
+ from autofaker import Autodata, PandasDataFrameGenerator
11
+
12
+
13
+ def autodata(*types: object, use_fake_data: bool = False):
14
+ """
15
+ Creates anonymous variable of the requested types
16
+ and pass them as arguments to a unit test function
17
+
18
+ Example:
19
+
20
+ import unittest
21
+
22
+ from autofaker import autodata
23
+
24
+ class SampleTest(unittest.TestCase):
25
+ @autodata(str, int, float, bool)
26
+
27
+ def test_create_str_argument_using_decorator(self, text, number, decimal, boolean):
28
+ self.assertIsNotNone(text)
29
+
30
+
31
+ :param use_fake_data: bool
32
+ Set this to True to use Faker to generate data,
33
+ otherwise False to generate anonymous data
34
+
35
+ :param type types: tuple
36
+ The types to generate data.
37
+ This is optional and will use the arguments
38
+ from the function being decorated if not specified
39
+ """
40
+ if _is_bare_decoration(types):
41
+ return _make_arg_decorator((), use_fake_data=use_fake_data)(types[0])
42
+ return _make_arg_decorator(types, use_fake_data=use_fake_data)
43
+
44
+
45
+ def fakedata(*types: object):
46
+ """
47
+ Creates fake values for the variables of the requested types
48
+ and pass them as arguments to a unit test function
49
+
50
+ Example:
51
+
52
+ import unittest
53
+
54
+ from autofaker import fakedata
55
+
56
+ class SampleTest(unittest.TestCase):
57
+ @fakedata()
58
+
59
+ def test_create_fake_arguments(self, text: str, number: int, decimal: float, boolean: bool):
60
+ self.assertIsNotNone(text)
61
+
62
+
63
+ :param types: object
64
+ The types to generate data.
65
+ This is optional and will use the arguments from the function
66
+ being decorated if not specified
67
+ """
68
+ if _is_bare_decoration(types):
69
+ return _make_arg_decorator((), use_fake_data=True)(types[0])
70
+ return _make_arg_decorator(types, use_fake_data=True)
71
+
72
+
73
+ def autopandas(t: object, rows: int = 3, use_fake_data: bool = False):
74
+ """
75
+ Create a Pandas DataFrame containing anonymous data
76
+ with the specified number of rows (default 3)
77
+
78
+ :param type t: object
79
+ The class that represents the DataFrame.
80
+ This can be a plain old class or a @dataclass
81
+
82
+ :param type rows: int
83
+ The number of rows to generate for the DataFrame (default 3)
84
+
85
+ :param use_fake_data: bool
86
+ Set this to True to use Faker to generate data,
87
+ otherwise False to generate anonymous data
88
+ """
89
+
90
+ def decorator(function):
91
+ def wrapper(*args):
92
+ pdf = PandasDataFrameGenerator(
93
+ t, rows, use_fake_data=use_fake_data
94
+ ).generate()
95
+ return _invoke(function, args, lambda: [pdf])
96
+
97
+ return wrapper
98
+
99
+ return decorator
100
+
101
+
102
+ def fakepandas(t, rows: int = 3):
103
+ """
104
+ Create a Pandas DataFrame containing fake data with
105
+ the specified number of rows (default 3)
106
+
107
+ :param type t: object
108
+ The class that represents the DataFrame.
109
+ This can be a plain old class or a @dataclass
110
+
111
+ :param type rows: int
112
+ The number of rows to generate for the DataFrame (default 3)
113
+ """
114
+ return autopandas(t, rows, use_fake_data=True)
115
+
116
+
117
+ def _is_bare_decoration(types) -> bool:
118
+ """True when a decorator was applied without parentheses (``@autodata``).
119
+
120
+ In that case the decorated function itself is passed as the single
121
+ positional argument. Distinguished from a callable type argument by the
122
+ presence of ``__code__`` and not being a class.
123
+ """
124
+ return (
125
+ len(types) == 1
126
+ and callable(types[0])
127
+ and not isinstance(types[0], type)
128
+ and hasattr(types[0], "__code__")
129
+ )
130
+
131
+
132
+ def _make_arg_decorator(types, use_fake_data: bool):
133
+ """Build a decorator that injects generated arguments into a test function.
134
+
135
+ When ``types`` is empty the argument types are taken from the decorated
136
+ function's annotations; otherwise the explicit ``types`` are used.
137
+ """
138
+
139
+ def decorator(function):
140
+ def wrapper(*args):
141
+ return _invoke(
142
+ function,
143
+ args,
144
+ lambda: __create_function_args(
145
+ function, *tuple(types), use_fake_data=use_fake_data
146
+ ),
147
+ )
148
+
149
+ return wrapper
150
+
151
+ return decorator
152
+
153
+
154
+ def _invoke(function, call_args, build_args):
155
+ """Call ``function`` with generated arguments.
156
+
157
+ When the decorated function is a ``unittest.TestCase`` method, its instance
158
+ (``self``) is recovered from the wrapper's call arguments and prepended.
159
+ The test-class check runs before ``build_args`` so an invalid target raises
160
+ ``NotImplementedError`` ahead of any argument-shape error. ``build_args`` is
161
+ a thunk to defer that work until the target is known.
162
+ """
163
+ if __get_class_that_defined_method(function) is None:
164
+ function(*build_args())
165
+ return None
166
+ test_class = __get_test_class(*call_args)
167
+ function(test_class, *build_args())
168
+ return None
169
+
170
+
171
+ def __get_test_class(*args):
172
+ test_class = args[0]
173
+ if not issubclass(test_class.__class__, unittest.TestCase):
174
+ raise NotImplementedError(
175
+ "This way of creating anonymous objects are only supported from unit tests"
176
+ )
177
+ return test_class
178
+
179
+
180
+ def __get_class_that_defined_method(meth):
181
+ if inspect.isfunction(meth):
182
+ cls = getattr(
183
+ inspect.getmodule(meth),
184
+ meth.__qualname__.split(".<locals>", 1)[0].rsplit(".", 1)[0],
185
+ None,
186
+ )
187
+ if isinstance(cls, type):
188
+ return cls
189
+ return getattr(meth, "__objclass__", None) # handle special descriptor objects
190
+
191
+
192
+ def __create_function_args(function, *types, use_fake_data: bool = False) -> List:
193
+ values = []
194
+ argtypes = inspect.getfullargspec(function)
195
+ annotations = {
196
+ k: v for k, v in argtypes.annotations.items() if k != 'return'
197
+ }
198
+ args = annotations.values() if types is None or len(types) == 0 else types
199
+ for t in args:
200
+ value = Autodata.create(t, use_fake_data)
201
+ values.append(value)
202
+ pos = 1
203
+ if __get_class_that_defined_method(function) is None:
204
+ pos = 0
205
+ if len(argtypes.args) - pos != len(values):
206
+ raise ValueError(
207
+ "Missing argument annotations. Please declare the type of every argument"
208
+ )
209
+ return values
autofaker/enums.py ADDED
@@ -0,0 +1,16 @@
1
+ import random
2
+ from enum import Enum
3
+
4
+ from autofaker.base import TypeDataGeneratorBase
5
+
6
+
7
+ def is_enum(t) -> bool:
8
+ return isinstance(t, Enum.__class__)
9
+
10
+
11
+ class EnumGenerator(TypeDataGeneratorBase):
12
+ def __init__(self, enum):
13
+ self.enum = enum
14
+
15
+ def generate(self):
16
+ return random.choice(list(self.enum))
autofaker/fakes.py ADDED
@@ -0,0 +1,30 @@
1
+ from faker import Faker
2
+
3
+ from autofaker.attributes import Attributes
4
+ from autofaker.base import TypeDataGeneratorBase
5
+ from autofaker.builtins import StringGenerator
6
+
7
+ faker = Faker()
8
+
9
+
10
+ def generate(field_name: str):
11
+ try:
12
+ attributes = Attributes(faker)
13
+ func = attributes.get_attribute(field_name)
14
+ return func()
15
+ except AttributeError:
16
+ return None
17
+
18
+
19
+ class FakeStringGenerator(TypeDataGeneratorBase):
20
+ def __init__(self, field_name: str):
21
+ self.field_name = field_name
22
+
23
+ def generate(self):
24
+ fake = generate(self.field_name)
25
+ return fake if fake is not None else StringGenerator().generate()
26
+
27
+
28
+ class FakeIntegerGenerator(TypeDataGeneratorBase):
29
+ def generate(self):
30
+ return faker.random_int(min=1, max=10000)