capper 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {capper-0.2.0 → capper-0.4.0}/PKG-INFO +37 -10
- {capper-0.2.0 → capper-0.4.0}/README.md +31 -8
- {capper-0.2.0 → capper-0.4.0}/capper/__init__.py +20 -1
- capper-0.4.0/capper/barcode.py +15 -0
- capper-0.4.0/capper/base.py +127 -0
- {capper-0.2.0 → capper-0.4.0}/capper/cli.py +15 -20
- capper-0.4.0/capper/color.py +10 -0
- capper-0.4.0/capper/file.py +21 -0
- capper-0.4.0/capper/misc.py +9 -0
- capper-0.4.0/capper/registry.py +26 -0
- {capper-0.2.0 → capper-0.4.0}/capper/strategies.py +16 -38
- capper-0.4.0/capper/tests/benchmark_core.py +56 -0
- capper-0.4.0/capper/tests/test_cli.py +78 -0
- capper-0.4.0/capper/tests/test_edge_cases.py +141 -0
- capper-0.4.0/capper/tests/test_thread_safety.py +102 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/test_types.py +88 -2
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/PKG-INFO +37 -10
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/SOURCES.txt +9 -0
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/requires.txt +5 -0
- {capper-0.2.0 → capper-0.4.0}/pyproject.toml +18 -4
- capper-0.2.0/capper/base.py +0 -71
- {capper-0.2.0 → capper-0.4.0}/capper/commerce.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/date_time.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/examples/user_factory.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/finance.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/geo.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/internet.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/person.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/phone.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/__init__.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/conftest.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/test_docs_examples.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/test_hypothesis_strategies.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/tests/test_polyfactory_integration.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper/text.py +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/dependency_links.txt +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/entry_points.txt +0 -0
- {capper-0.2.0 → capper-0.4.0}/capper.egg-info/top_level.txt +0 -0
- {capper-0.2.0 → capper-0.4.0}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Semantic, typed wrappers for Faker with automatic Polyfactory integration
|
|
5
5
|
Author-email: Odos Matthews <odosmatthews@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://github.com/eddiethedean/capper#readme
|
|
7
7
|
Project-URL: Repository, https://github.com/eddiethedean/capper
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
Requires-Dist: Faker>=20.0
|
|
11
11
|
Requires-Dist: Polyfactory>=2.0
|
|
@@ -15,18 +15,22 @@ Provides-Extra: hypothesis
|
|
|
15
15
|
Requires-Dist: hypothesis>=6.0; extra == "hypothesis"
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
18
19
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
19
20
|
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
20
21
|
Requires-Dist: pydantic>=2.0; extra == "dev"
|
|
21
22
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
22
23
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
23
24
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: mkdocs>=1.5; extra == "docs"
|
|
27
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
24
28
|
|
|
25
29
|
# Capper
|
|
26
30
|
|
|
27
31
|
[](https://pypi.org/project/capper/)
|
|
28
|
-
[](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
|
|
32
|
+
[](https://pypi.org/project/capper/)
|
|
33
|
+
[)](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
|
|
30
34
|
[](https://docs.astral.sh/ruff/)
|
|
31
35
|
[](https://mypy-lang.org/)
|
|
32
36
|
|
|
@@ -34,11 +38,21 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
34
38
|
|
|
35
39
|
**Source:** [github.com/eddiethedean/capper](https://github.com/eddiethedean/capper)
|
|
36
40
|
|
|
41
|
+
## CI pipeline
|
|
42
|
+
|
|
43
|
+
The `ci.yml` workflow runs on pushes and PRs to `main` and includes:
|
|
44
|
+
|
|
45
|
+
- **Linting:** `ruff check .` and `ruff format --check .`
|
|
46
|
+
- **Type checking:** `mypy capper`
|
|
47
|
+
- **Tests:** `pytest -n auto capper/tests -v -m "not benchmark" --cov=capper --cov-report=term-missing --cov-fail-under=98`
|
|
48
|
+
- **Docs:** `mkdocs build --strict`
|
|
49
|
+
|
|
37
50
|
## Why Capper?
|
|
38
51
|
|
|
39
52
|
- **Zero config** — Import a type; Polyfactory uses the right Faker provider. No manual registration.
|
|
40
53
|
- **Typed** — Use `Name`, `Email`, `PhoneNumber`, etc. in your models for clear intent and IDE support.
|
|
41
54
|
- **Multi-backend** — Works with Pydantic, dataclasses, attrs, and other [Polyfactory-supported](https://polyfactory.litestar.dev/) model types.
|
|
55
|
+
- **Thread-safe** — Per-thread Faker via a proxy; seeding and locales are isolated per thread, so concurrent tests are safe.
|
|
42
56
|
- **Optional Pydantic** — Install `capper` alone for dataclasses/attrs; add `capper[pydantic]` when you use Pydantic models.
|
|
43
57
|
|
|
44
58
|
## Install
|
|
@@ -47,7 +61,7 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
47
61
|
pip install capper
|
|
48
62
|
```
|
|
49
63
|
|
|
50
|
-
Requires **Python 3.
|
|
64
|
+
Requires **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
|
|
51
65
|
|
|
52
66
|
- **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
|
|
53
67
|
- **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
|
|
@@ -121,6 +135,10 @@ Works automatically. No extra steps. IDE autocompletion.
|
|
|
121
135
|
- **Text**: `Paragraph`, `Sentence`
|
|
122
136
|
- **Phone**: `PhoneNumber`, `CountryCallingCode`
|
|
123
137
|
- **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
|
|
138
|
+
- **File**: `FilePath`, `FileName`, `FileExtension`
|
|
139
|
+
- **Misc**: `UUID`
|
|
140
|
+
- **Color**: `HexColor`
|
|
141
|
+
- **Barcode**: `EAN13`, `EAN8`
|
|
124
142
|
|
|
125
143
|
Import from the top level: `from capper import Name, Email, Address, ...`
|
|
126
144
|
See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
|
|
@@ -142,7 +160,13 @@ Use `-n`/`--count` for the number of rows and `-s`/`--seed` for reproducible out
|
|
|
142
160
|
|
|
143
161
|
## Compatibility
|
|
144
162
|
|
|
145
|
-
Capper targets **Faker >= 20.0
|
|
163
|
+
Capper targets **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For version ranges, upgrade guidance, and the deprecation policy, see [Compatibility](docs/compatibility.md).
|
|
164
|
+
|
|
165
|
+
## What's new in 0.4.0
|
|
166
|
+
|
|
167
|
+
- **Thread safety:** Capper is now thread-safe via a per-thread Faker proxy; `seed()` and `use_faker()` only affect the current thread.
|
|
168
|
+
- **Reliability and coverage:** Phase 9 adds a coverage gate (≥ 98% for `capper/`), targeted edge-case tests, and a lightweight performance check in CI.
|
|
169
|
+
- **Tooling and docs:** CI runs Ruff, mypy, tests (with coverage gate), and a strict MkDocs build on all supported Python versions; docs and roadmap have been updated to reflect Phase 9.
|
|
146
170
|
|
|
147
171
|
## Development
|
|
148
172
|
|
|
@@ -153,7 +177,7 @@ pytest capper/tests
|
|
|
153
177
|
|
|
154
178
|
Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
|
|
155
179
|
|
|
156
|
-
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
180
|
+
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`. CI requires coverage ≥ 98% for the `capper/` package (`--cov-fail-under=98`).
|
|
157
181
|
|
|
158
182
|
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
159
183
|
|
|
@@ -176,20 +200,23 @@ UserFactory.seed_random(42)
|
|
|
176
200
|
user2 = UserFactory.build() # same data as user1 if you seed the same before each
|
|
177
201
|
```
|
|
178
202
|
|
|
179
|
-
Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is created, or call `seed(42)` / `UserFactory.seed_random(42)` before each build for identical builds.
|
|
203
|
+
Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is created, or call `seed(42)` / `UserFactory.seed_random(42)` before each build for identical builds. For a custom locale (e.g. German names), use **`use_faker(Faker('de_DE'))`** so both Capper and Polyfactory use the same Faker instance; see [Reproducible data](docs/user_guides/reproducible_data.md#locales-and-custom-faker).
|
|
180
204
|
|
|
181
205
|
## Publishing
|
|
182
206
|
|
|
183
207
|
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
184
208
|
|
|
185
|
-
1.
|
|
186
|
-
2.
|
|
209
|
+
1. Update [CHANGELOG.md](CHANGELOG.md): move Unreleased entries into a new version section and date it.
|
|
210
|
+
2. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
|
|
211
|
+
3. Create a GitHub release (tag e.g. `v0.4.0`). The workflow runs tests, builds the package, and uploads to PyPI.
|
|
187
212
|
|
|
188
213
|
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
189
214
|
|
|
190
215
|
## Documentation
|
|
191
216
|
|
|
192
217
|
- **[Docs index](docs/README.md)** — overview and links to all documentation
|
|
218
|
+
- **[API reference](docs/api.md)** — generated API docs (build with `mkdocs serve`; see [Contributing](CONTRIBUTING.md))
|
|
219
|
+
- **[Contributing](CONTRIBUTING.md)** — dev setup and how to add new types
|
|
193
220
|
- **User guides** (step-by-step, with runnable examples):
|
|
194
221
|
- [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
|
|
195
222
|
- [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Capper
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/capper/)
|
|
4
|
-
[](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/capper/)
|
|
5
|
+
[)](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
|
|
6
6
|
[](https://docs.astral.sh/ruff/)
|
|
7
7
|
[](https://mypy-lang.org/)
|
|
8
8
|
|
|
@@ -10,11 +10,21 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
10
10
|
|
|
11
11
|
**Source:** [github.com/eddiethedean/capper](https://github.com/eddiethedean/capper)
|
|
12
12
|
|
|
13
|
+
## CI pipeline
|
|
14
|
+
|
|
15
|
+
The `ci.yml` workflow runs on pushes and PRs to `main` and includes:
|
|
16
|
+
|
|
17
|
+
- **Linting:** `ruff check .` and `ruff format --check .`
|
|
18
|
+
- **Type checking:** `mypy capper`
|
|
19
|
+
- **Tests:** `pytest -n auto capper/tests -v -m "not benchmark" --cov=capper --cov-report=term-missing --cov-fail-under=98`
|
|
20
|
+
- **Docs:** `mkdocs build --strict`
|
|
21
|
+
|
|
13
22
|
## Why Capper?
|
|
14
23
|
|
|
15
24
|
- **Zero config** — Import a type; Polyfactory uses the right Faker provider. No manual registration.
|
|
16
25
|
- **Typed** — Use `Name`, `Email`, `PhoneNumber`, etc. in your models for clear intent and IDE support.
|
|
17
26
|
- **Multi-backend** — Works with Pydantic, dataclasses, attrs, and other [Polyfactory-supported](https://polyfactory.litestar.dev/) model types.
|
|
27
|
+
- **Thread-safe** — Per-thread Faker via a proxy; seeding and locales are isolated per thread, so concurrent tests are safe.
|
|
18
28
|
- **Optional Pydantic** — Install `capper` alone for dataclasses/attrs; add `capper[pydantic]` when you use Pydantic models.
|
|
19
29
|
|
|
20
30
|
## Install
|
|
@@ -23,7 +33,7 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
23
33
|
pip install capper
|
|
24
34
|
```
|
|
25
35
|
|
|
26
|
-
Requires **Python 3.
|
|
36
|
+
Requires **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
|
|
27
37
|
|
|
28
38
|
- **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
|
|
29
39
|
- **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
|
|
@@ -97,6 +107,10 @@ Works automatically. No extra steps. IDE autocompletion.
|
|
|
97
107
|
- **Text**: `Paragraph`, `Sentence`
|
|
98
108
|
- **Phone**: `PhoneNumber`, `CountryCallingCode`
|
|
99
109
|
- **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
|
|
110
|
+
- **File**: `FilePath`, `FileName`, `FileExtension`
|
|
111
|
+
- **Misc**: `UUID`
|
|
112
|
+
- **Color**: `HexColor`
|
|
113
|
+
- **Barcode**: `EAN13`, `EAN8`
|
|
100
114
|
|
|
101
115
|
Import from the top level: `from capper import Name, Email, Address, ...`
|
|
102
116
|
See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
|
|
@@ -118,7 +132,13 @@ Use `-n`/`--count` for the number of rows and `-s`/`--seed` for reproducible out
|
|
|
118
132
|
|
|
119
133
|
## Compatibility
|
|
120
134
|
|
|
121
|
-
Capper targets **Faker >= 20.0
|
|
135
|
+
Capper targets **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For version ranges, upgrade guidance, and the deprecation policy, see [Compatibility](docs/compatibility.md).
|
|
136
|
+
|
|
137
|
+
## What's new in 0.4.0
|
|
138
|
+
|
|
139
|
+
- **Thread safety:** Capper is now thread-safe via a per-thread Faker proxy; `seed()` and `use_faker()` only affect the current thread.
|
|
140
|
+
- **Reliability and coverage:** Phase 9 adds a coverage gate (≥ 98% for `capper/`), targeted edge-case tests, and a lightweight performance check in CI.
|
|
141
|
+
- **Tooling and docs:** CI runs Ruff, mypy, tests (with coverage gate), and a strict MkDocs build on all supported Python versions; docs and roadmap have been updated to reflect Phase 9.
|
|
122
142
|
|
|
123
143
|
## Development
|
|
124
144
|
|
|
@@ -129,7 +149,7 @@ pytest capper/tests
|
|
|
129
149
|
|
|
130
150
|
Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
|
|
131
151
|
|
|
132
|
-
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
152
|
+
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`. CI requires coverage ≥ 98% for the `capper/` package (`--cov-fail-under=98`).
|
|
133
153
|
|
|
134
154
|
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
135
155
|
|
|
@@ -152,20 +172,23 @@ UserFactory.seed_random(42)
|
|
|
152
172
|
user2 = UserFactory.build() # same data as user1 if you seed the same before each
|
|
153
173
|
```
|
|
154
174
|
|
|
155
|
-
Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is created, or call `seed(42)` / `UserFactory.seed_random(42)` before each build for identical builds.
|
|
175
|
+
Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is created, or call `seed(42)` / `UserFactory.seed_random(42)` before each build for identical builds. For a custom locale (e.g. German names), use **`use_faker(Faker('de_DE'))`** so both Capper and Polyfactory use the same Faker instance; see [Reproducible data](docs/user_guides/reproducible_data.md#locales-and-custom-faker).
|
|
156
176
|
|
|
157
177
|
## Publishing
|
|
158
178
|
|
|
159
179
|
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
160
180
|
|
|
161
|
-
1.
|
|
162
|
-
2.
|
|
181
|
+
1. Update [CHANGELOG.md](CHANGELOG.md): move Unreleased entries into a new version section and date it.
|
|
182
|
+
2. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
|
|
183
|
+
3. Create a GitHub release (tag e.g. `v0.4.0`). The workflow runs tests, builds the package, and uploads to PyPI.
|
|
163
184
|
|
|
164
185
|
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
165
186
|
|
|
166
187
|
## Documentation
|
|
167
188
|
|
|
168
189
|
- **[Docs index](docs/README.md)** — overview and links to all documentation
|
|
190
|
+
- **[API reference](docs/api.md)** — generated API docs (build with `mkdocs serve`; see [Contributing](CONTRIBUTING.md))
|
|
191
|
+
- **[Contributing](CONTRIBUTING.md)** — dev setup and how to add new types
|
|
169
192
|
- **User guides** (step-by-step, with runnable examples):
|
|
170
193
|
- [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
|
|
171
194
|
- [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
|
|
@@ -6,12 +6,23 @@ With Hypothesis installed (pip install capper[hypothesis]), import capper.strate
|
|
|
6
6
|
and use st.from_type(Name) for property-based tests.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
try:
|
|
10
|
+
from importlib.metadata import version as _version
|
|
11
|
+
|
|
12
|
+
__version__ = _version("capper")
|
|
13
|
+
except Exception: # Package not installed (e.g. dev tree) or metadata missing
|
|
14
|
+
__version__ = "0.4.0"
|
|
15
|
+
|
|
16
|
+
from .barcode import EAN8, EAN13
|
|
17
|
+
from .base import FakerType, faker, seed, use_faker
|
|
18
|
+
from .color import HexColor
|
|
10
19
|
from .commerce import Company, Currency, Price, Product
|
|
11
20
|
from .date_time import Date, DateTime, Time
|
|
21
|
+
from .file import FileExtension, FileName, FilePath
|
|
12
22
|
from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
|
|
13
23
|
from .geo import Address, City, Country
|
|
14
24
|
from .internet import IP, URL, Email, UserName
|
|
25
|
+
from .misc import UUID
|
|
15
26
|
from .person import FirstName, Job, LastName, Name
|
|
16
27
|
from .phone import CountryCallingCode, PhoneNumber
|
|
17
28
|
from .text import Paragraph, Sentence
|
|
@@ -28,9 +39,15 @@ __all__ = [
|
|
|
28
39
|
"Currency",
|
|
29
40
|
"Date",
|
|
30
41
|
"DateTime",
|
|
42
|
+
"EAN13",
|
|
43
|
+
"EAN8",
|
|
31
44
|
"Email",
|
|
32
45
|
"FakerType",
|
|
46
|
+
"FileExtension",
|
|
47
|
+
"FileName",
|
|
48
|
+
"FilePath",
|
|
33
49
|
"FirstName",
|
|
50
|
+
"HexColor",
|
|
34
51
|
"IP",
|
|
35
52
|
"Job",
|
|
36
53
|
"LastName",
|
|
@@ -42,7 +59,9 @@ __all__ = [
|
|
|
42
59
|
"Sentence",
|
|
43
60
|
"Time",
|
|
44
61
|
"URL",
|
|
62
|
+
"UUID",
|
|
45
63
|
"UserName",
|
|
46
64
|
"faker",
|
|
47
65
|
"seed",
|
|
66
|
+
"use_faker",
|
|
48
67
|
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Barcode-related semantic Faker types."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EAN13(FakerType):
|
|
7
|
+
"""EAN-13 barcode. Uses Faker provider ``ean13``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "ean13"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EAN8(FakerType):
|
|
13
|
+
"""EAN-8 barcode. Uses Faker provider ``ean8``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "ean8"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""FakerType base class and per-thread Faker proxy with automatic Polyfactory registration.
|
|
2
|
+
|
|
3
|
+
The module-level ``faker`` is a proxy to a per-thread Faker instance. Each thread has its own
|
|
4
|
+
Faker; ``seed(n)`` and ``use_faker(instance)`` only affect the current thread. Polyfactory's
|
|
5
|
+
BaseFactory.__faker__ is set to the same proxy, so one seed per thread controls both capper
|
|
6
|
+
types and built-in types. Thread-safe for concurrent use from multiple threads.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
|
|
12
|
+
from faker import Faker
|
|
13
|
+
from polyfactory.factories.base import BaseFactory
|
|
14
|
+
|
|
15
|
+
_faker_local = threading.local()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_faker() -> Faker:
|
|
19
|
+
"""Return the current thread's Faker instance, creating one if needed."""
|
|
20
|
+
try:
|
|
21
|
+
instance = _faker_local.current
|
|
22
|
+
except AttributeError:
|
|
23
|
+
instance = None
|
|
24
|
+
if instance is None:
|
|
25
|
+
instance = Faker()
|
|
26
|
+
_faker_local.current = instance
|
|
27
|
+
return cast(Faker, instance)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _FakerProxy:
|
|
31
|
+
"""Proxy that forwards all attribute access to the current thread's Faker."""
|
|
32
|
+
|
|
33
|
+
__slots__ = ()
|
|
34
|
+
|
|
35
|
+
def __getattr__(self, name: str) -> Any:
|
|
36
|
+
return getattr(_get_faker(), name)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Single process-wide proxy; each thread gets its own Faker via _get_faker().
|
|
40
|
+
faker: Faker = _FakerProxy() # type: ignore[assignment]
|
|
41
|
+
BaseFactory.__faker__ = faker
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def seed(seed_value: int) -> None:
|
|
45
|
+
"""Seed the current thread's Faker instance for reproducible data.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
seed_value: Integer seed; same value produces the same sequence of values.
|
|
49
|
+
"""
|
|
50
|
+
_get_faker().seed_instance(seed_value)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def use_faker(instance: Faker | None) -> None:
|
|
54
|
+
"""Use a custom Faker instance for the current thread only.
|
|
55
|
+
|
|
56
|
+
Sets the Faker used by Capper and Polyfactory for this thread (e.g. a
|
|
57
|
+
locale-specific Faker). Other threads are unaffected. Call before building
|
|
58
|
+
any models in this thread. Pass None to reset this thread to a new default
|
|
59
|
+
Faker (e.g. after temporarily using a custom instance).
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
instance: The Faker instance to use (e.g. Faker('de_DE')), or None to reset.
|
|
63
|
+
"""
|
|
64
|
+
if instance is None or instance is faker:
|
|
65
|
+
try:
|
|
66
|
+
del _faker_local.current
|
|
67
|
+
except AttributeError:
|
|
68
|
+
pass
|
|
69
|
+
else:
|
|
70
|
+
_faker_local.current = instance
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _install_pydantic_schema() -> None:
|
|
74
|
+
"""If Pydantic is available, attach __get_pydantic_core_schema__ to FakerType."""
|
|
75
|
+
try:
|
|
76
|
+
from pydantic import GetCoreSchemaHandler
|
|
77
|
+
from pydantic_core import CoreSchema, core_schema
|
|
78
|
+
except ImportError:
|
|
79
|
+
return # pragma: no cover — pydantic not installed
|
|
80
|
+
|
|
81
|
+
def __get_pydantic_core_schema__(
|
|
82
|
+
cls, source_type: Any, handler: GetCoreSchemaHandler
|
|
83
|
+
) -> CoreSchema:
|
|
84
|
+
"""Validate as str then coerce to the FakerType subclass."""
|
|
85
|
+
return core_schema.no_info_after_validator_function(cls, handler(str))
|
|
86
|
+
|
|
87
|
+
FakerType.__get_pydantic_core_schema__ = classmethod(__get_pydantic_core_schema__) # type: ignore[attr-defined]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class FakerType(str):
|
|
91
|
+
"""Base class for semantic Faker types; subclasses auto-register with Polyfactory.
|
|
92
|
+
|
|
93
|
+
Subclasses must set a non-empty ``faker_provider`` (the Faker method name).
|
|
94
|
+
Optional ``faker_kwargs`` is a dict of keyword arguments passed to that provider
|
|
95
|
+
(e.g. ``faker_kwargs = {"nb_words": 10}`` for ``sentence``).
|
|
96
|
+
When Hypothesis is installed, use ``st.from_type(YourFakerType)`` for property-based tests.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
faker_provider: str = ""
|
|
100
|
+
|
|
101
|
+
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
102
|
+
super().__init_subclass__(**kwargs)
|
|
103
|
+
provider = getattr(cls, "faker_provider", None)
|
|
104
|
+
if provider:
|
|
105
|
+
_register(cls, provider)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_install_pydantic_schema()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _register(cls: type[FakerType], provider_name: str) -> None:
|
|
112
|
+
"""Register a FakerType subclass with Polyfactory so factories can generate values."""
|
|
113
|
+
if not hasattr(faker, provider_name):
|
|
114
|
+
raise AttributeError(
|
|
115
|
+
f"Faker has no provider {provider_name!r} (used by {cls.__name__}). "
|
|
116
|
+
"Check faker_provider on the type."
|
|
117
|
+
)
|
|
118
|
+
provider_fn = getattr(faker, provider_name)
|
|
119
|
+
if not callable(provider_fn):
|
|
120
|
+
raise TypeError(f"Faker.{provider_name} is not callable (used by {cls.__name__}).")
|
|
121
|
+
provider_kwargs = dict(getattr(cls, "faker_kwargs", None) or {})
|
|
122
|
+
|
|
123
|
+
def _provide() -> str:
|
|
124
|
+
value = getattr(faker, provider_name)(**provider_kwargs)
|
|
125
|
+
return str(value)
|
|
126
|
+
|
|
127
|
+
BaseFactory.add_provider(cls, _provide)
|
|
@@ -2,29 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import sys
|
|
5
|
-
from typing import Type
|
|
5
|
+
from typing import Mapping, Sequence, Type
|
|
6
6
|
|
|
7
7
|
import capper
|
|
8
8
|
|
|
9
9
|
from .base import FakerType, faker, seed
|
|
10
|
+
from .registry import build_type_registry
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
def _type_registry() ->
|
|
13
|
+
def _type_registry() -> Mapping[str, Type[FakerType]]:
|
|
13
14
|
"""Map type name -> FakerType subclass (only types with faker_provider)."""
|
|
14
|
-
|
|
15
|
-
for name in getattr(capper, "__all__", []):
|
|
16
|
-
if name in ("FakerType", "faker", "seed"):
|
|
17
|
-
continue
|
|
18
|
-
obj = getattr(capper, name, None)
|
|
19
|
-
provider = getattr(obj, "faker_provider", None)
|
|
20
|
-
if (
|
|
21
|
-
isinstance(obj, type)
|
|
22
|
-
and issubclass(obj, FakerType)
|
|
23
|
-
and isinstance(provider, str)
|
|
24
|
-
and len(provider) > 0
|
|
25
|
-
):
|
|
26
|
-
registry[name] = obj
|
|
27
|
-
return registry
|
|
15
|
+
return build_type_registry(capper)
|
|
28
16
|
|
|
29
17
|
|
|
30
18
|
def _generate_one(typ: Type[FakerType]) -> str:
|
|
@@ -35,6 +23,15 @@ def _generate_one(typ: Type[FakerType]) -> str:
|
|
|
35
23
|
return str(value)
|
|
36
24
|
|
|
37
25
|
|
|
26
|
+
def _generate_rows(types: Sequence[Type[FakerType]], count: int) -> list[str]:
|
|
27
|
+
"""Generate tab-separated rows for the given types."""
|
|
28
|
+
rows: list[str] = []
|
|
29
|
+
for _ in range(max(0, count)):
|
|
30
|
+
row = [_generate_one(t) for t in types]
|
|
31
|
+
rows.append("\t".join(row))
|
|
32
|
+
return rows
|
|
33
|
+
|
|
34
|
+
|
|
38
35
|
def cmd_generate(args: argparse.Namespace) -> int:
|
|
39
36
|
"""Run generate subcommand."""
|
|
40
37
|
registry = _type_registry()
|
|
@@ -49,10 +46,8 @@ def cmd_generate(args: argparse.Namespace) -> int:
|
|
|
49
46
|
if args.seed is not None:
|
|
50
47
|
seed(args.seed)
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
row = [_generate_one(t) for t in types]
|
|
55
|
-
print("\t".join(row))
|
|
49
|
+
for line in _generate_rows(types, args.count):
|
|
50
|
+
print(line)
|
|
56
51
|
return 0
|
|
57
52
|
|
|
58
53
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Color-related semantic Faker types."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HexColor(FakerType):
|
|
7
|
+
"""Hex color string (e.g. #ff5500). Uses Faker provider ``color`` with hex format."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "color"
|
|
10
|
+
faker_kwargs = {"color_format": "hex"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""File-related semantic Faker types (path, name, extension)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FilePath(FakerType):
|
|
7
|
+
"""File path. Uses Faker provider ``file_path``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "file_path"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileName(FakerType):
|
|
13
|
+
"""File name with extension. Uses Faker provider ``file_name``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "file_name"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileExtension(FakerType):
|
|
19
|
+
"""File extension. Uses Faker provider ``file_extension``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "file_extension"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
from typing import Dict, Type
|
|
5
|
+
|
|
6
|
+
from .base import FakerType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_type_registry(module: ModuleType) -> dict[str, Type[FakerType]]:
|
|
10
|
+
"""Return a mapping of public name -> FakerType subclass for the given module.
|
|
11
|
+
|
|
12
|
+
Only names exported via ``module.__all__`` and backed by FakerType subclasses
|
|
13
|
+
with a non-empty ``faker_provider`` are included.
|
|
14
|
+
"""
|
|
15
|
+
registry: Dict[str, Type[FakerType]] = {}
|
|
16
|
+
for name in getattr(module, "__all__", []):
|
|
17
|
+
obj = getattr(module, name, None)
|
|
18
|
+
provider = getattr(obj, "faker_provider", None)
|
|
19
|
+
if (
|
|
20
|
+
isinstance(obj, type)
|
|
21
|
+
and issubclass(obj, FakerType)
|
|
22
|
+
and isinstance(provider, str)
|
|
23
|
+
and len(provider) > 0
|
|
24
|
+
):
|
|
25
|
+
registry[name] = obj
|
|
26
|
+
return registry
|
|
@@ -4,50 +4,21 @@ Use ``st.from_type(Name)`` after importing capper and capper.strategies, or call
|
|
|
4
4
|
``strategies.for_type(Name)`` to get a strategy that generates instances of that type.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Type, TypeVar, cast
|
|
8
10
|
|
|
9
11
|
from hypothesis import strategies as st
|
|
10
12
|
|
|
13
|
+
import capper
|
|
14
|
+
|
|
11
15
|
from .base import FakerType, faker
|
|
12
|
-
from .
|
|
13
|
-
from .date_time import Date, DateTime, Time
|
|
14
|
-
from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
|
|
15
|
-
from .geo import Address, City, Country
|
|
16
|
-
from .internet import IP, URL, Email, UserName
|
|
17
|
-
from .person import FirstName, Job, LastName, Name
|
|
18
|
-
from .phone import CountryCallingCode, PhoneNumber
|
|
19
|
-
from .text import Paragraph, Sentence
|
|
16
|
+
from .registry import build_type_registry
|
|
20
17
|
|
|
21
18
|
T = TypeVar("T", bound=FakerType)
|
|
22
19
|
|
|
23
|
-
# All built-in FakerType subclasses for registration
|
|
24
|
-
_BUILTIN_TYPES: tuple[type[FakerType], ...] = (
|
|
25
|
-
Address,
|
|
26
|
-
City,
|
|
27
|
-
Company,
|
|
28
|
-
Country,
|
|
29
|
-
CountryCallingCode,
|
|
30
|
-
CreditCardExpiry,
|
|
31
|
-
CreditCardNumber,
|
|
32
|
-
CreditCardProvider,
|
|
33
|
-
Currency,
|
|
34
|
-
Date,
|
|
35
|
-
DateTime,
|
|
36
|
-
Email,
|
|
37
|
-
FirstName,
|
|
38
|
-
IP,
|
|
39
|
-
Job,
|
|
40
|
-
LastName,
|
|
41
|
-
Name,
|
|
42
|
-
Paragraph,
|
|
43
|
-
PhoneNumber,
|
|
44
|
-
Price,
|
|
45
|
-
Product,
|
|
46
|
-
Sentence,
|
|
47
|
-
Time,
|
|
48
|
-
URL,
|
|
49
|
-
UserName,
|
|
50
|
-
)
|
|
20
|
+
# All built-in FakerType subclasses for registration, discovered from the public API.
|
|
21
|
+
_BUILTIN_TYPES: tuple[type[FakerType], ...] = tuple(build_type_registry(capper).values())
|
|
51
22
|
|
|
52
23
|
|
|
53
24
|
def for_type(cls: Type[T]) -> st.SearchStrategy[T]:
|
|
@@ -55,6 +26,13 @@ def for_type(cls: Type[T]) -> st.SearchStrategy[T]:
|
|
|
55
26
|
provider = getattr(cls, "faker_provider", None)
|
|
56
27
|
if not provider:
|
|
57
28
|
raise ValueError(f"{cls.__name__} has no faker_provider")
|
|
29
|
+
if not hasattr(faker, provider):
|
|
30
|
+
raise AttributeError(
|
|
31
|
+
f"Faker has no provider {provider!r} (used by {cls.__name__}). "
|
|
32
|
+
"Check faker_provider on the type."
|
|
33
|
+
)
|
|
34
|
+
if not callable(getattr(faker, provider)):
|
|
35
|
+
raise TypeError(f"Faker.{provider} is not callable (used by {cls.__name__}).")
|
|
58
36
|
kwargs = getattr(cls, "faker_kwargs", None) or {}
|
|
59
37
|
|
|
60
38
|
@st.composite
|
|
@@ -64,7 +42,7 @@ def for_type(cls: Type[T]) -> st.SearchStrategy[T]:
|
|
|
64
42
|
value = getattr(faker, provider)(**kwargs)
|
|
65
43
|
return cls(str(value))
|
|
66
44
|
|
|
67
|
-
return _draw()
|
|
45
|
+
return cast(st.SearchStrategy[T], _draw())
|
|
68
46
|
|
|
69
47
|
|
|
70
48
|
def _register_strategies() -> None:
|