capper 0.1.1__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.
- {capper-0.1.1 → capper-0.2.0}/PKG-INFO +24 -6
- {capper-0.1.1 → capper-0.2.0}/README.md +18 -5
- {capper-0.1.1 → capper-0.2.0}/capper/__init__.py +3 -1
- {capper-0.1.1 → capper-0.2.0}/capper/base.py +3 -4
- capper-0.2.0/capper/cli.py +79 -0
- capper-0.2.0/capper/strategies.py +80 -0
- capper-0.2.0/capper/tests/test_hypothesis_strategies.py +34 -0
- {capper-0.1.1 → capper-0.2.0}/capper/tests/test_polyfactory_integration.py +8 -6
- {capper-0.1.1 → capper-0.2.0}/capper/tests/test_types.py +7 -6
- {capper-0.1.1 → capper-0.2.0}/capper.egg-info/PKG-INFO +24 -6
- {capper-0.1.1 → capper-0.2.0}/capper.egg-info/SOURCES.txt +4 -0
- capper-0.2.0/capper.egg-info/entry_points.txt +2 -0
- {capper-0.1.1 → capper-0.2.0}/capper.egg-info/requires.txt +6 -0
- {capper-0.1.1 → capper-0.2.0}/pyproject.toml +24 -1
- {capper-0.1.1 → capper-0.2.0}/capper/commerce.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/date_time.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/examples/user_factory.py +1 -1
- {capper-0.1.1 → capper-0.2.0}/capper/finance.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/geo.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/internet.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/person.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/phone.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/tests/__init__.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/tests/conftest.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/tests/test_docs_examples.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper/text.py +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper.egg-info/dependency_links.txt +0 -0
- {capper-0.1.1 → capper-0.2.0}/capper.egg-info/top_level.txt +0 -0
- {capper-0.1.1 → 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.
|
|
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,11 +11,16 @@ 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"
|
|
19
24
|
|
|
20
25
|
# Capper
|
|
21
26
|
|
|
@@ -42,11 +47,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
42
47
|
pip install capper
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**.
|
|
50
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
pip install capper[
|
|
49
|
-
```
|
|
52
|
+
- **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
|
|
53
|
+
- **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
|
|
50
54
|
|
|
51
55
|
## Usage
|
|
52
56
|
|
|
@@ -125,6 +129,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
|
|
|
125
129
|
|
|
126
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.
|
|
127
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
|
+
|
|
128
143
|
## Compatibility
|
|
129
144
|
|
|
130
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.
|
|
@@ -136,6 +151,8 @@ pip install -e ".[dev]"
|
|
|
136
151
|
pytest capper/tests
|
|
137
152
|
```
|
|
138
153
|
|
|
154
|
+
Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
|
|
155
|
+
|
|
139
156
|
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
140
157
|
|
|
141
158
|
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
@@ -166,7 +183,7 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
|
|
|
166
183
|
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
167
184
|
|
|
168
185
|
1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
|
|
169
|
-
2. Create a GitHub release (tag e.g. `v0.
|
|
186
|
+
2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
|
|
170
187
|
|
|
171
188
|
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
172
189
|
|
|
@@ -181,3 +198,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
|
|
|
181
198
|
- [Package plan](docs/capper_package_plan.md) — design and rationale
|
|
182
199
|
- [Roadmap](docs/ROADMAP.md) — development phases and status
|
|
183
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/`
|
|
@@ -23,11 +23,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
23
23
|
pip install capper
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**.
|
|
26
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
pip install capper[
|
|
30
|
-
```
|
|
28
|
+
- **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
|
|
29
|
+
- **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
|
|
31
30
|
|
|
32
31
|
## Usage
|
|
33
32
|
|
|
@@ -106,6 +105,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
|
|
|
106
105
|
|
|
107
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.
|
|
108
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
|
+
|
|
109
119
|
## Compatibility
|
|
110
120
|
|
|
111
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.
|
|
@@ -117,6 +127,8 @@ pip install -e ".[dev]"
|
|
|
117
127
|
pytest capper/tests
|
|
118
128
|
```
|
|
119
129
|
|
|
130
|
+
Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
|
|
131
|
+
|
|
120
132
|
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
121
133
|
|
|
122
134
|
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
@@ -147,7 +159,7 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
|
|
|
147
159
|
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
148
160
|
|
|
149
161
|
1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
|
|
150
|
-
2. Create a GitHub release (tag e.g. `v0.
|
|
162
|
+
2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
|
|
151
163
|
|
|
152
164
|
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
153
165
|
|
|
@@ -162,3 +174,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
|
|
|
162
174
|
- [Package plan](docs/capper_package_plan.md) — design and rationale
|
|
163
175
|
- [Roadmap](docs/ROADMAP.md) — development phases and status
|
|
164
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
|
|
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
|
|
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 = ""
|
|
@@ -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
|
+
"""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
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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,7 +77,7 @@ 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
|
-
"""
|
|
80
|
+
"""Pydantic model with one capper type via ModelFactory; asserts non-empty and type."""
|
|
81
81
|
from typing import Any, Type
|
|
82
82
|
|
|
83
83
|
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
@@ -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
|
-
"""
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capper
|
|
3
|
-
Version: 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,11 +11,16 @@ 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"
|
|
19
24
|
|
|
20
25
|
# Capper
|
|
21
26
|
|
|
@@ -42,11 +47,10 @@ Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automat
|
|
|
42
47
|
pip install capper
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**.
|
|
50
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. Optional extras:
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
pip install capper[
|
|
49
|
-
```
|
|
52
|
+
- **Pydantic** (for Pydantic models): `pip install capper[pydantic]`
|
|
53
|
+
- **Hypothesis** (for property-based tests with `st.from_type(...)`): `pip install capper[hypothesis]`
|
|
50
54
|
|
|
51
55
|
## Usage
|
|
52
56
|
|
|
@@ -125,6 +129,17 @@ See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/d
|
|
|
125
129
|
|
|
126
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.
|
|
127
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
|
+
|
|
128
143
|
## Compatibility
|
|
129
144
|
|
|
130
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.
|
|
@@ -136,6 +151,8 @@ pip install -e ".[dev]"
|
|
|
136
151
|
pytest capper/tests
|
|
137
152
|
```
|
|
138
153
|
|
|
154
|
+
Lint and type-check: `ruff check .`, `ruff format .`, `mypy capper`.
|
|
155
|
+
|
|
139
156
|
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
140
157
|
|
|
141
158
|
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
@@ -166,7 +183,7 @@ Use `UserFactory.__random_seed__ = 42` to seed once when the factory class is cr
|
|
|
166
183
|
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
167
184
|
|
|
168
185
|
1. Add a `PYPI_API_TOKEN` secret (PyPI API token) to the repo.
|
|
169
|
-
2. Create a GitHub release (tag e.g. `v0.
|
|
186
|
+
2. Create a GitHub release (tag e.g. `v0.2.0`). The workflow runs tests, builds the package, and uploads to PyPI.
|
|
170
187
|
|
|
171
188
|
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
172
189
|
|
|
@@ -181,3 +198,4 @@ To build and upload manually: `pip install build twine`, `python -m build`, `twi
|
|
|
181
198
|
- [Package plan](docs/capper_package_plan.md) — design and rationale
|
|
182
199
|
- [Roadmap](docs/ROADMAP.md) — development phases and status
|
|
183
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,15 +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
|
|
21
24
|
capper/tests/test_docs_examples.py
|
|
25
|
+
capper/tests/test_hypothesis_strategies.py
|
|
22
26
|
capper/tests/test_polyfactory_integration.py
|
|
23
27
|
capper/tests/test_types.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "capper"
|
|
7
|
-
version = "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,17 +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",
|
|
26
29
|
"pytest-xdist>=3.0",
|
|
27
30
|
"pydantic>=2.0",
|
|
31
|
+
"ruff>=0.4",
|
|
32
|
+
"mypy>=1.0",
|
|
33
|
+
"hypothesis>=6.0",
|
|
28
34
|
]
|
|
29
35
|
|
|
30
36
|
[project.urls]
|
|
31
37
|
Documentation = "https://github.com/eddiethedean/capper#readme"
|
|
32
38
|
Repository = "https://github.com/eddiethedean/capper"
|
|
33
39
|
|
|
40
|
+
[project.scripts]
|
|
41
|
+
capper = "capper.cli:main"
|
|
42
|
+
|
|
34
43
|
[tool.setuptools.packages.find]
|
|
35
44
|
where = ["."]
|
|
36
45
|
include = ["capper*"]
|
|
@@ -39,3 +48,17 @@ include = ["capper*"]
|
|
|
39
48
|
testpaths = ["capper/tests"]
|
|
40
49
|
pythonpath = ["."]
|
|
41
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|