capper 0.1.1__tar.gz → 0.3.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 (34) hide show
  1. {capper-0.1.1/capper.egg-info → capper-0.3.0}/PKG-INFO +39 -11
  2. capper-0.1.1/PKG-INFO → capper-0.3.0/README.md +29 -28
  3. {capper-0.1.1 → capper-0.3.0}/capper/__init__.py +23 -2
  4. capper-0.3.0/capper/barcode.py +15 -0
  5. {capper-0.1.1 → capper-0.3.0}/capper/base.py +33 -5
  6. capper-0.3.0/capper/cli.py +79 -0
  7. capper-0.3.0/capper/color.py +10 -0
  8. capper-0.3.0/capper/file.py +21 -0
  9. capper-0.3.0/capper/misc.py +9 -0
  10. capper-0.3.0/capper/strategies.py +100 -0
  11. capper-0.3.0/capper/tests/test_cli.py +41 -0
  12. capper-0.3.0/capper/tests/test_hypothesis_strategies.py +34 -0
  13. {capper-0.1.1 → capper-0.3.0}/capper/tests/test_polyfactory_integration.py +8 -6
  14. {capper-0.1.1 → capper-0.3.0}/capper/tests/test_types.py +80 -8
  15. capper-0.1.1/README.md → capper-0.3.0/capper.egg-info/PKG-INFO +56 -9
  16. {capper-0.1.1 → capper-0.3.0}/capper.egg-info/SOURCES.txt +9 -0
  17. capper-0.3.0/capper.egg-info/entry_points.txt +2 -0
  18. {capper-0.1.1 → capper-0.3.0}/capper.egg-info/requires.txt +10 -0
  19. {capper-0.1.1 → capper-0.3.0}/pyproject.toml +30 -2
  20. {capper-0.1.1 → capper-0.3.0}/capper/commerce.py +0 -0
  21. {capper-0.1.1 → capper-0.3.0}/capper/date_time.py +0 -0
  22. {capper-0.1.1 → capper-0.3.0}/capper/examples/user_factory.py +1 -1
  23. {capper-0.1.1 → capper-0.3.0}/capper/finance.py +0 -0
  24. {capper-0.1.1 → capper-0.3.0}/capper/geo.py +0 -0
  25. {capper-0.1.1 → capper-0.3.0}/capper/internet.py +0 -0
  26. {capper-0.1.1 → capper-0.3.0}/capper/person.py +0 -0
  27. {capper-0.1.1 → capper-0.3.0}/capper/phone.py +0 -0
  28. {capper-0.1.1 → capper-0.3.0}/capper/tests/__init__.py +0 -0
  29. {capper-0.1.1 → capper-0.3.0}/capper/tests/conftest.py +0 -0
  30. {capper-0.1.1 → capper-0.3.0}/capper/tests/test_docs_examples.py +0 -0
  31. {capper-0.1.1 → capper-0.3.0}/capper/text.py +0 -0
  32. {capper-0.1.1 → capper-0.3.0}/capper.egg-info/dependency_links.txt +0 -0
  33. {capper-0.1.1 → capper-0.3.0}/capper.egg-info/top_level.txt +0 -0
  34. {capper-0.1.1 → capper-0.3.0}/setup.cfg +0 -0
@@ -1,26 +1,34 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capper
3
- Version: 0.1.1
3
+ Version: 0.3.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
12
12
  Provides-Extra: pydantic
13
13
  Requires-Dist: pydantic>=2.0; extra == "pydantic"
14
+ Provides-Extra: hypothesis
15
+ Requires-Dist: hypothesis>=6.0; extra == "hypothesis"
14
16
  Provides-Extra: dev
15
17
  Requires-Dist: pytest>=7.0; extra == "dev"
16
18
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
17
19
  Requires-Dist: pytest-xdist>=3.0; extra == "dev"
18
20
  Requires-Dist: pydantic>=2.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.4; extra == "dev"
22
+ Requires-Dist: mypy>=1.0; extra == "dev"
23
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
26
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
19
27
 
20
28
  # Capper
21
29
 
22
30
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
23
- [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://pypi.org/project/capper/)
31
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/capper/)
24
32
  [![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)
25
33
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
26
34
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
@@ -42,11 +50,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
42
50
  pip install capper
43
51
  ```
44
52
 
45
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
53
+ Requires **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
46
54
 
47
- ```bash
48
- pip install capper[pydantic]
49
- ```
55
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
56
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
50
57
 
51
58
  ## Usage
52
59
 
@@ -117,6 +124,10 @@ Works automatically. No extra steps. IDE autocompletion.
117
124
  - **Text**: `Paragraph`, `Sentence`
118
125
  - **Phone**: `PhoneNumber`, `CountryCallingCode`
119
126
  - **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
127
+ - **File**: `FilePath`, `FileName`, `FileExtension`
128
+ - **Misc**: `UUID`
129
+ - **Color**: `HexColor`
130
+ - **Barcode**: `EAN13`, `EAN8`
120
131
 
121
132
  Import from the top level: `from capper import Name, Email, Address, ...`
122
133
  See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
@@ -125,9 +136,20 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
125
136
 
126
137
  **Custom types:** Subclass `FakerType`, set `faker_provider` to the Faker method name (e.g. `"company"`), and optionally `faker_kwargs`. The type auto-registers with Polyfactory when the class is defined.
127
138
 
139
+ ## CLI
140
+
141
+ Generate fake values from the command line:
142
+
143
+ ```bash
144
+ capper generate Name Email --count 5
145
+ capper generate Name Email --count 3 --seed 42
146
+ ```
147
+
148
+ Use `-n`/`--count` for the number of rows and `-s`/`--seed` for reproducible output. Type names are the same as the Python types (e.g. `Name`, `Email`, `Address`).
149
+
128
150
  ## Compatibility
129
151
 
130
- 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.
152
+ 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).
131
153
 
132
154
  ## Development
133
155
 
@@ -136,6 +158,8 @@ pip install -e ".[dev]"
136
158
  pytest capper/tests
137
159
  ```
138
160
 
161
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
162
+
139
163
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
140
164
 
141
165
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -159,20 +183,23 @@ UserFactory.seed_random(42)
159
183
  user2 = UserFactory.build() # same data as user1 if you seed the same before each
160
184
  ```
161
185
 
162
- 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.
186
+ 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).
163
187
 
164
188
  ## Publishing
165
189
 
166
190
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
167
191
 
168
- 1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
169
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
192
+ 1. Update [CHANGELOG.md](CHANGELOG.md): move Unreleased entries into a new version section and date it.
193
+ 2. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
194
+ 3. Create a GitHub release (tag e.g. `v0.3.0`). The workflow runs tests, builds the package, and uploads to PyPI.
170
195
 
171
196
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
172
197
 
173
198
  ## Documentation
174
199
 
175
200
  - **[Docs index](docs/README.md)** — overview and links to all documentation
201
+ - **[API reference](docs/api.md)** — generated API docs (build with `mkdocs serve`; see [Contributing](CONTRIBUTING.md))
202
+ - **[Contributing](CONTRIBUTING.md)** — dev setup and how to add new types
176
203
  - **User guides** (step-by-step, with runnable examples):
177
204
  - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
178
205
  - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
@@ -181,3 +208,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
181
208
  - [Package plan](docs/capper_package_plan.md) — design and rationale
182
209
  - [Roadmap](docs/ROADMAP.md) — development phases and status
183
210
  - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
211
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -1,26 +1,7 @@
1
- Metadata-Version: 2.4
2
- Name: capper
3
- Version: 0.1.1
4
- Summary: Semantic, typed wrappers for Faker with automatic Polyfactory integration
5
- Author-email: Odos Matthews <odosmatthews@gmail.com>
6
- Project-URL: Documentation, https://github.com/eddiethedean/capper#readme
7
- Project-URL: Repository, https://github.com/eddiethedean/capper
8
- Requires-Python: >=3.9
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: Faker>=20.0
11
- Requires-Dist: Polyfactory>=2.0
12
- Provides-Extra: pydantic
13
- Requires-Dist: pydantic>=2.0; extra == "pydantic"
14
- Provides-Extra: dev
15
- Requires-Dist: pytest>=7.0; extra == "dev"
16
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
17
- Requires-Dist: pytest-xdist>=3.0; extra == "dev"
18
- Requires-Dist: pydantic>=2.0; extra == "dev"
19
-
20
1
  # Capper
21
2
 
22
3
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
23
- [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://pypi.org/project/capper/)
4
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/capper/)
24
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)
25
6
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
26
7
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
@@ -42,11 +23,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
42
23
  pip install capper
43
24
  ```
44
25
 
45
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
26
+ Requires **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
46
27
 
47
- ```bash
48
- pip install capper[pydantic]
49
- ```
28
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
29
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
50
30
 
51
31
  ## Usage
52
32
 
@@ -117,6 +97,10 @@ Works automatically. No extra steps. IDE autocompletion.
117
97
  - **Text**: `Paragraph`, `Sentence`
118
98
  - **Phone**: `PhoneNumber`, `CountryCallingCode`
119
99
  - **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
100
+ - **File**: `FilePath`, `FileName`, `FileExtension`
101
+ - **Misc**: `UUID`
102
+ - **Color**: `HexColor`
103
+ - **Barcode**: `EAN13`, `EAN8`
120
104
 
121
105
  Import from the top level: `from capper import Name, Email, Address, ...`
122
106
  See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
@@ -125,9 +109,20 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
125
109
 
126
110
  **Custom types:** Subclass `FakerType`, set `faker_provider` to the Faker method name (e.g. `"company"`), and optionally `faker_kwargs`. The type auto-registers with Polyfactory when the class is defined.
127
111
 
112
+ ## CLI
113
+
114
+ Generate fake values from the command line:
115
+
116
+ ```bash
117
+ capper generate Name Email --count 5
118
+ capper generate Name Email --count 3 --seed 42
119
+ ```
120
+
121
+ Use `-n`/`--count` for the number of rows and `-s`/`--seed` for reproducible output. Type names are the same as the Python types (e.g. `Name`, `Email`, `Address`).
122
+
128
123
  ## Compatibility
129
124
 
130
- 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.
125
+ 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).
131
126
 
132
127
  ## Development
133
128
 
@@ -136,6 +131,8 @@ pip install -e ".[dev]"
136
131
  pytest capper/tests
137
132
  ```
138
133
 
134
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
135
+
139
136
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
140
137
 
141
138
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -159,20 +156,23 @@ UserFactory.seed_random(42)
159
156
  user2 = UserFactory.build() # same data as user1 if you seed the same before each
160
157
  ```
161
158
 
162
- 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.
159
+ 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).
163
160
 
164
161
  ## Publishing
165
162
 
166
163
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
167
164
 
168
- 1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
169
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
165
+ 1. Update [CHANGELOG.md](CHANGELOG.md): move Unreleased entries into a new version section and date it.
166
+ 2. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
167
+ 3. Create a GitHub release (tag e.g. `v0.3.0`). The workflow runs tests, builds the package, and uploads to PyPI.
170
168
 
171
169
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
172
170
 
173
171
  ## Documentation
174
172
 
175
173
  - **[Docs index](docs/README.md)** — overview and links to all documentation
174
+ - **[API reference](docs/api.md)** — generated API docs (build with `mkdocs serve`; see [Contributing](CONTRIBUTING.md))
175
+ - **[Contributing](CONTRIBUTING.md)** — dev setup and how to add new types
176
176
  - **User guides** (step-by-step, with runnable examples):
177
177
  - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
178
178
  - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
@@ -181,3 +181,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
181
181
  - [Package plan](docs/capper_package_plan.md) — design and rationale
182
182
  - [Roadmap](docs/ROADMAP.md) — development phases and status
183
183
  - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
184
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -2,14 +2,27 @@
2
2
 
3
3
  Import types (e.g. ``Name``, ``Email``) and use them in Pydantic models, dataclasses,
4
4
  or attrs; Polyfactory will generate values via Faker. Use ``seed(n)`` for reproducibility.
5
+ With Hypothesis installed (pip install capper[hypothesis]), import capper.strategies
6
+ and use st.from_type(Name) for property-based tests.
5
7
  """
6
8
 
7
- 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.3.0"
15
+
16
+ from .barcode import EAN8, EAN13
17
+ from .base import FakerType, faker, seed, use_faker
18
+ from .color import HexColor
8
19
  from .commerce import Company, Currency, Price, Product
9
20
  from .date_time import Date, DateTime, Time
21
+ from .file import FileExtension, FileName, FilePath
10
22
  from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
11
23
  from .geo import Address, City, Country
12
- from .internet import Email, IP, URL, UserName
24
+ from .internet import IP, URL, Email, UserName
25
+ from .misc import UUID
13
26
  from .person import FirstName, Job, LastName, Name
14
27
  from .phone import CountryCallingCode, PhoneNumber
15
28
  from .text import Paragraph, Sentence
@@ -26,9 +39,15 @@ __all__ = [
26
39
  "Currency",
27
40
  "Date",
28
41
  "DateTime",
42
+ "EAN13",
43
+ "EAN8",
29
44
  "Email",
30
45
  "FakerType",
46
+ "FileExtension",
47
+ "FileName",
48
+ "FilePath",
31
49
  "FirstName",
50
+ "HexColor",
32
51
  "IP",
33
52
  "Job",
34
53
  "LastName",
@@ -40,7 +59,9 @@ __all__ = [
40
59
  "Sentence",
41
60
  "Time",
42
61
  "URL",
62
+ "UUID",
43
63
  "UserName",
44
64
  "faker",
45
65
  "seed",
66
+ "use_faker",
46
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"
@@ -2,6 +2,9 @@
2
2
 
3
3
  The module-level ``faker`` is attached to Polyfactory's BaseFactory so that one seed
4
4
  controls both capper types and built-in types. Use ``seed(n)`` for reproducible data.
5
+
6
+ Note: The shared ``faker`` and ``use_faker()`` are not thread-safe; do not switch
7
+ the global Faker from multiple threads while building models.
5
8
  """
6
9
 
7
10
  from typing import Any
@@ -24,6 +27,24 @@ def seed(seed_value: int) -> None:
24
27
  faker.seed_instance(seed_value)
25
28
 
26
29
 
30
+ def use_faker(instance: Faker) -> None:
31
+ """Use a custom Faker instance for both Capper and Polyfactory.
32
+
33
+ Replaces the module-level faker and Polyfactory's BaseFactory.__faker__
34
+ so that one instance (e.g. a locale-specific Faker) is used everywhere.
35
+ Call before building any models.
36
+
37
+ Note: The shared faker is not thread-safe. Do not call use_faker() from
38
+ multiple threads while other threads are building models.
39
+
40
+ Args:
41
+ instance: The Faker instance to use (e.g. Faker('de_DE')).
42
+ """
43
+ global faker
44
+ faker = instance
45
+ BaseFactory.__faker__ = instance
46
+
47
+
27
48
  def _install_pydantic_schema() -> None:
28
49
  """If Pydantic is available, attach __get_pydantic_core_schema__ to FakerType."""
29
50
  try:
@@ -32,9 +53,7 @@ def _install_pydantic_schema() -> None:
32
53
  except ImportError:
33
54
  return
34
55
 
35
- def __get_pydantic_core_schema__(
36
- source_type: Any, handler: GetCoreSchemaHandler
37
- ) -> CoreSchema:
56
+ def __get_pydantic_core_schema__(source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
38
57
  """Validate as str then coerce to the FakerType subclass."""
39
58
  return core_schema.no_info_after_validator_function(source_type, handler(str))
40
59
 
@@ -42,11 +61,12 @@ def _install_pydantic_schema() -> None:
42
61
 
43
62
 
44
63
  class FakerType(str):
45
- """Base class for semantic Faker types; subclasses are auto-registered with Polyfactory.
64
+ """Base class for semantic Faker types; subclasses auto-register with Polyfactory.
46
65
 
47
66
  Subclasses must set a non-empty ``faker_provider`` (the Faker method name).
48
67
  Optional ``faker_kwargs`` is a dict of keyword arguments passed to that provider
49
68
  (e.g. ``faker_kwargs = {"nb_words": 10}`` for ``sentence``).
69
+ When Hypothesis is installed, use ``st.from_type(YourFakerType)`` for property-based tests.
50
70
  """
51
71
 
52
72
  faker_provider: str = ""
@@ -63,7 +83,15 @@ _install_pydantic_schema()
63
83
 
64
84
  def _register(cls: type, provider_name: str) -> None:
65
85
  """Register a FakerType subclass with Polyfactory so factories can generate values."""
66
- provider_kwargs = getattr(cls, "faker_kwargs", None) or {}
86
+ if not hasattr(faker, provider_name):
87
+ raise AttributeError(
88
+ f"Faker has no provider {provider_name!r} (used by {cls.__name__}). "
89
+ "Check faker_provider on the type."
90
+ )
91
+ provider_fn = getattr(faker, provider_name)
92
+ if not callable(provider_fn):
93
+ raise TypeError(f"Faker.{provider_name} is not callable (used by {cls.__name__}).")
94
+ provider_kwargs = dict(getattr(cls, "faker_kwargs", None) or {})
67
95
 
68
96
  def _provide() -> str:
69
97
  value = getattr(faker, provider_name)(**provider_kwargs)
@@ -0,0 +1,79 @@
1
+ """CLI for ad-hoc fake data generation."""
2
+
3
+ import argparse
4
+ import sys
5
+ from typing import Type
6
+
7
+ import capper
8
+
9
+ from .base import FakerType, faker, seed
10
+
11
+
12
+ def _type_registry() -> dict[str, Type[FakerType]]:
13
+ """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", "use_faker"):
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
28
+
29
+
30
+ def _generate_one(typ: Type[FakerType]) -> str:
31
+ """Generate a single value for the given FakerType."""
32
+ provider = getattr(typ, "faker_provider", "")
33
+ kwargs = getattr(typ, "faker_kwargs", None) or {}
34
+ value = getattr(faker, provider)(**kwargs)
35
+ return str(value)
36
+
37
+
38
+ def cmd_generate(args: argparse.Namespace) -> int:
39
+ """Run generate subcommand."""
40
+ registry = _type_registry()
41
+ types: list[Type[FakerType]] = []
42
+ for name in args.types:
43
+ if name not in registry:
44
+ known = ", ".join(sorted(registry))
45
+ print(f"Unknown type: {name}. Known: {known}", file=sys.stderr)
46
+ return 1
47
+ types.append(registry[name])
48
+
49
+ if args.seed is not None:
50
+ seed(args.seed)
51
+
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))
56
+ return 0
57
+
58
+
59
+ def main() -> int:
60
+ """Entry point for the capper CLI."""
61
+ parser = argparse.ArgumentParser(
62
+ prog="capper", description="Capper: fake data via Faker types."
63
+ )
64
+ subparsers = parser.add_subparsers(dest="command", required=True)
65
+
66
+ gen = subparsers.add_parser("generate", help="Generate fake values for one or more types.")
67
+ gen.add_argument(
68
+ "types",
69
+ nargs="+",
70
+ metavar="TYPE",
71
+ help="Type names (e.g. Name, Email).",
72
+ )
73
+ gen.add_argument("-n", "--count", type=int, default=1, help="Number of rows (default: 1).")
74
+ gen.add_argument("-s", "--seed", type=int, default=None, help="Seed for reproducible output.")
75
+ gen.set_defaults(func=cmd_generate)
76
+
77
+ args = parser.parse_args()
78
+ result: int = args.func(args)
79
+ return result
@@ -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,100 @@
1
+ """Hypothesis strategies for Capper types (optional; requires hypothesis>=6.0).
2
+
3
+ Use ``st.from_type(Name)`` after importing capper and capper.strategies, or call
4
+ ``strategies.for_type(Name)`` to get a strategy that generates instances of that type.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Type, TypeVar, cast
10
+
11
+ from hypothesis import strategies as st
12
+
13
+ from .barcode import EAN8, EAN13
14
+ from .base import FakerType, faker
15
+ from .color import HexColor
16
+ from .commerce import Company, Currency, Price, Product
17
+ from .date_time import Date, DateTime, Time
18
+ from .file import FileExtension, FileName, FilePath
19
+ from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
20
+ from .geo import Address, City, Country
21
+ from .internet import IP, URL, Email, UserName
22
+ from .misc import UUID
23
+ from .person import FirstName, Job, LastName, Name
24
+ from .phone import CountryCallingCode, PhoneNumber
25
+ from .text import Paragraph, Sentence
26
+
27
+ T = TypeVar("T", bound=FakerType)
28
+
29
+ # All built-in FakerType subclasses for registration
30
+ _BUILTIN_TYPES: tuple[type[FakerType], ...] = (
31
+ Address,
32
+ City,
33
+ Company,
34
+ Country,
35
+ CountryCallingCode,
36
+ CreditCardExpiry,
37
+ CreditCardNumber,
38
+ CreditCardProvider,
39
+ Currency,
40
+ Date,
41
+ DateTime,
42
+ EAN13,
43
+ EAN8,
44
+ Email,
45
+ FileExtension,
46
+ FileName,
47
+ FilePath,
48
+ FirstName,
49
+ HexColor,
50
+ IP,
51
+ Job,
52
+ LastName,
53
+ Name,
54
+ Paragraph,
55
+ PhoneNumber,
56
+ Price,
57
+ Product,
58
+ Sentence,
59
+ Time,
60
+ URL,
61
+ UUID,
62
+ UserName,
63
+ )
64
+
65
+
66
+ def for_type(cls: Type[T]) -> st.SearchStrategy[T]:
67
+ """Return a Hypothesis strategy that generates instances of the given FakerType subclass."""
68
+ provider = getattr(cls, "faker_provider", None)
69
+ if not provider:
70
+ raise ValueError(f"{cls.__name__} has no faker_provider")
71
+ if not hasattr(faker, provider):
72
+ raise AttributeError(
73
+ f"Faker has no provider {provider!r} (used by {cls.__name__}). "
74
+ "Check faker_provider on the type."
75
+ )
76
+ if not callable(getattr(faker, provider)):
77
+ raise TypeError(f"Faker.{provider} is not callable (used by {cls.__name__}).")
78
+ kwargs = getattr(cls, "faker_kwargs", None) or {}
79
+
80
+ @st.composite
81
+ def _draw(draw: st.DrawFn) -> T:
82
+ seed_val = draw(st.integers(min_value=0, max_value=2**32 - 1))
83
+ faker.seed_instance(seed_val)
84
+ value = getattr(faker, provider)(**kwargs)
85
+ return cls(str(value))
86
+
87
+ return cast(st.SearchStrategy[T], _draw())
88
+
89
+
90
+ def _register_strategies() -> None:
91
+ """Register all built-in Capper types with Hypothesis so st.from_type() works."""
92
+ from hypothesis.strategies import register_type_strategy
93
+
94
+ for typ in _BUILTIN_TYPES:
95
+ register_type_strategy(typ, for_type(typ))
96
+
97
+
98
+ _register_strategies()
99
+
100
+ __all__ = ["for_type"]
@@ -0,0 +1,41 @@
1
+ """Tests for the capper CLI (generate subcommand)."""
2
+
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ if sys.version_info >= (3, 10):
8
+ from capper import cli
9
+ else:
10
+ cli = None # type: ignore[assignment]
11
+
12
+
13
+ @pytest.mark.skipif(sys.version_info < (3, 10), reason="capper CLI uses Python 3.10+ syntax")
14
+ def test_cli_generate_exit_zero_and_non_empty_output() -> None:
15
+ """Invoke generate subcommand; assert exit 0 and non-empty output for a known type."""
16
+ orig_argv = sys.argv
17
+ try:
18
+ sys.argv = ["capper", "generate", "Name", "--count", "1"]
19
+ exit_code = cli.main()
20
+ assert exit_code == 0
21
+ finally:
22
+ sys.argv = orig_argv
23
+
24
+
25
+ @pytest.mark.skipif(sys.version_info < (3, 10), reason="capper CLI uses Python 3.10+ syntax")
26
+ def test_cli_generate_produces_output(capsys: pytest.CaptureFixture[str]) -> None:
27
+ """Generate subcommand prints one tab-separated value per row."""
28
+ orig_argv = sys.argv
29
+ try:
30
+ sys.argv = ["capper", "generate", "Name", "Email", "--count", "2"]
31
+ exit_code = cli.main()
32
+ assert exit_code == 0
33
+ out = capsys.readouterr().out
34
+ lines = [line for line in out.strip().splitlines() if line.strip()]
35
+ assert len(lines) == 2
36
+ for line in lines:
37
+ parts = line.split("\t")
38
+ assert len(parts) == 2
39
+ assert len(parts[0]) > 0 and len(parts[1]) > 0
40
+ finally:
41
+ sys.argv = orig_argv
@@ -0,0 +1,34 @@
1
+ """Tests for Hypothesis strategies (require capper[hypothesis])."""
2
+
3
+ import pytest
4
+
5
+ pytest.importorskip("hypothesis")
6
+
7
+ from hypothesis import given
8
+ from hypothesis import strategies as st
9
+
10
+ import capper.strategies # noqa: F401 - register types with Hypothesis
11
+ from capper import Email, Name
12
+
13
+
14
+ @given(st.from_type(Name))
15
+ def test_from_type_name_produces_non_empty_string(name: Name) -> None:
16
+ """st.from_type(Name) yields non-empty Name instances."""
17
+ assert isinstance(name, Name)
18
+ assert isinstance(name, str)
19
+ assert len(name) > 0
20
+
21
+
22
+ @given(st.from_type(Email))
23
+ def test_from_type_email_contains_at(email: Email) -> None:
24
+ """st.from_type(Email) yields strings containing '@'."""
25
+ assert isinstance(email, Email)
26
+ assert "@" in email
27
+ assert len(email) > 0
28
+
29
+
30
+ @given(capper.strategies.for_type(Name))
31
+ def test_for_type_name_produces_name(name: Name) -> None:
32
+ """strategies.for_type(Name) yields Name instances."""
33
+ assert isinstance(name, Name)
34
+ assert len(name) > 0
@@ -1,10 +1,10 @@
1
- """Tests for Polyfactory integration: ModelFactory, DataclassFactory, shared Faker, auto-registration."""
1
+ """Tests for Polyfactory: ModelFactory, DataclassFactory, shared Faker, auto-registration."""
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
- from pydantic import BaseModel
6
5
  from polyfactory.factories import DataclassFactory
7
6
  from polyfactory.factories.pydantic_factory import ModelFactory
7
+ from pydantic import BaseModel
8
8
 
9
9
  from capper import Email, Name
10
10
 
@@ -50,14 +50,15 @@ def test_seed_random_and_capper_seed_produce_same_value() -> None:
50
50
 
51
51
  def test_model_factory_uses_capper_faker() -> None:
52
52
  """Asserts ModelFactory.__faker__ is capper's faker to document the shared instance."""
53
- import capper
54
53
  from polyfactory.factories.pydantic_factory import ModelFactory
55
54
 
55
+ import capper
56
+
56
57
  assert ModelFactory.__faker__ is capper.faker
57
58
 
58
59
 
59
60
  def test_dataclass_factory_builds_with_capper_types() -> None:
60
- """Builds a dataclass with Name and Email via DataclassFactory; asserts non-empty and valid email."""
61
+ """Dataclass with Name/Email via DataclassFactory; asserts non-empty and valid email."""
61
62
 
62
63
  @dataclass
63
64
  class Person:
@@ -94,11 +95,12 @@ def test_dataclass_factory_batch() -> None:
94
95
 
95
96
 
96
97
  def test_capper_types_auto_registered_with_polyfactory() -> None:
97
- """Asserts that importing capper registers types so Polyfactory can build a model with PhoneNumber."""
98
- from capper import PhoneNumber
98
+ """Importing capper registers types so Polyfactory can build a model with PhoneNumber."""
99
99
  from polyfactory.factories.pydantic_factory import ModelFactory
100
100
  from pydantic import BaseModel
101
101
 
102
+ from capper import PhoneNumber
103
+
102
104
  class Contact(BaseModel):
103
105
  phone: PhoneNumber
104
106
 
@@ -3,6 +3,11 @@
3
3
  import pytest
4
4
 
5
5
  from capper import (
6
+ EAN8,
7
+ EAN13,
8
+ IP,
9
+ URL,
10
+ UUID,
6
11
  Address,
7
12
  City,
8
13
  Company,
@@ -15,8 +20,11 @@ from capper import (
15
20
  Date,
16
21
  DateTime,
17
22
  Email,
23
+ FileExtension,
24
+ FileName,
25
+ FilePath,
18
26
  FirstName,
19
- IP,
27
+ HexColor,
20
28
  Job,
21
29
  LastName,
22
30
  Name,
@@ -26,7 +34,6 @@ from capper import (
26
34
  Product,
27
35
  Sentence,
28
36
  Time,
29
- URL,
30
37
  UserName,
31
38
  )
32
39
 
@@ -59,25 +66,45 @@ from capper import (
59
66
  CreditCardNumber,
60
67
  CreditCardExpiry,
61
68
  CreditCardProvider,
69
+ FilePath,
70
+ FileName,
71
+ FileExtension,
72
+ UUID,
73
+ HexColor,
74
+ EAN13,
75
+ EAN8,
62
76
  ],
63
77
  )
64
78
  def test_type_generates_non_empty_string(type_class: type) -> None:
65
- """Asserts each type's Faker provider exists and returns a non-empty value (provider availability)."""
79
+ """Each type's Faker provider exists and returns a non-empty value."""
66
80
  from faker import Faker
67
81
 
68
82
  faker = Faker()
69
83
  provider_name = getattr(type_class, "faker_provider")
70
- value = getattr(faker, provider_name)()
84
+ kwargs = getattr(type_class, "faker_kwargs", None) or {}
85
+ value = getattr(faker, provider_name)(**kwargs)
71
86
  assert isinstance(value, (str, type_class))
72
87
  assert len(str(value)) > 0
73
88
 
74
89
 
75
90
  @pytest.mark.parametrize(
76
91
  "type_class",
77
- [Name, Email, PhoneNumber, FirstName, Address, Sentence, CreditCardNumber],
92
+ [
93
+ Name,
94
+ Email,
95
+ PhoneNumber,
96
+ FirstName,
97
+ Address,
98
+ Sentence,
99
+ CreditCardNumber,
100
+ FilePath,
101
+ UUID,
102
+ HexColor,
103
+ EAN13,
104
+ ],
78
105
  )
79
106
  def test_model_factory_builds_capper_type(type_class: type) -> None:
80
- """Builds a Pydantic model with a single capper type via ModelFactory; asserts non-empty and type."""
107
+ """Pydantic model with one capper type via ModelFactory; asserts non-empty and type."""
81
108
  from typing import Any, Type
82
109
 
83
110
  from polyfactory.factories.pydantic_factory import ModelFactory
@@ -94,7 +121,7 @@ def test_model_factory_builds_capper_type(type_class: type) -> None:
94
121
 
95
122
 
96
123
  def test_faker_kwargs_support() -> None:
97
- """Asserts types can set faker_kwargs and provider is called with them; builds via ModelFactory and checks value."""
124
+ """faker_kwargs passed to provider; ModelFactory builds and checks value."""
98
125
  from capper.base import FakerType
99
126
 
100
127
  class ShortSentence(FakerType):
@@ -121,12 +148,22 @@ def test_faker_kwargs_support() -> None:
121
148
  assert isinstance(instance.text, (str, ShortSentence)) and len(instance.text) > 0
122
149
 
123
150
 
151
+ def test_hex_color_format() -> None:
152
+ """HexColor yields a string starting with #."""
153
+ from faker import Faker
154
+
155
+ faker = Faker()
156
+ value = faker.color(color_format="hex")
157
+ assert isinstance(value, str) and value.startswith("#") and len(value) == 7
158
+
159
+
124
160
  def test_seed_reproducibility(seeded_faker: None) -> None:
125
161
  """Builds twice after seeding with same value; asserts identical generated name."""
126
- from capper import Name, seed
127
162
  from polyfactory.factories.pydantic_factory import ModelFactory
128
163
  from pydantic import BaseModel
129
164
 
165
+ from capper import Name, seed
166
+
130
167
  class User(BaseModel):
131
168
  name: Name
132
169
 
@@ -138,3 +175,38 @@ def test_seed_reproducibility(seeded_faker: None) -> None:
138
175
  seed(42)
139
176
  user2 = UserFactory.build()
140
177
  assert user1.name == user2.name
178
+
179
+
180
+ def test_use_faker_switches_global_faker() -> None:
181
+ """use_faker() switches the global Faker; next factory build uses that instance (e.g. seed)."""
182
+ from faker import Faker
183
+ from polyfactory.factories.base import BaseFactory
184
+ from polyfactory.factories.pydantic_factory import ModelFactory
185
+ from pydantic import BaseModel
186
+
187
+ from capper import Name, use_faker
188
+ from capper import faker as default_faker
189
+
190
+ try:
191
+ custom = Faker()
192
+ custom.seed_instance(123)
193
+ use_faker(custom)
194
+ from capper.base import faker as module_faker
195
+
196
+ assert module_faker is custom
197
+ assert BaseFactory.__faker__ is custom
198
+
199
+ class User(BaseModel):
200
+ name: Name
201
+
202
+ class UserFactory(ModelFactory[User]):
203
+ pass
204
+
205
+ seed_val = 999
206
+ custom.seed_instance(seed_val)
207
+ user1 = UserFactory.build()
208
+ custom.seed_instance(seed_val)
209
+ user2 = UserFactory.build()
210
+ assert user1.name == user2.name
211
+ finally:
212
+ use_faker(default_faker)
@@ -1,7 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: capper
3
+ Version: 0.3.0
4
+ Summary: Semantic, typed wrappers for Faker with automatic Polyfactory integration
5
+ Author-email: Odos Matthews <odosmatthews@gmail.com>
6
+ Project-URL: Documentation, https://github.com/eddiethedean/capper#readme
7
+ Project-URL: Repository, https://github.com/eddiethedean/capper
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: Faker>=20.0
11
+ Requires-Dist: Polyfactory>=2.0
12
+ Provides-Extra: pydantic
13
+ Requires-Dist: pydantic>=2.0; extra == "pydantic"
14
+ Provides-Extra: hypothesis
15
+ Requires-Dist: hypothesis>=6.0; extra == "hypothesis"
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.0; extra == "dev"
18
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
19
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
20
+ Requires-Dist: pydantic>=2.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.4; extra == "dev"
22
+ Requires-Dist: mypy>=1.0; extra == "dev"
23
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
26
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
27
+
1
28
  # Capper
2
29
 
3
30
  [![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/)
31
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://pypi.org/project/capper/)
5
32
  [![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)
6
33
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
7
34
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
@@ -23,11 +50,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
23
50
  pip install capper
24
51
  ```
25
52
 
26
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
53
+ Requires **Python 3.10+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
27
54
 
28
- ```bash
29
- pip install capper[pydantic]
30
- ```
55
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
56
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
31
57
 
32
58
  ## Usage
33
59
 
@@ -98,6 +124,10 @@ Works automatically. No extra steps. IDE autocompletion.
98
124
  - **Text**: `Paragraph`, `Sentence`
99
125
  - **Phone**: `PhoneNumber`, `CountryCallingCode`
100
126
  - **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
127
+ - **File**: `FilePath`, `FileName`, `FileExtension`
128
+ - **Misc**: `UUID`
129
+ - **Color**: `HexColor`
130
+ - **Barcode**: `EAN13`, `EAN8`
101
131
 
102
132
  Import from the top level: `from capper import Name, Email, Address, ...`
103
133
  See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
@@ -106,9 +136,20 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
106
136
 
107
137
  **Custom types:** Subclass `FakerType`, set `faker_provider` to the Faker method name (e.g. `"company"`), and optionally `faker_kwargs`. The type auto-registers with Polyfactory when the class is defined.
108
138
 
139
+ ## CLI
140
+
141
+ Generate fake values from the command line:
142
+
143
+ ```bash
144
+ capper generate Name Email --count 5
145
+ capper generate Name Email --count 3 --seed 42
146
+ ```
147
+
148
+ Use `-n`/`--count` for the number of rows and `-s`/`--seed` for reproducible output. Type names are the same as the Python types (e.g. `Name`, `Email`, `Address`).
149
+
109
150
  ## Compatibility
110
151
 
111
- 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.
152
+ 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).
112
153
 
113
154
  ## Development
114
155
 
@@ -117,6 +158,8 @@ pip install -e ".[dev]"
117
158
  pytest capper/tests
118
159
  ```
119
160
 
161
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
162
+
120
163
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
121
164
 
122
165
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -140,20 +183,23 @@ UserFactory.seed_random(42)
140
183
  user2 = UserFactory.build() # same data as user1 if you seed the same before each
141
184
  ```
142
185
 
143
- 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.
186
+ 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).
144
187
 
145
188
  ## Publishing
146
189
 
147
190
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
148
191
 
149
- 1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
150
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
192
+ 1. Update [CHANGELOG.md](CHANGELOG.md): move Unreleased entries into a new version section and date it.
193
+ 2. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
194
+ 3. Create a GitHub release (tag e.g. `v0.3.0`). The workflow runs tests, builds the package, and uploads to PyPI.
151
195
 
152
196
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
153
197
 
154
198
  ## Documentation
155
199
 
156
200
  - **[Docs index](docs/README.md)** — overview and links to all documentation
201
+ - **[API reference](docs/api.md)** — generated API docs (build with `mkdocs serve`; see [Contributing](CONTRIBUTING.md))
202
+ - **[Contributing](CONTRIBUTING.md)** — dev setup and how to add new types
157
203
  - **User guides** (step-by-step, with runnable examples):
158
204
  - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
159
205
  - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
@@ -162,3 +208,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
162
208
  - [Package plan](docs/capper_package_plan.md) — design and rationale
163
209
  - [Roadmap](docs/ROADMAP.md) — development phases and status
164
210
  - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
211
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -1,23 +1,32 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  capper/__init__.py
4
+ capper/barcode.py
4
5
  capper/base.py
6
+ capper/cli.py
7
+ capper/color.py
5
8
  capper/commerce.py
6
9
  capper/date_time.py
10
+ capper/file.py
7
11
  capper/finance.py
8
12
  capper/geo.py
9
13
  capper/internet.py
14
+ capper/misc.py
10
15
  capper/person.py
11
16
  capper/phone.py
17
+ capper/strategies.py
12
18
  capper/text.py
13
19
  capper.egg-info/PKG-INFO
14
20
  capper.egg-info/SOURCES.txt
15
21
  capper.egg-info/dependency_links.txt
22
+ capper.egg-info/entry_points.txt
16
23
  capper.egg-info/requires.txt
17
24
  capper.egg-info/top_level.txt
18
25
  capper/examples/user_factory.py
19
26
  capper/tests/__init__.py
20
27
  capper/tests/conftest.py
28
+ capper/tests/test_cli.py
21
29
  capper/tests/test_docs_examples.py
30
+ capper/tests/test_hypothesis_strategies.py
22
31
  capper/tests/test_polyfactory_integration.py
23
32
  capper/tests/test_types.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ capper = capper.cli:main
@@ -6,6 +6,16 @@ pytest>=7.0
6
6
  pytest-cov>=4.0
7
7
  pytest-xdist>=3.0
8
8
  pydantic>=2.0
9
+ ruff>=0.4
10
+ mypy>=1.0
11
+ hypothesis>=6.0
12
+
13
+ [docs]
14
+ mkdocs>=1.5
15
+ mkdocstrings[python]>=0.24
16
+
17
+ [hypothesis]
18
+ hypothesis>=6.0
9
19
 
10
20
  [pydantic]
11
21
  pydantic>=2.0
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "capper"
7
- version = "0.1.1"
7
+ version = "0.3.0"
8
8
  description = "Semantic, typed wrappers for Faker with automatic Polyfactory integration"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  authors = [
12
12
  { name = "Odos Matthews", email = "odosmatthews@gmail.com" },
13
13
  ]
@@ -20,17 +20,30 @@ dependencies = [
20
20
  pydantic = [
21
21
  "pydantic>=2.0",
22
22
  ]
23
+ hypothesis = [
24
+ "hypothesis>=6.0",
25
+ ]
23
26
  dev = [
24
27
  "pytest>=7.0",
25
28
  "pytest-cov>=4.0",
26
29
  "pytest-xdist>=3.0",
27
30
  "pydantic>=2.0",
31
+ "ruff>=0.4",
32
+ "mypy>=1.0",
33
+ "hypothesis>=6.0",
34
+ ]
35
+ docs = [
36
+ "mkdocs>=1.5",
37
+ "mkdocstrings[python]>=0.24",
28
38
  ]
29
39
 
30
40
  [project.urls]
31
41
  Documentation = "https://github.com/eddiethedean/capper#readme"
32
42
  Repository = "https://github.com/eddiethedean/capper"
33
43
 
44
+ [project.scripts]
45
+ capper = "capper.cli:main"
46
+
34
47
  [tool.setuptools.packages.find]
35
48
  where = ["."]
36
49
  include = ["capper*"]
@@ -39,3 +52,18 @@ include = ["capper*"]
39
52
  testpaths = ["capper/tests"]
40
53
  pythonpath = ["."]
41
54
  addopts = ["-v", "--tb=short"]
55
+
56
+ [tool.ruff]
57
+ line-length = 100
58
+ target-version = "py310"
59
+ exclude = ["docs/notebooks"]
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I"]
63
+
64
+ [tool.mypy]
65
+ python_version = "3.10"
66
+ warn_return_any = true
67
+ warn_unused_configs = true
68
+ disallow_untyped_defs = false
69
+ exclude = ["capper/tests"]
File without changes
File without changes
@@ -4,8 +4,8 @@ Run: python -m capper.examples.user_factory
4
4
  Output varies each run; use capper.seed(n) for reproducible data.
5
5
  """
6
6
 
7
- from pydantic import BaseModel
8
7
  from polyfactory.factories.pydantic_factory import ModelFactory
8
+ from pydantic import BaseModel
9
9
 
10
10
  from capper import Email, Name
11
11
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes