crashbytes-testkit 1.0.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.
@@ -0,0 +1,5 @@
1
+ """crashbytes-testkit — Test data builders for Python."""
2
+
3
+ from crashbytes_testkit._core import Builder, Fixture
4
+
5
+ __all__ = ["Builder", "Fixture"]
@@ -0,0 +1,146 @@
1
+ """Test data builders — auto-generate dataclass instances from type hints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+ import random
7
+ import string
8
+ import uuid
9
+ from datetime import date, datetime
10
+ from typing import Any, TypeVar, get_type_hints
11
+
12
+ T = TypeVar("T")
13
+
14
+ _counter = 0
15
+
16
+
17
+ def _next_id() -> int:
18
+ global _counter # noqa: PLW0603
19
+ _counter += 1
20
+ return _counter
21
+
22
+
23
+ def _random_string(length: int = 8) -> str:
24
+ return "".join(random.choices(string.ascii_lowercase, k=length))
25
+
26
+
27
+ def _generate_value(type_hint: Any, field_name: str = "") -> Any:
28
+ """Generate a plausible value for a given type hint."""
29
+ origin = getattr(type_hint, "__origin__", None)
30
+ args = getattr(type_hint, "__args__", ())
31
+
32
+ # Handle Union types (Optional[X], X | Y, etc.)
33
+ import types
34
+ import typing
35
+ if origin is typing.Union or isinstance(type_hint, types.UnionType):
36
+ type_args = args if args else getattr(type_hint, "__args__", ())
37
+ non_none = [a for a in type_args if a is not type(None)]
38
+ if non_none:
39
+ return _generate_value(non_none[0], field_name)
40
+ return None
41
+
42
+ if type_hint is str:
43
+ return f"{field_name}_{_random_string()}" if field_name else _random_string()
44
+ if type_hint is int:
45
+ return _next_id()
46
+ if type_hint is float:
47
+ return round(random.uniform(0.0, 100.0), 2)
48
+ if type_hint is bool:
49
+ return random.choice([True, False])
50
+ if type_hint is bytes:
51
+ return _random_string().encode()
52
+ if type_hint is datetime:
53
+ return datetime(2024, 1, 1, 12, 0, 0)
54
+ if type_hint is date:
55
+ return date(2024, 1, 1)
56
+
57
+ # list[X]
58
+ if origin is list:
59
+ inner = args[0] if args else str
60
+ return [_generate_value(inner, field_name) for _ in range(2)]
61
+
62
+ # dict[K, V]
63
+ if origin is dict:
64
+ k_type = args[0] if args else str
65
+ v_type = args[1] if len(args) > 1 else str # type: ignore[misc]
66
+ return {_generate_value(k_type): _generate_value(v_type) for _ in range(2)}
67
+
68
+ # set[X]
69
+ if origin is set:
70
+ inner = args[0] if args else str
71
+ return {_generate_value(inner, field_name) for _ in range(2)}
72
+
73
+ # tuple[X, ...]
74
+ if origin is tuple:
75
+ if args:
76
+ return tuple(_generate_value(a, field_name) for a in args if a is not Ellipsis)
77
+ return ()
78
+
79
+ # UUID
80
+ if type_hint is uuid.UUID:
81
+ return uuid.uuid4()
82
+
83
+ # Nested dataclass
84
+ if dataclasses.is_dataclass(type_hint) and isinstance(type_hint, type):
85
+ return _create_instance(type_hint)
86
+
87
+ # Fallback
88
+ return None
89
+
90
+
91
+ def _create_instance(cls: type[T], overrides: dict[str, Any] | None = None) -> T:
92
+ """Create an instance of *cls* with auto-generated values."""
93
+ hints = get_type_hints(cls)
94
+ kwargs: dict[str, Any] = {}
95
+
96
+ for field in dataclasses.fields(cls): # type: ignore[arg-type]
97
+ if overrides and field.name in overrides:
98
+ kwargs[field.name] = overrides[field.name]
99
+ elif field.default is not dataclasses.MISSING:
100
+ kwargs[field.name] = field.default
101
+ elif field.default_factory is not dataclasses.MISSING:
102
+ kwargs[field.name] = field.default_factory()
103
+ else:
104
+ type_hint = hints.get(field.name, str)
105
+ kwargs[field.name] = _generate_value(type_hint, field.name)
106
+
107
+ return cls(**kwargs)
108
+
109
+
110
+ class Fixture:
111
+ """Auto-generate test data from dataclass type hints."""
112
+
113
+ @staticmethod
114
+ def create(cls: type[T], **overrides: Any) -> T:
115
+ """Create a single instance of *cls*."""
116
+ if not dataclasses.is_dataclass(cls):
117
+ raise TypeError(f"{cls.__name__} is not a dataclass")
118
+ return _create_instance(cls, overrides or None)
119
+
120
+ @staticmethod
121
+ def create_many(cls: type[T], count: int = 3, **overrides: Any) -> list[T]:
122
+ """Create *count* instances of *cls*."""
123
+ return [Fixture.create(cls, **overrides) for _ in range(count)]
124
+
125
+
126
+ class Builder:
127
+ """Fluent builder for constructing test data."""
128
+
129
+ def __init__(self, cls: type[Any]) -> None:
130
+ if not dataclasses.is_dataclass(cls):
131
+ raise TypeError(f"{cls.__name__} is not a dataclass")
132
+ self._cls = cls
133
+ self._overrides: dict[str, Any] = {}
134
+
135
+ def with_field(self, name: str, value: Any) -> Builder:
136
+ """Set a specific field value."""
137
+ self._overrides[name] = value
138
+ return self
139
+
140
+ def build(self) -> Any:
141
+ """Build the instance."""
142
+ return _create_instance(self._cls, self._overrides)
143
+
144
+ def build_many(self, count: int = 3) -> list[Any]:
145
+ """Build *count* instances."""
146
+ return [self.build() for _ in range(count)]
File without changes
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: crashbytes-testkit
3
+ Version: 1.0.0
4
+ Summary: Test data builders for Python — auto-generate dataclass instances from type hints.
5
+ Project-URL: Homepage, https://github.com/CrashBytes/crashbytes-testkit
6
+ Project-URL: Repository, https://github.com/CrashBytes/crashbytes-testkit
7
+ Project-URL: Issues, https://github.com/CrashBytes/crashbytes-testkit/issues
8
+ Author-email: CrashBytes <crashbytes@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: builder,dataclass,fixture,test-data,testing
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy; extra == 'dev'
24
+ Requires-Dist: pytest; extra == 'dev'
25
+ Requires-Dist: pytest-cov; extra == 'dev'
26
+ Requires-Dist: ruff; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # crashbytes-testkit
30
+
31
+ Test data builders for Python — auto-generate dataclass instances from type hints.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install crashbytes-testkit
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ from dataclasses import dataclass
43
+ from crashbytes_testkit import Fixture, Builder
44
+
45
+ @dataclass
46
+ class User:
47
+ name: str
48
+ age: int
49
+ email: str
50
+
51
+ # Auto-generate from type hints
52
+ user = Fixture.create(User)
53
+ users = Fixture.create_many(User, 5)
54
+
55
+ # Override specific fields
56
+ admin = Fixture.create(User, name="Admin", age=30)
57
+
58
+ # Fluent builder
59
+ user = (
60
+ Builder(User)
61
+ .with_field("name", "Alice")
62
+ .with_field("age", 25)
63
+ .build()
64
+ )
65
+ ```
66
+
67
+ Supports: `str`, `int`, `float`, `bool`, `bytes`, `datetime`, `date`, `UUID`, `list[T]`, `dict[K,V]`, `set[T]`, `tuple`, `Optional[T]`, nested dataclasses.
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,7 @@
1
+ crashbytes_testkit/__init__.py,sha256=1d-qRcn-tNj1OFasUvXwSo9gcAglurlrpvRhdytLH6Q,149
2
+ crashbytes_testkit/_core.py,sha256=eg2qV-5xk96lch0BxKb3DhWYhg23lLVY6QpErnS_XMo,4692
3
+ crashbytes_testkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ crashbytes_testkit-1.0.0.dist-info/METADATA,sha256=21pRAJoRu8sECcESGT_L8PcMGWhwrREkhhxCKwQBFB4,2017
5
+ crashbytes_testkit-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ crashbytes_testkit-1.0.0.dist-info/licenses/LICENSE,sha256=Ic61HOO4EsyXXAGNY-D-1nG62feh_IBbipY1v_QcYcY,1067
7
+ crashbytes_testkit-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CrashBytes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.