capper 0.1.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.0/PKG-INFO +175 -0
- capper-0.1.0/README.md +157 -0
- capper-0.1.0/capper/__init__.py +46 -0
- capper-0.1.0/capper/base.py +71 -0
- capper-0.1.0/capper/commerce.py +27 -0
- capper-0.1.0/capper/date_time.py +21 -0
- capper-0.1.0/capper/examples/user_factory.py +29 -0
- capper-0.1.0/capper/finance.py +21 -0
- capper-0.1.0/capper/geo.py +21 -0
- capper-0.1.0/capper/internet.py +27 -0
- capper-0.1.0/capper/person.py +27 -0
- capper-0.1.0/capper/phone.py +15 -0
- capper-0.1.0/capper/tests/__init__.py +1 -0
- capper-0.1.0/capper/tests/conftest.py +11 -0
- capper-0.1.0/capper/tests/test_polyfactory_integration.py +110 -0
- capper-0.1.0/capper/tests/test_types.py +140 -0
- capper-0.1.0/capper/text.py +15 -0
- capper-0.1.0/capper.egg-info/PKG-INFO +175 -0
- capper-0.1.0/capper.egg-info/SOURCES.txt +22 -0
- capper-0.1.0/capper.egg-info/dependency_links.txt +1 -0
- capper-0.1.0/capper.egg-info/requires.txt +10 -0
- capper-0.1.0/capper.egg-info/top_level.txt +1 -0
- capper-0.1.0/pyproject.toml +40 -0
- capper-0.1.0/setup.cfg +4 -0
capper-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
# Capper
|
|
20
|
+
|
|
21
|
+
[](https://pypi.org/project/capper/)
|
|
22
|
+
[](https://pypi.org/project/capper/)
|
|
23
|
+
[](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
|
|
24
|
+
[](https://docs.astral.sh/ruff/)
|
|
25
|
+
[](https://mypy-lang.org/)
|
|
26
|
+
|
|
27
|
+
Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automatic [Polyfactory](https://polyfactory.litestar.dev/) integration.
|
|
28
|
+
|
|
29
|
+
**Source:** [github.com/eddiethedean/capper](https://github.com/eddiethedean/capper)
|
|
30
|
+
|
|
31
|
+
## Why Capper?
|
|
32
|
+
|
|
33
|
+
- **Zero config** — Import a type; Polyfactory uses the right Faker provider. No manual registration.
|
|
34
|
+
- **Typed** — Use `Name`, `Email`, `PhoneNumber`, etc. in your models for clear intent and IDE support.
|
|
35
|
+
- **Multi-backend** — Works with Pydantic, dataclasses, attrs, and other [Polyfactory-supported](https://polyfactory.litestar.dev/) model types.
|
|
36
|
+
- **Optional Pydantic** — Install `capper` alone for dataclasses/attrs; add `capper[pydantic]` when you use Pydantic models.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install capper
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install capper[pydantic]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
**With Pydantic** (requires `capper[pydantic]`):
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from pydantic import BaseModel
|
|
56
|
+
from capper import Name, Email
|
|
57
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
58
|
+
|
|
59
|
+
class User(BaseModel):
|
|
60
|
+
name: Name
|
|
61
|
+
email: Email
|
|
62
|
+
|
|
63
|
+
class UserFactory(ModelFactory[User]):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
user = UserFactory.build()
|
|
67
|
+
print(user.name)
|
|
68
|
+
print(user.email)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Example output (varies each run):
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Paul Blair
|
|
75
|
+
linda00@example.net
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**With dataclasses** (no Pydantic needed):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from dataclasses import dataclass
|
|
82
|
+
from capper import Name, Email
|
|
83
|
+
from polyfactory.factories import DataclassFactory
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class User:
|
|
87
|
+
name: Name
|
|
88
|
+
email: Email
|
|
89
|
+
|
|
90
|
+
class UserFactory(DataclassFactory[User]):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
user = UserFactory.build()
|
|
94
|
+
print(user.name)
|
|
95
|
+
print(user.email)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Example output (varies each run):
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Carly Jenkins
|
|
102
|
+
oevans@example.com
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Works automatically. No extra steps. IDE autocompletion.
|
|
106
|
+
|
|
107
|
+
## Available types
|
|
108
|
+
|
|
109
|
+
- **Person**: `Name`, `FirstName`, `LastName`, `Job`
|
|
110
|
+
- **Geo**: `Address`, `City`, `Country`
|
|
111
|
+
- **Internet**: `Email`, `URL`, `IP`, `UserName`
|
|
112
|
+
- **Commerce**: `Company`, `Product`, `Currency`, `Price`
|
|
113
|
+
- **Date/time**: `Date`, `DateTime`, `Time`
|
|
114
|
+
- **Text**: `Paragraph`, `Sentence`
|
|
115
|
+
- **Phone**: `PhoneNumber`, `CountryCallingCode`
|
|
116
|
+
- **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
|
|
117
|
+
|
|
118
|
+
Import from the top level: `from capper import Name, Email, Address, ...`
|
|
119
|
+
See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
|
|
120
|
+
|
|
121
|
+
**Optional kwargs:** Subclass `FakerType` and set `faker_kwargs` to pass arguments to the Faker provider (e.g. `faker_kwargs = {"nb_words": 10}` for `Sentence`).
|
|
122
|
+
|
|
123
|
+
**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
|
+
|
|
125
|
+
## Compatibility
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
pytest capper/tests
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
137
|
+
|
|
138
|
+
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from capper import seed, Name
|
|
142
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
143
|
+
from pydantic import BaseModel
|
|
144
|
+
|
|
145
|
+
class User(BaseModel):
|
|
146
|
+
name: Name
|
|
147
|
+
|
|
148
|
+
class UserFactory(ModelFactory[User]):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Either way seeds the shared Faker (same effect):
|
|
152
|
+
seed(42)
|
|
153
|
+
user1 = UserFactory.build()
|
|
154
|
+
|
|
155
|
+
UserFactory.seed_random(42)
|
|
156
|
+
user2 = UserFactory.build() # same data as user1 if you seed the same before each
|
|
157
|
+
```
|
|
158
|
+
|
|
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.
|
|
160
|
+
|
|
161
|
+
## Publishing
|
|
162
|
+
|
|
163
|
+
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
164
|
+
|
|
165
|
+
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.
|
|
167
|
+
|
|
168
|
+
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
169
|
+
|
|
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
|
capper-0.1.0/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Capper
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/capper/)
|
|
4
|
+
[](https://pypi.org/project/capper/)
|
|
5
|
+
[](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
|
|
6
|
+
[](https://docs.astral.sh/ruff/)
|
|
7
|
+
[](https://mypy-lang.org/)
|
|
8
|
+
|
|
9
|
+
Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automatic [Polyfactory](https://polyfactory.litestar.dev/) integration.
|
|
10
|
+
|
|
11
|
+
**Source:** [github.com/eddiethedean/capper](https://github.com/eddiethedean/capper)
|
|
12
|
+
|
|
13
|
+
## Why Capper?
|
|
14
|
+
|
|
15
|
+
- **Zero config** — Import a type; Polyfactory uses the right Faker provider. No manual registration.
|
|
16
|
+
- **Typed** — Use `Name`, `Email`, `PhoneNumber`, etc. in your models for clear intent and IDE support.
|
|
17
|
+
- **Multi-backend** — Works with Pydantic, dataclasses, attrs, and other [Polyfactory-supported](https://polyfactory.litestar.dev/) model types.
|
|
18
|
+
- **Optional Pydantic** — Install `capper` alone for dataclasses/attrs; add `capper[pydantic]` when you use Pydantic models.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install capper
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install capper[pydantic]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
**With Pydantic** (requires `capper[pydantic]`):
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pydantic import BaseModel
|
|
38
|
+
from capper import Name, Email
|
|
39
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
40
|
+
|
|
41
|
+
class User(BaseModel):
|
|
42
|
+
name: Name
|
|
43
|
+
email: Email
|
|
44
|
+
|
|
45
|
+
class UserFactory(ModelFactory[User]):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
user = UserFactory.build()
|
|
49
|
+
print(user.name)
|
|
50
|
+
print(user.email)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Example output (varies each run):
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Paul Blair
|
|
57
|
+
linda00@example.net
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**With dataclasses** (no Pydantic needed):
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from dataclasses import dataclass
|
|
64
|
+
from capper import Name, Email
|
|
65
|
+
from polyfactory.factories import DataclassFactory
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class User:
|
|
69
|
+
name: Name
|
|
70
|
+
email: Email
|
|
71
|
+
|
|
72
|
+
class UserFactory(DataclassFactory[User]):
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
user = UserFactory.build()
|
|
76
|
+
print(user.name)
|
|
77
|
+
print(user.email)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Example output (varies each run):
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Carly Jenkins
|
|
84
|
+
oevans@example.com
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Works automatically. No extra steps. IDE autocompletion.
|
|
88
|
+
|
|
89
|
+
## Available types
|
|
90
|
+
|
|
91
|
+
- **Person**: `Name`, `FirstName`, `LastName`, `Job`
|
|
92
|
+
- **Geo**: `Address`, `City`, `Country`
|
|
93
|
+
- **Internet**: `Email`, `URL`, `IP`, `UserName`
|
|
94
|
+
- **Commerce**: `Company`, `Product`, `Currency`, `Price`
|
|
95
|
+
- **Date/time**: `Date`, `DateTime`, `Time`
|
|
96
|
+
- **Text**: `Paragraph`, `Sentence`
|
|
97
|
+
- **Phone**: `PhoneNumber`, `CountryCallingCode`
|
|
98
|
+
- **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
|
|
99
|
+
|
|
100
|
+
Import from the top level: `from capper import Name, Email, Address, ...`
|
|
101
|
+
See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
|
|
102
|
+
|
|
103
|
+
**Optional kwargs:** Subclass `FakerType` and set `faker_kwargs` to pass arguments to the Faker provider (e.g. `faker_kwargs = {"nb_words": 10}` for `Sentence`).
|
|
104
|
+
|
|
105
|
+
**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
|
+
|
|
107
|
+
## Compatibility
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install -e ".[dev]"
|
|
115
|
+
pytest capper/tests
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
119
|
+
|
|
120
|
+
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from capper import seed, Name
|
|
124
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
125
|
+
from pydantic import BaseModel
|
|
126
|
+
|
|
127
|
+
class User(BaseModel):
|
|
128
|
+
name: Name
|
|
129
|
+
|
|
130
|
+
class UserFactory(ModelFactory[User]):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
# Either way seeds the shared Faker (same effect):
|
|
134
|
+
seed(42)
|
|
135
|
+
user1 = UserFactory.build()
|
|
136
|
+
|
|
137
|
+
UserFactory.seed_random(42)
|
|
138
|
+
user2 = UserFactory.build() # same data as user1 if you seed the same before each
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
## Publishing
|
|
144
|
+
|
|
145
|
+
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
146
|
+
|
|
147
|
+
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.
|
|
149
|
+
|
|
150
|
+
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
151
|
+
|
|
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
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Capper: semantic Faker types with automatic Polyfactory integration.
|
|
2
|
+
|
|
3
|
+
Import types (e.g. ``Name``, ``Email``) and use them in Pydantic models, dataclasses,
|
|
4
|
+
or attrs; Polyfactory will generate values via Faker. Use ``seed(n)`` for reproducibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import FakerType, faker, seed
|
|
8
|
+
from .commerce import Company, Currency, Price, Product
|
|
9
|
+
from .date_time import Date, DateTime, Time
|
|
10
|
+
from .finance import CreditCardExpiry, CreditCardNumber, CreditCardProvider
|
|
11
|
+
from .geo import Address, City, Country
|
|
12
|
+
from .internet import Email, IP, URL, UserName
|
|
13
|
+
from .person import FirstName, Job, LastName, Name
|
|
14
|
+
from .phone import CountryCallingCode, PhoneNumber
|
|
15
|
+
from .text import Paragraph, Sentence
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Address",
|
|
19
|
+
"City",
|
|
20
|
+
"Company",
|
|
21
|
+
"Country",
|
|
22
|
+
"CountryCallingCode",
|
|
23
|
+
"CreditCardExpiry",
|
|
24
|
+
"CreditCardNumber",
|
|
25
|
+
"CreditCardProvider",
|
|
26
|
+
"Currency",
|
|
27
|
+
"Date",
|
|
28
|
+
"DateTime",
|
|
29
|
+
"Email",
|
|
30
|
+
"FakerType",
|
|
31
|
+
"FirstName",
|
|
32
|
+
"IP",
|
|
33
|
+
"Job",
|
|
34
|
+
"LastName",
|
|
35
|
+
"Name",
|
|
36
|
+
"Paragraph",
|
|
37
|
+
"PhoneNumber",
|
|
38
|
+
"Price",
|
|
39
|
+
"Product",
|
|
40
|
+
"Sentence",
|
|
41
|
+
"Time",
|
|
42
|
+
"URL",
|
|
43
|
+
"UserName",
|
|
44
|
+
"faker",
|
|
45
|
+
"seed",
|
|
46
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""FakerType base class and shared Faker instance with automatic Polyfactory registration.
|
|
2
|
+
|
|
3
|
+
The module-level ``faker`` is attached to Polyfactory's BaseFactory so that one seed
|
|
4
|
+
controls both capper types and built-in types. Use ``seed(n)`` for reproducible data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from faker import Faker
|
|
10
|
+
from polyfactory.factories.base import BaseFactory
|
|
11
|
+
|
|
12
|
+
faker = Faker()
|
|
13
|
+
|
|
14
|
+
# Share this Faker with Polyfactory so one seed controls both capper types and built-in types.
|
|
15
|
+
BaseFactory.__faker__ = faker
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def seed(seed_value: int) -> None:
|
|
19
|
+
"""Seed the shared Faker instance for reproducible data.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
seed_value: Integer seed; same value produces the same sequence of values.
|
|
23
|
+
"""
|
|
24
|
+
faker.seed_instance(seed_value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _install_pydantic_schema() -> None:
|
|
28
|
+
"""If Pydantic is available, attach __get_pydantic_core_schema__ to FakerType."""
|
|
29
|
+
try:
|
|
30
|
+
from pydantic import GetCoreSchemaHandler
|
|
31
|
+
from pydantic_core import CoreSchema, core_schema
|
|
32
|
+
except ImportError:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
def __get_pydantic_core_schema__(
|
|
36
|
+
source_type: Any, handler: GetCoreSchemaHandler
|
|
37
|
+
) -> CoreSchema:
|
|
38
|
+
"""Validate as str then coerce to the FakerType subclass."""
|
|
39
|
+
return core_schema.no_info_after_validator_function(source_type, handler(str))
|
|
40
|
+
|
|
41
|
+
FakerType.__get_pydantic_core_schema__ = __get_pydantic_core_schema__ # type: ignore[attr-defined]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FakerType(str):
|
|
45
|
+
"""Base class for semantic Faker types; subclasses are auto-registered with Polyfactory.
|
|
46
|
+
|
|
47
|
+
Subclasses must set a non-empty ``faker_provider`` (the Faker method name).
|
|
48
|
+
Optional ``faker_kwargs`` is a dict of keyword arguments passed to that provider
|
|
49
|
+
(e.g. ``faker_kwargs = {"nb_words": 10}`` for ``sentence``).
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
faker_provider: str = ""
|
|
53
|
+
|
|
54
|
+
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
55
|
+
super().__init_subclass__(**kwargs)
|
|
56
|
+
provider = getattr(cls, "faker_provider", None)
|
|
57
|
+
if provider:
|
|
58
|
+
_register(cls, provider)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_install_pydantic_schema()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _register(cls: type, provider_name: str) -> None:
|
|
65
|
+
"""Register a FakerType subclass with Polyfactory so factories can generate values."""
|
|
66
|
+
provider_kwargs = getattr(cls, "faker_kwargs", None) or {}
|
|
67
|
+
|
|
68
|
+
def _provide() -> str:
|
|
69
|
+
return getattr(faker, provider_name)(**provider_kwargs)
|
|
70
|
+
|
|
71
|
+
BaseFactory.add_provider(cls, _provide)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Commerce-related semantic Faker types (company, product, currency, price)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Company(FakerType):
|
|
7
|
+
"""Company name. Uses Faker provider ``company``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "company"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Product(FakerType):
|
|
13
|
+
"""Product/catch phrase. Uses Faker provider ``catch_phrase``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "catch_phrase"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Currency(FakerType):
|
|
19
|
+
"""Currency code. Uses Faker provider ``currency_code``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "currency_code"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Price(FakerType):
|
|
25
|
+
"""Price tag string. Uses Faker provider ``pricetag``."""
|
|
26
|
+
|
|
27
|
+
faker_provider = "pricetag"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Date/time-related semantic Faker types (date, datetime, time as strings)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Date(FakerType):
|
|
7
|
+
"""Date string. Uses Faker provider ``date``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "date"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DateTime(FakerType):
|
|
13
|
+
"""ISO 8601 datetime string. Uses Faker provider ``iso8601``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "iso8601"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Time(FakerType):
|
|
19
|
+
"""Time string. Uses Faker provider ``time``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "time"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Example: Pydantic model with capper types and Polyfactory ModelFactory.
|
|
2
|
+
|
|
3
|
+
Run: python -m capper.examples.user_factory
|
|
4
|
+
Output varies each run; use capper.seed(n) for reproducible data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
9
|
+
|
|
10
|
+
from capper import Email, Name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class User(BaseModel):
|
|
14
|
+
"""Example Pydantic model with capper types."""
|
|
15
|
+
|
|
16
|
+
name: Name
|
|
17
|
+
email: Email
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UserFactory(ModelFactory[User]):
|
|
21
|
+
"""Polyfactory factory for User; uses capper's Faker for name and email."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
user = UserFactory.build()
|
|
28
|
+
print(user.name)
|
|
29
|
+
print(user.email)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Finance / credit card semantic Faker types."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreditCardNumber(FakerType):
|
|
7
|
+
"""Credit card number string. Uses Faker provider ``credit_card_number``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "credit_card_number"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CreditCardExpiry(FakerType):
|
|
13
|
+
"""Credit card expiry string. Uses Faker provider ``credit_card_expire``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "credit_card_expire"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CreditCardProvider(FakerType):
|
|
19
|
+
"""Credit card provider name. Uses Faker provider ``credit_card_provider``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "credit_card_provider"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Geography-related semantic Faker types (address, city, country)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Address(FakerType):
|
|
7
|
+
"""Street address. Uses Faker provider ``address``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "address"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class City(FakerType):
|
|
13
|
+
"""City name. Uses Faker provider ``city``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "city"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Country(FakerType):
|
|
19
|
+
"""Country name. Uses Faker provider ``country``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "country"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Internet-related semantic Faker types (email, URL, IP, username)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Email(FakerType):
|
|
7
|
+
"""Email address. Uses Faker provider ``email``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "email"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class URL(FakerType):
|
|
13
|
+
"""URL. Uses Faker provider ``url``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "url"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IP(FakerType):
|
|
19
|
+
"""IPv4 address. Uses Faker provider ``ipv4``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "ipv4"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UserName(FakerType):
|
|
25
|
+
"""Username. Uses Faker provider ``user_name``."""
|
|
26
|
+
|
|
27
|
+
faker_provider = "user_name"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Person-related semantic Faker types (name, job)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Name(FakerType):
|
|
7
|
+
"""Full name. Uses Faker provider ``name``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "name"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FirstName(FakerType):
|
|
13
|
+
"""First/given name. Uses Faker provider ``first_name``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "first_name"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LastName(FakerType):
|
|
19
|
+
"""Last/family name. Uses Faker provider ``last_name``."""
|
|
20
|
+
|
|
21
|
+
faker_provider = "last_name"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Job(FakerType):
|
|
25
|
+
"""Job title. Uses Faker provider ``job``."""
|
|
26
|
+
|
|
27
|
+
faker_provider = "job"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Phone-related semantic Faker types (phone number, country calling code)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PhoneNumber(FakerType):
|
|
7
|
+
"""Phone number string. Uses Faker provider ``phone_number``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "phone_number"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CountryCallingCode(FakerType):
|
|
13
|
+
"""Country calling code. Uses Faker provider ``country_calling_code``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "country_calling_code"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Capper test suite."""
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tests for Polyfactory integration: ModelFactory, DataclassFactory, shared Faker, auto-registration."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from polyfactory.factories import DataclassFactory
|
|
7
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
8
|
+
|
|
9
|
+
from capper import Email, Name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class User(BaseModel):
|
|
13
|
+
name: Name
|
|
14
|
+
email: Email
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UserFactory(ModelFactory[User]):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_user_factory_builds_with_capper_types() -> None:
|
|
22
|
+
"""Builds a User with Name and Email via ModelFactory; asserts non-empty and valid email."""
|
|
23
|
+
user = UserFactory.build()
|
|
24
|
+
assert isinstance(user.name, (str, Name))
|
|
25
|
+
assert isinstance(user.email, (str, Email))
|
|
26
|
+
assert len(user.name) > 0
|
|
27
|
+
assert "@" in user.email
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_user_factory_builds_batch() -> None:
|
|
31
|
+
"""Builds a batch of users; asserts count and that each has non-empty name and email."""
|
|
32
|
+
users = UserFactory.batch(3)
|
|
33
|
+
assert len(users) == 3
|
|
34
|
+
for user in users:
|
|
35
|
+
assert isinstance(user, User)
|
|
36
|
+
assert len(user.name) > 0
|
|
37
|
+
assert "@" in user.email
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_seed_random_and_capper_seed_produce_same_value() -> None:
|
|
41
|
+
"""Builds with UserFactory.seed_random(42) and with capper.seed(42) yield the same name."""
|
|
42
|
+
from capper import seed
|
|
43
|
+
|
|
44
|
+
UserFactory.seed_random(42)
|
|
45
|
+
user_after_seed_random = UserFactory.build()
|
|
46
|
+
seed(42)
|
|
47
|
+
user_after_capper_seed = UserFactory.build()
|
|
48
|
+
assert user_after_seed_random.name == user_after_capper_seed.name
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_model_factory_uses_capper_faker() -> None:
|
|
52
|
+
"""Asserts ModelFactory.__faker__ is capper's faker to document the shared instance."""
|
|
53
|
+
import capper
|
|
54
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
55
|
+
|
|
56
|
+
assert ModelFactory.__faker__ is capper.faker
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
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
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class Person:
|
|
64
|
+
name: Name
|
|
65
|
+
email: Email
|
|
66
|
+
|
|
67
|
+
class PersonFactory(DataclassFactory[Person]):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
person = PersonFactory.build()
|
|
71
|
+
assert isinstance(person.name, (str, Name))
|
|
72
|
+
assert isinstance(person.email, (str, Email))
|
|
73
|
+
assert len(person.name) > 0
|
|
74
|
+
assert "@" in person.email
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_dataclass_factory_batch() -> None:
|
|
78
|
+
"""Builds a batch of dataclass instances; asserts count and non-empty name and email."""
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class Person:
|
|
82
|
+
name: Name
|
|
83
|
+
email: Email
|
|
84
|
+
|
|
85
|
+
class PersonFactory(DataclassFactory[Person]):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
people = PersonFactory.batch(2)
|
|
89
|
+
assert len(people) == 2
|
|
90
|
+
for person in people:
|
|
91
|
+
assert isinstance(person, Person)
|
|
92
|
+
assert len(person.name) > 0
|
|
93
|
+
assert "@" in person.email
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
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
|
|
99
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
100
|
+
from pydantic import BaseModel
|
|
101
|
+
|
|
102
|
+
class Contact(BaseModel):
|
|
103
|
+
phone: PhoneNumber
|
|
104
|
+
|
|
105
|
+
class ContactFactory(ModelFactory[Contact]):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
contact = ContactFactory.build()
|
|
109
|
+
assert isinstance(contact.phone, (str, PhoneNumber))
|
|
110
|
+
assert len(contact.phone) > 0
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Tests for capper types: Faker provider availability, ModelFactory registration, kwargs, seed."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from capper import (
|
|
6
|
+
Address,
|
|
7
|
+
City,
|
|
8
|
+
Company,
|
|
9
|
+
Country,
|
|
10
|
+
CountryCallingCode,
|
|
11
|
+
CreditCardExpiry,
|
|
12
|
+
CreditCardNumber,
|
|
13
|
+
CreditCardProvider,
|
|
14
|
+
Currency,
|
|
15
|
+
Date,
|
|
16
|
+
DateTime,
|
|
17
|
+
Email,
|
|
18
|
+
FirstName,
|
|
19
|
+
IP,
|
|
20
|
+
Job,
|
|
21
|
+
LastName,
|
|
22
|
+
Name,
|
|
23
|
+
Paragraph,
|
|
24
|
+
PhoneNumber,
|
|
25
|
+
Price,
|
|
26
|
+
Product,
|
|
27
|
+
Sentence,
|
|
28
|
+
Time,
|
|
29
|
+
URL,
|
|
30
|
+
UserName,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize(
|
|
35
|
+
"type_class",
|
|
36
|
+
[
|
|
37
|
+
Name,
|
|
38
|
+
FirstName,
|
|
39
|
+
LastName,
|
|
40
|
+
Job,
|
|
41
|
+
Address,
|
|
42
|
+
City,
|
|
43
|
+
Country,
|
|
44
|
+
Email,
|
|
45
|
+
URL,
|
|
46
|
+
IP,
|
|
47
|
+
UserName,
|
|
48
|
+
Company,
|
|
49
|
+
Product,
|
|
50
|
+
Currency,
|
|
51
|
+
Price,
|
|
52
|
+
Date,
|
|
53
|
+
DateTime,
|
|
54
|
+
Time,
|
|
55
|
+
Paragraph,
|
|
56
|
+
Sentence,
|
|
57
|
+
PhoneNumber,
|
|
58
|
+
CountryCallingCode,
|
|
59
|
+
CreditCardNumber,
|
|
60
|
+
CreditCardExpiry,
|
|
61
|
+
CreditCardProvider,
|
|
62
|
+
],
|
|
63
|
+
)
|
|
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)."""
|
|
66
|
+
from faker import Faker
|
|
67
|
+
|
|
68
|
+
faker = Faker()
|
|
69
|
+
provider_name = getattr(type_class, "faker_provider")
|
|
70
|
+
value = getattr(faker, provider_name)()
|
|
71
|
+
assert isinstance(value, (str, type_class))
|
|
72
|
+
assert len(str(value)) > 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize(
|
|
76
|
+
"type_class",
|
|
77
|
+
[Name, Email, PhoneNumber, FirstName, Address, Sentence, CreditCardNumber],
|
|
78
|
+
)
|
|
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
|
|
82
|
+
|
|
83
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
84
|
+
from pydantic import create_model
|
|
85
|
+
|
|
86
|
+
model_cls: type[Any] = create_model("Model", value=(type_class, ...))
|
|
87
|
+
|
|
88
|
+
class ModelFactoryCls(ModelFactory[model_cls]): # type: ignore[valid-type]
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
instance: Any = ModelFactoryCls.build()
|
|
92
|
+
assert isinstance(instance.value, (str, type_class))
|
|
93
|
+
assert len(str(instance.value)) > 0
|
|
94
|
+
|
|
95
|
+
|
|
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."""
|
|
98
|
+
from capper.base import FakerType
|
|
99
|
+
|
|
100
|
+
class ShortSentence(FakerType):
|
|
101
|
+
faker_provider = "sentence"
|
|
102
|
+
faker_kwargs = {"nb_words": 5}
|
|
103
|
+
|
|
104
|
+
from faker import Faker
|
|
105
|
+
|
|
106
|
+
faker = Faker()
|
|
107
|
+
value = getattr(faker, "sentence")(**ShortSentence.faker_kwargs)
|
|
108
|
+
assert isinstance(value, str) and len(value) > 0
|
|
109
|
+
|
|
110
|
+
# Polyfactory uses the same kwargs via registration
|
|
111
|
+
from polyfactory.factories.pydantic_factory import ModelFactory as PFModelFactory
|
|
112
|
+
from pydantic import BaseModel
|
|
113
|
+
|
|
114
|
+
class FakerKwargsModel(BaseModel):
|
|
115
|
+
text: ShortSentence
|
|
116
|
+
|
|
117
|
+
class FakerKwargsModelFactory(PFModelFactory[FakerKwargsModel]):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
instance = FakerKwargsModelFactory.build()
|
|
121
|
+
assert isinstance(instance.text, (str, ShortSentence)) and len(instance.text) > 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_seed_reproducibility(seeded_faker: None) -> None:
|
|
125
|
+
"""Builds twice after seeding with same value; asserts identical generated name."""
|
|
126
|
+
from capper import Name, seed
|
|
127
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
128
|
+
from pydantic import BaseModel
|
|
129
|
+
|
|
130
|
+
class User(BaseModel):
|
|
131
|
+
name: Name
|
|
132
|
+
|
|
133
|
+
class UserFactory(ModelFactory[User]):
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
seed(42)
|
|
137
|
+
user1 = UserFactory.build()
|
|
138
|
+
seed(42)
|
|
139
|
+
user2 = UserFactory.build()
|
|
140
|
+
assert user1.name == user2.name
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Text-related semantic Faker types (paragraph, sentence)."""
|
|
2
|
+
|
|
3
|
+
from .base import FakerType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Paragraph(FakerType):
|
|
7
|
+
"""Paragraph of text. Uses Faker provider ``paragraph``."""
|
|
8
|
+
|
|
9
|
+
faker_provider = "paragraph"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Sentence(FakerType):
|
|
13
|
+
"""Single sentence. Uses Faker provider ``sentence``."""
|
|
14
|
+
|
|
15
|
+
faker_provider = "sentence"
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
# Capper
|
|
20
|
+
|
|
21
|
+
[](https://pypi.org/project/capper/)
|
|
22
|
+
[](https://pypi.org/project/capper/)
|
|
23
|
+
[](https://github.com/eddiethedean/capper/actions/workflows/publish.yml)
|
|
24
|
+
[](https://docs.astral.sh/ruff/)
|
|
25
|
+
[](https://mypy-lang.org/)
|
|
26
|
+
|
|
27
|
+
Semantic, typed wrappers for [Faker](https://faker.readthedocs.io/) with automatic [Polyfactory](https://polyfactory.litestar.dev/) integration.
|
|
28
|
+
|
|
29
|
+
**Source:** [github.com/eddiethedean/capper](https://github.com/eddiethedean/capper)
|
|
30
|
+
|
|
31
|
+
## Why Capper?
|
|
32
|
+
|
|
33
|
+
- **Zero config** — Import a type; Polyfactory uses the right Faker provider. No manual registration.
|
|
34
|
+
- **Typed** — Use `Name`, `Email`, `PhoneNumber`, etc. in your models for clear intent and IDE support.
|
|
35
|
+
- **Multi-backend** — Works with Pydantic, dataclasses, attrs, and other [Polyfactory-supported](https://polyfactory.litestar.dev/) model types.
|
|
36
|
+
- **Optional Pydantic** — Install `capper` alone for dataclasses/attrs; add `capper[pydantic]` when you use Pydantic models.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install capper
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires **Python 3.9+**, **Faker >= 20.0**, and **Polyfactory >= 2.0**. For **Pydantic** models, install the optional extra:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install capper[pydantic]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
**With Pydantic** (requires `capper[pydantic]`):
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from pydantic import BaseModel
|
|
56
|
+
from capper import Name, Email
|
|
57
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
58
|
+
|
|
59
|
+
class User(BaseModel):
|
|
60
|
+
name: Name
|
|
61
|
+
email: Email
|
|
62
|
+
|
|
63
|
+
class UserFactory(ModelFactory[User]):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
user = UserFactory.build()
|
|
67
|
+
print(user.name)
|
|
68
|
+
print(user.email)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Example output (varies each run):
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Paul Blair
|
|
75
|
+
linda00@example.net
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**With dataclasses** (no Pydantic needed):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from dataclasses import dataclass
|
|
82
|
+
from capper import Name, Email
|
|
83
|
+
from polyfactory.factories import DataclassFactory
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class User:
|
|
87
|
+
name: Name
|
|
88
|
+
email: Email
|
|
89
|
+
|
|
90
|
+
class UserFactory(DataclassFactory[User]):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
user = UserFactory.build()
|
|
94
|
+
print(user.name)
|
|
95
|
+
print(user.email)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Example output (varies each run):
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Carly Jenkins
|
|
102
|
+
oevans@example.com
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Works automatically. No extra steps. IDE autocompletion.
|
|
106
|
+
|
|
107
|
+
## Available types
|
|
108
|
+
|
|
109
|
+
- **Person**: `Name`, `FirstName`, `LastName`, `Job`
|
|
110
|
+
- **Geo**: `Address`, `City`, `Country`
|
|
111
|
+
- **Internet**: `Email`, `URL`, `IP`, `UserName`
|
|
112
|
+
- **Commerce**: `Company`, `Product`, `Currency`, `Price`
|
|
113
|
+
- **Date/time**: `Date`, `DateTime`, `Time`
|
|
114
|
+
- **Text**: `Paragraph`, `Sentence`
|
|
115
|
+
- **Phone**: `PhoneNumber`, `CountryCallingCode`
|
|
116
|
+
- **Finance**: `CreditCardNumber`, `CreditCardExpiry`, `CreditCardProvider`
|
|
117
|
+
|
|
118
|
+
Import from the top level: `from capper import Name, Email, Address, ...`
|
|
119
|
+
See [docs/FAKER_PROVIDERS.md](https://github.com/eddiethedean/capper/blob/main/docs/FAKER_PROVIDERS.md) for the Faker provider used by each type.
|
|
120
|
+
|
|
121
|
+
**Optional kwargs:** Subclass `FakerType` and set `faker_kwargs` to pass arguments to the Faker provider (e.g. `faker_kwargs = {"nb_words": 10}` for `Sentence`).
|
|
122
|
+
|
|
123
|
+
**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
|
+
|
|
125
|
+
## Compatibility
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
pytest capper/tests
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run tests with coverage: `pytest capper/tests --cov=capper --cov-report=term-missing`.
|
|
137
|
+
|
|
138
|
+
**Reproducibility:** Capper and Polyfactory share the same Faker instance, so one seed controls both capper types and built-in types (`str`, `int`, etc.):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from capper import seed, Name
|
|
142
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
143
|
+
from pydantic import BaseModel
|
|
144
|
+
|
|
145
|
+
class User(BaseModel):
|
|
146
|
+
name: Name
|
|
147
|
+
|
|
148
|
+
class UserFactory(ModelFactory[User]):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Either way seeds the shared Faker (same effect):
|
|
152
|
+
seed(42)
|
|
153
|
+
user1 = UserFactory.build()
|
|
154
|
+
|
|
155
|
+
UserFactory.seed_random(42)
|
|
156
|
+
user2 = UserFactory.build() # same data as user1 if you seed the same before each
|
|
157
|
+
```
|
|
158
|
+
|
|
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.
|
|
160
|
+
|
|
161
|
+
## Publishing
|
|
162
|
+
|
|
163
|
+
Releases are built and published to PyPI via [GitHub Actions](https://github.com/eddiethedean/capper/blob/main/.github/workflows/publish.yml). To publish:
|
|
164
|
+
|
|
165
|
+
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.
|
|
167
|
+
|
|
168
|
+
To build and upload manually: `pip install build twine`, `python -m build`, `twine upload dist/*`.
|
|
169
|
+
|
|
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
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
capper/__init__.py
|
|
4
|
+
capper/base.py
|
|
5
|
+
capper/commerce.py
|
|
6
|
+
capper/date_time.py
|
|
7
|
+
capper/finance.py
|
|
8
|
+
capper/geo.py
|
|
9
|
+
capper/internet.py
|
|
10
|
+
capper/person.py
|
|
11
|
+
capper/phone.py
|
|
12
|
+
capper/text.py
|
|
13
|
+
capper.egg-info/PKG-INFO
|
|
14
|
+
capper.egg-info/SOURCES.txt
|
|
15
|
+
capper.egg-info/dependency_links.txt
|
|
16
|
+
capper.egg-info/requires.txt
|
|
17
|
+
capper.egg-info/top_level.txt
|
|
18
|
+
capper/examples/user_factory.py
|
|
19
|
+
capper/tests/__init__.py
|
|
20
|
+
capper/tests/conftest.py
|
|
21
|
+
capper/tests/test_polyfactory_integration.py
|
|
22
|
+
capper/tests/test_types.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
capper
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "capper"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Semantic, typed wrappers for Faker with automatic Polyfactory integration"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Odos Matthews", email = "odosmatthews@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"Faker>=20.0",
|
|
16
|
+
"Polyfactory>=2.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
pydantic = [
|
|
21
|
+
"pydantic>=2.0",
|
|
22
|
+
]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=7.0",
|
|
25
|
+
"pytest-cov>=4.0",
|
|
26
|
+
"pydantic>=2.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Documentation = "https://github.com/eddiethedean/capper#readme"
|
|
31
|
+
Repository = "https://github.com/eddiethedean/capper"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["."]
|
|
35
|
+
include = ["capper*"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["capper/tests"]
|
|
39
|
+
pythonpath = ["."]
|
|
40
|
+
addopts = ["-v", "--tb=short"]
|
capper-0.1.0/setup.cfg
ADDED