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.
Files changed (39) hide show
  1. {capper-0.2.0 → capper-0.4.0}/PKG-INFO +37 -10
  2. {capper-0.2.0 → capper-0.4.0}/README.md +31 -8
  3. {capper-0.2.0 → capper-0.4.0}/capper/__init__.py +20 -1
  4. capper-0.4.0/capper/barcode.py +15 -0
  5. capper-0.4.0/capper/base.py +127 -0
  6. {capper-0.2.0 → capper-0.4.0}/capper/cli.py +15 -20
  7. capper-0.4.0/capper/color.py +10 -0
  8. capper-0.4.0/capper/file.py +21 -0
  9. capper-0.4.0/capper/misc.py +9 -0
  10. capper-0.4.0/capper/registry.py +26 -0
  11. {capper-0.2.0 → capper-0.4.0}/capper/strategies.py +16 -38
  12. capper-0.4.0/capper/tests/benchmark_core.py +56 -0
  13. capper-0.4.0/capper/tests/test_cli.py +78 -0
  14. capper-0.4.0/capper/tests/test_edge_cases.py +141 -0
  15. capper-0.4.0/capper/tests/test_thread_safety.py +102 -0
  16. {capper-0.2.0 → capper-0.4.0}/capper/tests/test_types.py +88 -2
  17. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/PKG-INFO +37 -10
  18. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/SOURCES.txt +9 -0
  19. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/requires.txt +5 -0
  20. {capper-0.2.0 → capper-0.4.0}/pyproject.toml +18 -4
  21. capper-0.2.0/capper/base.py +0 -71
  22. {capper-0.2.0 → capper-0.4.0}/capper/commerce.py +0 -0
  23. {capper-0.2.0 → capper-0.4.0}/capper/date_time.py +0 -0
  24. {capper-0.2.0 → capper-0.4.0}/capper/examples/user_factory.py +0 -0
  25. {capper-0.2.0 → capper-0.4.0}/capper/finance.py +0 -0
  26. {capper-0.2.0 → capper-0.4.0}/capper/geo.py +0 -0
  27. {capper-0.2.0 → capper-0.4.0}/capper/internet.py +0 -0
  28. {capper-0.2.0 → capper-0.4.0}/capper/person.py +0 -0
  29. {capper-0.2.0 → capper-0.4.0}/capper/phone.py +0 -0
  30. {capper-0.2.0 → capper-0.4.0}/capper/tests/__init__.py +0 -0
  31. {capper-0.2.0 → capper-0.4.0}/capper/tests/conftest.py +0 -0
  32. {capper-0.2.0 → capper-0.4.0}/capper/tests/test_docs_examples.py +0 -0
  33. {capper-0.2.0 → capper-0.4.0}/capper/tests/test_hypothesis_strategies.py +0 -0
  34. {capper-0.2.0 → capper-0.4.0}/capper/tests/test_polyfactory_integration.py +0 -0
  35. {capper-0.2.0 → capper-0.4.0}/capper/text.py +0 -0
  36. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/dependency_links.txt +0 -0
  37. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/entry_points.txt +0 -0
  38. {capper-0.2.0 → capper-0.4.0}/capper.egg-info/top_level.txt +0 -0
  39. {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.2.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.9
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
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
28
- [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://pypi.org/project/capper/)
29
- [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/ci.yml?branch=main&label=CI)](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
32
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/capper/)
33
+ [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/ci.yml?branch=main&label=CI%20(lint%2C%20types%2C%20tests%2C%20docs))](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
30
34
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
31
35
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](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.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
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** and **Polyfactory >= 2.0**. Major Faker upgrades may change or rename provider methods; if a type fails, check [Faker's changelog](https://faker.readthedocs.io/en/stable/changelog.html) and [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) and update the provider name if needed.
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. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
186
- 2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
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
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
4
- [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://pypi.org/project/capper/)
5
- [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/ci.yml?branch=main&label=CI)](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
4
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/capper/)
5
+ [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/ci.yml?branch=main&label=CI%20(lint%2C%20types%2C%20tests%2C%20docs))](https://github.com/eddiethedean/capper/actions/workflows/ci.yml)
6
6
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
7
7
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](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.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
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** and **Polyfactory >= 2.0**. Major Faker upgrades may change or rename provider methods; if a type fails, check [Faker's changelog](https://faker.readthedocs.io/en/stable/changelog.html) and [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) and update the provider name if needed.
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. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
162
- 2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
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
- from .base import FakerType, faker, seed
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() -> dict[str, Type[FakerType]]:
13
+ def _type_registry() -> Mapping[str, Type[FakerType]]:
13
14
  """Map type name -> FakerType subclass (only types with faker_provider)."""
14
- registry: dict[str, Type[FakerType]] = {}
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
- count = max(0, args.count)
53
- for _ in range(count):
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,9 @@
1
+ """Miscellaneous semantic Faker types (e.g. UUID)."""
2
+
3
+ from .base import FakerType
4
+
5
+
6
+ class UUID(FakerType):
7
+ """UUID v4 string. Uses Faker provider ``uuid4``."""
8
+
9
+ faker_provider = "uuid4"
@@ -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 typing import Type, TypeVar
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 .commerce import Company, Currency, Price, Product
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: