capper 0.1.0__tar.gz → 0.2.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 (29) hide show
  1. {capper-0.1.0/capper.egg-info → capper-0.2.0}/PKG-INFO +40 -14
  2. capper-0.1.0/PKG-INFO → capper-0.2.0/README.md +33 -31
  3. {capper-0.1.0 → capper-0.2.0}/capper/__init__.py +3 -1
  4. {capper-0.1.0 → capper-0.2.0}/capper/base.py +5 -5
  5. capper-0.2.0/capper/cli.py +79 -0
  6. capper-0.2.0/capper/strategies.py +80 -0
  7. capper-0.2.0/capper/tests/test_docs_examples.py +34 -0
  8. capper-0.2.0/capper/tests/test_hypothesis_strategies.py +34 -0
  9. {capper-0.1.0 → capper-0.2.0}/capper/tests/test_polyfactory_integration.py +8 -6
  10. {capper-0.1.0 → capper-0.2.0}/capper/tests/test_types.py +9 -8
  11. capper-0.1.0/README.md → capper-0.2.0/capper.egg-info/PKG-INFO +57 -13
  12. {capper-0.1.0 → capper-0.2.0}/capper.egg-info/SOURCES.txt +5 -0
  13. capper-0.2.0/capper.egg-info/entry_points.txt +2 -0
  14. {capper-0.1.0 → capper-0.2.0}/capper.egg-info/requires.txt +7 -0
  15. {capper-0.1.0 → capper-0.2.0}/pyproject.toml +25 -1
  16. {capper-0.1.0 → capper-0.2.0}/capper/commerce.py +0 -0
  17. {capper-0.1.0 → capper-0.2.0}/capper/date_time.py +0 -0
  18. {capper-0.1.0 → capper-0.2.0}/capper/examples/user_factory.py +1 -1
  19. {capper-0.1.0 → capper-0.2.0}/capper/finance.py +0 -0
  20. {capper-0.1.0 → capper-0.2.0}/capper/geo.py +0 -0
  21. {capper-0.1.0 → capper-0.2.0}/capper/internet.py +0 -0
  22. {capper-0.1.0 → capper-0.2.0}/capper/person.py +0 -0
  23. {capper-0.1.0 → capper-0.2.0}/capper/phone.py +0 -0
  24. {capper-0.1.0 → capper-0.2.0}/capper/tests/__init__.py +0 -0
  25. {capper-0.1.0 → capper-0.2.0}/capper/tests/conftest.py +0 -0
  26. {capper-0.1.0 → capper-0.2.0}/capper/text.py +0 -0
  27. {capper-0.1.0 → capper-0.2.0}/capper.egg-info/dependency_links.txt +0 -0
  28. {capper-0.1.0 → capper-0.2.0}/capper.egg-info/top_level.txt +0 -0
  29. {capper-0.1.0 → capper-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capper
3
- Version: 0.1.0
3
+ Version: 0.2.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
@@ -11,16 +11,22 @@ 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"
19
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
17
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"
18
24
 
19
25
  # Capper
20
26
 
21
27
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
22
- [![Python](https://img.shields.io/pypi/pyversions/capper.svg)](https://pypi.org/project/capper/)
23
- [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/publish.yml?branch=main)](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
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)
24
30
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
25
31
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
26
32
 
@@ -41,11 +47,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
41
47
  pip install capper
42
48
  ```
43
49
 
44
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
50
+ Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
45
51
 
46
- ```bash
47
- pip install capper[pydantic]
48
- ```
52
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
53
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
49
54
 
50
55
  ## Usage
51
56
 
@@ -104,6 +109,8 @@ oevans@example.com
104
109
 
105
110
  Works automatically. No extra steps. IDE autocompletion.
106
111
 
112
+ **New to Capper?** See the [Getting started](docs/user_guides/getting_started.md) guide and run the examples in `docs/examples/`.
113
+
107
114
  ## Available types
108
115
 
109
116
  - **Person**: `Name`, `FirstName`, `LastName`, `Job`
@@ -122,6 +129,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
122
129
 
123
130
  **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.
124
131
 
132
+ ## CLI
133
+
134
+ Generate fake values from the command line:
135
+
136
+ ```bash
137
+ capper generate Name Email --count 5
138
+ capper generate Name Email --count 3 --seed 42
139
+ ```
140
+
141
+ 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`).
142
+
125
143
  ## Compatibility
126
144
 
127
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.
@@ -133,6 +151,8 @@ pip install -e ".[dev]"
133
151
  pytest capper/tests
134
152
  ```
135
153
 
154
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
155
+
136
156
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
137
157
 
138
158
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -163,13 +183,19 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
163
183
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
164
184
 
165
185
  1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
166
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
186
+ 2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
167
187
 
168
188
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
169
189
 
170
- ## Links
171
-
172
- - [Documentation index](https://github.com/eddiethedean/capper/blob/main/docs/README.md) — overview of all docs
173
- - [Package plan](https://github.com/eddiethedean/capper/blob/main/docs/capper_package_plan.md) design and rationale
174
- - [Roadmap](https://github.com/eddiethedean/capper/blob/main/docs/ROADMAP.md) — development phases and status
175
- - [Faker provider mapping](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) — type-to-provider reference
190
+ ## Documentation
191
+
192
+ - **[Docs index](docs/README.md)** — overview and links to all documentation
193
+ - **User guides** (step-by-step, with runnable examples):
194
+ - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
195
+ - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
196
+ - [Reproducible data](docs/user_guides/reproducible_data.md) — seeding for tests and demos
197
+ - [Custom types](docs/user_guides/custom_types.md) — `FakerType` subclasses and `faker_kwargs`
198
+ - [Package plan](docs/capper_package_plan.md) — design and rationale
199
+ - [Roadmap](docs/ROADMAP.md) — development phases and status
200
+ - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
201
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -1,26 +1,8 @@
1
- Metadata-Version: 2.4
2
- Name: capper
3
- Version: 0.1.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.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: pydantic>=2.0; extra == "dev"
18
-
19
1
  # Capper
20
2
 
21
3
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
22
- [![Python](https://img.shields.io/pypi/pyversions/capper.svg)](https://pypi.org/project/capper/)
23
- [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/publish.yml?branch=main)](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
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)
24
6
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
25
7
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
26
8
 
@@ -41,11 +23,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
41
23
  pip install capper
42
24
  ```
43
25
 
44
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
26
+ Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
45
27
 
46
- ```bash
47
- pip install capper[pydantic]
48
- ```
28
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
29
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
49
30
 
50
31
  ## Usage
51
32
 
@@ -104,6 +85,8 @@ oevans@example.com
104
85
 
105
86
  Works automatically. No extra steps. IDE autocompletion.
106
87
 
88
+ **New to Capper?** See the [Getting started](docs/user_guides/getting_started.md) guide and run the examples in `docs/examples/`.
89
+
107
90
  ## Available types
108
91
 
109
92
  - **Person**: `Name`, `FirstName`, `LastName`, `Job`
@@ -122,6 +105,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
122
105
 
123
106
  **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.
124
107
 
108
+ ## CLI
109
+
110
+ Generate fake values from the command line:
111
+
112
+ ```bash
113
+ capper generate Name Email --count 5
114
+ capper generate Name Email --count 3 --seed 42
115
+ ```
116
+
117
+ 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`).
118
+
125
119
  ## Compatibility
126
120
 
127
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.
@@ -133,6 +127,8 @@ pip install -e ".[dev]"
133
127
  pytest capper/tests
134
128
  ```
135
129
 
130
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
131
+
136
132
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
137
133
 
138
134
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -163,13 +159,19 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
163
159
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
164
160
 
165
161
  1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
166
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
162
+ 2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
167
163
 
168
164
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
169
165
 
170
- ## Links
171
-
172
- - [Documentation index](https://github.com/eddiethedean/capper/blob/main/docs/README.md) — overview of all docs
173
- - [Package plan](https://github.com/eddiethedean/capper/blob/main/docs/capper_package_plan.md) design and rationale
174
- - [Roadmap](https://github.com/eddiethedean/capper/blob/main/docs/ROADMAP.md) — development phases and status
175
- - [Faker provider mapping](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) — type-to-provider reference
166
+ ## Documentation
167
+
168
+ - **[Docs index](docs/README.md)** — overview and links to all documentation
169
+ - **User guides** (step-by-step, with runnable examples):
170
+ - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
171
+ - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
172
+ - [Reproducible data](docs/user_guides/reproducible_data.md) — seeding for tests and demos
173
+ - [Custom types](docs/user_guides/custom_types.md) — `FakerType` subclasses and `faker_kwargs`
174
+ - [Package plan](docs/capper_package_plan.md) — design and rationale
175
+ - [Roadmap](docs/ROADMAP.md) — development phases and status
176
+ - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
177
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -2,6 +2,8 @@
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
9
  from .base import FakerType, faker, seed
@@ -9,7 +11,7 @@ from .commerce import Company, Currency, Price, Product
9
11
  from .date_time import Date, DateTime, Time
10
12
  from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
11
13
  from .geo import Address, City, Country
12
- from .internet import Email, IP, URL, UserName
14
+ from .internet import IP, URL, Email, UserName
13
15
  from .person import FirstName, Job, LastName, Name
14
16
  from .phone import CountryCallingCode, PhoneNumber
15
17
  from .text import Paragraph, Sentence
@@ -32,9 +32,7 @@ def _install_pydantic_schema() -> None:
32
32
  except ImportError:
33
33
  return
34
34
 
35
- def __get_pydantic_core_schema__(
36
- source_type: Any, handler: GetCoreSchemaHandler
37
- ) -> CoreSchema:
35
+ def __get_pydantic_core_schema__(source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
38
36
  """Validate as str then coerce to the FakerType subclass."""
39
37
  return core_schema.no_info_after_validator_function(source_type, handler(str))
40
38
 
@@ -42,11 +40,12 @@ def _install_pydantic_schema() -> None:
42
40
 
43
41
 
44
42
  class FakerType(str):
45
- """Base class for semantic Faker types; subclasses are auto-registered with Polyfactory.
43
+ """Base class for semantic Faker types; subclasses auto-register with Polyfactory.
46
44
 
47
45
  Subclasses must set a non-empty ``faker_provider`` (the Faker method name).
48
46
  Optional ``faker_kwargs`` is a dict of keyword arguments passed to that provider
49
47
  (e.g. ``faker_kwargs = {"nb_words": 10}`` for ``sentence``).
48
+ When Hypothesis is installed, use ``st.from_type(YourFakerType)`` for property-based tests.
50
49
  """
51
50
 
52
51
  faker_provider: str = ""
@@ -66,6 +65,7 @@ def _register(cls: type, provider_name: str) -> None:
66
65
  provider_kwargs = getattr(cls, "faker_kwargs", None) or {}
67
66
 
68
67
  def _provide() -> str:
69
- return getattr(faker, provider_name)(**provider_kwargs)
68
+ value = getattr(faker, provider_name)(**provider_kwargs)
69
+ return str(value)
70
70
 
71
71
  BaseFactory.add_provider(cls, _provide)
@@ -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"):
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,80 @@
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 typing import Type, TypeVar
8
+
9
+ from hypothesis import strategies as st
10
+
11
+ 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
20
+
21
+ T = TypeVar("T", bound=FakerType)
22
+
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
+ )
51
+
52
+
53
+ def for_type(cls: Type[T]) -> st.SearchStrategy[T]:
54
+ """Return a Hypothesis strategy that generates instances of the given FakerType subclass."""
55
+ provider = getattr(cls, "faker_provider", None)
56
+ if not provider:
57
+ raise ValueError(f"{cls.__name__} has no faker_provider")
58
+ kwargs = getattr(cls, "faker_kwargs", None) or {}
59
+
60
+ @st.composite
61
+ def _draw(draw: st.DrawFn) -> T:
62
+ seed_val = draw(st.integers(min_value=0, max_value=2**32 - 1))
63
+ faker.seed_instance(seed_val)
64
+ value = getattr(faker, provider)(**kwargs)
65
+ return cls(str(value))
66
+
67
+ return _draw()
68
+
69
+
70
+ def _register_strategies() -> None:
71
+ """Register all built-in Capper types with Hypothesis so st.from_type() works."""
72
+ from hypothesis.strategies import register_type_strategy
73
+
74
+ for typ in _BUILTIN_TYPES:
75
+ register_type_strategy(typ, for_type(typ))
76
+
77
+
78
+ _register_strategies()
79
+
80
+ __all__ = ["for_type"]
@@ -0,0 +1,34 @@
1
+ """Ensure docs/examples/*.py run without error (user guide code)."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ # Repo root: parent of capper/tests
11
+ REPO_ROOT = Path(__file__).resolve().parent.parent.parent
12
+
13
+
14
+ @pytest.mark.parametrize(
15
+ "script",
16
+ ["getting_started", "models_and_factories", "reproducible_data", "custom_types"],
17
+ )
18
+ def test_docs_example_runs(script: str) -> None:
19
+ """Run each docs example script; fail if it exits non-zero."""
20
+ path = REPO_ROOT / "docs" / "examples" / f"{script}.py"
21
+ assert path.exists(), f"Missing {path}"
22
+ env = os.environ.copy()
23
+ env["PYTHONPATH"] = str(REPO_ROOT) + os.pathsep + env.get("PYTHONPATH", "")
24
+ result = subprocess.run(
25
+ [sys.executable, str(path)],
26
+ cwd=str(REPO_ROOT),
27
+ env=env,
28
+ capture_output=True,
29
+ text=True,
30
+ timeout=10,
31
+ )
32
+ assert result.returncode == 0, (
33
+ f"docs/examples/{script}.py failed:\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
34
+ )
@@ -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,8 @@
3
3
  import pytest
4
4
 
5
5
  from capper import (
6
+ IP,
7
+ URL,
6
8
  Address,
7
9
  City,
8
10
  Company,
@@ -16,7 +18,6 @@ from capper import (
16
18
  DateTime,
17
19
  Email,
18
20
  FirstName,
19
- IP,
20
21
  Job,
21
22
  LastName,
22
23
  Name,
@@ -26,7 +27,6 @@ from capper import (
26
27
  Product,
27
28
  Sentence,
28
29
  Time,
29
- URL,
30
30
  UserName,
31
31
  )
32
32
 
@@ -62,7 +62,7 @@ from capper import (
62
62
  ],
63
63
  )
64
64
  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)."""
65
+ """Each type's Faker provider exists and returns a non-empty value."""
66
66
  from faker import Faker
67
67
 
68
68
  faker = Faker()
@@ -77,13 +77,13 @@ def test_type_generates_non_empty_string(type_class: type) -> None:
77
77
  [Name, Email, PhoneNumber, FirstName, Address, Sentence, CreditCardNumber],
78
78
  )
79
79
  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."""
81
- from typing import Any
80
+ """Pydantic model with one capper type via ModelFactory; asserts non-empty and type."""
81
+ from typing import Any, Type
82
82
 
83
83
  from polyfactory.factories.pydantic_factory import ModelFactory
84
84
  from pydantic import create_model
85
85
 
86
- model_cls: type[Any] = create_model("Model", value=(type_class, ...))
86
+ model_cls: Type[Any] = create_model("Model", value=(type_class, ...))
87
87
 
88
88
  class ModelFactoryCls(ModelFactory[model_cls]): # type: ignore[valid-type]
89
89
  pass
@@ -94,7 +94,7 @@ def test_model_factory_builds_capper_type(type_class: type) -> None:
94
94
 
95
95
 
96
96
  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."""
97
+ """faker_kwargs passed to provider; ModelFactory builds and checks value."""
98
98
  from capper.base import FakerType
99
99
 
100
100
  class ShortSentence(FakerType):
@@ -123,10 +123,11 @@ def test_faker_kwargs_support() -> None:
123
123
 
124
124
  def test_seed_reproducibility(seeded_faker: None) -> None:
125
125
  """Builds twice after seeding with same value; asserts identical generated name."""
126
- from capper import Name, seed
127
126
  from polyfactory.factories.pydantic_factory import ModelFactory
128
127
  from pydantic import BaseModel
129
128
 
129
+ from capper import Name, seed
130
+
130
131
  class User(BaseModel):
131
132
  name: Name
132
133
 
@@ -1,8 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: capper
3
+ Version: 0.2.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.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: 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
+
1
25
  # Capper
2
26
 
3
27
  [![PyPI](https://img.shields.io/pypi/v/capper.svg)](https://pypi.org/project/capper/)
4
- [![Python](https://img.shields.io/pypi/pyversions/capper.svg)](https://pypi.org/project/capper/)
5
- [![CI](https://img.shields.io/github/actions/workflow/status/eddiethedean/capper/publish.yml?branch=main)](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
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)
6
30
  [![Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://docs.astral.sh/ruff/)
7
31
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
8
32
 
@@ -23,11 +47,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
23
47
  pip install capper
24
48
  ```
25
49
 
26
- Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
50
+ Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
27
51
 
28
- ```bash
29
- pip install capper[pydantic]
30
- ```
52
+ - **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
53
+ - **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
31
54
 
32
55
  ## Usage
33
56
 
@@ -86,6 +109,8 @@ oevans@example.com
86
109
 
87
110
  Works automatically. No extra steps. IDE autocompletion.
88
111
 
112
+ **New to Capper?** See the [Getting started](docs/user_guides/getting_started.md) guide and run the examples in `docs/examples/`.
113
+
89
114
  ## Available types
90
115
 
91
116
  - **Person**: `Name`, `FirstName`, `LastName`, `Job`
@@ -104,6 +129,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
104
129
 
105
130
  **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.
106
131
 
132
+ ## CLI
133
+
134
+ Generate fake values from the command line:
135
+
136
+ ```bash
137
+ capper generate Name Email --count 5
138
+ capper generate Name Email --count 3 --seed 42
139
+ ```
140
+
141
+ 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`).
142
+
107
143
  ## Compatibility
108
144
 
109
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.
@@ -115,6 +151,8 @@ pip install -e ".[dev]"
115
151
  pytest capper/tests
116
152
  ```
117
153
 
154
+ Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
155
+
118
156
  Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
119
157
 
120
158
  **Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
@@ -145,13 +183,19 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
145
183
  Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
146
184
 
147
185
  1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
148
- 2. Create a GitHub release (tag e.g. `v0.1.0`). The workflow runs tests, builds the package, and uploads to PyPI.
186
+ 2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
149
187
 
150
188
  To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
151
189
 
152
- ## Links
153
-
154
- - [Documentation index](https://github.com/eddiethedean/capper/blob/main/docs/README.md) — overview of all docs
155
- - [Package plan](https://github.com/eddiethedean/capper/blob/main/docs/capper_package_plan.md) design and rationale
156
- - [Roadmap](https://github.com/eddiethedean/capper/blob/main/docs/ROADMAP.md) — development phases and status
157
- - [Faker provider mapping](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) — type-to-provider reference
190
+ ## Documentation
191
+
192
+ - **[Docs index](docs/README.md)** — overview and links to all documentation
193
+ - **User guides** (step-by-step, with runnable examples):
194
+ - [Getting started](docs/user_guides/getting_started.md) — install, first model, first factory
195
+ - [Models and factories](docs/user_guides/models_and_factories.md) — Pydantic, dataclasses, batches
196
+ - [Reproducible data](docs/user_guides/reproducible_data.md) — seeding for tests and demos
197
+ - [Custom types](docs/user_guides/custom_types.md) — `FakerType` subclasses and `faker_kwargs`
198
+ - [Package plan](docs/capper_package_plan.md) — design and rationale
199
+ - [Roadmap](docs/ROADMAP.md) — development phases and status
200
+ - [Faker provider mapping](docs/FAKER_PROVIDERS.md) — which Faker method each type uses
201
+ - [Example notebooks](docs/notebooks/README.md) — Jupyter notebooks in `docs/notebooks/`
@@ -2,6 +2,7 @@ README.md
2
2
  pyproject.toml
3
3
  capper/__init__.py
4
4
  capper/base.py
5
+ capper/cli.py
5
6
  capper/commerce.py
6
7
  capper/date_time.py
7
8
  capper/finance.py
@@ -9,14 +10,18 @@ capper/geo.py
9
10
  capper/internet.py
10
11
  capper/person.py
11
12
  capper/phone.py
13
+ capper/strategies.py
12
14
  capper/text.py
13
15
  capper.egg-info/PKG-INFO
14
16
  capper.egg-info/SOURCES.txt
15
17
  capper.egg-info/dependency_links.txt
18
+ capper.egg-info/entry_points.txt
16
19
  capper.egg-info/requires.txt
17
20
  capper.egg-info/top_level.txt
18
21
  capper/examples/user_factory.py
19
22
  capper/tests/__init__.py
20
23
  capper/tests/conftest.py
24
+ capper/tests/test_docs_examples.py
25
+ capper/tests/test_hypothesis_strategies.py
21
26
  capper/tests/test_polyfactory_integration.py
22
27
  capper/tests/test_types.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ capper = capper.cli:main
@@ -4,7 +4,14 @@ Polyfactory>=2.0
4
4
  [dev]
5
5
  pytest>=7.0
6
6
  pytest-cov>=4.0
7
+ pytest-xdist>=3.0
7
8
  pydantic>=2.0
9
+ ruff>=0.4
10
+ mypy>=1.0
11
+ hypothesis>=6.0
12
+
13
+ [hypothesis]
14
+ hypothesis>=6.0
8
15
 
9
16
  [pydantic]
10
17
  pydantic>=2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "capper"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Semantic, typed wrappers for Faker with automatic Polyfactory integration"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -20,16 +20,26 @@ 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",
29
+ "pytest-xdist>=3.0",
26
30
  "pydantic>=2.0",
31
+ "ruff>=0.4",
32
+ "mypy>=1.0",
33
+ "hypothesis>=6.0",
27
34
  ]
28
35
 
29
36
  [project.urls]
30
37
  Documentation = "https://github.com/eddiethedean/capper#readme"
31
38
  Repository = "https://github.com/eddiethedean/capper"
32
39
 
40
+ [project.scripts]
41
+ capper = "capper.cli:main"
42
+
33
43
  [tool.setuptools.packages.find]
34
44
  where = ["."]
35
45
  include = ["capper*"]
@@ -38,3 +48,17 @@ include = ["capper*"]
38
48
  testpaths = ["capper/tests"]
39
49
  pythonpath = ["."]
40
50
  addopts = ["-v", "--tb=short"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py39"
55
+ exclude = ["docs/notebooks"]
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "I"]
59
+
60
+ [tool.mypy]
61
+ python_version = "3.9"
62
+ warn_return_any = true
63
+ warn_unused_configs = true
64
+ disallow_untyped_defs = false
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