├── .all-contributorsrc
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── BUG.yaml
│ ├── DOCS.yaml
│ ├── REQUEST.yaml
│ └── config.yml
├── dependabot.yaml
└── workflows
│ ├── ci.yml
│ ├── docs-preview.yml
│ ├── docs.yml
│ ├── pr-title.yml
│ └── publish.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .sourcery.yaml
├── CONTRIBUTING.rst
├── LICENSE
├── Makefile
├── README.md
├── codecov.yml
├── docs
├── PYPI_README.md
├── __init__.py
├── _static
│ └── logo.svg
├── changelog.rst
├── conf.py
├── contribution-guide.rst
├── examples
│ ├── __init__.py
│ ├── configuration
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ ├── test_example_10.py
│ │ ├── test_example_2.py
│ │ ├── test_example_3.py
│ │ ├── test_example_4.py
│ │ ├── test_example_5.py
│ │ ├── test_example_6.py
│ │ ├── test_example_7.py
│ │ ├── test_example_8.py
│ │ └── test_example_9.py
│ ├── creating_base_factories
│ │ └── __init__.py
│ ├── declaring_factories
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ ├── test_example_2.py
│ │ ├── test_example_3.py
│ │ ├── test_example_4.py
│ │ ├── test_example_5.py
│ │ ├── test_example_6.py
│ │ ├── test_example_7.py
│ │ └── test_example_8.py
│ ├── decorators
│ │ └── test_example_1.py
│ ├── fields
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ ├── test_example_2.py
│ │ ├── test_example_3.py
│ │ ├── test_example_4.py
│ │ ├── test_example_5.py
│ │ ├── test_example_6.py
│ │ ├── test_example_7.py
│ │ ├── test_example_8.py
│ │ └── test_example_sqla_pre_fetched_data.py
│ ├── fixtures
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ ├── test_example_2.py
│ │ └── test_example_3.py
│ ├── handling_custom_types
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ ├── test_example_2.py
│ │ └── test_example_3.py
│ ├── library_factories
│ │ ├── __init__.py
│ │ └── sqlalchemy_factory
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_example_1.py
│ │ │ ├── test_example_2.py
│ │ │ ├── test_example_3.py
│ │ │ ├── test_example_4.py
│ │ │ ├── test_example_5.py
│ │ │ └── test_example_association_proxy.py
│ └── model_coverage
│ │ ├── __init__.py
│ │ ├── test_example_1.py
│ │ └── test_example_2.py
├── getting-started.rst
├── index.rst
├── reference
│ ├── constants.rst
│ ├── decorators.rst
│ ├── exceptions.rst
│ ├── factories
│ │ ├── attrs_factory.rst
│ │ ├── base.rst
│ │ ├── beanie_odm_factory.rst
│ │ ├── dataclass_factory.rst
│ │ ├── index.rst
│ │ ├── msgspec_factory.rst
│ │ ├── odmantic_odm_factory.rst
│ │ ├── pydantic_factory.rst
│ │ ├── sqlalchemy_factory.rst
│ │ └── typed_dict_factory.rst
│ ├── field_meta.rst
│ ├── fields.rst
│ ├── index.rst
│ ├── persistence.rst
│ ├── pytest_plugin.rst
│ └── value_generators
│ │ ├── complex_types.rst
│ │ ├── constrained_collections.rst
│ │ ├── constrained_dates.rst
│ │ ├── constrained_numbers.rst
│ │ ├── constrained_strings.rst
│ │ ├── index.rst
│ │ └── primitives.rst
├── releases.rst
└── usage
│ ├── configuration.rst
│ ├── declaring_factories.rst
│ ├── decorators.rst
│ ├── fields.rst
│ ├── fixtures.rst
│ ├── handling_custom_types.rst
│ ├── index.rst
│ ├── library_factories
│ ├── index.rst
│ └── sqlalchemy_factory.rst
│ └── model_coverage.rst
├── polyfactory
├── __init__.py
├── __metadata__.py
├── collection_extender.py
├── constants.py
├── decorators.py
├── exceptions.py
├── factories
│ ├── __init__.py
│ ├── attrs_factory.py
│ ├── base.py
│ ├── beanie_odm_factory.py
│ ├── dataclass_factory.py
│ ├── msgspec_factory.py
│ ├── odmantic_odm_factory.py
│ ├── pydantic_factory.py
│ ├── sqlalchemy_factory.py
│ └── typed_dict_factory.py
├── field_meta.py
├── fields.py
├── persistence.py
├── py.typed
├── pytest_plugin.py
├── utils
│ ├── __init__.py
│ ├── _internal.py
│ ├── deprecation.py
│ ├── helpers.py
│ ├── model_coverage.py
│ ├── predicates.py
│ └── types.py
└── value_generators
│ ├── __init__.py
│ ├── complex_types.py
│ ├── constrained_collections.py
│ ├── constrained_dates.py
│ ├── constrained_numbers.py
│ ├── constrained_path.py
│ ├── constrained_strings.py
│ ├── constrained_url.py
│ ├── constrained_uuid.py
│ ├── primitives.py
│ └── regex.py
├── pyproject.toml
├── tests
├── __init__.py
├── conftest.py
├── constraints
│ ├── test_byte_constraints.py
│ ├── test_date_constraints.py
│ ├── test_decimal_constraints.py
│ ├── test_float_constraints.py
│ ├── test_frozen_set_constraints.py
│ ├── test_get_field_value_constraints.py
│ ├── test_int_constraints.py
│ ├── test_list_constraints.py
│ ├── test_mapping_constraints.py
│ ├── test_set_constraints.py
│ └── test_string_constraints.py
├── models.py
├── sqlalchemy_factory
│ ├── __init__.py
│ ├── conftest.py
│ ├── models.py
│ ├── test_association_proxy.py
│ ├── test_sqlalchemy_factory_common.py
│ └── test_sqlalchemy_factory_v2.py
├── test_attrs_factory.py
├── test_auto_registration.py
├── test_base_factories.py
├── test_beanie_factory.py
├── test_build.py
├── test_collection_extender.py
├── test_collection_length.py
├── test_complex_types.py
├── test_data_parsing.py
├── test_dataclass_factory.py
├── test_dicts.py
├── test_factory_configuration.py
├── test_factory_fields.py
├── test_factory_subclassing.py
├── test_faker_customization.py
├── test_generics.py
├── test_msgspec_factory.py
├── test_new_types.py
├── test_number_generation.py
├── test_odmantic_factory.py
├── test_optional_model_field_inference.py
├── test_options_validation.py
├── test_passing_build_args_to_child_factories.py
├── test_persistence_handlers.py
├── test_provider_map.py
├── test_pydantic_factory.py
├── test_pydantic_v1_v2.py
├── test_pytest_plugin.py
├── test_random_configuration.py
├── test_random_seed.py
├── test_recursive_models.py
├── test_regex_factory.py
├── test_type_coverage_generation.py
├── test_typeddict_factory.py
├── test_utils.py
├── typing_test_strict.py
└── utils
│ ├── test_deprecation.py
│ └── test_frozendict.py
├── tools
├── build_docs.py
├── convert_docs.sh
└── prepare-release.sh
├── typos.toml
└── uv.lock
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Code owner settings for `litestar`
2 | # @maintainers should be assigned to all reviews.
3 | # Most specific assignment takes precedence though, so if you add a more specific thing than the `*` glob, you must also add @maintainers
4 | # For more info about code owners see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-example
5 |
6 | # Global Assignment
7 | * @litestar-org/maintainers @litestar-org/members
8 |
9 | # Documentation
10 | docs/* @litestar-org/maintainers @JacobCoffee @provinzkraut
11 |
12 | # Polyfactory
13 | * @guacs
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG.yaml:
--------------------------------------------------------------------------------
1 | name: "Bug Report"
2 | description: Create an issue for a bug.
3 | title: "Bug:
"
4 | labels: ["bug", "triage required"]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: "Description"
10 | description: Please enter an description of the bug you are encountering
11 | placeholder:
12 | validations:
13 | required: true
14 | - type: input
15 | id: reprod-url
16 | attributes:
17 | label: "URL to code causing the issue"
18 | description: Please enter the URL to provide a reproduction of the issue, if applicable
19 | placeholder: ex. https://github.com/USERNAME/REPO-NAME
20 | validations:
21 | required: false
22 | - type: textarea
23 | id: mcve
24 | attributes:
25 | label: "MCVE"
26 | description: "Please provide a minimal, complete, and verifiable example of the issue."
27 | value: |
28 | ```py
29 | # Your MCVE code here
30 | ```
31 | render: python
32 | validations:
33 | required: false
34 | - type: textarea
35 | id: reprod
36 | attributes:
37 | label: "Steps to reproduce"
38 | description: Please enter the exact steps to reproduce the issue
39 | value: |
40 | 1. Go to '...'
41 | 2. Click on '....'
42 | 3. Scroll down to '....'
43 | 4. See error
44 | render: bash
45 | validations:
46 | required: false
47 | - type: textarea
48 | id: screenshot
49 | attributes:
50 | label: "Screenshots"
51 | description: If applicable, add screenshots to help explain your problem.
52 | value: |
53 | "In the format of: ``"
54 | validations:
55 | required: false
56 | - type: textarea
57 | id: logs
58 | attributes:
59 | label: "Logs"
60 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
61 | render: bash
62 | validations:
63 | required: false
64 | - type: textarea
65 | id: version
66 | attributes:
67 | label: "Release Version"
68 | description: What version of the project are you using when encountering this issue?
69 | validations:
70 | required: true
71 | - type: checkboxes
72 | id: platform
73 | attributes:
74 | label: "Platform"
75 | description: What platform are you encountering the issue on?
76 | options:
77 | - label: "Linux"
78 | - label: "Mac"
79 | - label: "Windows"
80 | - label: "Other (Please specify in the description above)"
81 | validations:
82 | required: false
83 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/DOCS.yaml:
--------------------------------------------------------------------------------
1 | name: "Documentation Update"
2 | description: Create an issue for documentation changes
3 | title: "Docs: "
4 | labels: ["documentation"]
5 | body:
6 | - type: textarea
7 | id: summary
8 | attributes:
9 | label: "Summary"
10 | description: Provide a brief summary of your feature request
11 | placeholder: Describe in a few lines your feature request
12 | validations:
13 | required: true
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/REQUEST.yaml:
--------------------------------------------------------------------------------
1 | name: "Feature Request"
2 | description: Create an issue for a new feature request
3 | title: "Enhancement: "
4 | labels: ["enhancement"]
5 | body:
6 | - type: textarea
7 | id: summary
8 | attributes:
9 | label: "Summary"
10 | description: Provide a brief summary of your feature request
11 | placeholder: Describe in a few lines your feature request
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: basic_example
16 | attributes:
17 | label: "Basic Example"
18 | description: Indicate here some basic examples of your feature.
19 | placeholder: Provide some basic example of your feature request
20 | validations:
21 | required: false
22 | - type: textarea
23 | id: drawbacks
24 | attributes:
25 | label: "Drawbacks and Impact"
26 | description: What are the drawbacks or impacts of your feature request?
27 | placeholder: Describe any the drawbacks or impacts of your feature request
28 | validations:
29 | required: false
30 | - type: textarea
31 | id: unresolved_question
32 | attributes:
33 | label: "Unresolved questions"
34 | description: What, if any, unresolved questions do you have about your feature request?
35 | placeholder: Identify any unresolved issues.
36 | validations:
37 | required: false
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_lines_enabled: true
2 | contact_links:
3 | - name: Polyfactory Documentation
4 | url: https://polyfactory.litestar.dev/
5 | about: Official Polyfactory documentation - please check here before opening an issue.
6 | - name: Litestar Documentation
7 | url: https://docs.litestar.dev/
8 | about: Official Litestar documentation - please check here before opening an issue.
9 | - name: Litestar Website
10 | url: https://litestar.dev/
11 | about: Main Litestar website - for details about Litestar's projects.
12 | - name: Discord
13 | url: https://discord.gg/MmcwxztmQb
14 | about: Join our Discord community to chat or get in touch with the maintainers.
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.github/workflows/docs-preview.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Documentation Preview
2 |
3 | on:
4 | workflow_run:
5 | workflows: [Tests And Linting]
6 | types: [completed]
7 |
8 | jobs:
9 | deploy:
10 | if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
11 | runs-on: ubuntu-latest
12 | permissions:
13 | issues: write
14 | pull-requests: write
15 |
16 | steps:
17 | - name: Check out repository
18 | uses: actions/checkout@v4
19 |
20 | - name: Download artifact
21 | uses: dawidd6/action-download-artifact@v10
22 | with:
23 | workflow_conclusion: success
24 | run_id: ${{ github.event.workflow_run.id }}
25 | path: docs-preview
26 | name: docs-preview
27 |
28 | - name: Set PR number
29 | run: echo "PR_NUMBER=$(cat docs-preview/.pr_number)" >> $GITHUB_ENV
30 |
31 | - name: Deploy docs preview
32 | uses: JamesIves/github-pages-deploy-action@v4
33 | with:
34 | folder: docs-preview/docs/_build/html
35 | token: ${{ secrets.DOCS_PREVIEW_DEPLOY_TOKEN }}
36 | repository-name: litestar-org/polyfactory-docs-preview
37 | clean: false
38 | target-folder: ${{ env.PR_NUMBER }}
39 | branch: gh-pages
40 |
41 | - uses: actions/github-script@v7
42 | env:
43 | PR_NUMBER: ${{ env.PR_NUMBER }}
44 | with:
45 | script: |
46 | const issue_number = process.env.PR_NUMBER
47 | const body = "Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/" + issue_number
48 |
49 | const opts = github.rest.issues.listComments.endpoint.merge({
50 | owner: context.repo.owner,
51 | repo: context.repo.repo,
52 | issue_number: issue_number,
53 | });
54 |
55 | const comments = await github.paginate(opts)
56 |
57 | for (const comment of comments) {
58 | if (comment.user.id === 41898282 && comment.body === body) {
59 | await github.rest.issues.deleteComment({
60 | owner: context.repo.owner,
61 | repo: context.repo.repo,
62 | comment_id: comment.id
63 | })
64 | }
65 | }
66 |
67 | await github.rest.issues.createComment({
68 | owner: context.repo.owner,
69 | repo: context.repo.repo,
70 | issue_number: issue_number,
71 | body: body,
72 | })
73 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Documentation Building
2 |
3 | on:
4 | release:
5 | types: [published]
6 | push:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | docs:
12 | permissions:
13 | contents: write
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - uses: actions/setup-python@v5
19 | with:
20 | python-version: "3.13"
21 |
22 | - name: Install uv
23 | uses: astral-sh/setup-uv@v6
24 | with:
25 | version: "0.5.4"
26 | enable-cache: true
27 |
28 | - name: Install dependencies
29 | run: uv sync --all-extras
30 |
31 | - name: Fetch gh pages
32 | run: git fetch origin gh-pages --depth=1
33 |
34 | - name: Build release docs
35 | run: uv run python tools/build_docs.py docs-build
36 | if: github.event_name == 'release'
37 |
38 | - name: Build dev docs
39 | run: uv run python tools/build_docs.py docs-build
40 | if: github.event_name == 'push'
41 |
42 | - name: Deploy
43 | uses: JamesIves/github-pages-deploy-action@v4
44 | with:
45 | folder: docs-build
46 |
--------------------------------------------------------------------------------
/.github/workflows/pr-title.yml:
--------------------------------------------------------------------------------
1 | name: "Lint PR Title"
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | permissions:
11 | pull-requests: read
12 |
13 | jobs:
14 | main:
15 | name: Validate PR title
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: amannn/action-semantic-pull-request@v5
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Latest Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | publish-release:
10 | name: upload release to PyPI
11 | runs-on: ubuntu-latest
12 | permissions:
13 | id-token: write
14 | environment: release
15 | steps:
16 | - name: Check out repository
17 | uses: actions/checkout@v4
18 |
19 | - uses: actions/setup-python@v5
20 | with:
21 | python-version: "3.13"
22 |
23 | - name: Install uv
24 | uses: astral-sh/setup-uv@v6
25 | with:
26 | version: "0.5.4"
27 | enable-cache: true
28 |
29 | - name: Build package
30 | run: uv build
31 |
32 | - name: Publish package distributions to PyPI
33 | uses: pypa/gh-action-pypi-publish@release/v1
34 |
35 | build-docs:
36 | permissions:
37 | contents: write
38 | runs-on: ubuntu-latest
39 | steps:
40 | - name: Check out repository
41 | uses: actions/checkout@v4
42 |
43 | - name: Set up Python
44 | uses: actions/setup-python@v5
45 | with:
46 | python-version: "3.13"
47 |
48 | - name: Install uv
49 | uses: astral-sh/setup-uv@v6
50 | with:
51 | version: "0.5.4"
52 | enable-cache: true
53 |
54 | - name: Install dependencies
55 | run: uv sync --all-extras
56 |
57 | - name: Build docs
58 | run: uv run make docs
59 |
60 | - name: Deploy
61 | uses: JamesIves/github-pages-deploy-action@v4
62 | with:
63 | folder: docs/_build/html
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # folders
2 | __pycache__/
3 | .auto_pytabs_cache/
4 | .hypothesis/
5 | .idea/
6 | .mypy_cache/
7 | .pytest_cache/
8 | .scannerwork/
9 | .venv/
10 | .vscode/
11 | *.egg-info/
12 | build/
13 | dist/
14 | node_modules/
15 | results/
16 | site/
17 | target/
18 |
19 | # files
20 | **/*.so
21 | **/*.sqlite
22 | **/*.sqlite*
23 | *.iml
24 | .DS_Store
25 | .coverage
26 | .python-version
27 | .ruff_cache
28 | /docs/_build/
29 | coverage.*
30 | setup.py
31 |
32 | # pdm
33 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
34 | #pdm.lock
35 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
36 | # in version control.
37 | # https://pdm.fming.dev/#use-with-ide
38 | .pdm.toml
39 |
40 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
41 | __pypackages__/
42 |
43 | /.pdm-python
44 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | default_language_version:
2 | python: "3.13"
3 | repos:
4 | - repo: https://github.com/compilerla/conventional-pre-commit
5 | rev: v4.0.0
6 | hooks:
7 | - id: conventional-pre-commit
8 | stages: [commit-msg]
9 | - repo: https://github.com/pre-commit/pre-commit-hooks
10 | rev: v5.0.0
11 | hooks:
12 | - id: check-ast
13 | - id: check-case-conflict
14 | - id: check-merge-conflict
15 | - id: check-toml
16 | - id: debug-statements
17 | - id: end-of-file-fixer
18 | - id: mixed-line-ending
19 | - id: trailing-whitespace
20 | - repo: https://github.com/charliermarsh/ruff-pre-commit
21 | rev: "v0.11.0"
22 | hooks:
23 | - id: ruff
24 | args: ["--fix"]
25 | - id: ruff-format
26 | - repo: https://github.com/crate-ci/typos
27 | rev: v1.30.3
28 | hooks:
29 | - id: typos
30 | - repo: https://github.com/pycontribs/mirrors-prettier
31 | rev: "v3.5.3"
32 | hooks:
33 | - id: prettier
34 | exclude: ".all-contributorsrc"
35 | - repo: https://github.com/ComPWA/taplo-pre-commit
36 | rev: v0.9.3
37 | hooks:
38 | - id: taplo-format
39 | exclude: "uv.lock"
40 | - repo: https://github.com/pre-commit/mirrors-mypy
41 | rev: "v1.15.0"
42 | hooks:
43 | - id: mypy
44 | exclude: "test_decimal_constraints|examples/fields/test_example_2|examples/configuration|tools/"
45 | additional_dependencies:
46 | [
47 | attrs>=22.2.0,
48 | beanie,
49 | faker,
50 | hypothesis,
51 | mongomock_motor,
52 | msgspec,
53 | odmantic<1,
54 | pydantic>=2,
55 | pytest,
56 | sphinx,
57 | sqlalchemy>=2,
58 | ]
59 | - repo: https://github.com/RobertCraigie/pyright-python
60 | rev: v1.1.397
61 | hooks:
62 | - id: pyright
63 | exclude: "tests"
64 | additional_dependencies:
65 | [
66 | attrs>=22.2.0,
67 | beanie,
68 | faker,
69 | hypothesis,
70 | mongomock_motor,
71 | msgspec,
72 | odmantic<1,
73 | pydantic>=2,
74 | pytest,
75 | sphinx,
76 | sqlalchemy>=2,
77 | ]
78 | - repo: https://github.com/sphinx-contrib/sphinx-lint
79 | rev: "v1.0.0"
80 | hooks:
81 | - id: sphinx-lint
82 |
--------------------------------------------------------------------------------
/.sourcery.yaml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - .tox/
3 | - .venv/
4 | - dist/
5 | - docs/_build/
6 | - docs/_static/
7 | - node_modules/
8 | - vendor/
9 | - venv/
10 |
11 | rule_settings:
12 | enable: [default]
13 | disable: [dont-import-test-modules]
14 | rule_types:
15 | - refactoring
16 | - suggestion
17 | - comment
18 | python_version: "3.8"
19 |
20 | rules: []
21 |
22 | metrics:
23 | quality_threshold: 25.0
24 |
25 | github:
26 | ignore_labels:
27 | - sourcery-ignore
28 | - docs
29 | labels:
30 | - build-ignore
31 | request_review:
32 | origin: owner
33 | forked: author
34 | sourcery_branch: sourcery/{base_branch}
35 |
36 | clone_detection:
37 | min_lines: 3
38 | min_duplicates: 2
39 | identical_clones_only: false
40 |
41 | proxy:
42 | no_ssl_verify: false
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021, 2022, 2023 Litestar Org.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | target: auto
6 | threshold: 0.1%
7 | patch:
8 | default:
9 | target: auto
10 | comment:
11 | require_changes: true
12 |
--------------------------------------------------------------------------------
/docs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/__init__.py
--------------------------------------------------------------------------------
/docs/_static/logo.svg:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/docs/contribution-guide.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | .. include:: ../CONTRIBUTING.rst
4 |
--------------------------------------------------------------------------------
/docs/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/__init__.py
--------------------------------------------------------------------------------
/docs/examples/configuration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/configuration/__init__.py
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from polyfactory.factories import DataclassFactory
4 |
5 |
6 | @dataclass
7 | class Person:
8 | name: str
9 | age: float
10 | height: float
11 | weight: float
12 |
13 |
14 | class PersonFactory(DataclassFactory[Person]):
15 | __random_seed__ = 1
16 |
17 | @classmethod
18 | def name(cls) -> str:
19 | return cls.__random__.choice(["John", "Alice", "George"])
20 |
21 |
22 | def test_random_seed() -> None:
23 | # the outcome of 'factory.__random__.choice' is deterministic, because Random has been seeded with a set value.
24 | assert PersonFactory.build().name == "John"
25 | assert PersonFactory.build().name == "George"
26 | assert PersonFactory.build().name == "John"
27 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_10.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, Field
2 |
3 | from polyfactory.factories.pydantic_factory import ModelFactory
4 |
5 |
6 | class Payment(BaseModel):
7 | amount: int = Field(0)
8 | currency: str = Field(examples=["USD", "EUR", "INR"])
9 |
10 |
11 | class PaymentFactory(ModelFactory[Payment]):
12 | __use_examples__ = True
13 |
14 |
15 | def test_use_examples() -> None:
16 | instance = PaymentFactory.build()
17 | assert instance.currency in ["USD", "EUR", "INR"]
18 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_2.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from random import Random
3 |
4 | from polyfactory.factories import DataclassFactory
5 |
6 |
7 | @dataclass
8 | class Person:
9 | name: str
10 | age: float
11 | height: float
12 | weight: float
13 |
14 |
15 | class PersonFactory(DataclassFactory[Person]):
16 | __random__ = Random(10)
17 |
18 | @classmethod
19 | def name(cls) -> str:
20 | return cls.__random__.choice(["John", "Alice", "George"])
21 |
22 |
23 | def test_setting_random() -> None:
24 | # the outcome of 'factory.__random__.choice' is deterministic, because Random is configured with a set value.
25 | assert PersonFactory.build().name == "George"
26 | assert PersonFactory.build().name == "John"
27 | assert PersonFactory.build().name == "Alice"
28 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_3.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from faker import Faker
4 |
5 | from polyfactory.factories import DataclassFactory
6 |
7 |
8 | @dataclass
9 | class Person:
10 | name: str
11 | age: float
12 | height: float
13 | weight: float
14 |
15 |
16 | class PersonFactory(DataclassFactory[Person]):
17 | __faker__ = Faker(locale="es_ES")
18 |
19 | __random_seed__ = 10
20 |
21 | @classmethod
22 | def name(cls) -> str:
23 | return cls.__faker__.name()
24 |
25 |
26 | def test_setting_faker() -> None:
27 | # the outcome of faker deterministic because we seeded random, and it uses a spanish locale.
28 | assert PersonFactory.build().name == "Alejandra Romeu-Tolosa"
29 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_4.py:
--------------------------------------------------------------------------------
1 | from asyncio import sleep
2 | from dataclasses import dataclass
3 | from typing import Dict, List
4 | from uuid import UUID
5 |
6 | from polyfactory import AsyncPersistenceProtocol, SyncPersistenceProtocol
7 | from polyfactory.factories import DataclassFactory
8 |
9 |
10 | @dataclass
11 | class Person:
12 | id: UUID
13 | name: str
14 |
15 |
16 | # we will use a dictionary to persist values for the example
17 | mock_db: Dict[UUID, Person] = {}
18 |
19 |
20 | class SyncPersistenceHandler(SyncPersistenceProtocol[Person]):
21 | def save(self, data: Person) -> Person:
22 | # do stuff here to persist the value, such as use an ORM or ODM, cache in redis etc.
23 | # in our case we simply save it in the dictionary.
24 | mock_db[data.id] = data
25 | return data
26 |
27 | def save_many(self, data: List[Person]) -> List[Person]:
28 | # same as for save, here we should store the list in persistence.
29 | # in this case, we use the same dictionary.
30 | for person in data:
31 | mock_db[person.id] = person
32 | return data
33 |
34 |
35 | class AsyncPersistenceHandler(AsyncPersistenceProtocol[Person]):
36 | async def save(self, data: Person) -> Person:
37 | # do stuff here to persist the value using an async method, such as an async ORM or ODM.
38 | # in our case we simply save it in the dictionary and add a minimal sleep to mock async.
39 | mock_db[data.id] = data
40 | await sleep(0.0001)
41 | return data
42 |
43 | async def save_many(self, data: List[Person]) -> List[Person]:
44 | # same as for the async save, here we should store the list in persistence using async logic.
45 | # we again store in dict, and mock async using sleep.
46 | for person in data:
47 | mock_db[person.id] = person
48 | await sleep(0.0001)
49 | return data
50 |
51 |
52 | class PersonFactory(DataclassFactory[Person]):
53 | __sync_persistence__ = SyncPersistenceHandler
54 | __async_persistence__ = AsyncPersistenceHandler
55 |
56 |
57 | def test_sync_persistence_build() -> None:
58 | person_instance = PersonFactory.create_sync()
59 | assert mock_db[person_instance.id] is person_instance
60 |
61 |
62 | def test_sync_persistence_batch() -> None:
63 | person_batch = PersonFactory.create_batch_sync(10)
64 | for person_instance in person_batch:
65 | assert mock_db[person_instance.id] is person_instance
66 |
67 |
68 | async def test_async_persistence_build() -> None:
69 | person_instance = await PersonFactory.create_async()
70 | assert mock_db[person_instance.id] is person_instance
71 |
72 |
73 | async def test_async_persistence_batch() -> None:
74 | person_batch = await PersonFactory.create_batch_async(10)
75 | for person_instance in person_batch:
76 | assert mock_db[person_instance.id] is person_instance
77 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_5.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory import Use
8 | from polyfactory.factories import DataclassFactory
9 |
10 |
11 | class Species(str, Enum):
12 | CAT = "Cat"
13 | DOG = "Dog"
14 |
15 |
16 | @dataclass
17 | class Pet:
18 | name: str
19 | species: Species
20 | sound: str
21 |
22 |
23 | @dataclass
24 | class Person:
25 | id: UUID
26 | name: str
27 | hobbies: List[str]
28 | age: Union[float, int]
29 | birthday: Union[datetime, date]
30 | pets: List[Pet]
31 | assets: List[Dict[str, Dict[str, Any]]]
32 |
33 |
34 | class PetFactory(DataclassFactory[Pet]):
35 | __set_as_default_factory_for_type__ = True
36 |
37 | name = Use(DataclassFactory.__random__.choice, ["Roxy", "Spammy", "Moshe"])
38 |
39 |
40 | class PersonFactory(DataclassFactory[Person]): ...
41 |
42 |
43 | def test_default_pet_factory() -> None:
44 | person_instance = PersonFactory.build()
45 | assert len(person_instance.pets) > 0
46 | assert person_instance.pets[0].name in ["Roxy", "Spammy", "Moshe"]
47 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_6.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Tuple
3 |
4 | from polyfactory.factories import DataclassFactory
5 |
6 |
7 | @dataclass
8 | class Owner:
9 | cars: Tuple[str, ...]
10 |
11 |
12 | class OwnerFactory(DataclassFactory[Owner]):
13 | __randomize_collection_length__ = True
14 | __min_collection_length__ = 2
15 | __max_collection_length__ = 5
16 |
17 |
18 | def test_randomized_collection_length() -> None:
19 | owner = OwnerFactory.build()
20 | assert 2 <= len(owner.cars) <= 5
21 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_7.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Optional
3 | from uuid import UUID
4 |
5 | from polyfactory.factories.dataclass_factory import DataclassFactory
6 |
7 |
8 | @dataclass
9 | class Person:
10 | id: UUID
11 | name: Optional[str]
12 |
13 |
14 | class PersonFactory(DataclassFactory[Person]):
15 | __allow_none_optionals__ = False
16 |
17 |
18 | def test_optional_type_ignored() -> None:
19 | person_instance = PersonFactory.build()
20 | assert isinstance(person_instance.name, str)
21 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_8.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from uuid import UUID
3 |
4 | import pytest
5 |
6 | from polyfactory import ConfigurationException, PostGenerated
7 | from polyfactory.factories.dataclass_factory import DataclassFactory
8 |
9 |
10 | @dataclass
11 | class Person:
12 | id: UUID
13 |
14 |
15 | def test_check_factory_fields() -> None:
16 | with pytest.raises(
17 | ConfigurationException,
18 | match="unknown_field is declared on the factory PersonFactory but it is not part of the model Person",
19 | ):
20 |
21 | class PersonFactory(DataclassFactory[Person]):
22 | __check_model__ = True
23 | unknown_field = PostGenerated(lambda: "foo")
24 |
--------------------------------------------------------------------------------
/docs/examples/configuration/test_example_9.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from enum import Enum
3 |
4 | from polyfactory.factories import DataclassFactory
5 |
6 |
7 | class Species(str, Enum):
8 | CAT = "Cat"
9 | DOG = "Dog"
10 |
11 |
12 | @dataclass
13 | class Pet:
14 | name: str
15 | sound: str = "meow"
16 | species: Species = Species.CAT
17 |
18 |
19 | class PetFactory(DataclassFactory[Pet]):
20 | __model__ = Pet
21 | __use_defaults__ = True
22 |
23 |
24 | def test_use_default() -> None:
25 | pet = PetFactory.build()
26 |
27 | assert pet.species == Species.CAT
28 | assert pet.sound == "meow"
29 |
--------------------------------------------------------------------------------
/docs/examples/creating_base_factories/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/creating_base_factories/__init__.py
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/declaring_factories/__init__.py
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from polyfactory.factories import DataclassFactory
4 |
5 |
6 | @dataclass
7 | class Person:
8 | name: str
9 | age: float
10 | height: float
11 | weight: float
12 |
13 |
14 | class PersonFactory(DataclassFactory[Person]):
15 | __model__ = Person
16 |
17 |
18 | def test_is_person() -> None:
19 | person_instance = PersonFactory.build()
20 | assert isinstance(person_instance, Person)
21 | assert isinstance(person_instance.name, str)
22 | assert isinstance(person_instance.age, float)
23 | assert isinstance(person_instance.height, float)
24 | assert isinstance(person_instance.weight, float)
25 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_2.py:
--------------------------------------------------------------------------------
1 | from typing import TypedDict
2 |
3 | from polyfactory.factories import TypedDictFactory
4 |
5 |
6 | class Person(TypedDict):
7 | name: str
8 | age: float
9 | height: float
10 | weight: float
11 |
12 |
13 | class PersonFactory(TypedDictFactory[Person]): ...
14 |
15 |
16 | def test_is_person() -> None:
17 | person_instance = PersonFactory.build()
18 | assert isinstance(person_instance, dict)
19 | assert isinstance(person_instance.get("name"), str)
20 | assert isinstance(person_instance.get("age"), float)
21 | assert isinstance(person_instance.get("height"), float)
22 | assert isinstance(person_instance.get("weight"), float)
23 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_3.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 | from polyfactory.factories.pydantic_factory import ModelFactory
4 |
5 |
6 | class Person(BaseModel):
7 | name: str
8 | age: float
9 | height: float
10 | weight: float
11 |
12 |
13 | class PersonFactory(ModelFactory[Person]): ...
14 |
15 |
16 | def test_is_person() -> None:
17 | person_instance = PersonFactory.build()
18 | assert isinstance(person_instance, Person)
19 | assert isinstance(person_instance.name, str)
20 | assert isinstance(person_instance.age, float)
21 | assert isinstance(person_instance.height, float)
22 | assert isinstance(person_instance.weight, float)
23 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_4.py:
--------------------------------------------------------------------------------
1 | from pydantic.dataclasses import dataclass
2 |
3 | from polyfactory.factories import DataclassFactory
4 |
5 |
6 | @dataclass
7 | class Person:
8 | name: str
9 | age: float
10 | height: float
11 | weight: float
12 |
13 |
14 | class PersonFactory(DataclassFactory[Person]): ...
15 |
16 |
17 | def test_is_person() -> None:
18 | person_instance = PersonFactory.build()
19 | assert isinstance(person_instance, Person)
20 | assert isinstance(person_instance.name, str)
21 | assert isinstance(person_instance.age, float)
22 | assert isinstance(person_instance.height, float)
23 | assert isinstance(person_instance.weight, float)
24 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_5.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory.factories import DataclassFactory
8 |
9 |
10 | class Species(str, Enum):
11 | CAT = "Cat"
12 | DOG = "Dog"
13 |
14 |
15 | @dataclass
16 | class Pet:
17 | name: str
18 | species: Species
19 | sound: str
20 |
21 |
22 | @dataclass
23 | class Person:
24 | id: UUID
25 | name: str
26 | hobbies: List[str]
27 | age: Union[float, int]
28 | birthday: Union[datetime, date]
29 | pets: List[Pet]
30 | assets: List[Dict[str, Dict[str, Any]]]
31 |
32 |
33 | class PersonFactory(DataclassFactory[Person]): ...
34 |
35 |
36 | def test_dynamic_factory_generation() -> None:
37 | person_instance = PersonFactory.build()
38 | assert len(person_instance.pets) > 0
39 | assert isinstance(person_instance.pets[0], Pet)
40 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_6.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from enum import Enum
3 |
4 | from polyfactory.factories import DataclassFactory
5 |
6 |
7 | class Species(str, Enum):
8 | CAT = "Cat"
9 | DOG = "Dog"
10 |
11 |
12 | @dataclass
13 | class Pet:
14 | name: str
15 | species: Species
16 | sound: str
17 |
18 |
19 | def test_imperative_factory_creation() -> None:
20 | pet_factory = DataclassFactory.create_factory(model=Pet)
21 | pet_instance = pet_factory.build()
22 | assert isinstance(pet_instance, Pet)
23 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_7.py:
--------------------------------------------------------------------------------
1 | from datetime import date, datetime
2 | from typing import Any, Dict, List, Union
3 | from uuid import UUID
4 |
5 | import attrs
6 |
7 | from polyfactory.factories.attrs_factory import AttrsFactory
8 |
9 |
10 | @attrs.define
11 | class Person:
12 | id: UUID
13 | name: str
14 | hobbies: List[str]
15 | age: Union[float, int]
16 | # an aliased variable
17 | birthday: Union[datetime, date] = attrs.field(alias="date_of_birth")
18 | # a "private" variable
19 | _assets: List[Dict[str, Dict[str, Any]]]
20 |
21 |
22 | class PersonFactory(AttrsFactory[Person]): ...
23 |
24 |
25 | def test_person_factory() -> None:
26 | person = PersonFactory.build()
27 |
28 | assert isinstance(person, Person)
29 |
--------------------------------------------------------------------------------
/docs/examples/declaring_factories/test_example_8.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from enum import Enum
3 |
4 | from polyfactory.factories import DataclassFactory
5 |
6 |
7 | class Species(str, Enum):
8 | CAT = "Cat"
9 | DOG = "Dog"
10 | RABBIT = "Rabbit"
11 | MOUSE = "Mouse"
12 |
13 |
14 | @dataclass
15 | class Pet:
16 | name: str
17 | species: Species
18 | sound: str
19 |
20 |
21 | def test_imperative_sub_factory_creation() -> None:
22 | pet_factory = DataclassFactory.create_factory(model=Pet)
23 | cat_factory = pet_factory.create_factory(species=Species.CAT)
24 | cat_instance = cat_factory.build()
25 |
26 | assert isinstance(cat_instance, Pet)
27 | assert cat_instance.species == Species.CAT
28 |
--------------------------------------------------------------------------------
/docs/examples/decorators/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 | from datetime import datetime, timedelta
3 |
4 | from polyfactory.decorators import post_generated
5 | from polyfactory.factories import DataclassFactory
6 |
7 |
8 | @dataclass
9 | class DatetimeRange:
10 | to_dt: datetime
11 | from_dt: datetime = field(default_factory=datetime.now)
12 |
13 |
14 | class DatetimeRangeFactory(DataclassFactory[DatetimeRange]):
15 | @post_generated
16 | @classmethod
17 | def to_dt(cls, from_dt: datetime) -> datetime:
18 | return from_dt + cls.__faker__.time_delta("+3d")
19 |
20 |
21 | def test_post_generated() -> None:
22 | date_range_instance = DatetimeRangeFactory.build()
23 | assert date_range_instance.to_dt > date_range_instance.from_dt
24 | assert date_range_instance.to_dt < date_range_instance.from_dt + timedelta(days=3)
25 |
--------------------------------------------------------------------------------
/docs/examples/fields/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/fields/__init__.py
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory.factories import DataclassFactory
8 |
9 |
10 | class Species(str, Enum):
11 | CAT = "Cat"
12 | DOG = "Dog"
13 |
14 |
15 | @dataclass
16 | class Pet:
17 | name: str
18 | species: Species
19 | sound: str
20 |
21 |
22 | @dataclass
23 | class Person:
24 | id: UUID
25 | name: str
26 | hobbies: List[str]
27 | age: Union[float, int]
28 | birthday: Union[datetime, date]
29 | pets: List[Pet]
30 | assets: List[Dict[str, Dict[str, Any]]]
31 |
32 |
33 | pet_instance = Pet(name="Roxy", sound="woof woof", species=Species.DOG)
34 |
35 |
36 | class PersonFactory(DataclassFactory[Person]):
37 | pets = [pet_instance]
38 |
39 |
40 | def test_is_pet_instance() -> None:
41 | person_instance = PersonFactory.build()
42 | assert len(person_instance.pets) == 1
43 | assert person_instance.pets[0] == pet_instance
44 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_2.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory import Use
8 | from polyfactory.factories import DataclassFactory
9 |
10 |
11 | class Species(str, Enum):
12 | CAT = "Cat"
13 | DOG = "Dog"
14 |
15 |
16 | @dataclass
17 | class Pet:
18 | name: str
19 | species: Species
20 | sound: str
21 |
22 |
23 | @dataclass
24 | class Person:
25 | id: UUID
26 | name: str
27 | hobbies: List[str]
28 | age: Union[float, int]
29 | birthday: Union[datetime, date]
30 | pets: List[Pet]
31 | assets: List[Dict[str, Dict[str, Any]]]
32 |
33 |
34 | class PetFactory(DataclassFactory[Pet]):
35 | name = Use(DataclassFactory.__random__.choice, ["Ralph", "Roxy"])
36 | species = Use(DataclassFactory.__random__.choice, list(Species))
37 |
38 |
39 | class PersonFactory(DataclassFactory[Person]):
40 | pets = Use(PetFactory.batch, size=2)
41 |
42 |
43 | def test_pet_choices() -> None:
44 | person_instance = PersonFactory.build()
45 |
46 | assert len(person_instance.pets) == 2
47 | assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
48 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_3.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory import Use
8 | from polyfactory.factories import DataclassFactory
9 |
10 |
11 | class Species(str, Enum):
12 | CAT = "Cat"
13 | DOG = "Dog"
14 |
15 |
16 | @dataclass
17 | class Pet:
18 | name: str
19 | species: Species
20 | sound: str
21 |
22 |
23 | @dataclass
24 | class Person:
25 | id: UUID
26 | name: str
27 | hobbies: List[str]
28 | age: Union[float, int]
29 | birthday: Union[datetime, date]
30 | pets: List[Pet]
31 | assets: List[Dict[str, Dict[str, Any]]]
32 |
33 |
34 | class PetFactory(DataclassFactory[Pet]):
35 | name = lambda: DataclassFactory.__random__.choice(["Ralph", "Roxy"])
36 | species = lambda: DataclassFactory.__random__.choice(list(Species))
37 |
38 |
39 | class PersonFactory(DataclassFactory[Person]):
40 | pets = Use(PetFactory.batch, size=2)
41 |
42 |
43 | def test_pet_choices() -> None:
44 | person_instance = PersonFactory.build()
45 |
46 | assert len(person_instance.pets) == 2
47 | assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
48 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_4.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory import Use
8 | from polyfactory.factories import DataclassFactory
9 |
10 |
11 | class Species(str, Enum):
12 | CAT = "Cat"
13 | DOG = "Dog"
14 |
15 |
16 | @dataclass
17 | class Pet:
18 | name: str
19 | species: Species
20 | sound: str
21 |
22 |
23 | @dataclass
24 | class Person:
25 | id: UUID
26 | name: str
27 | hobbies: List[str]
28 | age: Union[float, int]
29 | birthday: Union[datetime, date]
30 | pets: List[Pet]
31 | assets: List[Dict[str, Dict[str, Any]]]
32 |
33 |
34 | class PetFactory(DataclassFactory[Pet]):
35 | @classmethod
36 | def name(cls) -> str:
37 | return cls.__random__.choice(["Ralph", "Roxy"])
38 |
39 | @classmethod
40 | def species(cls) -> str:
41 | return cls.__random__.choice(list(Species))
42 |
43 |
44 | class PersonFactory(DataclassFactory[Person]):
45 | pets = Use(PetFactory.batch, size=2)
46 |
47 |
48 | def test_pet_choices() -> None:
49 | person_instance = PersonFactory.build()
50 |
51 | assert len(person_instance.pets) == 2
52 | assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
53 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_5.py:
--------------------------------------------------------------------------------
1 | from typing import TypedDict
2 |
3 | from polyfactory import Ignore
4 | from polyfactory.factories import TypedDictFactory
5 |
6 |
7 | class Person(TypedDict):
8 | id: int
9 | name: str
10 |
11 |
12 | class PersonFactory(TypedDictFactory[Person]):
13 | id = Ignore()
14 |
15 |
16 | def test_id_is_ignored() -> None:
17 | person_instance = PersonFactory.build()
18 |
19 | assert person_instance.get("name")
20 | assert person_instance.get("id") is None
21 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_6.py:
--------------------------------------------------------------------------------
1 | from typing import TypedDict
2 |
3 | import pytest
4 |
5 | from polyfactory import Require
6 | from polyfactory.exceptions import MissingBuildKwargException
7 | from polyfactory.factories import TypedDictFactory
8 |
9 |
10 | class Person(TypedDict):
11 | id: int
12 | name: str
13 |
14 |
15 | class PersonFactory(TypedDictFactory[Person]):
16 | id = Require()
17 |
18 |
19 | def test_id_is_required() -> None:
20 | # this will not raise an exception
21 | person_instance = PersonFactory.build(id=1)
22 |
23 | assert person_instance.get("name")
24 | assert person_instance.get("id") == 1
25 |
26 | # but when no kwarg is passed, an exception will be raised:
27 | with pytest.raises(MissingBuildKwargException):
28 | PersonFactory.build()
29 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_7.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 | from datetime import datetime, timedelta
3 | from typing import Any, Dict
4 |
5 | from polyfactory import PostGenerated
6 | from polyfactory.factories import DataclassFactory
7 |
8 |
9 | def add_timedelta(name: str, values: Dict[str, datetime], *args: Any, **kwargs: Any) -> datetime:
10 | delta = timedelta(days=1)
11 | return values["from_dt"] + delta
12 |
13 |
14 | @dataclass
15 | class DatetimeRange:
16 | to_dt: datetime
17 | from_dt: datetime = field(default_factory=datetime.now)
18 |
19 |
20 | class DatetimeRangeFactory(DataclassFactory[DatetimeRange]):
21 | to_dt = PostGenerated(add_timedelta)
22 |
23 |
24 | def test_post_generated() -> None:
25 | date_range_instance = DatetimeRangeFactory.build()
26 | assert date_range_instance.to_dt == date_range_instance.from_dt + timedelta(days=1)
27 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_8.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from enum import Enum
4 | from typing import Any, Dict, List, Union
5 | from uuid import UUID
6 |
7 | from polyfactory.factories import DataclassFactory
8 |
9 |
10 | class Species(str, Enum):
11 | CAT = "Cat"
12 | DOG = "Dog"
13 |
14 |
15 | @dataclass
16 | class Pet:
17 | name: str
18 | species: Species
19 | sound: str
20 |
21 |
22 | @dataclass
23 | class Person:
24 | id: UUID
25 | name: str
26 | hobbies: List[str]
27 | age: Union[float, int]
28 | birthday: Union[datetime, date]
29 | pet: Pet
30 | assets: List[Dict[str, Dict[str, Any]]]
31 |
32 |
33 | class PetFactory(DataclassFactory[Pet]):
34 | name = lambda: DataclassFactory.__random__.choice(["Ralph", "Roxy"])
35 |
36 |
37 | class PersonFactory(DataclassFactory[Person]):
38 | pet = PetFactory
39 |
40 |
41 | def test_subfactory() -> None:
42 | person_instance = PersonFactory.build()
43 |
44 | assert isinstance(person_instance.pet, Pet)
45 | assert person_instance.pet.name in ["Ralph", "Roxy"]
46 |
47 | person_instance_with_pet_name = PersonFactory.build(pet={"name": "Winston"})
48 | assert person_instance_with_pet_name.pet.name == "Winston"
49 |
--------------------------------------------------------------------------------
/docs/examples/fields/test_example_sqla_pre_fetched_data.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from sqlalchemy import ForeignKey, select
4 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
5 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
6 |
7 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
8 |
9 | async_engine = create_async_engine("sqlite+aiosqlite:///:memory:")
10 |
11 |
12 | class Base(DeclarativeBase): ...
13 |
14 |
15 | class User(Base):
16 | __tablename__ = "users"
17 |
18 | id: Mapped[int] = mapped_column(primary_key=True)
19 |
20 |
21 | class Department(Base):
22 | __tablename__ = "departments"
23 |
24 | id: Mapped[int] = mapped_column(primary_key=True)
25 | director_id: Mapped[str] = mapped_column(ForeignKey("users.id"))
26 |
27 |
28 | class UserFactory(SQLAlchemyFactory[User]): ...
29 |
30 |
31 | class DepartmentFactory(SQLAlchemyFactory[Department]): ...
32 |
33 |
34 | async def get_director_ids() -> int:
35 | async with AsyncSession(async_engine) as session:
36 | result = (await session.scalars(select(User.id))).all()
37 | return UserFactory.__random__.choice(result)
38 |
39 |
40 | async def test_factory_with_pre_fetched_async_data() -> None:
41 | async with async_engine.begin() as conn:
42 | await conn.run_sync(Base.metadata.drop_all)
43 | await conn.run_sync(Base.metadata.create_all)
44 |
45 | async with AsyncSession(async_engine) as session:
46 | UserFactory.__async_session__ = session
47 | await UserFactory.create_batch_async(3)
48 |
49 | async with AsyncSession(async_engine) as session:
50 | DepartmentFactory.__async_session__ = session
51 | department = await DepartmentFactory.create_async(director_id=await get_director_ids())
52 | user = await session.scalar(select(User).where(User.id == department.director_id))
53 | assert isinstance(user, User)
54 |
--------------------------------------------------------------------------------
/docs/examples/fixtures/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/fixtures/__init__.py
--------------------------------------------------------------------------------
/docs/examples/fixtures/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from typing import List, Optional, Union
4 | from uuid import UUID
5 |
6 | from polyfactory.factories import DataclassFactory
7 | from polyfactory.pytest_plugin import register_fixture
8 |
9 |
10 | @dataclass
11 | class Person:
12 | id: UUID
13 | name: str
14 | hobbies: Optional[List[str]]
15 | nicks: List[str]
16 | age: Union[float, int]
17 | birthday: Union[datetime, date]
18 |
19 |
20 | @register_fixture
21 | class PersonFactory(DataclassFactory[Person]): ...
22 |
23 |
24 | def test_person_factory(person_factory: PersonFactory) -> None:
25 | person_instance = person_factory.build()
26 | assert isinstance(person_instance, Person)
27 |
28 | # The factory class itself can still be used
29 | another_person_instance = PersonFactory.build()
30 | assert isinstance(another_person_instance, Person)
31 |
--------------------------------------------------------------------------------
/docs/examples/fixtures/test_example_2.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from typing import List, Optional, Union
4 | from uuid import UUID
5 |
6 | from polyfactory.factories import DataclassFactory
7 | from polyfactory.pytest_plugin import register_fixture
8 |
9 |
10 | @dataclass
11 | class Person:
12 | id: UUID
13 | name: str
14 | hobbies: Optional[List[str]]
15 | nicks: List[str]
16 | age: Union[float, int]
17 | birthday: Union[datetime, date]
18 |
19 |
20 | class PersonFactory(DataclassFactory[Person]): ...
21 |
22 |
23 | register_fixture(PersonFactory)
24 |
25 |
26 | def test_person_factory(person_factory: PersonFactory) -> None:
27 | person_instance = person_factory.build()
28 | assert isinstance(person_instance, Person)
29 |
--------------------------------------------------------------------------------
/docs/examples/fixtures/test_example_3.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from datetime import date, datetime
3 | from typing import List, Optional, Union
4 | from uuid import UUID
5 |
6 | from polyfactory.factories import DataclassFactory
7 | from polyfactory.pytest_plugin import register_fixture
8 |
9 |
10 | @dataclass
11 | class Person:
12 | id: UUID
13 | name: str
14 | hobbies: Optional[List[str]]
15 | nicks: List[str]
16 | age: Union[float, int]
17 | birthday: Union[datetime, date]
18 |
19 |
20 | @register_fixture(name="aliased_person_factory")
21 | class PersonFactory(DataclassFactory[Person]): ...
22 |
23 |
24 | def test_person_factory(aliased_person_factory: PersonFactory) -> None:
25 | person_instance = aliased_person_factory.build()
26 | assert isinstance(person_instance, Person)
27 |
--------------------------------------------------------------------------------
/docs/examples/handling_custom_types/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/handling_custom_types/__init__.py
--------------------------------------------------------------------------------
/docs/examples/handling_custom_types/test_example_1.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Any, Dict, Type
3 | from uuid import UUID
4 |
5 | from polyfactory.factories import DataclassFactory
6 |
7 |
8 | # we created a special class we will use in our code
9 | class CustomSecret:
10 | def __init__(self, value: str) -> None:
11 | self.value = value
12 |
13 | def __repr__(self) -> str:
14 | return "*" * len(self.value)
15 |
16 | def __str__(self) -> str:
17 | return "*" * len(self.value)
18 |
19 |
20 | @dataclass
21 | class Person:
22 | id: UUID
23 | secret: CustomSecret
24 |
25 |
26 | # by default the factory class cannot handle unknown types,
27 | # so we need to override the provider map to add it:
28 | class PersonFactory(DataclassFactory[Person]):
29 | @classmethod
30 | def get_provider_map(cls) -> Dict[Type, Any]:
31 | providers_map = super().get_provider_map()
32 |
33 | return {
34 | CustomSecret: lambda: CustomSecret("jeronimo"),
35 | **providers_map,
36 | }
37 |
38 |
39 | def test_custom_secret_creation() -> None:
40 | person_instance = PersonFactory.build()
41 | assert isinstance(person_instance.secret, CustomSecret)
42 | assert repr(person_instance.secret) == "*" * len("jeronimo")
43 | assert str(person_instance.secret) == "*" * len("jeronimo")
44 | assert person_instance.secret.value == "jeronimo"
45 |
--------------------------------------------------------------------------------
/docs/examples/handling_custom_types/test_example_2.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Any, Dict, Generic, Type, TypeVar
3 | from uuid import UUID
4 |
5 | from polyfactory.factories import DataclassFactory
6 |
7 |
8 | # we created a special class we will use in our code
9 | class CustomSecret:
10 | def __init__(self, value: str) -> None:
11 | self.value = value
12 |
13 | def __repr__(self) -> str:
14 | return "*" * len(self.value)
15 |
16 | def __str__(self) -> str:
17 | return "*" * len(self.value)
18 |
19 |
20 | T = TypeVar("T")
21 |
22 |
23 | # we create a custom base factory to handle dataclasses, with an extended provider map
24 | class CustomDataclassFactory(Generic[T], DataclassFactory[T]):
25 | __is_base_factory__ = True
26 |
27 | @classmethod
28 | def get_provider_map(cls) -> Dict[Type, Any]:
29 | providers_map = super().get_provider_map()
30 |
31 | return {
32 | CustomSecret: lambda: CustomSecret("jeronimo"),
33 | **providers_map,
34 | }
35 |
36 |
37 | @dataclass
38 | class Person:
39 | id: UUID
40 | secret: CustomSecret
41 |
42 |
43 | # we use our CustomDataclassFactory as a base for the PersonFactory
44 | class PersonFactory(CustomDataclassFactory[Person]): ...
45 |
46 |
47 | def test_custom_dataclass_base_factory() -> None:
48 | person_instance = PersonFactory.build()
49 | assert isinstance(person_instance.secret, CustomSecret)
50 | assert repr(person_instance.secret) == "*" * len("jeronimo")
51 | assert str(person_instance.secret) == "*" * len("jeronimo")
52 | assert person_instance.secret.value == "jeronimo"
53 |
--------------------------------------------------------------------------------
/docs/examples/handling_custom_types/test_example_3.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import TypeVar
3 | from uuid import UUID
4 |
5 | from polyfactory.factories import DataclassFactory
6 | from polyfactory.factories.base import BaseFactory
7 |
8 |
9 | # we created a special class we will use in our code
10 | class CustomSecret:
11 | def __init__(self, value: str) -> None:
12 | self.value = value
13 |
14 | def __repr__(self) -> str:
15 | return "*" * len(self.value)
16 |
17 | def __str__(self) -> str:
18 | return "*" * len(self.value)
19 |
20 |
21 | T = TypeVar("T")
22 |
23 |
24 | # we add a function to generate the custom type
25 | BaseFactory.add_provider(CustomSecret, lambda: CustomSecret("jeronimo"))
26 |
27 |
28 | @dataclass
29 | class Person:
30 | id: UUID
31 | secret: CustomSecret
32 |
33 |
34 | # we don't have to use a custom base factory!
35 | class PersonFactory(DataclassFactory[Person]): ...
36 |
37 |
38 | def test_custom_dataclass_base_factory() -> None:
39 | person_instance = PersonFactory.build()
40 | assert isinstance(person_instance.secret, CustomSecret)
41 | assert repr(person_instance.secret) == "*" * len("jeronimo")
42 | assert str(person_instance.secret) == "*" * len("jeronimo")
43 | assert person_instance.secret.value == "jeronimo"
44 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/library_factories/__init__.py
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/library_factories/sqlalchemy_factory/__init__.py
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/conftest.py:
--------------------------------------------------------------------------------
1 | from collections.abc import Iterable
2 |
3 | import pytest
4 |
5 | from docs.examples.library_factories.sqlalchemy_factory.test_example_4 import BaseFactory
6 |
7 |
8 | @pytest.fixture(scope="module")
9 | def _remove_default_factories() -> Iterable[None]:
10 | yield
11 | BaseFactory._base_factories.remove(BaseFactory) # noqa: SLF001
12 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_1.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
2 |
3 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
4 |
5 |
6 | class Base(DeclarativeBase): ...
7 |
8 |
9 | class Author(Base):
10 | __tablename__ = "authors"
11 |
12 | id: Mapped[int] = mapped_column(primary_key=True)
13 | name: Mapped[str]
14 |
15 |
16 | class AuthorFactory(SQLAlchemyFactory[Author]): ...
17 |
18 |
19 | def test_sqla_factory() -> None:
20 | author = AuthorFactory.build()
21 | assert isinstance(author, Author)
22 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_2.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from sqlalchemy import ForeignKey
4 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
5 |
6 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
7 |
8 |
9 | class Base(DeclarativeBase): ...
10 |
11 |
12 | class Author(Base):
13 | __tablename__ = "authors"
14 |
15 | id: Mapped[int] = mapped_column(primary_key=True)
16 | name: Mapped[str]
17 |
18 | books: Mapped[List["Book"]] = relationship("Book", uselist=True)
19 |
20 |
21 | class Book(Base):
22 | __tablename__ = "books"
23 |
24 | id: Mapped[int] = mapped_column(primary_key=True)
25 | author_id: Mapped[int] = mapped_column(ForeignKey(Author.id))
26 |
27 |
28 | class AuthorFactory(SQLAlchemyFactory[Author]): ...
29 |
30 |
31 | class AuthorFactoryWithRelationship(SQLAlchemyFactory[Author]):
32 | __set_relationships__ = True
33 |
34 |
35 | def test_sqla_factory() -> None:
36 | author = AuthorFactory.build()
37 | assert author.books == []
38 |
39 |
40 | def test_sqla_factory_with_relationship() -> None:
41 | author = AuthorFactoryWithRelationship.build()
42 | assert isinstance(author, Author)
43 | assert isinstance(author.books[0], Book)
44 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_3.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from sqlalchemy import ForeignKey, create_engine
4 | from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship
5 |
6 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
7 |
8 |
9 | class Base(DeclarativeBase): ...
10 |
11 |
12 | class Author(Base):
13 | __tablename__ = "authors"
14 |
15 | id: Mapped[int] = mapped_column(primary_key=True)
16 | name: Mapped[str]
17 |
18 | books: Mapped[List["Book"]] = relationship("Book", uselist=True)
19 |
20 |
21 | class Book(Base):
22 | __tablename__ = "books"
23 |
24 | id: Mapped[int] = mapped_column(primary_key=True)
25 | author_id: Mapped[int] = mapped_column(ForeignKey(Author.id))
26 |
27 |
28 | class AuthorFactory(SQLAlchemyFactory[Author]):
29 | __set_relationships__ = True
30 |
31 |
32 | def test_sqla_factory_persistence() -> None:
33 | engine = create_engine("sqlite:///:memory:")
34 | Base.metadata.create_all(engine)
35 | session = Session(engine)
36 |
37 | AuthorFactory.__session__ = session # Or using a callable that returns a session
38 |
39 | author = AuthorFactory.create_sync()
40 | assert author.id is not None
41 | assert author.id == author.books[0].author_id
42 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_4.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from sqlalchemy import ForeignKey, create_engine
4 | from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship
5 |
6 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory, T
7 |
8 |
9 | class Base(DeclarativeBase): ...
10 |
11 |
12 | class Author(Base):
13 | __tablename__ = "authors"
14 |
15 | id: Mapped[int] = mapped_column(primary_key=True)
16 | name: Mapped[str]
17 |
18 | books: Mapped[List["Book"]] = relationship(
19 | "Book",
20 | uselist=True,
21 | back_populates="author",
22 | )
23 |
24 |
25 | class Book(Base):
26 | __tablename__ = "books"
27 |
28 | id: Mapped[int] = mapped_column(primary_key=True)
29 | author_id: Mapped[int] = mapped_column(ForeignKey(Author.id), nullable=False)
30 | author: Mapped[Author] = relationship(
31 | "Author",
32 | uselist=False,
33 | back_populates="books",
34 | )
35 |
36 |
37 | class BaseFactory(SQLAlchemyFactory[T]):
38 | __is_base_factory__ = True
39 | __set_relationships__ = True
40 | __randomize_collection_length__ = True
41 | __min_collection_length__ = 3
42 |
43 |
44 | def test_custom_sqla_factory() -> None:
45 | engine = create_engine("sqlite:///:memory:")
46 | Base.metadata.create_all(engine)
47 | session = Session(engine)
48 |
49 | BaseFactory.__session__ = session # Or using a callable that returns a session
50 |
51 | author = BaseFactory.create_factory(Author).create_sync()
52 | assert author.id is not None
53 | assert author.id == author.books[0].author_id
54 |
55 | book = BaseFactory.create_factory(Book).create_sync()
56 | assert book.id is not None
57 | assert book.author.books == [book]
58 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_5.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from collections.abc import Callable
4 | from decimal import Decimal
5 | from typing import Any
6 |
7 | from sqlalchemy import types
8 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
9 |
10 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
11 |
12 |
13 | class Location(types.TypeEngine):
14 | cache_ok = True
15 |
16 |
17 | class Base(DeclarativeBase): ...
18 |
19 |
20 | class Place(Base):
21 | __tablename__ = "location"
22 |
23 | id: Mapped[int] = mapped_column(primary_key=True)
24 | location: Mapped[tuple[Decimal, Decimal]] = mapped_column(Location)
25 |
26 |
27 | class PlaceFactory(SQLAlchemyFactory[Place]):
28 | @classmethod
29 | def get_sqlalchemy_types(cls) -> dict[Any, Callable[[], Any]]:
30 | mapping = super().get_sqlalchemy_types()
31 | mapping[Location] = cls.__faker__.latlng
32 | return mapping
33 |
34 |
35 | def test_custom_sqla_factory() -> None:
36 | result = PlaceFactory.build()
37 | assert isinstance(result.location, tuple)
38 |
--------------------------------------------------------------------------------
/docs/examples/library_factories/sqlalchemy_factory/test_example_association_proxy.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from sqlalchemy import ForeignKey
4 | from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
5 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
6 |
7 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
8 |
9 |
10 | class Base(DeclarativeBase): ...
11 |
12 |
13 | class User(Base):
14 | __tablename__ = "users"
15 |
16 | id: Mapped[int] = mapped_column(primary_key=True)
17 | name: Mapped[str]
18 |
19 | user_keyword_associations: Mapped[list["UserKeywordAssociation"]] = relationship(
20 | back_populates="user",
21 | )
22 | keywords: AssociationProxy[list["Keyword"]] = association_proxy(
23 | "user_keyword_associations",
24 | "keyword",
25 | creator=lambda keyword_obj: UserKeywordAssociation(keyword=keyword_obj),
26 | )
27 |
28 |
29 | class UserKeywordAssociation(Base):
30 | __tablename__ = "user_keyword"
31 | user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), primary_key=True)
32 | keyword_id: Mapped[int] = mapped_column(ForeignKey("keywords.id"), primary_key=True)
33 |
34 | user: Mapped[User] = relationship(back_populates="user_keyword_associations")
35 | keyword: Mapped["Keyword"] = relationship()
36 |
37 |
38 | class Keyword(Base):
39 | __tablename__ = "keywords"
40 | id: Mapped[int] = mapped_column(primary_key=True)
41 | keyword: Mapped[str]
42 |
43 |
44 | class UserFactory(SQLAlchemyFactory[User]): ...
45 |
46 |
47 | class UserFactoryWithAssociation(SQLAlchemyFactory[User]):
48 | __set_association_proxy__ = True
49 |
50 |
51 | def test_sqla_factory() -> None:
52 | user = UserFactory.build()
53 | assert not user.user_keyword_associations
54 | assert not user.keywords
55 |
56 |
57 | def test_sqla_factory_with_association() -> None:
58 | user = UserFactoryWithAssociation.build()
59 | assert isinstance(user.user_keyword_associations[0], UserKeywordAssociation)
60 | assert isinstance(user.keywords[0], Keyword)
61 |
--------------------------------------------------------------------------------
/docs/examples/model_coverage/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/docs/examples/model_coverage/__init__.py
--------------------------------------------------------------------------------
/docs/examples/model_coverage/test_example_1.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import dataclass
4 | from typing import Literal
5 |
6 | from polyfactory.factories.dataclass_factory import DataclassFactory
7 |
8 |
9 | @dataclass
10 | class Car:
11 | model: str
12 |
13 |
14 | @dataclass
15 | class Boat:
16 | can_float: bool
17 |
18 |
19 | @dataclass
20 | class Profile:
21 | age: int
22 | favourite_color: Literal["red", "green", "blue"]
23 | vehicle: Car | Boat
24 |
25 |
26 | class ProfileFactory(DataclassFactory[Profile]): ...
27 |
28 |
29 | def test_profile_coverage() -> None:
30 | profiles = list(ProfileFactory.coverage())
31 |
32 | assert profiles[0].favourite_color == "red"
33 | assert isinstance(profiles[0].vehicle, Car)
34 | assert profiles[1].favourite_color == "green"
35 | assert isinstance(profiles[1].vehicle, Boat)
36 | assert profiles[2].favourite_color == "blue"
37 | assert isinstance(profiles[2].vehicle, Car)
38 |
--------------------------------------------------------------------------------
/docs/examples/model_coverage/test_example_2.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import dataclass
4 | from typing import Literal
5 |
6 | from polyfactory.factories.dataclass_factory import DataclassFactory
7 |
8 |
9 | @dataclass
10 | class Car:
11 | model: str
12 |
13 |
14 | @dataclass
15 | class Boat:
16 | can_float: bool
17 |
18 |
19 | @dataclass
20 | class Profile:
21 | age: int
22 | favourite_color: Literal["red", "green", "blue"]
23 | vehicle: Car | Boat
24 |
25 |
26 | @dataclass
27 | class SocialGroup:
28 | members: list[Profile]
29 |
30 |
31 | class SocialGroupFactory(DataclassFactory[SocialGroup]): ...
32 |
33 |
34 | def test_social_group_coverage() -> None:
35 | groups = list(SocialGroupFactory.coverage())
36 | assert len(groups) == 3
37 |
38 | for group in groups:
39 | assert len(group.members) == 1
40 |
41 | assert groups[0].members[0].favourite_color == "red"
42 | assert isinstance(groups[0].members[0].vehicle, Car)
43 | assert groups[1].members[0].favourite_color == "green"
44 | assert isinstance(groups[1].members[0].vehicle, Boat)
45 | assert groups[2].members[0].favourite_color == "blue"
46 | assert isinstance(groups[2].members[0].vehicle, Car)
47 |
--------------------------------------------------------------------------------
/docs/getting-started.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Getting Started
3 | ===============
4 |
5 | Polyfactory is a simple and powerful mock data generation library, based around type
6 | hints and supporting :doc:`dataclasses `, :class:`TypedDicts `,
7 | Pydantic models, :class:`msgspec Struct's ` and more.
8 |
9 | Installation
10 | ------------
11 |
12 | .. code-block:: bash
13 |
14 | pip install polyfactory
15 |
16 | Example
17 | -------
18 |
19 | .. literalinclude:: /examples/declaring_factories/test_example_1.py
20 | :caption: Minimal example using a dataclass
21 | :language: python
22 |
23 | That is it - with almost no work, we are able to create a mock data object fitting the ``Person`` class model definition.
24 |
25 | This is possible because of the typing information available on the dataclass, which are used as a
26 | source of truth for data generation.
27 |
28 | The factory parses the information stored in the dataclass and generates a dictionary of kwargs that are passed to
29 | the ``Person`` class constructor.
30 |
31 | Relation to ``pydantic-factories``
32 | ----------------------------------
33 |
34 | Prior to version 2, this library was known as `pydantic-factories `_, a
35 | name under which it gained quite a bit of popularity.
36 |
37 | A main motivator for the 2.0 release was that we wanted to support more than just Pydantic models, something which also
38 | required a change to its core architecture. As this library would no longer be directly tied to Pydantic, ``polyfactory``
39 | was chosen as its new name to reflect its capabilities; It can generate mock data for
40 | :doc:`dataclasses `, :class:`TypedDicts `,
41 | `Pydantic `_, `Odmantic `_,
42 | and `Beanie ODM `_ models, as well as custom factories.
43 |
--------------------------------------------------------------------------------
/docs/reference/constants.rst:
--------------------------------------------------------------------------------
1 | constants
2 | =========
3 |
4 |
5 | .. note:: Mapping of type annotations into concrete types.
6 | This is used to normalize Python <= 3.9 annotations.
7 |
8 | .. automodule:: polyfactory.constants
9 | :members:
10 |
--------------------------------------------------------------------------------
/docs/reference/decorators.rst:
--------------------------------------------------------------------------------
1 | decorators
2 | ==========
3 |
4 | .. automodule:: polyfactory.decorators
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/exceptions.rst:
--------------------------------------------------------------------------------
1 | exceptions
2 | ==========
3 |
4 | .. automodule:: polyfactory.exceptions
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/attrs_factory.rst:
--------------------------------------------------------------------------------
1 | attrs_factory
2 | ================
3 |
4 | .. automodule:: polyfactory.factories.attrs_factory
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/base.rst:
--------------------------------------------------------------------------------
1 | base_factory
2 | ============
3 |
4 | .. automodule:: polyfactory.factories.base
5 |
--------------------------------------------------------------------------------
/docs/reference/factories/beanie_odm_factory.rst:
--------------------------------------------------------------------------------
1 | beanie_odm_factory
2 | ==================
3 |
4 | .. automodule:: polyfactory.factories.beanie_odm_factory
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/dataclass_factory.rst:
--------------------------------------------------------------------------------
1 | dataclass_factory
2 | ==================
3 |
4 | .. automodule:: polyfactory.factories.dataclass_factory
5 |
--------------------------------------------------------------------------------
/docs/reference/factories/index.rst:
--------------------------------------------------------------------------------
1 | factories
2 | =========
3 |
4 |
5 |
6 |
7 | .. toctree::
8 | :maxdepth: 1
9 |
10 | base
11 | dataclass_factory
12 | typed_dict_factory
13 | pydantic_factory
14 | msgspec_factory
15 | odmantic_odm_factory
16 | beanie_odm_factory
17 | attrs_factory
18 | sqlalchemy_factory
19 |
--------------------------------------------------------------------------------
/docs/reference/factories/msgspec_factory.rst:
--------------------------------------------------------------------------------
1 | msgspec_factory
2 | ================
3 |
4 | .. automodule:: polyfactory.factories.msgspec_factory
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/odmantic_odm_factory.rst:
--------------------------------------------------------------------------------
1 | odmantic_odm_factory
2 | ====================
3 |
4 | .. automodule:: polyfactory.factories.odmantic_odm_factory
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/pydantic_factory.rst:
--------------------------------------------------------------------------------
1 | pydantic_factory
2 | ================
3 |
4 | .. automodule:: polyfactory.factories.pydantic_factory
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/factories/sqlalchemy_factory.rst:
--------------------------------------------------------------------------------
1 | sqlalchemy_factory
2 | ==================
3 |
4 | .. automodule:: polyfactory.factories.sqlalchemy_factory
5 |
--------------------------------------------------------------------------------
/docs/reference/factories/typed_dict_factory.rst:
--------------------------------------------------------------------------------
1 | typed_dict_factory
2 | ==================
3 |
4 | .. automodule:: polyfactory.factories.typed_dict_factory
5 |
--------------------------------------------------------------------------------
/docs/reference/field_meta.rst:
--------------------------------------------------------------------------------
1 | field_meta
2 | ==========
3 |
4 | .. automodule:: polyfactory.field_meta
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/fields.rst:
--------------------------------------------------------------------------------
1 | fields
2 | ======
3 |
4 | .. automodule:: polyfactory.fields
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/index.rst:
--------------------------------------------------------------------------------
1 | API Reference
2 | =============
3 |
4 | This section contains sections for API reference documentation for the ``polyfactory`` package.
5 |
6 | .. toctree::
7 | :titlesonly:
8 | :maxdepth: 1
9 | :caption: Articles
10 |
11 | constants
12 | exceptions
13 | factories/index
14 | field_meta
15 | fields
16 | decorators
17 | persistence
18 | pytest_plugin
19 | value_generators/index
20 |
--------------------------------------------------------------------------------
/docs/reference/persistence.rst:
--------------------------------------------------------------------------------
1 | persistence
2 | ===========
3 |
4 | .. automodule:: polyfactory.persistence
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/pytest_plugin.rst:
--------------------------------------------------------------------------------
1 | pytest_plugin
2 | =============
3 |
4 | .. automodule:: polyfactory.pytest_plugin
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/complex_types.rst:
--------------------------------------------------------------------------------
1 | complex_types
2 | =============
3 |
4 | .. automodule:: polyfactory.value_generators.complex_types
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/constrained_collections.rst:
--------------------------------------------------------------------------------
1 | constrained_collections
2 | ========================
3 |
4 | .. automodule:: polyfactory.value_generators.constrained_collections
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/constrained_dates.rst:
--------------------------------------------------------------------------------
1 | constrained_dates
2 | ==================
3 |
4 | .. automodule:: polyfactory.value_generators.constrained_dates
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/constrained_numbers.rst:
--------------------------------------------------------------------------------
1 | constrained_numbers
2 | ===================
3 |
4 | .. automodule:: polyfactory.value_generators.constrained_numbers
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/constrained_strings.rst:
--------------------------------------------------------------------------------
1 | constrained_strings
2 | ===================
3 |
4 | .. automodule:: polyfactory.value_generators.constrained_strings
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/index.rst:
--------------------------------------------------------------------------------
1 | value_generators
2 | ================
3 |
4 | .. toctree::
5 | :maxdepth: 1
6 |
7 | complex_types
8 | constrained_collections
9 | constrained_dates
10 | constrained_numbers
11 | constrained_strings
12 | primitives
13 |
--------------------------------------------------------------------------------
/docs/reference/value_generators/primitives.rst:
--------------------------------------------------------------------------------
1 | primitives
2 | ==========
3 |
4 | .. automodule:: polyfactory.value_generators.primitives
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/releases.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | ====================
4 | Polyfactory Releases
5 | ====================
6 |
7 | This document outlines the release structure, versioning, and stability guarantees for the Polyfactory library.
8 |
9 | Version Numbering
10 | -----------------
11 |
12 | This library follows the `Semantic Versioning standard `_, using the ``..``
13 | schema:
14 |
15 | **Major**
16 | Backwards incompatible changes have been made
17 |
18 | **Minor**
19 | Functionality was added in a backwards compatible manner
20 |
21 | **Patch**
22 | Bugfixes were applied in a backwards compatible manner
23 |
24 | Pre-release Versions
25 | ++++++++++++++++++++
26 |
27 | Before a new major release, we will make ``alpha``, ``beta``, and release candidate (``rc``) releases, numbered as
28 | ``..``. For example, ``2.0.0alpha1``, ``2.0.0beta1``, ``2.0.0rc1``.
29 |
30 | - ``alpha``
31 | Early developer preview. Features may not be complete and breaking changes can occur.
32 |
33 | - ``beta``
34 | More stable preview release. Feature complete, no major breaking changes expected.
35 |
36 | - ``rc``
37 | Release candidate. Feature freeze, only bugfixes until final release.
38 | Suitable for testing migration to the upcoming major release.
39 |
40 | Long-term Support Releases (LTS)
41 | --------------------------------
42 |
43 | Major releases are designated as LTS releases for the life of that major release series.
44 | These releases will receive bugfixes for a guaranteed period of time as defined in
45 | `Supported Versions <#supported-versions>`_.
46 |
47 | Deprecation Policy
48 | ------------------
49 |
50 | When a feature is going to be removed, a deprecation warning will be added in a **minor** release.
51 | The feature will continue to work for all releases in that major series, and will be removed in the next major release.
52 |
53 | For example, if a deprecation warning is added in ``1.1``, the feature will work throughout all ``1.x`` releases,
54 | and be removed in ``2.0``.
55 |
56 | Supported Versions
57 | ------------------
58 |
59 | At any time, the Litestar organization will actively support:
60 |
61 | - The current major release series
62 | - The previous major release series
63 | - Any other designated LTS releases (Special cases)
64 |
65 | For example, if the current release is ``2.0``, we will actively support ``2.x`` and ``1.x``.
66 | When ``3.0`` is released, we will drop support for ``1.x``.
67 |
68 | Bugfixes will be applied to the current major release, and selectively backported to older
69 | supported versions based on severity and feasibility.
70 |
71 | Release Process
72 | ---------------
73 |
74 | Each major release cycle consists of a few phases:
75 |
76 | #. **Planning**: Define roadmap, spec out major features. Work should begin on implementation.
77 | #. **Development**: Active development on planned features. Ends with an alpha release and branch of ``A.B.x``
78 | branch from `main`.
79 | #. **Bugfixes**: Only bugfixes, no new features. Progressively release beta, release candidates.
80 | Feature freeze at RC. Become more selective with backports to avoid regressions.
81 |
--------------------------------------------------------------------------------
/docs/usage/declaring_factories.rst:
--------------------------------------------------------------------------------
1 | Declaring Factories
2 | ===================
3 |
4 | Defining factories is done using a declarative syntax. That is - users declare factory classes, which are subclasses of
5 | base factories.
6 |
7 | .. literalinclude:: /examples/declaring_factories/test_example_1.py
8 | :caption: Declaring a factory for a dataclass
9 | :language: python
10 |
11 | You can also specify the model type by only specifying the factory generic type parameter.
12 |
13 | .. code-block:: python
14 |
15 | @dataclass
16 | class Person:
17 | name: str
18 | age: float
19 | height: float
20 | weight: float
21 |
22 | class PersonFactory(DataclassFactory[Person]):
23 | ...
24 |
25 | .. note::
26 | The syntax with the ``__model__`` class attribute omitting
27 | is only available since version 2.13.0.
28 |
29 | The same applies to the other factories exported by this library, for example:
30 |
31 | .. literalinclude:: /examples/declaring_factories/test_example_2.py
32 | :caption: Declaring a factory for a typed-dict
33 | :language: python
34 |
35 | Or for pydantic models:
36 |
37 | .. literalinclude:: /examples/declaring_factories/test_example_3.py
38 | :caption: Declaring a factory for a pydantic model
39 | :language: python
40 |
41 | .. note::
42 | You can also define factories for any 3rd party implementation of dataclasses, as long as it fulfills the stdlib
43 | dataclasses interface. For example, this is using the pydantic ``@dataclass`` decorator:
44 |
45 | .. literalinclude:: /examples/declaring_factories/test_example_4.py
46 | :caption: Declaring a factory for a pydantic dataclass
47 | :language: python
48 |
49 | Or for attrs models:
50 |
51 | .. literalinclude:: /examples/declaring_factories/test_example_7.py
52 | :caption: Declaring a factory for a attrs model
53 | :language: python
54 |
55 | .. note::
56 | Validators are not currently supported - neither the built in validators that come
57 | with `attrs` nor custom validators.
58 |
59 |
60 | Imperative Factory Creation
61 | ---------------------------
62 |
63 | Although the definition of factories is primarily meant to be done declaratively, factories expose the
64 | :meth:`create_factory ` method. This method is used internally
65 | inside factories to dynamically create factories for models. For example, below the ``PersonFactory`` will dynamically
66 | create a ``PetFactory``:
67 |
68 | .. literalinclude:: /examples/declaring_factories/test_example_5.py
69 | :caption: Dynamic factory generation
70 | :language: python
71 |
72 | You can also use this method to create factories imperatively:
73 |
74 | .. literalinclude:: /examples/declaring_factories/test_example_6.py
75 | :caption: Imperative factory creation
76 | :language: python
77 |
78 | Eventually you can use this method on an existing concrete factory to create a sub factory overriding some parent configuration:
79 |
80 | .. literalinclude:: /examples/declaring_factories/test_example_8.py
81 | :caption: Imperative sub factory creation
82 | :language: python
83 |
84 | In this case you don't need to specify the `model` argument to the :meth:`create_factory ` method. The one from the parent factory will be used.
85 |
--------------------------------------------------------------------------------
/docs/usage/decorators.rst:
--------------------------------------------------------------------------------
1 | The ``post_generated`` decorator
2 | --------------------------------
3 |
4 | The :class:`post_generated ` decorator wraps a ``classmethod`` into a
5 | :class:`PostGenerated ` field. This is useful when the post generated field depends
6 | on the current factory, usually its ``__faker__`` and/or ``__random__`` attribute. For example:
7 |
8 | .. literalinclude:: /examples/decorators/test_example_1.py
9 | :caption: Using the ``post_generated`` decorator
10 | :language: python
11 |
12 | All classmethod parameters after ``cls`` must be named as the fields this post generated field depends on.
13 |
--------------------------------------------------------------------------------
/docs/usage/fixtures.rst:
--------------------------------------------------------------------------------
1 | Pytest fixtures
2 | ===============
3 |
4 | Polyfactory support registering factories as pytest fixtures using the
5 | :func:`register_fixture ` decorator:
6 |
7 | .. literalinclude:: /examples/fixtures/test_example_1.py
8 | :caption: Using the ``register_fixture_decorator`` field.
9 | :language: python
10 |
11 | The fixture can be registered separately from the declaration of the class. This is useful when a different pytest fixture location to where the factory is defined.
12 |
13 | .. literalinclude:: /examples/fixtures/test_example_2.py
14 | :caption: Using the ``register_fixture_decorator`` field separately.
15 | :language: python
16 |
17 | You can also control the name of the fixture using the optional ``name`` kwarg:
18 |
19 | .. literalinclude:: /examples/fixtures/test_example_3.py
20 | :caption: Aliasing a factory fixture using the `name` kwarg.
21 | :language: python
22 |
--------------------------------------------------------------------------------
/docs/usage/handling_custom_types.rst:
--------------------------------------------------------------------------------
1 | Handling Custom Types
2 | =====================
3 |
4 | Sometimes you need to handle custom types, either from 3rd party libraries or from your own codebase. To achieve this,
5 | you can extend what's referred as the `providers_map`:
6 |
7 | .. literalinclude:: /examples/handling_custom_types/test_example_1.py
8 | :caption: Extending the provider map
9 | :language: python
10 |
11 | In the above example we override the `get_provider_map` class method, which maps types to callables. Each callable in the map
12 | returns an appropriate mock value for the type. In the above example, an instance of `CustomSecret` with the hardcoded
13 | string 'jeronimo'.
14 |
15 | Add a Type Provider
16 | -------------------
17 | Alternatively, you can add a provider for your custom type to `BaseFactory`.
18 |
19 | .. literalinclude:: /examples/handling_custom_types/test_example_3.py
20 | :caption: Creating a custom type factory using add_provider
21 | :language: python
22 |
23 | Creating Custom Base Factories
24 | ------------------------------
25 |
26 | The above works great when you need to do this in a localised fashion, but if you need to use some custom types in many
27 | places it will lead to unnecessary duplication. The solution for this is to create a custom base factory, in this case
28 | for handling dataclasses:
29 |
30 | .. literalinclude:: /examples/handling_custom_types/test_example_2.py
31 | :caption: Creating a custom dataclass factory with extended provider map
32 | :language: python
33 |
34 | .. note::
35 | If extra configs values are defined for custom base classes, then ``__config_keys__`` should be extended so
36 | that these values are correctly passed onto to concrete factories.
37 |
--------------------------------------------------------------------------------
/docs/usage/index.rst:
--------------------------------------------------------------------------------
1 | Usage Guide
2 | ===========
3 |
4 | This section contains articles that explain how to use Polyfactory.
5 |
6 | .. toctree::
7 | :titlesonly:
8 | :maxdepth: 1
9 | :caption: Articles
10 |
11 | declaring_factories
12 | library_factories/index
13 | configuration
14 | fields
15 | decorators
16 | fixtures
17 | handling_custom_types
18 | model_coverage
19 |
--------------------------------------------------------------------------------
/docs/usage/library_factories/index.rst:
--------------------------------------------------------------------------------
1 | Polyfactory Factories
2 | =====================
3 |
4 | The factories exported by this library are classes that extend the
5 | Abstract Base Class (ABC) :class:`BaseFactory `.
6 |
7 | These include:
8 |
9 | :class:`DataclassFactory `
10 | a base factory for dataclasses
11 |
12 | :class:`TypedDictFactory `
13 | a base factory for typed-dicts
14 | :class:`ModelFactory `
15 | a base factory for `pydantic `_ models
16 |
17 | :class:`BeanieDocumentFactory `
18 | a base factory for `beanie `_ documents
19 |
20 | :class:`OdmanticModelFactory `
21 | a base factory for `odmantic `_ models.
22 |
23 | :class:`MsgspecFactory `
24 | a base factory for `msgspec `_ Structs
25 |
26 | :class:`AttrsFactory `
27 | a base factory for `attrs `_ models.
28 |
29 | :class:`SQLAlchemyFactory `
30 | a base factory for `SQLAlchemy `_ models.
31 |
32 | .. note::
33 | All factories exported from ``polyfactory.factories`` do not require any additional dependencies. The other factories,
34 | such as :class:`ModelFactory `, require an additional but optional
35 | dependency, in this case `pydantic `_. As such, they are exported only from their respective
36 | namespaced module, e.g. ``polyfactory.factories.pydantic_factory.ModelFactory``.
37 |
38 | .. note::
39 | We will be adding additional factories to this package, so make sure to checkout the above list from time to time.
40 |
41 |
42 | .. toctree::
43 | :maxdepth: 1
44 |
45 | sqlalchemy_factory
46 |
--------------------------------------------------------------------------------
/docs/usage/model_coverage.rst:
--------------------------------------------------------------------------------
1 | Model coverage generation
2 | =========================
3 |
4 | The ``BaseFactory.coverage()`` function is an alternative approach to ``BaseFactory.batch()``, where the examples that are generated attempt to provide full coverage of all the forms a model can take with the minimum number of instances. For example:
5 |
6 | .. literalinclude:: /examples/model_coverage/test_example_1.py
7 | :caption: Defining a factory and generating examples with coverage
8 | :language: python
9 |
10 | As you can see in the above example, the ``Profile`` model has 3 options for ``favourite_color``, and 2 options for ``vehicle``. In the output you can expect to see instances of ``Profile`` that have each of these options. The largest variance dictates the length of the output, in this case ``favourite_color`` has the most, at 3 options, so expect to see 3 ``Profile`` instances.
11 |
12 |
13 | .. note::
14 | Notice that the same ``Car`` instance is used in the first and final generated example. When the coverage examples for a field are exhausted before another field, values for that field are reused.
15 |
16 | Notes on collection types
17 | -------------------------
18 |
19 | When generating coverage for models with fields that are collections, in particular collections that contain sub-models, the contents of the collection will be the all coverage examples for that sub-model. For example:
20 |
21 | .. literalinclude:: /examples/model_coverage/test_example_2.py
22 | :caption: Coverage output for the SocialGroup model
23 | :language: python
24 |
25 | Known Limitations
26 | -----------------
27 |
28 | - Recursive models will cause an error: ``RecursionError: maximum recursion depth exceeded``.
29 | - ``__min_collection_length__`` and ``__max_collection_length__`` are currently ignored in coverage generation.
30 |
--------------------------------------------------------------------------------
/polyfactory/__init__.py:
--------------------------------------------------------------------------------
1 | from .exceptions import ConfigurationException
2 | from .factories import BaseFactory
3 | from .fields import Fixture, Ignore, PostGenerated, Require, Use
4 | from .persistence import AsyncPersistenceProtocol, SyncPersistenceProtocol
5 |
6 | __all__ = (
7 | "AsyncPersistenceProtocol",
8 | "BaseFactory",
9 | "ConfigurationException",
10 | "Fixture",
11 | "Ignore",
12 | "PostGenerated",
13 | "Require",
14 | "SyncPersistenceProtocol",
15 | "Use",
16 | )
17 |
--------------------------------------------------------------------------------
/polyfactory/__metadata__.py:
--------------------------------------------------------------------------------
1 | """Metadata for the Project."""
2 |
3 | from __future__ import annotations
4 |
5 | import importlib.metadata
6 |
7 | __all__ = ["__project__", "__version__"]
8 |
9 | __version__ = importlib.metadata.version("polyfactory")
10 | """Version of the project."""
11 | __project__ = importlib.metadata.metadata("polyfactory")["Name"]
12 | """Name of the project."""
13 |
--------------------------------------------------------------------------------
/polyfactory/collection_extender.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import random
4 | from abc import ABC, abstractmethod
5 | from collections import deque
6 | from typing import Any
7 |
8 | from polyfactory.utils.deprecation import deprecated
9 | from polyfactory.utils.predicates import is_safe_subclass
10 |
11 |
12 | class CollectionExtender(ABC):
13 | __types__: tuple[type, ...]
14 |
15 | @staticmethod
16 | @abstractmethod
17 | def _extend_type_args(type_args: tuple[Any, ...], number_of_args: int) -> tuple[Any, ...]:
18 | raise NotImplementedError
19 |
20 | @classmethod
21 | def _subclass_for_type(cls, annotation_alias: Any) -> type[CollectionExtender]:
22 | return next(
23 | (
24 | subclass
25 | for subclass in cls.__subclasses__()
26 | if any(is_safe_subclass(annotation_alias, t) for t in subclass.__types__)
27 | ),
28 | FallbackExtender,
29 | )
30 |
31 | @classmethod
32 | @deprecated("2.20.0")
33 | def extend_type_args(
34 | cls,
35 | annotation_alias: Any,
36 | type_args: tuple[Any, ...],
37 | number_of_args: int,
38 | ) -> tuple[Any, ...]:
39 | return cls._subclass_for_type(annotation_alias)._extend_type_args(type_args, number_of_args)
40 |
41 |
42 | class TupleExtender(CollectionExtender):
43 | __types__ = (tuple,)
44 |
45 | @staticmethod
46 | def _extend_type_args(type_args: tuple[Any, ...], number_of_args: int) -> tuple[Any, ...]:
47 | if not type_args:
48 | return type_args
49 | if type_args[-1] is not ...:
50 | return type_args
51 | type_to_extend = type_args[-2]
52 | return type_args[:-2] + (type_to_extend,) * number_of_args
53 |
54 |
55 | class ListLikeExtender(CollectionExtender):
56 | __types__ = (list, deque)
57 |
58 | @staticmethod
59 | def _extend_type_args(type_args: tuple[Any, ...], number_of_args: int) -> tuple[Any, ...]:
60 | if not type_args:
61 | return type_args
62 | return tuple(random.choice(type_args) for _ in range(number_of_args))
63 |
64 |
65 | class SetExtender(CollectionExtender):
66 | __types__ = (set, frozenset)
67 |
68 | @staticmethod
69 | def _extend_type_args(type_args: tuple[Any, ...], number_of_args: int) -> tuple[Any, ...]:
70 | if not type_args:
71 | return type_args
72 | return tuple(random.choice(type_args) for _ in range(number_of_args))
73 |
74 |
75 | class DictExtender(CollectionExtender):
76 | __types__ = (dict,)
77 |
78 | @staticmethod
79 | def _extend_type_args(type_args: tuple[Any, ...], number_of_args: int) -> tuple[Any, ...]:
80 | return type_args * number_of_args
81 |
82 |
83 | class FallbackExtender(CollectionExtender):
84 | __types__ = ()
85 |
86 | @staticmethod
87 | def _extend_type_args(
88 | type_args: tuple[Any, ...],
89 | number_of_args: int, # noqa: ARG004
90 | ) -> tuple[Any, ...]: # - investigate @guacs
91 | return type_args
92 |
--------------------------------------------------------------------------------
/polyfactory/constants.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import sys
4 | from collections import abc, defaultdict, deque
5 | from random import Random
6 | from typing import (
7 | DefaultDict,
8 | Deque,
9 | Dict,
10 | FrozenSet,
11 | Iterable,
12 | List,
13 | Mapping,
14 | Sequence,
15 | Set,
16 | Tuple,
17 | Union,
18 | )
19 |
20 | try:
21 | from types import UnionType
22 | except ImportError:
23 | UnionType = Union # type: ignore[misc,assignment]
24 |
25 | PY_38 = sys.version_info.major == 3 and sys.version_info.minor == 8 # noqa: PLR2004
26 |
27 | # Mapping of type annotations into concrete types. This is used to normalize python <= 3.9 annotations.
28 | INSTANTIABLE_TYPE_MAPPING = {
29 | DefaultDict: defaultdict,
30 | Deque: deque,
31 | Dict: dict,
32 | FrozenSet: frozenset,
33 | Iterable: list,
34 | List: list,
35 | Mapping: dict,
36 | Sequence: list,
37 | Set: set,
38 | Tuple: tuple,
39 | abc.Iterable: list,
40 | abc.Mapping: dict,
41 | abc.Sequence: list,
42 | abc.Set: set,
43 | UnionType: Union,
44 | }
45 |
46 |
47 | if not PY_38:
48 | TYPE_MAPPING = INSTANTIABLE_TYPE_MAPPING
49 | else:
50 | # For 3.8, we have to keep the types from typing since dict[str] syntax is not supported in 3.8.
51 | TYPE_MAPPING = {
52 | DefaultDict: DefaultDict,
53 | Deque: Deque,
54 | Dict: Dict,
55 | FrozenSet: FrozenSet,
56 | Iterable: List,
57 | List: List,
58 | Mapping: Dict,
59 | Sequence: List,
60 | Set: Set,
61 | Tuple: Tuple,
62 | abc.Iterable: List,
63 | abc.Mapping: Dict,
64 | abc.Sequence: List,
65 | abc.Set: Set,
66 | }
67 |
68 |
69 | DEFAULT_RANDOM = Random()
70 | RANDOMIZE_COLLECTION_LENGTH = False
71 | MIN_COLLECTION_LENGTH = 0
72 | MAX_COLLECTION_LENGTH = 5
73 |
--------------------------------------------------------------------------------
/polyfactory/decorators.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import contextlib
4 | import inspect
5 | from typing import Any, Callable
6 |
7 | from polyfactory import PostGenerated
8 |
9 |
10 | class post_generated: # noqa: N801
11 | """Descriptor class for wrapping a classmethod into a ``PostGenerated`` field."""
12 |
13 | __slots__ = ("cache", "method")
14 |
15 | def __init__(self, method: Callable | classmethod) -> None:
16 | if not isinstance(method, classmethod):
17 | msg = "post_generated decorator can only be used on classmethods"
18 | raise TypeError(msg)
19 | self.method = method
20 | self.cache: dict[type, PostGenerated] = {}
21 |
22 | def __get__(self, obj: Any, objtype: type) -> PostGenerated:
23 | with contextlib.suppress(KeyError):
24 | return self.cache[objtype]
25 | fn = self.method.__func__ # pyright: ignore[reportFunctionMemberAccess]
26 | fn_args = inspect.getfullargspec(fn).args[1:]
27 |
28 | def new_fn(name: str, values: dict[str, Any]) -> Any: # noqa: ARG001 - investigate @guacs
29 | return fn(objtype, **{arg: values[arg] for arg in fn_args if arg in values})
30 |
31 | return self.cache.setdefault(objtype, PostGenerated(new_fn))
32 |
--------------------------------------------------------------------------------
/polyfactory/exceptions.py:
--------------------------------------------------------------------------------
1 | class FactoryException(Exception):
2 | """Base Factory error class"""
3 |
4 |
5 | class ConfigurationException(FactoryException):
6 | """Configuration Error class - used for misconfiguration"""
7 |
8 |
9 | class ParameterException(FactoryException):
10 | """Parameter exception - used when wrong parameters are used"""
11 |
12 |
13 | class MissingBuildKwargException(FactoryException):
14 | """Missing Build Kwarg exception - used when a required build kwarg is not provided"""
15 |
16 |
17 | class MissingDependencyException(FactoryException, ImportError):
18 | """Missing dependency exception - used when a dependency is not installed"""
19 |
--------------------------------------------------------------------------------
/polyfactory/factories/__init__.py:
--------------------------------------------------------------------------------
1 | from polyfactory.factories.base import BaseFactory
2 | from polyfactory.factories.dataclass_factory import DataclassFactory
3 | from polyfactory.factories.typed_dict_factory import TypedDictFactory
4 |
5 | __all__ = ("BaseFactory", "DataclassFactory", "TypedDictFactory")
6 |
--------------------------------------------------------------------------------
/polyfactory/factories/attrs_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from inspect import isclass
4 | from typing import TYPE_CHECKING, Generic, TypeVar
5 |
6 | from polyfactory.exceptions import MissingDependencyException
7 | from polyfactory.factories.base import BaseFactory
8 | from polyfactory.field_meta import FieldMeta, Null
9 |
10 | if TYPE_CHECKING:
11 | from typing import Any, TypeGuard
12 |
13 |
14 | try:
15 | import attrs
16 | from attr._make import Factory
17 | from attrs import AttrsInstance
18 | except ImportError as ex:
19 | msg = "attrs is not installed"
20 | raise MissingDependencyException(msg) from ex
21 |
22 |
23 | T = TypeVar("T", bound=AttrsInstance)
24 |
25 |
26 | class AttrsFactory(Generic[T], BaseFactory[T]):
27 | """Base factory for attrs classes."""
28 |
29 | __model__: type[T]
30 |
31 | __is_base_factory__ = True
32 |
33 | @classmethod
34 | def is_supported_type(cls, value: Any) -> TypeGuard[type[T]]:
35 | return isclass(value) and hasattr(value, "__attrs_attrs__")
36 |
37 | @classmethod
38 | def get_model_fields(cls) -> list[FieldMeta]:
39 | field_metas: list[FieldMeta] = []
40 | none_type = type(None)
41 |
42 | cls.resolve_types(cls.__model__)
43 | fields = attrs.fields(cls.__model__)
44 |
45 | for field in fields:
46 | if not field.init:
47 | continue
48 |
49 | annotation = none_type if field.type is None else field.type
50 |
51 | default = field.default
52 | if isinstance(default, Factory):
53 | # The default value is not currently being used when generating
54 | # the field values. When that is implemented, this would need
55 | # to be handled differently since the `default.factory` could
56 | # take a `self` argument.
57 | default_value = default.factory
58 | elif default is None:
59 | default_value = Null
60 | else:
61 | default_value = default
62 |
63 | field_metas.append(
64 | FieldMeta.from_type(
65 | annotation=annotation,
66 | name=field.alias,
67 | default=default_value,
68 | ),
69 | )
70 |
71 | return field_metas
72 |
73 | @classmethod
74 | def resolve_types(cls, model: type[T], **kwargs: Any) -> None:
75 | """Resolve any strings and forward annotations in type annotations.
76 |
77 | :param model: The model to resolve the type annotations for.
78 | :param kwargs: Any parameters that need to be passed to `attrs.resolve_types`.
79 | """
80 |
81 | attrs.resolve_types(model, **kwargs)
82 |
--------------------------------------------------------------------------------
/polyfactory/factories/beanie_odm_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import TYPE_CHECKING, Any, Generic, TypeVar
4 |
5 | from typing_extensions import get_args
6 |
7 | from polyfactory.exceptions import MissingDependencyException
8 | from polyfactory.factories.pydantic_factory import ModelFactory
9 | from polyfactory.persistence import AsyncPersistenceProtocol
10 | from polyfactory.utils.predicates import is_safe_subclass
11 |
12 | if TYPE_CHECKING:
13 | from typing_extensions import TypeGuard
14 |
15 | from polyfactory.factories.base import BuildContext
16 | from polyfactory.field_meta import FieldMeta
17 |
18 | try:
19 | from beanie import Document
20 | except ImportError as e:
21 | msg = "beanie is not installed"
22 | raise MissingDependencyException(msg) from e
23 |
24 | T = TypeVar("T", bound=Document)
25 |
26 |
27 | class BeaniePersistenceHandler(Generic[T], AsyncPersistenceProtocol[T]):
28 | """Persistence Handler using beanie logic"""
29 |
30 | async def save(self, data: T) -> T:
31 | """Persist a single instance in mongoDB."""
32 | return await data.insert() # pyright: ignore[reportGeneralTypeIssues]
33 |
34 | async def save_many(self, data: list[T]) -> list[T]:
35 | """Persist multiple instances in mongoDB.
36 |
37 | .. note:: we cannot use the ``.insert_many`` method from Beanie here because it doesn't
38 | return the created instances
39 | """
40 | return [await doc.insert() for doc in data] # pyright: ignore[reportGeneralTypeIssues]
41 |
42 |
43 | class BeanieDocumentFactory(Generic[T], ModelFactory[T]):
44 | """Base factory for Beanie Documents"""
45 |
46 | __async_persistence__ = BeaniePersistenceHandler
47 | __is_base_factory__ = True
48 |
49 | @classmethod
50 | def is_supported_type(cls, value: Any) -> "TypeGuard[type[T]]":
51 | """Determine whether the given value is supported by the factory.
52 |
53 | :param value: An arbitrary value.
54 | :returns: A typeguard
55 | """
56 | return is_safe_subclass(value, Document)
57 |
58 | @classmethod
59 | def get_field_value(
60 | cls,
61 | field_meta: "FieldMeta",
62 | field_build_parameters: Any | None = None,
63 | build_context: BuildContext | None = None,
64 | ) -> Any:
65 | """Return a field value on the subclass if existing, otherwise returns a mock value.
66 |
67 | :param field_meta: FieldMeta instance.
68 | :param field_build_parameters: Any build parameters passed to the factory as kwarg values.
69 | :param build_context: BuildContext instance.
70 |
71 | :returns: An arbitrary value.
72 |
73 | """
74 | if hasattr(field_meta.annotation, "__name__"):
75 | if "Indexed " in field_meta.annotation.__name__:
76 | base_type = field_meta.annotation.__bases__[0]
77 | field_meta.annotation = base_type
78 |
79 | if "Link" in field_meta.annotation.__name__:
80 | link_class = get_args(field_meta.annotation)[0]
81 | field_meta.annotation = link_class
82 | field_meta.annotation = link_class
83 |
84 | return super().get_field_value(
85 | field_meta=field_meta,
86 | field_build_parameters=field_build_parameters,
87 | build_context=build_context,
88 | )
89 |
--------------------------------------------------------------------------------
/polyfactory/factories/dataclass_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import MISSING, fields, is_dataclass
4 | from typing import Any, Generic
5 |
6 | from typing_extensions import TypeGuard, get_type_hints
7 |
8 | from polyfactory.factories.base import BaseFactory, T
9 | from polyfactory.field_meta import FieldMeta, Null
10 |
11 |
12 | class DataclassFactory(Generic[T], BaseFactory[T]):
13 | """Dataclass base factory"""
14 |
15 | __is_base_factory__ = True
16 |
17 | @classmethod
18 | def is_supported_type(cls, value: Any) -> TypeGuard[type[T]]:
19 | """Determine whether the given value is supported by the factory.
20 |
21 | :param value: An arbitrary value.
22 | :returns: A typeguard
23 | """
24 | return bool(is_dataclass(value))
25 |
26 | @classmethod
27 | def get_model_fields(cls) -> list["FieldMeta"]:
28 | """Retrieve a list of fields from the factory's model.
29 |
30 |
31 | :returns: A list of field MetaData instances.
32 |
33 | """
34 | fields_meta: list["FieldMeta"] = []
35 |
36 | model_type_hints = get_type_hints(cls.__model__, include_extras=True)
37 |
38 | for field in fields(cls.__model__): # type: ignore[arg-type]
39 | if not field.init:
40 | continue
41 |
42 | if field.default_factory and field.default_factory is not MISSING:
43 | default_value = field.default_factory()
44 | elif field.default is not MISSING:
45 | default_value = field.default
46 | else:
47 | default_value = Null
48 |
49 | fields_meta.append(
50 | FieldMeta.from_type(
51 | annotation=model_type_hints[field.name],
52 | name=field.name,
53 | default=default_value,
54 | ),
55 | )
56 |
57 | return fields_meta
58 |
--------------------------------------------------------------------------------
/polyfactory/factories/msgspec_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from inspect import isclass
4 | from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar
5 |
6 | from typing_extensions import get_type_hints
7 |
8 | from polyfactory.exceptions import MissingDependencyException
9 | from polyfactory.factories.base import BaseFactory
10 | from polyfactory.field_meta import FieldMeta, Null
11 | from polyfactory.value_generators.constrained_numbers import handle_constrained_int
12 | from polyfactory.value_generators.primitives import create_random_bytes
13 |
14 | if TYPE_CHECKING:
15 | from typing_extensions import TypeGuard
16 |
17 | try:
18 | import msgspec
19 | from msgspec.structs import fields
20 | except ImportError as e:
21 | msg = "msgspec is not installed"
22 | raise MissingDependencyException(msg) from e
23 |
24 | T = TypeVar("T", bound=msgspec.Struct)
25 |
26 |
27 | class MsgspecFactory(Generic[T], BaseFactory[T]):
28 | """Base factory for msgspec Structs."""
29 |
30 | __is_base_factory__ = True
31 |
32 | @classmethod
33 | def get_provider_map(cls) -> dict[Any, Callable[[], Any]]:
34 | def get_msgpack_ext() -> msgspec.msgpack.Ext:
35 | code = handle_constrained_int(cls.__random__, ge=-128, le=127)
36 | data = create_random_bytes(cls.__random__)
37 | return msgspec.msgpack.Ext(code, data)
38 |
39 | msgspec_provider_map = {msgspec.UnsetType: lambda: msgspec.UNSET, msgspec.msgpack.Ext: get_msgpack_ext}
40 |
41 | provider_map = super().get_provider_map()
42 | provider_map.update(msgspec_provider_map)
43 |
44 | return provider_map
45 |
46 | @classmethod
47 | def is_supported_type(cls, value: Any) -> TypeGuard[type[T]]:
48 | return isclass(value) and hasattr(value, "__struct_fields__")
49 |
50 | @classmethod
51 | def get_model_fields(cls) -> list[FieldMeta]:
52 | fields_meta: list[FieldMeta] = []
53 |
54 | type_hints = get_type_hints(cls.__model__, include_extras=True)
55 | for field in fields(cls.__model__):
56 | annotation = type_hints[field.name]
57 | if field.default is not msgspec.NODEFAULT:
58 | default_value = field.default
59 | elif field.default_factory is not msgspec.NODEFAULT:
60 | default_value = field.default_factory()
61 | else:
62 | default_value = Null
63 |
64 | fields_meta.append(
65 | FieldMeta.from_type(
66 | annotation=annotation,
67 | name=field.name,
68 | default=default_value,
69 | ),
70 | )
71 | return fields_meta
72 |
--------------------------------------------------------------------------------
/polyfactory/factories/odmantic_odm_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import decimal
4 | from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, Union
5 |
6 | from polyfactory.exceptions import MissingDependencyException
7 | from polyfactory.factories.pydantic_factory import ModelFactory
8 | from polyfactory.utils.predicates import is_safe_subclass
9 | from polyfactory.value_generators.primitives import create_random_bytes
10 |
11 | try:
12 | from bson.decimal128 import Decimal128, create_decimal128_context
13 | from odmantic import EmbeddedModel, Model
14 | from odmantic import bson as odbson
15 |
16 | except ImportError as e:
17 | msg = "odmantic is not installed"
18 | raise MissingDependencyException(msg) from e
19 |
20 | T = TypeVar("T", bound=Union[Model, EmbeddedModel])
21 |
22 | if TYPE_CHECKING:
23 | from typing_extensions import TypeGuard
24 |
25 |
26 | class OdmanticModelFactory(Generic[T], ModelFactory[T]):
27 | """Base factory for odmantic models"""
28 |
29 | __is_base_factory__ = True
30 |
31 | @classmethod
32 | def is_supported_type(cls, value: Any) -> "TypeGuard[type[T]]":
33 | """Determine whether the given value is supported by the factory.
34 |
35 | :param value: An arbitrary value.
36 | :returns: A typeguard
37 | """
38 | return is_safe_subclass(value, (Model, EmbeddedModel))
39 |
40 | @classmethod
41 | def get_provider_map(cls) -> dict[Any, Callable[[], Any]]:
42 | provider_map = super().get_provider_map()
43 | provider_map.update(
44 | {
45 | odbson.Int64: lambda: odbson.Int64.validate(cls.__faker__.pyint()),
46 | odbson.Decimal128: lambda: _to_decimal128(cls.__faker__.pydecimal()),
47 | odbson.Binary: lambda: odbson.Binary.validate(create_random_bytes(cls.__random__)),
48 | odbson._datetime: lambda: odbson._datetime.validate(cls.__faker__.date_time_between()),
49 | # bson.Regex and bson._Pattern not supported as there is no way to generate
50 | # a random regular expression with Faker
51 | # bson.Regex:
52 | # bson._Pattern:
53 | },
54 | )
55 | return provider_map
56 |
57 |
58 | def _to_decimal128(value: decimal.Decimal) -> Decimal128:
59 | with decimal.localcontext(create_decimal128_context()) as ctx:
60 | return Decimal128(ctx.create_decimal(value))
61 |
--------------------------------------------------------------------------------
/polyfactory/factories/typed_dict_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Any, Generic, TypeVar, get_args
4 |
5 | from typing_extensions import ( # type: ignore[attr-defined]
6 | NotRequired,
7 | Required,
8 | TypeGuard,
9 | _TypedDictMeta, # pyright: ignore[reportAttributeAccessIssue]
10 | get_origin,
11 | get_type_hints,
12 | is_typeddict,
13 | )
14 |
15 | from polyfactory.factories.base import BaseFactory
16 | from polyfactory.field_meta import FieldMeta, Null
17 |
18 | TypedDictT = TypeVar("TypedDictT", bound=_TypedDictMeta)
19 |
20 |
21 | class TypedDictFactory(Generic[TypedDictT], BaseFactory[TypedDictT]):
22 | """TypedDict base factory"""
23 |
24 | __is_base_factory__ = True
25 |
26 | @classmethod
27 | def is_supported_type(cls, value: Any) -> TypeGuard[type[TypedDictT]]:
28 | """Determine whether the given value is supported by the factory.
29 |
30 | :param value: An arbitrary value.
31 | :returns: A typeguard
32 | """
33 | return is_typeddict(value)
34 |
35 | @classmethod
36 | def get_model_fields(cls) -> list["FieldMeta"]:
37 | """Retrieve a list of fields from the factory's model.
38 |
39 |
40 | :returns: A list of field MetaData instances.
41 |
42 | """
43 | model_type_hints = get_type_hints(cls.__model__, include_extras=True)
44 |
45 | field_metas: list[FieldMeta] = []
46 | for field_name, annotation in model_type_hints.items():
47 | origin = get_origin(annotation)
48 | if origin in (Required, NotRequired):
49 | annotation = get_args(annotation)[0] # noqa: PLW2901
50 |
51 | field_metas.append(
52 | FieldMeta.from_type(
53 | annotation=annotation,
54 | name=field_name,
55 | default=getattr(cls.__model__, field_name, Null),
56 | ),
57 | )
58 |
59 | return field_metas
60 |
--------------------------------------------------------------------------------
/polyfactory/fields.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Any, Callable, Generic, TypedDict, TypeVar, cast
4 |
5 | from typing_extensions import ParamSpec
6 |
7 | from polyfactory.exceptions import ParameterException
8 | from polyfactory.utils import deprecation
9 | from polyfactory.utils.predicates import is_safe_subclass
10 |
11 | T = TypeVar("T")
12 | P = ParamSpec("P")
13 |
14 |
15 | class WrappedCallable(TypedDict):
16 | """A ref storing a callable. This class is a utility meant to prevent binding of methods."""
17 |
18 | value: Callable
19 |
20 |
21 | class Require:
22 | """A factory field that marks an attribute as a required build-time kwarg."""
23 |
24 |
25 | class Ignore:
26 | """A factory field that marks an attribute as ignored."""
27 |
28 |
29 | class Use(Generic[P, T]):
30 | """Factory field used to wrap a callable.
31 |
32 | The callable will be invoked whenever building the given factory attribute.
33 |
34 |
35 | """
36 |
37 | __slots__ = ("args", "fn", "kwargs")
38 |
39 | def __init__(self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None:
40 | """Wrap a callable.
41 |
42 | :param fn: A callable to wrap.
43 | :param args: Any args to pass to the callable.
44 | :param kwargs: Any kwargs to pass to the callable.
45 | """
46 | self.fn: WrappedCallable = {"value": fn}
47 | self.kwargs = kwargs
48 | self.args = args
49 |
50 | def to_value(self) -> T:
51 | """Invoke the callable.
52 |
53 | :returns: The output of the callable.
54 |
55 |
56 | """
57 | return cast("T", self.fn["value"](*self.args, **self.kwargs))
58 |
59 |
60 | class PostGenerated:
61 | """Factory field that allows generating values after other fields are generated by the factory."""
62 |
63 | __slots__ = ("args", "fn", "kwargs")
64 |
65 | def __init__(self, fn: Callable, *args: Any, **kwargs: Any) -> None:
66 | """Designate field as post-generated.
67 |
68 | :param fn: A callable.
69 | :param args: Args for the callable.
70 | :param kwargs: Kwargs for the callable.
71 | """
72 | self.fn: WrappedCallable = {"value": fn}
73 | self.kwargs = kwargs
74 | self.args = args
75 |
76 | def to_value(self, name: str, values: dict[str, Any]) -> Any:
77 | """Invoke the post-generation callback passing to it the build results.
78 |
79 | :param name: Field name.
80 | :param values: Generated values.
81 |
82 | :returns: An arbitrary value.
83 | """
84 | return self.fn["value"](name, values, *self.args, **self.kwargs)
85 |
86 |
87 | class Fixture:
88 | """Factory field to create a pytest fixture from a factory."""
89 |
90 | __slots__ = ("kwargs", "ref", "size")
91 |
92 | @deprecation.deprecated(version="2.20.0", alternative="Use factory directly")
93 | def __init__(self, fixture: Callable, size: int | None = None, **kwargs: Any) -> None:
94 | """Create a fixture from a factory.
95 |
96 | :param fixture: A factory that was registered as a fixture.
97 | :param size: Optional batch size.
98 | :param kwargs: Any build kwargs.
99 | """
100 | self.ref: WrappedCallable = {"value": fixture}
101 | self.size = size
102 | self.kwargs = kwargs
103 |
104 | def to_value(self) -> Any:
105 | """Call the factory's build or batch method.
106 |
107 | :raises: ParameterException
108 |
109 | :returns: The build result.
110 | """
111 | from polyfactory.factories.base import BaseFactory
112 |
113 | factory = self.ref["value"]
114 | if not is_safe_subclass(factory, BaseFactory):
115 | msg = "fixture has not been registered using the register_factory decorator"
116 | raise ParameterException(msg)
117 |
118 | if self.size is not None:
119 | return factory.batch(self.size, **self.kwargs)
120 | return factory.build(**self.kwargs)
121 |
--------------------------------------------------------------------------------
/polyfactory/persistence.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Protocol, TypeVar, runtime_checkable
4 |
5 | T = TypeVar("T")
6 |
7 |
8 | @runtime_checkable
9 | class SyncPersistenceProtocol(Protocol[T]):
10 | """Protocol for sync persistence"""
11 |
12 | def save(self, data: T) -> T:
13 | """Persist a single instance synchronously.
14 |
15 | :param data: A single instance to persist.
16 |
17 | :returns: The persisted result.
18 |
19 | """
20 | ...
21 |
22 | def save_many(self, data: list[T]) -> list[T]:
23 | """Persist multiple instances synchronously.
24 |
25 | :param data: A list of instances to persist.
26 |
27 | :returns: The persisted result
28 |
29 | """
30 | ...
31 |
32 |
33 | @runtime_checkable
34 | class AsyncPersistenceProtocol(Protocol[T]):
35 | """Protocol for async persistence"""
36 |
37 | async def save(self, data: T) -> T:
38 | """Persist a single instance asynchronously.
39 |
40 | :param data: A single instance to persist.
41 |
42 | :returns: The persisted result.
43 | """
44 | ...
45 |
46 | async def save_many(self, data: list[T]) -> list[T]:
47 | """Persist multiple instances asynchronously.
48 |
49 | :param data: A list of instances to persist.
50 |
51 | :returns: The persisted result
52 | """
53 | ...
54 |
--------------------------------------------------------------------------------
/polyfactory/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/polyfactory/py.typed
--------------------------------------------------------------------------------
/polyfactory/pytest_plugin.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import inspect
4 | import re
5 | from typing import (
6 | Any,
7 | Callable,
8 | Literal,
9 | Type,
10 | TypeVar,
11 | Union,
12 | cast,
13 | overload,
14 | )
15 |
16 | import pytest
17 |
18 | from polyfactory.exceptions import ParameterException
19 | from polyfactory.factories.base import BaseFactory
20 | from polyfactory.utils.predicates import is_safe_subclass
21 |
22 | Scope = Union[
23 | Literal["session", "package", "module", "class", "function"],
24 | Callable[[str, pytest.Config], Literal["session", "package", "module", "class", "function"]],
25 | ]
26 | T = TypeVar("T", bound=BaseFactory[Any])
27 |
28 | split_pattern_1 = re.compile(r"([A-Z]+)([A-Z][a-z])")
29 | split_pattern_2 = re.compile(r"([a-z\d])([A-Z])")
30 |
31 |
32 | def _get_fixture_name(name: str) -> str:
33 | """From inflection.underscore.
34 |
35 | :param name: str: A name.
36 |
37 | :returns: Normalized fixture name.
38 |
39 | """
40 | name = re.sub(split_pattern_1, r"\1_\2", name)
41 | name = re.sub(split_pattern_2, r"\1_\2", name)
42 | name = name.replace("-", "_")
43 | return name.lower()
44 |
45 |
46 | class FactoryFixture:
47 | """Decorator that creates a pytest fixture from a factory"""
48 |
49 | __slots__ = ("autouse", "name", "scope")
50 |
51 | def __init__(
52 | self,
53 | scope: Scope = "function",
54 | autouse: bool = False,
55 | name: str | None = None,
56 | ) -> None:
57 | """Create a factory fixture decorator
58 |
59 | :param scope: Fixture scope
60 | :param autouse: Autouse the fixture
61 | :param name: Fixture name
62 | """
63 | self.scope = scope
64 | self.autouse = autouse
65 | self.name = name
66 |
67 | def __call__(self, factory: type[T], depth: int = 1) -> type[T]:
68 | if not is_safe_subclass(factory, BaseFactory):
69 | msg = f"{factory.__name__} is not a BaseFactory subclass."
70 | raise ParameterException(msg)
71 |
72 | fixture_name = self.name or _get_fixture_name(factory.__name__)
73 | fixture_register = pytest.fixture(
74 | scope=self.scope, # pyright: ignore[reportArgumentType]
75 | name=fixture_name,
76 | autouse=self.autouse,
77 | )
78 |
79 | def _factory_fixture() -> type[T]:
80 | """The wrapped factory"""
81 | return cast("Type[T]", factory)
82 |
83 | caller_globals = inspect.stack()[depth][0].f_globals
84 | caller_globals[fixture_name] = fixture_register(_factory_fixture)
85 |
86 | return factory # type: ignore[return-value]
87 |
88 |
89 | @overload
90 | def register_fixture(
91 | factory: None = None,
92 | *,
93 | scope: Scope = "function",
94 | autouse: bool = False,
95 | name: str | None = None,
96 | ) -> FactoryFixture: ...
97 |
98 |
99 | @overload
100 | def register_fixture(
101 | factory: type[T],
102 | *,
103 | scope: Scope = "function",
104 | autouse: bool = False,
105 | name: str | None = None,
106 | ) -> type[T]: ...
107 |
108 |
109 | def register_fixture(
110 | factory: type[T] | None = None,
111 | *,
112 | scope: Scope = "function",
113 | autouse: bool = False,
114 | name: str | None = None,
115 | ) -> type[T] | FactoryFixture:
116 | """A decorator that allows registering model factories as fixtures.
117 |
118 | :param factory: An optional factory class to decorate.
119 | :param scope: Pytest scope.
120 | :param autouse: Auto use fixture.
121 | :param name: Fixture name.
122 |
123 | :returns: A fixture factory instance.
124 | """
125 | factory_fixture = FactoryFixture(scope=scope, autouse=autouse, name=name)
126 | return factory_fixture(factory, depth=2) if factory else factory_fixture
127 |
--------------------------------------------------------------------------------
/polyfactory/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/polyfactory/utils/__init__.py
--------------------------------------------------------------------------------
/polyfactory/utils/_internal.py:
--------------------------------------------------------------------------------
1 | def is_attribute_overridden(base: type, cls: type, attribute_name: str) -> bool:
2 | """Check if attribute exists on `base` but not on `cls`."""
3 | for ancestor in cls.mro():
4 | if ancestor is base:
5 | return False
6 |
7 | if attribute_name in cls.__dict__:
8 | return True
9 | return False
10 |
--------------------------------------------------------------------------------
/polyfactory/utils/types.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING, Any, NoReturn, Union
2 |
3 | try:
4 | from types import NoneType, UnionType
5 |
6 | UNION_TYPES = {UnionType, Union}
7 | except ImportError:
8 | UNION_TYPES = {Union}
9 |
10 | NoneType = type(None) # type: ignore[misc]
11 |
12 |
13 | class Frozendict(dict):
14 | def _immutable_error(self, *_: Any, **__: Any) -> NoReturn:
15 | msg = f"Unable to mutate {type(self).__name__}"
16 | raise TypeError(msg)
17 |
18 | # Override all mutation methods to prevent changes
19 | if not TYPE_CHECKING:
20 | __setitem__ = _immutable_error
21 | __delitem__ = _immutable_error
22 | clear = _immutable_error
23 | pop = _immutable_error
24 | popitem = _immutable_error
25 | update = _immutable_error
26 |
27 | def __hash__(self) -> int: # type: ignore[override]
28 | return hash(tuple(self.items()))
29 |
30 |
31 | __all__ = ("UNION_TYPES", "Frozendict", "NoneType")
32 |
--------------------------------------------------------------------------------
/polyfactory/value_generators/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/polyfactory/value_generators/__init__.py
--------------------------------------------------------------------------------
/polyfactory/value_generators/constrained_dates.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import date, datetime, timedelta, timezone, tzinfo
4 | from typing import TYPE_CHECKING
5 |
6 | if TYPE_CHECKING:
7 | from faker import Faker
8 |
9 |
10 | def handle_constrained_date(
11 | faker: Faker,
12 | ge: date | None = None,
13 | gt: date | None = None,
14 | le: date | None = None,
15 | lt: date | None = None,
16 | tz: tzinfo = timezone.utc,
17 | ) -> date:
18 | """Generates a date value fulfilling the expected constraints.
19 |
20 | :param faker: An instance of faker.
21 | :param lt: Less than value.
22 | :param le: Less than or equal value.
23 | :param gt: Greater than value.
24 | :param ge: Greater than or equal value.
25 | :param tz: A timezone.
26 |
27 | :returns: A date instance.
28 | """
29 | start_date = datetime.now(tz=tz).date() - timedelta(days=100)
30 | if ge:
31 | start_date = ge
32 | elif gt:
33 | start_date = gt + timedelta(days=1)
34 |
35 | end_date = datetime.now(tz=timezone.utc).date() + timedelta(days=100)
36 | if le:
37 | end_date = le
38 | elif lt:
39 | end_date = lt - timedelta(days=1)
40 |
41 | return faker.date_between(start_date=start_date, end_date=end_date)
42 |
--------------------------------------------------------------------------------
/polyfactory/value_generators/constrained_path.py:
--------------------------------------------------------------------------------
1 | from os.path import realpath
2 | from pathlib import Path
3 | from typing import Literal, cast
4 |
5 | from faker import Faker
6 |
7 |
8 | def handle_constrained_path(constraint: Literal["file", "dir", "new"], faker: Faker) -> Path:
9 | if constraint == "new":
10 | return cast("Path", faker.file_path(depth=1, category=None, extension=None))
11 | if constraint == "file":
12 | return Path(realpath(__file__))
13 | return Path(realpath(__file__)).parent
14 |
--------------------------------------------------------------------------------
/polyfactory/value_generators/constrained_url.py:
--------------------------------------------------------------------------------
1 | from polyfactory.field_meta import UrlConstraints
2 |
3 |
4 | def handle_constrained_url(constraints: UrlConstraints) -> str:
5 | schema = (constraints.get("allowed_schemes") or ["http", "https"])[0]
6 | default_host = constraints.get("default_host") or "localhost"
7 | default_port = constraints.get("default_port") or 80
8 | default_path = constraints.get("default_path") or ""
9 |
10 | return f"{schema}://{default_host}:{default_port}{default_path}"
11 |
--------------------------------------------------------------------------------
/polyfactory/value_generators/constrained_uuid.py:
--------------------------------------------------------------------------------
1 | from typing import Literal, cast
2 | from uuid import NAMESPACE_DNS, UUID, uuid1, uuid3, uuid5
3 |
4 | from faker import Faker
5 |
6 | UUID_VERSION_1 = 1
7 | UUID_VERSION_3 = 3
8 | UUID_VERSION_4 = 4
9 | UUID_VERSION_5 = 5
10 |
11 |
12 | def handle_constrained_uuid(uuid_version: Literal[1, 3, 4, 5], faker: Faker) -> UUID:
13 | """Generate a UUID based on the version specified.
14 |
15 | Args:
16 | uuid_version: The version of the UUID to generate.
17 | faker: The Faker instance to use.
18 |
19 | Returns:
20 | The generated UUID.
21 | """
22 | if uuid_version == UUID_VERSION_1:
23 | return uuid1()
24 | if uuid_version == UUID_VERSION_3:
25 | return uuid3(NAMESPACE_DNS, faker.pystr())
26 | if uuid_version == UUID_VERSION_4:
27 | return cast("UUID", faker.uuid4())
28 | if uuid_version == UUID_VERSION_5:
29 | return uuid5(NAMESPACE_DNS, faker.pystr())
30 | msg = f"Unknown UUID version: {uuid_version}"
31 | raise ValueError(msg)
32 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import importlib.util
4 | import random
5 | import string
6 | import sys
7 | from typing import TYPE_CHECKING, TypeVar
8 |
9 | import pytest
10 |
11 | if TYPE_CHECKING:
12 | from pathlib import Path
13 | from types import ModuleType
14 | from typing import Callable
15 |
16 | from pytest import MonkeyPatch
17 |
18 |
19 | T = TypeVar("T")
20 |
21 |
22 | @pytest.fixture()
23 | def create_module(tmp_path: Path, monkeypatch: MonkeyPatch) -> Callable[[str], ModuleType]:
24 | """Utility fixture for dynamic module creation."""
25 |
26 | def wrapped(source: str) -> ModuleType:
27 | """
28 |
29 | Args:
30 | source: Source code as a string.
31 |
32 | Returns:
33 | An imported module.
34 | """
35 |
36 | def not_none(val: T | None) -> T:
37 | assert val is not None
38 | return val
39 |
40 | def module_name_generator() -> str:
41 | letters = string.ascii_lowercase
42 | return "".join(random.choice(letters) for _ in range(10))
43 |
44 | module_name = module_name_generator()
45 | path = tmp_path / f"{module_name}.py"
46 | path.write_text(source)
47 | # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
48 | spec = not_none(importlib.util.spec_from_file_location(module_name, path))
49 | module = not_none(importlib.util.module_from_spec(spec))
50 | monkeypatch.setitem(sys.modules, module_name, module)
51 | not_none(spec.loader).exec_module(module)
52 | return module
53 |
54 | return wrapped
55 |
--------------------------------------------------------------------------------
/tests/constraints/test_byte_constraints.py:
--------------------------------------------------------------------------------
1 | from random import Random
2 |
3 | import pytest
4 | from hypothesis import given
5 | from hypothesis.strategies import booleans, integers
6 |
7 | from polyfactory.exceptions import ParameterException
8 | from polyfactory.value_generators.constrained_strings import handle_constrained_string_or_bytes
9 |
10 |
11 | @given(booleans(), integers(max_value=10000), integers(max_value=10000))
12 | def test_handle_constrained_bytes_with_min_length_and_max_length(
13 | to_lower: bool,
14 | min_length: int,
15 | max_length: int,
16 | ) -> None:
17 | if min_length < 0 or max_length < 0 or min_length > max_length:
18 | with pytest.raises(ParameterException):
19 | handle_constrained_string_or_bytes(
20 | random=Random(),
21 | t_type=bytes,
22 | min_length=min_length,
23 | max_length=max_length,
24 | pattern=None,
25 | )
26 | else:
27 | result = handle_constrained_string_or_bytes(
28 | random=Random(),
29 | t_type=bytes,
30 | min_length=min_length,
31 | max_length=max_length,
32 | pattern=None,
33 | )
34 | if to_lower:
35 | assert result == result.lower()
36 | assert len(result) >= min_length
37 | assert len(result) <= max_length
38 |
39 |
40 | @given(booleans(), integers(max_value=10000))
41 | def test_handle_constrained_bytes_with_min_length(to_lower: bool, min_length: int) -> None:
42 | if min_length < 0:
43 | with pytest.raises(ParameterException):
44 | handle_constrained_string_or_bytes(
45 | random=Random(),
46 | t_type=bytes,
47 | min_length=min_length,
48 | pattern=None,
49 | )
50 | else:
51 | result = handle_constrained_string_or_bytes(
52 | random=Random(),
53 | t_type=bytes,
54 | min_length=min_length,
55 | pattern=None,
56 | )
57 | if to_lower:
58 | assert result == result.lower()
59 | assert len(result) >= min_length
60 |
61 |
62 | @given(booleans(), integers(max_value=10000))
63 | def test_handle_constrained_bytes_with_max_length(to_lower: bool, max_length: int) -> None:
64 | if max_length < 0:
65 | with pytest.raises(ParameterException):
66 | handle_constrained_string_or_bytes(
67 | random=Random(),
68 | t_type=bytes,
69 | max_length=max_length,
70 | pattern=None,
71 | )
72 | else:
73 | result = handle_constrained_string_or_bytes(
74 | random=Random(),
75 | t_type=bytes,
76 | max_length=max_length,
77 | pattern=None,
78 | )
79 | if to_lower:
80 | assert result == result.lower()
81 | assert len(result) <= max_length
82 |
--------------------------------------------------------------------------------
/tests/constraints/test_date_constraints.py:
--------------------------------------------------------------------------------
1 | from datetime import date, timedelta
2 | from typing import Dict, Optional
3 |
4 | import pytest
5 | from hypothesis import given
6 | from hypothesis.strategies import dates
7 |
8 | from pydantic import BaseModel, condate
9 |
10 | from polyfactory.factories.pydantic_factory import ModelFactory
11 |
12 |
13 | @given(
14 | dates(max_value=date.today() - timedelta(days=3)),
15 | dates(min_value=date.today()),
16 | )
17 | @pytest.mark.parametrize(("start", "end"), (("ge", "le"), ("gt", "lt"), ("ge", "lt"), ("gt", "le")))
18 | def test_handle_constrained_date(
19 | start: Optional[str],
20 | end: Optional[str],
21 | start_date: date,
22 | end_date: date,
23 | ) -> None:
24 | if start_date != end_date:
25 | kwargs: Dict[str, date] = {}
26 | if start:
27 | kwargs[start] = start_date
28 | if end:
29 | kwargs[end] = end_date
30 |
31 | class MyModel(BaseModel):
32 | value: condate(**kwargs) # type: ignore
33 |
34 | class MyFactory(ModelFactory):
35 | __model__ = MyModel
36 |
37 | result = MyFactory.build()
38 |
39 | assert result.value
40 |
--------------------------------------------------------------------------------
/tests/constraints/test_frozen_set_constraints.py:
--------------------------------------------------------------------------------
1 | from contextlib import suppress
2 | from typing import Any
3 |
4 | import pytest
5 | from hypothesis import given
6 | from hypothesis.strategies import integers
7 |
8 | from polyfactory.exceptions import ParameterException
9 | from polyfactory.factories.pydantic_factory import ModelFactory, PydanticFieldMeta
10 | from polyfactory.value_generators.constrained_collections import (
11 | handle_constrained_collection,
12 | )
13 |
14 |
15 | @given(
16 | integers(min_value=0, max_value=10),
17 | integers(min_value=0, max_value=10),
18 | )
19 | def test_handle_constrained_set_with_min_items_and_max_items(min_items: int, max_items: int) -> None:
20 | if max_items >= min_items:
21 | result = handle_constrained_collection(
22 | collection_type=frozenset,
23 | factory=ModelFactory,
24 | field_meta=PydanticFieldMeta(name="test", annotation=frozenset),
25 | item_type=str,
26 | max_items=max_items,
27 | min_items=min_items,
28 | )
29 | assert len(result) >= min_items
30 | assert len(result) <= max_items or 1
31 | else:
32 | with pytest.raises(ParameterException):
33 | handle_constrained_collection(
34 | collection_type=frozenset,
35 | factory=ModelFactory,
36 | field_meta=PydanticFieldMeta(name="test", annotation=frozenset),
37 | item_type=str,
38 | max_items=max_items,
39 | min_items=min_items,
40 | )
41 |
42 |
43 | @given(
44 | integers(min_value=0, max_value=10),
45 | )
46 | def test_handle_constrained_set_with_max_items(
47 | max_items: int,
48 | ) -> None:
49 | result = handle_constrained_collection(
50 | collection_type=frozenset,
51 | factory=ModelFactory,
52 | field_meta=PydanticFieldMeta(name="test", annotation=frozenset),
53 | item_type=str,
54 | max_items=max_items,
55 | )
56 | assert len(result) <= max_items or 1
57 |
58 |
59 | @given(
60 | integers(min_value=0, max_value=10),
61 | )
62 | def test_handle_constrained_set_with_min_items(
63 | min_items: int,
64 | ) -> None:
65 | result = handle_constrained_collection(
66 | collection_type=frozenset,
67 | factory=ModelFactory,
68 | field_meta=PydanticFieldMeta(name="test", annotation=frozenset),
69 | item_type=str,
70 | min_items=min_items,
71 | )
72 | assert len(result) >= min_items
73 |
74 |
75 | @pytest.mark.parametrize("t_type", tuple(ModelFactory.get_provider_map()))
76 | def test_handle_constrained_set_with_different_types(t_type: Any) -> None:
77 | with suppress(ParameterException):
78 | result = handle_constrained_collection(
79 | collection_type=frozenset,
80 | factory=ModelFactory,
81 | field_meta=PydanticFieldMeta(name="test", annotation=frozenset),
82 | item_type=t_type,
83 | )
84 | assert len(result) >= 0
85 |
--------------------------------------------------------------------------------
/tests/constraints/test_get_field_value_constraints.py:
--------------------------------------------------------------------------------
1 | from datetime import date, datetime, timedelta
2 | from decimal import Decimal
3 | from typing import FrozenSet, List, Set, Tuple, Type, Union, cast
4 |
5 | import pytest
6 | from typing_extensions import Annotated
7 |
8 | from polyfactory.factories.base import BaseFactory
9 | from polyfactory.field_meta import Constraints, FieldMeta
10 |
11 |
12 | @pytest.mark.parametrize("t", (int, float, Decimal))
13 | def test_numbers(t: Type[Union[int, float, Decimal]]) -> None:
14 | constraints: Constraints = {"ge": 1, "le": 20}
15 | field_meta = FieldMeta.from_type(annotation=t, name="foo", constraints=constraints)
16 | value = BaseFactory.get_field_value(field_meta)
17 |
18 | assert value >= constraints["ge"]
19 | assert value <= constraints["le"]
20 |
21 |
22 | @pytest.mark.parametrize("t", (str, bytes))
23 | def test_str_and_bytes(t: Type[Union[str, bytes]]) -> None:
24 | constraints: Constraints = {"min_length": 20, "max_length": 45}
25 | field_meta = FieldMeta.from_type(annotation=t, name="foo", constraints=constraints)
26 | value = BaseFactory.get_field_value(field_meta)
27 |
28 | assert len(value) >= constraints["min_length"]
29 | assert len(value) <= constraints["max_length"]
30 |
31 |
32 | @pytest.mark.parametrize("t", (List[int], Set[int], Tuple[int], FrozenSet[int]))
33 | def test_collections(t: Type[Union[Tuple, List, Set, FrozenSet]]) -> None:
34 | constraints: Constraints = {
35 | "min_length": 2,
36 | "max_length": 10,
37 | }
38 | field_meta = FieldMeta.from_type(annotation=t, name="foo", constraints=constraints)
39 | value = BaseFactory.get_field_value(field_meta)
40 |
41 | assert len(value) >= constraints["min_length"]
42 | assert len(value) <= constraints["max_length"]
43 |
44 |
45 | def test_date() -> None:
46 | ge_date = datetime.now().date()
47 | le_date = ge_date + timedelta(days=10)
48 | constraints = {"ge": ge_date, "le": le_date}
49 |
50 | field_meta = FieldMeta.from_type(
51 | annotation=date,
52 | name="foo",
53 | constraints=cast(Constraints, constraints),
54 | )
55 | value = BaseFactory.get_field_value(field_meta)
56 |
57 | assert value >= ge_date
58 | assert value <= le_date
59 |
60 |
61 | def test_constraints_parsing() -> None:
62 | constraints = Constraints(min_length=10)
63 | annotation = Annotated[str, constraints]
64 | field_meta = FieldMeta.from_type(annotation)
65 |
66 | assert field_meta.constraints == constraints
67 |
--------------------------------------------------------------------------------
/tests/constraints/test_list_constraints.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from typing import Any, List
3 |
4 | import pytest
5 | from hypothesis import given
6 | from hypothesis.strategies import integers
7 |
8 | from pydantic import VERSION
9 |
10 | from polyfactory.exceptions import ParameterException
11 | from polyfactory.factories.pydantic_factory import ModelFactory, PydanticFieldMeta
12 | from polyfactory.value_generators.constrained_collections import (
13 | handle_constrained_collection,
14 | )
15 |
16 |
17 | @given(
18 | integers(min_value=0, max_value=10),
19 | integers(min_value=0, max_value=10),
20 | )
21 | def test_handle_constrained_list_with_min_items_and_max_items(min_items: int, max_items: int) -> None:
22 | if max_items >= min_items:
23 | result = handle_constrained_collection(
24 | collection_type=list,
25 | factory=ModelFactory,
26 | field_meta=PydanticFieldMeta(name="test", annotation=list),
27 | item_type=str,
28 | max_items=max_items,
29 | min_items=min_items,
30 | )
31 | assert len(result) >= min_items
32 | assert len(result) <= max_items or 1
33 | else:
34 | with pytest.raises(ParameterException):
35 | handle_constrained_collection(
36 | collection_type=list,
37 | factory=ModelFactory,
38 | field_meta=PydanticFieldMeta(name="test", annotation=list),
39 | item_type=str,
40 | max_items=max_items,
41 | min_items=min_items,
42 | )
43 |
44 |
45 | @given(
46 | integers(min_value=0, max_value=10),
47 | )
48 | def test_handle_constrained_list_with_max_items(
49 | max_items: int,
50 | ) -> None:
51 | result = handle_constrained_collection(
52 | collection_type=list,
53 | factory=ModelFactory,
54 | field_meta=PydanticFieldMeta(name="test", annotation=list),
55 | item_type=str,
56 | max_items=max_items,
57 | )
58 | assert len(result) <= max_items or 1
59 |
60 |
61 | @given(
62 | integers(min_value=0, max_value=10),
63 | )
64 | def test_handle_constrained_list_with_min_items(
65 | min_items: int,
66 | ) -> None:
67 | result = handle_constrained_collection(
68 | collection_type=list,
69 | factory=ModelFactory,
70 | field_meta=PydanticFieldMeta.from_type(List[str], name="test"),
71 | item_type=str,
72 | min_items=min_items,
73 | )
74 | assert len(result) >= min_items
75 |
76 |
77 | @pytest.mark.skipif(
78 | sys.version_info < (3, 9) and VERSION.startswith("2"),
79 | reason="fails on python 3.8 with pydantic v2",
80 | )
81 | @pytest.mark.parametrize("t_type", tuple(ModelFactory.get_provider_map()))
82 | def test_handle_constrained_list_with_different_types(t_type: Any) -> None:
83 | field_meta = PydanticFieldMeta.from_type(List[t_type], name="test")
84 | result = handle_constrained_collection(
85 | collection_type=list,
86 | factory=ModelFactory,
87 | field_meta=field_meta.children[0], # type: ignore[index]
88 | item_type=t_type,
89 | )
90 | assert len(result) >= 0
91 |
92 |
93 | def test_handle_unique_items() -> None:
94 | field_meta = PydanticFieldMeta.from_type(List[str], name="test", constraints={"unique_items": True})
95 | result = handle_constrained_collection(
96 | collection_type=list,
97 | factory=ModelFactory,
98 | field_meta=field_meta.children[0], # type: ignore[index]
99 | item_type=str,
100 | unique_items=True,
101 | min_items=2,
102 | max_items=2,
103 | )
104 | assert len(result) == 2
105 | assert len(set(result)) == 2
106 |
--------------------------------------------------------------------------------
/tests/constraints/test_mapping_constraints.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from hypothesis import given
3 | from hypothesis.strategies import integers
4 |
5 | from polyfactory.exceptions import ParameterException
6 | from polyfactory.factories.pydantic_factory import ModelFactory, PydanticFieldMeta
7 | from polyfactory.value_generators.constrained_collections import (
8 | handle_constrained_mapping,
9 | )
10 |
11 |
12 | @given(
13 | integers(min_value=0, max_value=10),
14 | integers(min_value=0, max_value=10),
15 | )
16 | def test_handle_constrained_mapping_with_min_items_and_max_items(min_items: int, max_items: int) -> None:
17 | key_field = PydanticFieldMeta(name="key", annotation=str)
18 | value_field = PydanticFieldMeta(name="value", annotation=int)
19 | field_meta = PydanticFieldMeta(name="test", annotation=dict, children=[key_field, value_field])
20 |
21 | if max_items >= min_items:
22 | result = handle_constrained_mapping(
23 | factory=ModelFactory,
24 | field_meta=field_meta,
25 | max_items=max_items,
26 | min_items=min_items,
27 | )
28 | assert len(result) >= min_items
29 | assert len(result) <= max_items or 1
30 | for key, value in result.items():
31 | assert isinstance(key, str)
32 | assert isinstance(value, int)
33 | else:
34 | with pytest.raises(ParameterException):
35 | handle_constrained_mapping(
36 | factory=ModelFactory,
37 | field_meta=field_meta,
38 | max_items=max_items,
39 | min_items=min_items,
40 | )
41 |
42 |
43 | def test_handle_constrained_mapping_with_constrained_key_and_value() -> None:
44 | key_min_length = 5
45 | value_gt = 100
46 | min_length = 5
47 | max_length = 10
48 |
49 | key_field = PydanticFieldMeta(name="key", annotation=str, constraints={"min_length": key_min_length})
50 | value_field = PydanticFieldMeta(name="value", annotation=int, constraints={"gt": value_gt})
51 | field_meta = PydanticFieldMeta(name="test", annotation=dict, children=[key_field, value_field])
52 |
53 | result = handle_constrained_mapping(
54 | factory=ModelFactory,
55 | field_meta=field_meta,
56 | min_items=min_length,
57 | max_items=max_length,
58 | )
59 |
60 | assert len(result) >= min_length
61 | assert len(result) <= max_length
62 |
63 | for key, value in result.items():
64 | assert isinstance(key, str)
65 | assert isinstance(value, int)
66 |
67 | assert len(key) >= key_min_length
68 | assert value >= value_gt
69 |
--------------------------------------------------------------------------------
/tests/constraints/test_set_constraints.py:
--------------------------------------------------------------------------------
1 | from contextlib import suppress
2 | from typing import Any
3 |
4 | import pytest
5 | from hypothesis import given
6 | from hypothesis.strategies import integers
7 |
8 | from polyfactory.exceptions import ParameterException
9 | from polyfactory.factories.pydantic_factory import ModelFactory, PydanticFieldMeta
10 | from polyfactory.value_generators.constrained_collections import (
11 | handle_constrained_collection,
12 | )
13 |
14 |
15 | @given(
16 | integers(min_value=0, max_value=10),
17 | integers(min_value=0, max_value=10),
18 | )
19 | def test_handle_constrained_set_with_min_items_and_max_items(min_items: int, max_items: int) -> None:
20 | if max_items >= min_items:
21 | result = handle_constrained_collection(
22 | collection_type=list,
23 | factory=ModelFactory,
24 | field_meta=PydanticFieldMeta(name="test", annotation=set),
25 | item_type=str,
26 | max_items=max_items,
27 | min_items=min_items,
28 | )
29 | assert len(result) >= min_items
30 | assert len(result) <= max_items or 1
31 | else:
32 | with pytest.raises(ParameterException):
33 | handle_constrained_collection(
34 | collection_type=list,
35 | factory=ModelFactory,
36 | field_meta=PydanticFieldMeta(name="test", annotation=set),
37 | item_type=str,
38 | max_items=max_items,
39 | min_items=min_items,
40 | )
41 |
42 |
43 | @given(
44 | integers(min_value=0, max_value=10),
45 | )
46 | def test_handle_constrained_set_with_max_items(
47 | max_items: int,
48 | ) -> None:
49 | result = handle_constrained_collection(
50 | collection_type=list,
51 | factory=ModelFactory,
52 | field_meta=PydanticFieldMeta(name="test", annotation=set),
53 | item_type=str,
54 | max_items=max_items,
55 | )
56 | assert len(result) <= max_items or 1
57 |
58 |
59 | @given(
60 | integers(min_value=0, max_value=10),
61 | )
62 | def test_handle_constrained_set_with_min_items(
63 | min_items: int,
64 | ) -> None:
65 | result = handle_constrained_collection(
66 | collection_type=list,
67 | factory=ModelFactory,
68 | field_meta=PydanticFieldMeta(name="test", annotation=set),
69 | item_type=str,
70 | min_items=min_items,
71 | )
72 | assert len(result) >= min_items
73 |
74 |
75 | @pytest.mark.parametrize("t_type", tuple(ModelFactory.get_provider_map()))
76 | def test_handle_constrained_set_with_different_types(t_type: Any) -> None:
77 | with suppress(ParameterException):
78 | result = handle_constrained_collection(
79 | collection_type=list,
80 | factory=ModelFactory,
81 | field_meta=PydanticFieldMeta(name="test", annotation=set),
82 | item_type=t_type,
83 | )
84 | assert len(result) >= 0
85 |
--------------------------------------------------------------------------------
/tests/models.py:
--------------------------------------------------------------------------------
1 | from datetime import date, datetime
2 | from typing import List, Optional, Union
3 | from uuid import uuid4
4 |
5 | from pydantic import UUID4, BaseModel
6 |
7 | from polyfactory.factories.pydantic_factory import ModelFactory
8 |
9 |
10 | class Pet(BaseModel):
11 | name: str
12 | species: str
13 | color: str
14 | sound: str
15 | age: float
16 |
17 |
18 | class Person(BaseModel):
19 | id: UUID4
20 | name: str
21 | hobbies: Optional[List[str]]
22 | nicks: List[str]
23 | age: Union[float, int]
24 | pets: List[Pet]
25 | birthday: Union[datetime, date]
26 |
27 |
28 | class PersonFactoryWithoutDefaults(ModelFactory[Person]):
29 | __model__ = Person
30 |
31 |
32 | class PersonFactoryWithDefaults(PersonFactoryWithoutDefaults):
33 | id = uuid4()
34 | name = "moishe"
35 | hobbies = ["fishing"]
36 | nicks: List[str] = []
37 | age = 33
38 | pets: List[Pet] = []
39 | birthday = datetime(2021 - 33, 1, 1)
40 |
41 |
42 | class PetFactory(ModelFactory[Pet]):
43 | __model__ = Pet
44 |
--------------------------------------------------------------------------------
/tests/sqlalchemy_factory/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/litestar-org/polyfactory/d03aa1bfb6221d87cf5d83ad184beaf2d4d77178/tests/sqlalchemy_factory/__init__.py
--------------------------------------------------------------------------------
/tests/sqlalchemy_factory/conftest.py:
--------------------------------------------------------------------------------
1 | from typing import AsyncIterator
2 |
3 | import pytest
4 | from sqlalchemy import create_engine
5 | from sqlalchemy.engine import Engine
6 | from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
7 |
8 | from tests.sqlalchemy_factory.models import Base
9 |
10 |
11 | @pytest.fixture()
12 | def engine() -> Engine:
13 | return create_engine("sqlite:///:memory:")
14 |
15 |
16 | @pytest.fixture()
17 | def async_engine() -> AsyncEngine:
18 | return create_async_engine("sqlite+aiosqlite:///:memory:")
19 |
20 |
21 | @pytest.fixture(autouse=True)
22 | async def fx_drop_create_meta(async_engine: AsyncEngine) -> AsyncIterator[None]:
23 | async with async_engine.begin() as conn:
24 | await conn.run_sync(Base.metadata.drop_all)
25 | await conn.run_sync(Base.metadata.create_all)
26 | yield
27 |
--------------------------------------------------------------------------------
/tests/sqlalchemy_factory/models.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Any, Optional
3 |
4 | from sqlalchemy import (
5 | Boolean,
6 | Column,
7 | DateTime,
8 | ForeignKey,
9 | Integer,
10 | String,
11 | func,
12 | orm,
13 | text,
14 | )
15 | from sqlalchemy.ext.associationproxy import association_proxy
16 | from sqlalchemy.orm import relationship
17 | from sqlalchemy.orm.decl_api import DeclarativeMeta, registry
18 |
19 | _registry = registry()
20 |
21 |
22 | @dataclass
23 | class NonSQLAchemyClass:
24 | id: int
25 |
26 |
27 | class Base(metaclass=DeclarativeMeta):
28 | __abstract__ = True
29 | __allow_unmapped__ = True
30 |
31 | registry = _registry
32 | metadata = _registry.metadata
33 |
34 |
35 | class Author(Base):
36 | __tablename__ = "authors"
37 |
38 | id: Any = Column(Integer(), primary_key=True)
39 | books: Any = orm.relationship(
40 | "Book",
41 | collection_class=list,
42 | uselist=True,
43 | back_populates="author",
44 | )
45 |
46 |
47 | class Book(Base):
48 | __tablename__ = "books"
49 |
50 | id: Any = Column(Integer(), primary_key=True)
51 | author_id: Any = Column(
52 | Integer(),
53 | ForeignKey(Author.id),
54 | nullable=False,
55 | )
56 | author: Any = orm.relationship(
57 | Author,
58 | uselist=False,
59 | back_populates="books",
60 | )
61 |
62 |
63 | class AsyncModel(Base):
64 | __tablename__ = "async_model"
65 |
66 | id: Any = Column(Integer(), primary_key=True)
67 |
68 |
69 | class AsyncRefreshModel(Base):
70 | __tablename__ = "server_default_test"
71 |
72 | id: Any = Column(Integer(), primary_key=True)
73 | test_datetime: Any = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
74 | test_str: Any = Column(String, nullable=False, server_default=text("test_str"))
75 | test_int: Any = Column(Integer, nullable=False, server_default=text("123"))
76 | test_bool: Any = Column(Boolean, nullable=False, server_default=text("False"))
77 |
78 |
79 | class User(Base):
80 | __tablename__ = "users"
81 |
82 | id = Column(Integer, primary_key=True)
83 | name = Column(String)
84 |
85 | user_keyword_associations = relationship(
86 | "UserKeywordAssociation",
87 | back_populates="user",
88 | lazy="selectin",
89 | )
90 | keywords = association_proxy(
91 | "user_keyword_associations", "keyword", creator=lambda keyword_obj: UserKeywordAssociation(keyword=keyword_obj)
92 | )
93 |
94 |
95 | class UserKeywordAssociation(Base):
96 | __tablename__ = "user_keyword"
97 |
98 | user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
99 | keyword_id = Column(Integer, ForeignKey("keywords.id"), primary_key=True)
100 |
101 | user = relationship(User, back_populates="user_keyword_associations")
102 | keyword = relationship("Keyword", lazy="selectin")
103 |
104 | # for prevent mypy error: Unexpected keyword argument "keyword" for "UserKeywordAssociation" [call-arg]
105 | def __init__(self, keyword: Optional["Keyword"] = None):
106 | self.keyword = keyword
107 |
108 |
109 | class Keyword(Base):
110 | __tablename__ = "keywords"
111 |
112 | id = Column(Integer, primary_key=True)
113 | keyword = Column(String)
114 |
115 |
116 | class Department(Base):
117 | __tablename__ = "departments"
118 |
119 | id = Column(Integer, primary_key=True)
120 | director_id = Column(Integer, ForeignKey("users.id"))
121 |
--------------------------------------------------------------------------------
/tests/sqlalchemy_factory/test_association_proxy.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import select
2 | from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
3 |
4 | from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
5 | from tests.sqlalchemy_factory.models import Keyword, User, UserKeywordAssociation
6 |
7 |
8 | class KeywordFactory(SQLAlchemyFactory[Keyword]): ...
9 |
10 |
11 | def test_association_proxy() -> None:
12 | class UserFactory(SQLAlchemyFactory[User]):
13 | __set_association_proxy__ = True
14 |
15 | user = UserFactory.build()
16 | assert isinstance(user.keywords[0], Keyword)
17 | assert isinstance(user.user_keyword_associations[0], UserKeywordAssociation)
18 |
19 |
20 | async def test_async_persistence(async_engine: AsyncEngine) -> None:
21 | async with AsyncSession(async_engine) as session:
22 |
23 | class AsyncUserFactory(SQLAlchemyFactory[User]):
24 | __set_association_proxy__ = True
25 | __async_session__ = session
26 |
27 | instance = await AsyncUserFactory.create_async()
28 | instances = await AsyncUserFactory.create_batch_async(3)
29 |
30 | async with AsyncSession(async_engine) as session:
31 | result = await session.scalars(select(User))
32 | assert len(result.all()) == 4
33 |
34 | user = await session.get(User, instance.id)
35 | assert user
36 | assert isinstance(user.keywords[0], Keyword)
37 | assert isinstance(user.user_keyword_associations[0], UserKeywordAssociation)
38 |
39 | for instance in instances:
40 | user = await session.get(User, instance.id)
41 | assert user
42 | assert isinstance(user.keywords[0], Keyword)
43 | assert isinstance(user.user_keyword_associations[0], UserKeywordAssociation)
44 |
--------------------------------------------------------------------------------
/tests/test_auto_registration.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass as vanilla_dataclass
2 | from typing import List
3 |
4 | from typing_extensions import TypedDict
5 |
6 | from pydantic import BaseModel
7 |
8 | from polyfactory.factories import DataclassFactory, TypedDictFactory
9 | from polyfactory.factories.pydantic_factory import ModelFactory
10 |
11 |
12 | class A(BaseModel):
13 | a_text: str
14 |
15 |
16 | class B(BaseModel):
17 | b_text: str
18 | a: A
19 |
20 |
21 | class C(BaseModel):
22 | b: B
23 | b_list: List[B]
24 |
25 |
26 | def test_auto_register_model_factory() -> None:
27 | class AFactory(ModelFactory):
28 | a_text = "const value"
29 | __model__ = A
30 |
31 | class BFactory(ModelFactory):
32 | b_text = "const value"
33 | __model__ = B
34 | __set_as_default_factory_for_type__ = True
35 |
36 | class CFactory(ModelFactory):
37 | __model__ = C
38 |
39 | c = CFactory.build()
40 |
41 | assert c.b.b_text == BFactory.b_text
42 | assert c.b_list[0].b_text == BFactory.b_text
43 | assert c.b.a.a_text != AFactory.a_text
44 |
45 |
46 | def test_auto_register_model_factory_using_create_factory() -> None:
47 | const_value = "const value"
48 | ModelFactory.create_factory(model=A, a_text=const_value)
49 | ModelFactory.create_factory(model=B, b_text=const_value, __set_as_default_factory_for_type__=True)
50 | factory = ModelFactory.create_factory(model=C)
51 |
52 | c = factory.build()
53 |
54 | assert c.b.b_text == const_value
55 | assert c.b_list[0].b_text == const_value
56 | assert c.b.a.a_text != const_value
57 |
58 |
59 | def test_dataclass_model_factory_auto_registration() -> None:
60 | @vanilla_dataclass
61 | class DataClass:
62 | text: str
63 |
64 | class UpperModel(BaseModel):
65 | nested_field: DataClass
66 | nested_list_field: List[DataClass]
67 |
68 | class UpperModelFactory(ModelFactory):
69 | __model__ = UpperModel
70 |
71 | class DTFactory(DataclassFactory):
72 | text = "const value"
73 | __model__ = DataClass
74 | __set_as_default_factory_for_type__ = True
75 |
76 | upper = UpperModelFactory.build()
77 |
78 | assert upper.nested_field.text == DTFactory.text
79 | assert upper.nested_list_field[0].text == DTFactory.text
80 |
81 |
82 | def test_typeddict_model_factory_auto_registration() -> None:
83 | class TD(TypedDict):
84 | text: str
85 |
86 | class UpperSchema(BaseModel):
87 | nested_field: TD
88 | nested_list_field: List[TD]
89 |
90 | class UpperModelFactory(ModelFactory):
91 | __model__ = UpperSchema
92 |
93 | class TDFactory(TypedDictFactory):
94 | text = "const value"
95 | __model__ = TD
96 | __set_as_default_factory_for_type__ = True
97 |
98 | upper = UpperModelFactory.build()
99 |
100 | assert upper.nested_field["text"] == TDFactory.text
101 | assert upper.nested_list_field[0]["text"] == TDFactory.text
102 |
--------------------------------------------------------------------------------
/tests/test_beanie_factory.py:
--------------------------------------------------------------------------------
1 | from sys import version_info
2 | from typing import List
3 |
4 | import pymongo
5 | import pytest
6 | from beanie import Document, Link, init_beanie
7 | from beanie.odm.fields import Indexed, PydanticObjectId
8 | from mongomock_motor import AsyncMongoMockClient
9 |
10 | from polyfactory.factories.beanie_odm_factory import BeanieDocumentFactory
11 |
12 |
13 | @pytest.fixture()
14 | def mongo_connection() -> AsyncMongoMockClient:
15 | return AsyncMongoMockClient()
16 |
17 |
18 | class MyDocument(Document):
19 | id: PydanticObjectId
20 | name: str
21 | index: Indexed(str, pymongo.DESCENDING) # type: ignore
22 | siblings: List[PydanticObjectId]
23 |
24 |
25 | class MyOtherDocument(Document):
26 | id: PydanticObjectId
27 | document: Link[MyDocument]
28 |
29 |
30 | class MyFactory(BeanieDocumentFactory):
31 | __model__ = MyDocument
32 |
33 |
34 | class MyOtherFactory(BeanieDocumentFactory):
35 | __model__ = MyOtherDocument
36 |
37 |
38 | @pytest.fixture(autouse=True)
39 | async def beanie_init(mongo_connection: AsyncMongoMockClient) -> None:
40 | await init_beanie(database=mongo_connection.db_name, document_models=[MyDocument, MyOtherDocument])
41 |
42 |
43 | async def test_handling_of_beanie_types() -> None:
44 | result = MyFactory.build()
45 | assert result.name
46 | assert result.index
47 | assert isinstance(result.index, str)
48 |
49 |
50 | async def test_beanie_persistence_of_single_instance() -> None:
51 | result = await MyFactory.create_async()
52 | assert result.id
53 | assert result.name
54 | assert result.index
55 | assert isinstance(result.index, str)
56 |
57 |
58 | async def test_beanie_persistence_of_multiple_instances() -> None:
59 | result = await MyFactory.create_batch_async(size=3)
60 | assert len(result) == 3
61 | for instance in result:
62 | assert instance.id
63 | assert instance.name
64 | assert instance.index
65 | assert isinstance(instance.index, str)
66 |
67 |
68 | @pytest.mark.skipif(version_info < (3, 11), reason="test isolation issues on lower versions")
69 | async def test_beanie_links() -> None:
70 | result = await MyOtherFactory.create_async()
71 | assert isinstance(result.document, MyDocument)
72 |
--------------------------------------------------------------------------------
/tests/test_build.py:
--------------------------------------------------------------------------------
1 | from uuid import uuid4
2 |
3 | from pydantic import BaseModel
4 |
5 | from polyfactory.factories.pydantic_factory import ModelFactory
6 | from tests.models import PersonFactoryWithDefaults, Pet, PetFactory
7 |
8 |
9 | def test_merges_defaults_with_kwargs() -> None:
10 | first_obj = PersonFactoryWithDefaults.build()
11 | assert first_obj.id == PersonFactoryWithDefaults.id
12 | assert first_obj.name == PersonFactoryWithDefaults.name
13 | assert first_obj.hobbies == PersonFactoryWithDefaults.hobbies
14 | assert first_obj.age == PersonFactoryWithDefaults.age
15 | assert first_obj.pets == PersonFactoryWithDefaults.pets
16 | assert first_obj.birthday == PersonFactoryWithDefaults.birthday
17 | pet = Pet(
18 | name="bluey the blowfish",
19 | species="blowfish",
20 | color="bluish-green",
21 | sound="",
22 | age=1,
23 | )
24 | id_ = uuid4()
25 | hobbies = ["dancing"]
26 | age = 35
27 | pets = [pet]
28 | second_obj = PersonFactoryWithDefaults.build(id=id_, hobbies=hobbies, age=age, pets=pets)
29 | assert second_obj.id == id_
30 | assert second_obj.hobbies == hobbies
31 | assert second_obj.age == age
32 | assert second_obj.pets == [pet]
33 | assert second_obj.name == PersonFactoryWithDefaults.name
34 | assert second_obj.birthday == PersonFactoryWithDefaults.birthday
35 |
36 |
37 | def test_respects_none_overrides() -> None:
38 | result = PersonFactoryWithDefaults.build(hobbies=None)
39 | assert result.hobbies is None
40 |
41 |
42 | def test_uses_faker_to_set_values_when_none_available_on_class() -> None:
43 | result = PetFactory.build()
44 | assert isinstance(result.name, str)
45 | assert isinstance(result.species, str)
46 | assert isinstance(result.color, str)
47 | assert isinstance(result.sound, str)
48 | assert isinstance(result.age, float)
49 |
50 |
51 | def test_builds_batch() -> None:
52 | results = PetFactory.batch(10)
53 | assert isinstance(results, list)
54 | assert len(results) == 10
55 | for result in results:
56 | assert isinstance(result.name, str)
57 | assert isinstance(result.species, str)
58 | assert isinstance(result.color, str)
59 | assert isinstance(result.sound, str)
60 | assert isinstance(result.age, float)
61 |
62 |
63 | def test_build_model_with_fields_named_like_factory_fields() -> None:
64 | class C(BaseModel):
65 | batch: int
66 |
67 | class CFactory(ModelFactory):
68 | __model__ = C
69 |
70 | assert CFactory.build()
71 |
--------------------------------------------------------------------------------
/tests/test_collection_extender.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING, Dict, FrozenSet, List, Set, Tuple
2 |
3 | import pytest
4 |
5 | from polyfactory.collection_extender import CollectionExtender
6 |
7 | if TYPE_CHECKING:
8 | from typing import Any
9 |
10 | pytestmark = pytest.mark.parametrize("number_of_args", [0, 1, 3])
11 |
12 |
13 | def test_tuple_extender(number_of_args: int) -> None:
14 | annotation_alias: Any = Tuple[int, ...]
15 | type_args = (int, ...)
16 |
17 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
18 |
19 | assert extended_type_args == (int,) * number_of_args
20 |
21 |
22 | def test_tuple_extender__constant_length(number_of_args: int) -> None:
23 | annotation_alias: Any = Tuple[int, int, int]
24 | type_args = (int, int, int, int)
25 |
26 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
27 |
28 | assert extended_type_args == (int, int, int, int)
29 |
30 |
31 | def test_tuple_extender__not_typed(number_of_args: int) -> None:
32 | annotation_alias: Any = Tuple
33 | type_args = ()
34 |
35 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
36 |
37 | assert extended_type_args == ()
38 |
39 |
40 | def test_list_extender(number_of_args: int) -> None:
41 | annotation_alias: Any = List[int]
42 | type_args = (int,)
43 |
44 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
45 |
46 | assert extended_type_args == (int,) * number_of_args
47 |
48 |
49 | def test_set_extender(number_of_args: int) -> None:
50 | class Dummy: ...
51 |
52 | annotation_alias: Any = Set[Dummy]
53 | type_args = (Dummy,)
54 |
55 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
56 |
57 | assert extended_type_args == (Dummy,) * number_of_args
58 |
59 |
60 | def test_frozen_set_extender(number_of_args: int) -> None:
61 | class Dummy: ...
62 |
63 | annotation_alias: Any = FrozenSet[Dummy]
64 | type_args = (Dummy,)
65 |
66 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
67 |
68 | assert extended_type_args == (Dummy,) * number_of_args
69 |
70 |
71 | def test_dict_extender(number_of_args: int) -> None:
72 | annotation_alias: Any = Dict[str, int]
73 | type_args = (str, int)
74 |
75 | extended_type_args = CollectionExtender.extend_type_args(annotation_alias, type_args, number_of_args)
76 |
77 | assert extended_type_args == (str, int) * number_of_args
78 |
--------------------------------------------------------------------------------
/tests/test_dicts.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Union
2 |
3 | import pytest
4 |
5 | from pydantic import VERSION, BaseModel
6 |
7 | from polyfactory.factories.pydantic_factory import ModelFactory
8 |
9 |
10 | def test_passing_nested_dict() -> None:
11 | class MyMappedClass(BaseModel):
12 | val: str
13 |
14 | class MyClass(BaseModel):
15 | my_mapping_obj: Dict[str, MyMappedClass]
16 | my_mapping_str: Dict[str, str]
17 |
18 | class MyClassFactory(ModelFactory[MyClass]):
19 | __model__ = MyClass
20 |
21 | obj = MyClassFactory.build(
22 | my_mapping_str={"foo": "bar"},
23 | my_mapping_obj={"baz": MyMappedClass(val="bar")},
24 | )
25 |
26 | assert obj.dict() == {"my_mapping_obj": {"baz": {"val": "bar"}}, "my_mapping_str": {"foo": "bar"}}
27 |
28 |
29 | @pytest.mark.skipif(
30 | VERSION.startswith("2"),
31 | reason="indeterminate behaviour in pydantic 2.0",
32 | )
33 | def test_dict_with_union_random_types() -> None:
34 | class MyClass(BaseModel):
35 | val: Dict[str, Union[int, str]]
36 |
37 | class MyClassFactory(ModelFactory[MyClass]):
38 | __model__ = MyClass
39 |
40 | MyClassFactory.seed_random(10)
41 |
42 | test_obj_1 = MyClassFactory.build()
43 | test_obj_2 = MyClassFactory.build()
44 |
45 | assert isinstance(next(iter(test_obj_1.val.values())), int)
46 | assert isinstance(next(iter(test_obj_2.val.values())), str)
47 |
--------------------------------------------------------------------------------
/tests/test_factory_configuration.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Any, Callable, Dict, List, Type
3 |
4 | from typing_extensions import TypeGuard
5 |
6 | from polyfactory.factories.base import BaseFactory, T
7 | from polyfactory.factories.dataclass_factory import DataclassFactory
8 |
9 |
10 | def test_setting_set_as_default_factory_for_type_on_base_factory() -> None:
11 | """Setting the value to `True` shouldn't raise exception when initializing."""
12 |
13 | class CustomBaseFactory(BaseFactory[T]):
14 | __is_base_factory__ = True
15 | __set_as_default_factory_for_type__ = True
16 |
17 | @classmethod
18 | def is_supported_type(cls, value: Any) -> TypeGuard[Type[T]]:
19 | # Set this as false since this factory will be injected into the
20 | # list of base factories, but this obviously shouldn't be ran
21 | # for any of the types.
22 | return False
23 |
24 |
25 | def test_inheriting_config() -> None:
26 | class CustomType:
27 | def __init__(self, a: int) -> None:
28 | self.a = a
29 |
30 | @dataclass
31 | class Child:
32 | a: List[int]
33 | custom_type: CustomType
34 |
35 | @dataclass
36 | class Parent:
37 | children: List[Child]
38 |
39 | class ParentFactory(DataclassFactory[Parent]):
40 | __randomize_collection_length__ = True
41 | __min_collection_length__ = 5
42 | __max_collection_length__ = 5
43 |
44 | @classmethod
45 | def get_provider_map(cls) -> Dict[Any, Callable[[], Any]]:
46 | return {
47 | **super().get_provider_map(),
48 | int: lambda: 42,
49 | CustomType: lambda: CustomType(a=5),
50 | }
51 |
52 | result = ParentFactory.build()
53 | assert len(result.children) == 5
54 | assert result.children[0].a == [42] * 5
55 | assert result.children[0].custom_type.a == 5
56 |
--------------------------------------------------------------------------------
/tests/test_factory_subclassing.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | import pytest
4 |
5 | from pydantic import BaseModel
6 |
7 | from polyfactory import ConfigurationException
8 | from polyfactory.factories import DataclassFactory
9 | from polyfactory.factories.pydantic_factory import ModelFactory
10 | from polyfactory.field_meta import Null
11 |
12 |
13 | def test_factory_raises_config_error_for_unsupported_model_with_supported_factory() -> None:
14 | @dataclass
15 | class DataclassModel:
16 | id: int
17 |
18 | with pytest.raises(ConfigurationException):
19 |
20 | class MyFactory1(ModelFactory):
21 | __model__ = DataclassModel
22 |
23 | class MyFactory2(DataclassFactory):
24 | __model__ = DataclassModel
25 |
26 |
27 | def test_factory_raises_config_error_for_unsupported_model() -> None:
28 | with pytest.raises(ConfigurationException, match="Model type Null is not supported"):
29 |
30 | class MyFactory(ModelFactory):
31 | __model__ = Null
32 |
33 |
34 | def test_inherit_concrete_factory() -> None:
35 | class Parent(BaseModel):
36 | name: str
37 |
38 | class Child(Parent):
39 | n: int
40 |
41 | class ParentFactory(ModelFactory):
42 | __model__ = Parent
43 |
44 | @classmethod
45 | def name(cls) -> str:
46 | return cls.__model__.__name__
47 |
48 | class ChildFactory(ParentFactory):
49 | __model__ = Child # type: ignore[assignment]
50 |
51 | assert ParentFactory.build().name == "Parent"
52 | assert ChildFactory.build().name == "Child"
53 |
54 |
55 | def test_inherit_base_factory() -> None:
56 | class Parent(BaseModel):
57 | name: str
58 |
59 | class Child(Parent):
60 | n: int
61 |
62 | class ParentFactory(ModelFactory):
63 | __is_base_factory__ = True
64 |
65 | @classmethod
66 | def name(cls) -> str:
67 | return cls.__model__.__name__
68 |
69 | class ChildFactory(ParentFactory):
70 | __model__ = Child
71 |
72 | exc_info = pytest.raises(AttributeError, ParentFactory.build)
73 | assert "'ParentFactory' has no attribute '__model__'" in str(exc_info.value)
74 | assert ChildFactory.build().name == "Child"
75 |
76 | # remove the ParentFactory from _base_factories to prevent side effects in other tests
77 | # see https://github.com/litestar-org/polyfactory/issues/198
78 | ModelFactory._base_factories.remove(ParentFactory)
79 |
--------------------------------------------------------------------------------
/tests/test_faker_customization.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 |
3 | from polyfactory.factories.pydantic_factory import ModelFactory
4 | from tests.models import Pet
5 |
6 |
7 | def test_allows_user_to_define_faker_instance() -> None:
8 | my_faker = Faker()
9 | setattr(my_faker, "__test__attr__", None)
10 |
11 | class MyFactory(ModelFactory):
12 | __model__ = Pet
13 | __faker__ = my_faker
14 |
15 | assert hasattr(MyFactory.__faker__, "__test__attr__")
16 |
--------------------------------------------------------------------------------
/tests/test_generics.py:
--------------------------------------------------------------------------------
1 | from typing import Generic, List, Optional, TypeVar, Union
2 |
3 | from pydantic import BaseModel
4 |
5 | from polyfactory.factories.pydantic_factory import ModelFactory
6 |
7 | try:
8 | from pydantic.generics import GenericModel
9 | except ImportError:
10 | GenericModel = BaseModel
11 |
12 |
13 | Inner = TypeVar("Inner")
14 | APIResponseData = TypeVar("APIResponseData")
15 |
16 |
17 | class Attributes(GenericModel, Generic[Inner]): # type: ignore
18 | attributes: Inner
19 |
20 |
21 | class OneInner(BaseModel):
22 | one: str
23 | id: Optional[int]
24 | description: Optional[str]
25 |
26 |
27 | class OneResponse(BaseModel):
28 | one: Attributes[OneInner]
29 |
30 |
31 | class TwoInner(BaseModel):
32 | two: str
33 | id: Optional[int]
34 | description: Optional[str]
35 |
36 |
37 | class TwoResponse(BaseModel):
38 | two: Attributes[TwoInner]
39 |
40 |
41 | class ThreeInner(BaseModel):
42 | three: str
43 | relation: int
44 |
45 |
46 | class ThreeResponse(BaseModel):
47 | three: Attributes[ThreeInner]
48 |
49 |
50 | class APIResponse(GenericModel, Generic[APIResponseData]): # type: ignore
51 | data: List[APIResponseData]
52 |
53 |
54 | def test_generic_factory_one_response() -> None:
55 | class APIResponseFactory(ModelFactory[APIResponse[OneResponse]]):
56 | __model__ = APIResponse[OneResponse]
57 |
58 | result = APIResponseFactory.build()
59 |
60 | assert result.data
61 | assert isinstance(result.data[0], OneResponse)
62 |
63 |
64 | def test_generic_factory_two_response() -> None:
65 | class APIResponseFactory(ModelFactory):
66 | __model__ = APIResponse[TwoResponse]
67 |
68 | result = APIResponseFactory.build()
69 |
70 | assert result.data
71 | assert isinstance(result.data[0], TwoResponse)
72 |
73 |
74 | def test_generic_factory_three_response() -> None:
75 | class APIResponseFactory(ModelFactory):
76 | __model__ = APIResponse[ThreeResponse]
77 |
78 | result = APIResponseFactory.build()
79 |
80 | assert result.data
81 | assert isinstance(result.data[0], ThreeResponse)
82 |
83 |
84 | def test_generic_factory_union_response() -> None:
85 | class APIResponseFactory(ModelFactory):
86 | __model__ = APIResponse[Union[OneResponse, TwoResponse, ThreeResponse]]
87 |
88 | result = APIResponseFactory.build()
89 |
90 | assert result.data
91 | assert isinstance(result.data[0], (OneResponse, TwoResponse, ThreeResponse))
92 |
--------------------------------------------------------------------------------
/tests/test_number_generation.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from random import Random
4 |
5 | import pytest
6 |
7 | from polyfactory.value_generators.constrained_numbers import (
8 | generate_constrained_number,
9 | passes_pydantic_multiple_validator,
10 | )
11 | from polyfactory.value_generators.primitives import create_random_float
12 |
13 |
14 | @pytest.mark.parametrize(
15 | ("maximum", "minimum", "multiple_of"),
16 | ((100, 2, 8), (-100, -187, -10), (7.55, 0.13, 0.0123)),
17 | )
18 | def test_generate_constrained_number(maximum: float, minimum: float, multiple_of: float) -> None:
19 | assert passes_pydantic_multiple_validator(
20 | multiple_of=multiple_of,
21 | value=generate_constrained_number(
22 | random=Random(),
23 | minimum=minimum,
24 | maximum=maximum,
25 | multiple_of=multiple_of,
26 | method=create_random_float,
27 | ),
28 | )
29 |
30 |
31 | @pytest.mark.parametrize(
32 | "minimum, maximum",
33 | (
34 | (None, None),
35 | (-200, None),
36 | (200, None),
37 | (None, -200),
38 | (None, 200),
39 | (-200, 200),
40 | ),
41 | )
42 | def test_create_random_float_bounds(
43 | minimum: float | None,
44 | maximum: float | None,
45 | ) -> None:
46 | result = create_random_float(Random(), minimum, maximum)
47 | if minimum is not None:
48 | assert result >= minimum
49 | if maximum is not None:
50 | assert result <= maximum
51 |
52 |
53 | def test_passes_pydantic_multiple_validator_handles_zero_multiplier() -> None:
54 | assert passes_pydantic_multiple_validator(1.0, 0)
55 |
--------------------------------------------------------------------------------
/tests/test_odmantic_factory.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from typing import Any, List
3 | from uuid import UUID
4 |
5 | import bson
6 | import pytest
7 | from bson import ObjectId
8 |
9 | import pydantic
10 |
11 | if not pydantic.VERSION.startswith("1"):
12 | pytest.skip("only for pydantic v1", allow_module_level=True)
13 |
14 | from odmantic import AIOEngine, EmbeddedModel, Model
15 |
16 | from polyfactory.decorators import post_generated
17 | from polyfactory.factories.odmantic_odm_factory import OdmanticModelFactory
18 |
19 |
20 | class OtherEmbeddedDocument(EmbeddedModel): # type: ignore
21 | name: str
22 | serial: UUID
23 | created_on: datetime
24 | bson_id: bson.ObjectId
25 | bson_int64: bson.Int64
26 | bson_dec128: bson.Decimal128
27 | bson_binary: bson.Binary
28 |
29 |
30 | class MyEmbeddedDocument(EmbeddedModel): # type: ignore
31 | name: str
32 | serial: UUID
33 | other_embedded_document: OtherEmbeddedDocument
34 | created_on: datetime
35 | bson_id: bson.ObjectId
36 | bson_int64: bson.Int64
37 | bson_dec128: bson.Decimal128
38 | bson_binary: bson.Binary
39 |
40 |
41 | class MyModel(Model): # type: ignore
42 | created_on: datetime
43 | bson_id: bson.ObjectId
44 | bson_int64: bson.Int64
45 | bson_dec128: bson.Decimal128
46 | bson_binary: bson.Binary
47 | name: str
48 | embedded: MyEmbeddedDocument
49 | embedded_list: List[MyEmbeddedDocument]
50 |
51 |
52 | @pytest.fixture()
53 | async def odmantic_engine(mongo_connection: Any) -> AIOEngine:
54 | return AIOEngine(client=mongo_connection, database=mongo_connection.db_name)
55 |
56 |
57 | def test_handles_odmantic_models() -> None:
58 | class MyFactory(OdmanticModelFactory):
59 | __model__ = MyModel
60 |
61 | result = MyFactory.build()
62 |
63 | assert isinstance(result, MyModel)
64 | assert isinstance(result.id, bson.ObjectId)
65 | assert isinstance(result.created_on, datetime)
66 | assert isinstance(result.bson_id, bson.ObjectId)
67 | assert isinstance(result.bson_int64, bson.Int64)
68 | assert isinstance(result.bson_dec128, bson.Decimal128)
69 | assert isinstance(result.bson_binary, bson.Binary)
70 | assert isinstance(result.name, str)
71 | assert isinstance(result.embedded, MyEmbeddedDocument)
72 | assert isinstance(result.embedded_list, list)
73 | for item in result.embedded_list:
74 | assert isinstance(item, MyEmbeddedDocument)
75 | assert isinstance(item.name, str)
76 | assert isinstance(item.serial, UUID)
77 | assert isinstance(item.created_on, datetime)
78 | assert isinstance(item.bson_id, bson.ObjectId)
79 | assert isinstance(item.bson_int64, bson.Int64)
80 | assert isinstance(item.bson_dec128, bson.Decimal128)
81 | assert isinstance(item.bson_binary, bson.Binary)
82 |
83 | other = item.other_embedded_document
84 | assert isinstance(other, OtherEmbeddedDocument)
85 | assert isinstance(other.name, str)
86 | assert isinstance(other.serial, UUID)
87 | assert isinstance(other.created_on, datetime)
88 | assert isinstance(other.bson_id, bson.ObjectId)
89 | assert isinstance(other.bson_int64, bson.Int64)
90 | assert isinstance(other.bson_dec128, bson.Decimal128)
91 | assert isinstance(other.bson_binary, bson.Binary)
92 |
93 |
94 | def test_post_generated_from_id() -> None:
95 | class MyFactory(OdmanticModelFactory):
96 | __model__ = MyModel
97 |
98 | @post_generated
99 | @classmethod
100 | def name(cls, id: ObjectId) -> str:
101 | return f"{cls.__faker__.name()} [{id.generation_time}]"
102 |
103 | MyFactory.build()
104 |
--------------------------------------------------------------------------------
/tests/test_options_validation.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from polyfactory import ConfigurationException
4 | from polyfactory.factories.pydantic_factory import ModelFactory
5 | from tests.models import Person
6 |
7 |
8 | def test_validates_model_is_set_on_definition_of_factory() -> None:
9 | with pytest.raises(ConfigurationException):
10 |
11 | class MyFactory(ModelFactory):
12 | pass
13 |
14 |
15 | def test_validates_connection_in_create_sync() -> None:
16 | class MyFactory(ModelFactory):
17 | __model__ = Person
18 |
19 | with pytest.raises(ConfigurationException):
20 | MyFactory.create_sync()
21 |
22 |
23 | def test_validates_connection_in_create_batch_sync() -> None:
24 | class MyFactory(ModelFactory):
25 | __model__ = Person
26 |
27 | with pytest.raises(ConfigurationException):
28 | MyFactory.create_batch_sync(2)
29 |
30 |
31 | @pytest.mark.asyncio()
32 | async def test_validates_connection_in_create_async() -> None:
33 | class MyFactory(ModelFactory):
34 | __model__ = Person
35 |
36 | with pytest.raises(ConfigurationException):
37 | await MyFactory.create_async()
38 |
39 |
40 | @pytest.mark.asyncio()
41 | async def test_validates_connection_in_create_batch_async() -> None:
42 | class MyFactory(ModelFactory):
43 | __model__ = Person
44 |
45 | with pytest.raises(ConfigurationException):
46 | await MyFactory.create_batch_async(2)
47 |
--------------------------------------------------------------------------------
/tests/test_persistence_handlers.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | import pytest
4 |
5 | from pydantic import BaseModel
6 |
7 | from polyfactory import AsyncPersistenceProtocol, SyncPersistenceProtocol
8 | from polyfactory.factories.pydantic_factory import ModelFactory
9 |
10 |
11 | class MyModel(BaseModel):
12 | name: str
13 |
14 |
15 | class MySyncPersistenceHandler(SyncPersistenceProtocol):
16 | def save(self, data: Any, *args: Any, **kwargs: Any) -> Any:
17 | return data
18 |
19 | def save_many(self, data: Any, *args: Any, **kwargs: Any) -> Any:
20 | return data
21 |
22 |
23 | class MyAsyncPersistenceHandler(AsyncPersistenceProtocol):
24 | async def save(self, data: Any, *args: Any, **kwargs: Any) -> Any:
25 | return data
26 |
27 | async def save_many(self, data: Any, *args: Any, **kwargs: Any) -> Any:
28 | return data
29 |
30 |
31 | def test_sync_persistence_handler_is_set_and_called_with_instance() -> None:
32 | class MyFactory(ModelFactory):
33 | __model__ = MyModel
34 | __sync_persistence__ = MySyncPersistenceHandler()
35 |
36 | assert MyFactory.create_sync().name
37 | assert [instance.name for instance in MyFactory.create_batch_sync(size=2)]
38 |
39 |
40 | def test_sync_persistence_handler_is_set_and_called_with_class() -> None:
41 | class MyFactory(ModelFactory):
42 | __model__ = MyModel
43 | __sync_persistence__ = MySyncPersistenceHandler
44 |
45 | assert MyFactory.create_sync().name
46 | assert [instance.name for instance in MyFactory.create_batch_sync(size=2)]
47 |
48 |
49 | @pytest.mark.asyncio()
50 | async def test_async_persistence_handler_is_set_and_called_with_instance() -> None:
51 | class MyFactory(ModelFactory):
52 | __model__ = MyModel
53 | __async_persistence__ = MyAsyncPersistenceHandler()
54 |
55 | assert (await MyFactory.create_async()).name
56 | assert [instance.name for instance in (await MyFactory.create_batch_async(size=2))]
57 |
58 |
59 | @pytest.mark.asyncio()
60 | async def test_async_persistence_handler_is_set_and_called_with_class() -> None:
61 | class MyFactory(ModelFactory):
62 | __model__ = MyModel
63 | __async_persistence__ = MyAsyncPersistenceHandler
64 |
65 | assert (await MyFactory.create_async()).name
66 | assert [instance.name for instance in (await MyFactory.create_batch_async(size=2))]
67 |
--------------------------------------------------------------------------------
/tests/test_provider_map.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Any, Callable, Dict, Generic, TypeVar
3 |
4 | import pytest
5 |
6 | from polyfactory.exceptions import ParameterException
7 | from polyfactory.factories.base import BaseFactory
8 | from polyfactory.factories.dataclass_factory import DataclassFactory
9 |
10 |
11 | def test_provider_map() -> None:
12 | provider_map = BaseFactory.get_provider_map()
13 | provider_map.pop(Any)
14 |
15 | for type_, handler in provider_map.items():
16 | value = handler()
17 | assert isinstance(value, type_)
18 |
19 |
20 | def test_provider_map_with_any() -> None:
21 | @dataclass
22 | class Foo:
23 | foo: Any
24 |
25 | class FooFactory(DataclassFactory[Foo]):
26 | @classmethod
27 | def get_provider_map(cls) -> Dict[Any, Callable[[], Any]]:
28 | provider_map = super().get_provider_map()
29 | provider_map[Any] = lambda: "any"
30 |
31 | return provider_map
32 |
33 | foo = FooFactory.build()
34 |
35 | assert foo.foo == "any"
36 |
37 | coverage_result = list(FooFactory.coverage())
38 | assert all(result.foo == "any" for result in coverage_result)
39 |
40 |
41 | def test_provider_map_with_typevar() -> None:
42 | T = TypeVar("T")
43 |
44 | @dataclass
45 | class Foo(Generic[T]):
46 | foo: T
47 |
48 | class FooFactory(DataclassFactory[Foo]):
49 | @classmethod
50 | def get_provider_map(cls) -> Dict[Any, Callable[[], Any]]:
51 | provider_map = super().get_provider_map()
52 | provider_map[T] = lambda: "any"
53 |
54 | return provider_map
55 |
56 | foo = FooFactory.build()
57 | assert foo.foo == "any"
58 |
59 | coverage_result = list(FooFactory.coverage())
60 | assert all(result.foo == "any" for result in coverage_result)
61 |
62 |
63 | def test_add_custom_provider() -> None:
64 | class CustomType:
65 | def __init__(self, _: Any) -> None:
66 | pass
67 |
68 | @dataclass
69 | class Foo:
70 | foo: CustomType
71 |
72 | FooFactory = DataclassFactory.create_factory(Foo)
73 |
74 | with pytest.raises(ParameterException):
75 | FooFactory.build()
76 |
77 | BaseFactory.add_provider(CustomType, lambda: CustomType("custom"))
78 |
79 | # after adding the provider, nothing should raise!
80 | assert FooFactory.build()
81 |
--------------------------------------------------------------------------------
/tests/test_pydantic_v1_v2.py:
--------------------------------------------------------------------------------
1 | """Tests to check that usage of pydantic v1 and v2 at the same time works."""
2 |
3 | from typing import Dict, List, Optional, Tuple, Type, Union
4 |
5 | import pytest
6 | from typing_extensions import Annotated
7 |
8 | import pydantic
9 |
10 | from polyfactory.factories.pydantic_factory import ModelFactory
11 |
12 | if pydantic.VERSION.startswith("1"):
13 | pytest.skip("only for pydantic v2", allow_module_level=True)
14 |
15 | from pydantic import BaseModel as BaseModelV2
16 |
17 | try:
18 | from pydantic.v1 import BaseModel as BaseModelV1
19 | except ImportError:
20 | from pydantic import BaseModel as BaseModelV1 # type: ignore[assignment]
21 |
22 |
23 | @pytest.mark.parametrize("base_model", [BaseModelV1, BaseModelV2])
24 | def test_is_supported_type(base_model: Type[Union[BaseModelV1, BaseModelV2]]) -> None:
25 | class Foo(base_model): # type: ignore[valid-type, misc]
26 | ...
27 |
28 | assert ModelFactory.is_supported_type(Foo) is True
29 |
30 |
31 | @pytest.mark.parametrize("base_model", [BaseModelV1, BaseModelV2])
32 | def test_build(base_model: Type[Union[BaseModelV1, BaseModelV2]]) -> None:
33 | class Foo(base_model): # type: ignore[valid-type, misc]
34 | a: int
35 | b: str
36 | c: bool
37 |
38 | FooFactory = ModelFactory.create_factory(Foo)
39 | foo = FooFactory.build()
40 |
41 | assert isinstance(foo.a, int)
42 | assert isinstance(foo.b, str)
43 | assert isinstance(foo.c, bool)
44 |
45 |
46 | def test_build_v1_with_constrained_fields() -> None:
47 | from pydantic.v1.fields import Field
48 |
49 | ConstrainedInt = Annotated[int, Field(ge=100, le=200)]
50 | ConstrainedStr = Annotated[str, Field(min_length=1, max_length=3)]
51 |
52 | class Foo(BaseModelV1):
53 | a: ConstrainedInt
54 | b: ConstrainedStr
55 | c: Union[ConstrainedInt, ConstrainedStr]
56 | d: Optional[ConstrainedInt]
57 | e: Optional[Union[ConstrainedInt, ConstrainedStr]]
58 | f: List[ConstrainedInt]
59 | g: Dict[ConstrainedInt, ConstrainedStr]
60 |
61 | ModelFactory.create_factory(Foo).build()
62 |
63 |
64 | def test_build_v2_with_constrained_fields() -> None:
65 | from pydantic.fields import Field
66 |
67 | ConstrainedInt = Annotated[int, Field(ge=100, le=200)]
68 | ConstrainedStr = Annotated[str, Field(min_length=1, max_length=3)]
69 |
70 | class Foo(pydantic.BaseModel): # pyright: ignore[reportGeneralTypeIssues]
71 | a: ConstrainedInt
72 | b: ConstrainedStr
73 | c: Union[ConstrainedInt, ConstrainedStr]
74 | d: Optional[ConstrainedInt]
75 | e: Optional[Union[ConstrainedInt, ConstrainedStr]]
76 | f: List[ConstrainedInt]
77 | g: Dict[ConstrainedInt, ConstrainedStr]
78 |
79 | ModelFactory.create_factory(Foo).build()
80 |
81 |
82 | def test_variadic_tuple_length() -> None:
83 | class Foo(pydantic.BaseModel):
84 | bar: Tuple[int, ...]
85 |
86 | class Factory(ModelFactory[Foo]):
87 | __randomize_collection_length__ = True
88 | __min_collection_length__ = 7
89 | __max_collection_length__ = 8
90 |
91 | result = Factory.build()
92 | assert 7 <= len(result.bar) <= 8
93 |
94 |
95 | def test_build_v1_with_url_and_email_types() -> None:
96 | from pydantic.v1 import AnyHttpUrl, AnyUrl, EmailStr, HttpUrl, NameEmail
97 |
98 | class Foo(BaseModelV1):
99 | http_url: HttpUrl
100 | any_http_url: AnyHttpUrl
101 | any_url: AnyUrl
102 | email_str: EmailStr
103 | name_email: NameEmail
104 | composed: Tuple[HttpUrl, AnyHttpUrl, AnyUrl, EmailStr, NameEmail]
105 |
106 | ModelFactory.create_factory(Foo).build()
107 |
--------------------------------------------------------------------------------
/tests/test_pytest_plugin.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | import pytest
4 |
5 | from pydantic import BaseModel
6 |
7 | from polyfactory.exceptions import ParameterException
8 | from polyfactory.factories.pydantic_factory import ModelFactory
9 | from polyfactory.fields import Fixture, Use
10 | from polyfactory.pytest_plugin import register_fixture
11 | from tests.models import Person, PersonFactoryWithoutDefaults
12 |
13 |
14 | @register_fixture
15 | class PersonFactoryFixture(PersonFactoryWithoutDefaults):
16 | """Person Factory Fixture."""
17 |
18 |
19 | @register_fixture(name="another_fixture")
20 | class AnotherPersonFactoryFixture(PersonFactoryWithoutDefaults):
21 | """Another Person Factory Fixture."""
22 |
23 |
24 | def test_fixture_register_decorator(
25 | person_factory_fixture: PersonFactoryFixture,
26 | ) -> None:
27 | person = person_factory_fixture.build()
28 | assert isinstance(person, Person)
29 |
30 |
31 | def test_custom_naming_fixture_register_decorator(
32 | another_fixture: AnotherPersonFactoryFixture,
33 | ) -> None:
34 | person = another_fixture.build()
35 | assert isinstance(person, Person)
36 |
37 |
38 | def test_register_with_function_error() -> None:
39 | with pytest.raises(ParameterException):
40 |
41 | @register_fixture # type: ignore
42 | def foo() -> None:
43 | pass
44 |
45 |
46 | def test_register_with_class_not_model_factory_error() -> None:
47 | with pytest.raises(ParameterException):
48 |
49 | @register_fixture
50 | class Foo: # type: ignore[type-var]
51 | pass
52 |
53 |
54 | class MyModel(BaseModel):
55 | best_friend: Person
56 | all_friends: List[Person]
57 | enemies: List[Person]
58 |
59 |
60 | def test_using_a_fixture_as_field_value() -> None:
61 | class MyFactory(ModelFactory[MyModel]):
62 | __model__ = MyModel
63 |
64 | best_friend = Fixture(PersonFactoryFixture, name="mike")
65 | all_friends = Fixture(PersonFactoryFixture, size=5)
66 | enemies = Fixture(PersonFactoryFixture, size=0)
67 |
68 | result = MyFactory.build()
69 | assert result.best_friend.name == "mike"
70 | assert len(result.all_friends) == 5
71 | assert result.enemies == []
72 |
73 |
74 | def test_using_factory_directly() -> None:
75 | class MyFactory(ModelFactory[MyModel]):
76 | __model__ = MyModel
77 |
78 | best_friend = Use(PersonFactoryFixture.build, name="mike")
79 | all_friends = Use(PersonFactoryFixture.batch, size=5)
80 | enemies = Use(PersonFactoryFixture.batch, size=0)
81 |
82 | result = MyFactory.build()
83 | assert result.best_friend.name == "mike"
84 | assert len(result.all_friends) == 5
85 | assert result.enemies == []
86 |
87 |
88 | def test_using_non_fixture_with_the_fixture_field_raises() -> None:
89 | class MyFactory(ModelFactory[MyModel]):
90 | __model__ = MyModel
91 |
92 | all_friends = Fixture(123) # type: ignore
93 |
94 | with pytest.raises(ParameterException):
95 | MyFactory.build()
96 |
--------------------------------------------------------------------------------
/tests/test_random_configuration.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from random import Random
3 | from typing import List, Union
4 |
5 | import pytest
6 | from faker import Faker
7 | from faker.config import AVAILABLE_LOCALES
8 |
9 | from polyfactory.factories.dataclass_factory import DataclassFactory
10 |
11 | FakerLocaleType = Union[str, List[str]]
12 |
13 | RANDINT_MAP = {i: Random(i).randint(0, 10) for i in range(3)}
14 | FAKER_LOCALES: List[FakerLocaleType] = [
15 | AVAILABLE_LOCALES[0],
16 | AVAILABLE_LOCALES[1],
17 | AVAILABLE_LOCALES[2],
18 | AVAILABLE_LOCALES[:3],
19 | ]
20 |
21 |
22 | @pytest.mark.parametrize("seed", RANDINT_MAP.keys())
23 | def test_setting_random(seed: int) -> None:
24 | @dataclass
25 | class Foo:
26 | foo: int
27 |
28 | class FooFactory(DataclassFactory[Foo]):
29 | __model__ = Foo
30 | __random__ = Random(seed)
31 |
32 | @classmethod
33 | def foo(cls) -> int:
34 | return cls.__random__.randint(0, 10)
35 |
36 | assert FooFactory.build().foo == RANDINT_MAP[seed]
37 |
38 |
39 | @pytest.mark.parametrize("seed", RANDINT_MAP.keys())
40 | def test_setting_random_seed_on_random(seed: int) -> None:
41 | @dataclass
42 | class Foo:
43 | foo: int
44 |
45 | class FooFactory(DataclassFactory[Foo]):
46 | __model__ = Foo
47 | __random_seed__ = seed
48 |
49 | @classmethod
50 | def foo(cls) -> int:
51 | return cls.__random__.randint(0, 10)
52 |
53 | assert FooFactory.build().foo == RANDINT_MAP[seed]
54 |
55 |
56 | @pytest.mark.parametrize("seed", RANDINT_MAP.keys())
57 | def test_setting_random_seed_on_faker(seed: int) -> None:
58 | @dataclass
59 | class Foo:
60 | foo: int
61 |
62 | class FooFactory(DataclassFactory[Foo]):
63 | __model__ = Foo
64 | __random_seed__ = seed
65 |
66 | @classmethod
67 | def foo(cls) -> int:
68 | return cls.__faker__.random_digit()
69 |
70 | assert FooFactory.build().foo == RANDINT_MAP[seed]
71 |
72 |
73 | def test_setting_random_seed_on_multiple_factories() -> None:
74 | foo_seed = 0
75 | bar_seed = 1
76 |
77 | @dataclass
78 | class Foo:
79 | foo: int
80 |
81 | @dataclass
82 | class Bar:
83 | bar: int
84 |
85 | class FooFactory(DataclassFactory[Foo]):
86 | __model__ = Foo
87 | __random_seed__ = foo_seed
88 |
89 | @classmethod
90 | def foo(cls) -> int:
91 | return cls.__random__.randint(0, 10)
92 |
93 | class BarFactory(DataclassFactory[Bar]):
94 | __model__ = Bar
95 | __random_seed__ = bar_seed
96 |
97 | @classmethod
98 | def bar(cls) -> int:
99 | return cls.__random__.randint(0, 10)
100 |
101 | assert FooFactory.build().foo == RANDINT_MAP[foo_seed]
102 | assert BarFactory.build().bar == RANDINT_MAP[bar_seed]
103 |
104 |
105 | def test_no_override_of_faker() -> None:
106 | faker = Faker()
107 |
108 | @dataclass
109 | class Foo:
110 | foo: int
111 |
112 | class FooFactory(DataclassFactory[Foo]):
113 | __model__ = Foo
114 |
115 | __faker__ = faker
116 | __random_seed__ = 10
117 |
118 | assert FooFactory.__faker__ is faker
119 |
120 |
121 | @pytest.mark.parametrize("locale", FAKER_LOCALES)
122 | def test_faker_locale_preserved(locale: FakerLocaleType) -> None:
123 | @dataclass
124 | class Foo:
125 | foo: int
126 |
127 | class FooFactory(DataclassFactory[Foo]):
128 | __model__ = Foo
129 |
130 | __faker__ = Faker(locale=locale)
131 | __random_seed__ = 10
132 |
133 | expected_locales = [locale] if isinstance(locale, str) else locale
134 |
135 | assert FooFactory.__faker__.locales == expected_locales
136 |
--------------------------------------------------------------------------------
/tests/test_random_seed.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 | from typing import Optional
3 |
4 | from pydantic import VERSION, BaseModel, Field
5 |
6 | from polyfactory.factories.pydantic_factory import ModelFactory
7 |
8 |
9 | def test_random_seed() -> None:
10 | class MyModel(BaseModel):
11 | id: int
12 | special_id: str = (
13 | Field(pattern=r"ID-[1-9]{3}\.[1-9]{3}")
14 | if VERSION.startswith("2")
15 | else Field(regex=r"ID-[1-9]{3}\.[1-9]{3}") # type: ignore[call-overload]
16 | )
17 |
18 | class MyModelFactory(ModelFactory):
19 | __model__ = MyModel
20 |
21 | ModelFactory.seed_random(1651)
22 |
23 | ins = MyModelFactory.build()
24 |
25 | assert ins.id == 4
26 | assert ins.special_id == "ID-515.943"
27 |
28 |
29 | def test_deterministic_optionals_seeding() -> None:
30 | class ModelWithOptionalValues(BaseModel):
31 | name: Optional[str]
32 |
33 | class FactoryWithNoneOptionals(ModelFactory):
34 | __model__ = ModelWithOptionalValues
35 |
36 | seed = randint(0, 1000)
37 |
38 | ModelFactory.seed_random(seed)
39 | first_build = [FactoryWithNoneOptionals.build() for _ in range(10)]
40 | ModelFactory.seed_random(seed)
41 | second_build = [FactoryWithNoneOptionals.build() for _ in range(10)]
42 | assert first_build == second_build
43 |
44 |
45 | def test_deterministic_constrained_strings() -> None:
46 | class MyModel(BaseModel):
47 | id: int
48 | special_id: str = Field(min_length=10, max_length=24)
49 |
50 | class MyModelFactory(ModelFactory):
51 | __model__ = MyModel
52 |
53 | ModelFactory.seed_random(1651)
54 |
55 | ins = MyModelFactory.build()
56 |
57 | assert ins.id == 4
58 | assert ins.special_id == "48489ab53c59b24acfe1"
59 |
--------------------------------------------------------------------------------
/tests/test_recursive_models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import dataclass, field
4 | from typing import Any, Dict, List, Optional, Union
5 |
6 | import pytest
7 |
8 | from pydantic import BaseModel, Field
9 |
10 | from polyfactory.factories.dataclass_factory import DataclassFactory
11 | from polyfactory.factories.pydantic_factory import ModelFactory
12 |
13 |
14 | class _Sentinel: ...
15 |
16 |
17 | @dataclass
18 | class Node:
19 | value: int
20 | union_child: Union[Node, int] # noqa: UP007
21 | list_child: List[Node] # noqa: UP006
22 | optional_child: Optional[Node] # noqa: UP007
23 | child: Node = field(default=_Sentinel) # type: ignore[assignment]
24 |
25 | def __post_init__(self) -> None:
26 | # Emulate recursive models set by external init, e.g. ORM relationships
27 | if self.child is _Sentinel: # type: ignore[comparison-overlap]
28 | self.child = self
29 |
30 |
31 | def test_recursive_model() -> None:
32 | factory = DataclassFactory.create_factory(Node)
33 |
34 | result = factory.build()
35 | assert result.child is result, "Default is not used"
36 | assert isinstance(result.union_child, int)
37 | assert result.optional_child is None
38 | assert result.list_child == []
39 |
40 | assert factory.build(child={"child": None}).child.child is None
41 |
42 |
43 | class PydanticNode(BaseModel):
44 | value: int
45 | union_child: Union[PydanticNode, int] # noqa: UP007
46 | list_child: List[PydanticNode] # noqa: UP006
47 | optional_union_child: Union[PydanticNode, None] # noqa: UP007
48 | optional_child: Optional[PydanticNode] # noqa: UP007
49 | child: PydanticNode = Field(default=_Sentinel) # type: ignore[assignment]
50 | recursive_key: Dict[PydanticNode, Any] # noqa: UP006
51 | recursive_value: Dict[str, PydanticNode] # noqa: UP006
52 |
53 |
54 | @pytest.mark.parametrize("factory_use_construct", (True, False))
55 | def test_recursive_pydantic_models(factory_use_construct: bool) -> None:
56 | factory = ModelFactory.create_factory(PydanticNode)
57 |
58 | result = factory.build(factory_use_construct)
59 | assert result.child is _Sentinel, "Default is not used" # type: ignore[comparison-overlap]
60 | assert isinstance(result.union_child, int)
61 | assert result.optional_union_child is None
62 | assert result.optional_child is None
63 | assert result.list_child == []
64 | assert result.recursive_key == {}
65 | assert result.recursive_value == {}
66 |
67 |
68 | @dataclass
69 | class Author:
70 | name: str
71 | books: List[Book] # noqa: UP006
72 |
73 |
74 | _DEFAULT_AUTHOR = Author(name="default", books=[])
75 |
76 |
77 | @dataclass
78 | class Book:
79 | name: str
80 | author: Author = field(default_factory=lambda: _DEFAULT_AUTHOR)
81 |
82 |
83 | def test_recursive_list_model() -> None:
84 | factory = DataclassFactory.create_factory(Author)
85 | assert factory.build().books[0].author is _DEFAULT_AUTHOR
86 | assert factory.build(books=[]).books == []
87 |
88 | book_factory = DataclassFactory.create_factory(Book)
89 | assert book_factory.build().author.books == []
90 | assert book_factory.build(author=None).author is None
91 |
--------------------------------------------------------------------------------
/tests/test_typeddict_factory.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List, Optional
2 |
3 | from annotated_types import Ge
4 | from typing_extensions import Annotated, NotRequired, Required, TypedDict
5 |
6 | from pydantic import BaseModel
7 |
8 | from polyfactory.factories import TypedDictFactory
9 | from polyfactory.factories.pydantic_factory import ModelFactory
10 |
11 |
12 | class TypedDictModel(TypedDict):
13 | id: int
14 | name: str
15 | list_field: List[Dict[str, int]]
16 | int_field: Optional[int]
17 |
18 |
19 | def test_factory_with_typeddict() -> None:
20 | class MyFactory(TypedDictFactory[TypedDictModel]):
21 | __model__ = TypedDictModel
22 |
23 | result = MyFactory.build()
24 |
25 | assert isinstance(result, dict)
26 | assert result["id"]
27 | assert result["name"]
28 | assert result["list_field"][0]
29 | assert type(result["int_field"]) in (type(None), int)
30 |
31 |
32 | def test_factory_model_with_typeddict_attribute_value() -> None:
33 | class MyModel(BaseModel):
34 | td: TypedDictModel
35 | name: str
36 | list_field: List[Dict[str, int]]
37 | int_field: Optional[int]
38 |
39 | class MyFactory(ModelFactory[MyModel]):
40 | __model__ = MyModel
41 |
42 | result = MyFactory.build()
43 |
44 | assert isinstance(result.td, dict)
45 | assert result.td["id"]
46 | assert result.td["name"]
47 | assert result.td["list_field"][0]
48 | assert type(result.td["int_field"]) in (type(None), int)
49 |
50 |
51 | def test_typeddict_with_required_and_non_required_fields() -> None:
52 | class TypedDictModel(TypedDict):
53 | id: Required[int]
54 | name: NotRequired[str]
55 | annotated: Required[Annotated[int, Ge(100)]]
56 | list_field: List[Dict[str, int]]
57 | optional_int: Required[Optional[int]]
58 |
59 | class TypedDictModelFactory(TypedDictFactory[TypedDictModel]):
60 | __model__ = TypedDictModel
61 |
62 | result = TypedDictModelFactory.build()
63 |
64 | assert isinstance(result["id"], int)
65 | assert isinstance(result["annotated"], int)
66 | assert result["annotated"] >= 100
67 | assert isinstance(result["list_field"], list)
68 | assert isinstance(result["optional_int"], (type(None), int))
69 | assert "name" in result
70 | assert isinstance(result["name"], str)
71 |
--------------------------------------------------------------------------------
/tests/typing_test_strict.py:
--------------------------------------------------------------------------------
1 | """Module exists only to test generic boundaries.
2 |
3 | Filename should not start with "test_".
4 | """
5 |
6 | import dataclasses
7 | from typing import TypedDict
8 |
9 | import pydantic.dataclasses
10 | from pydantic import BaseModel
11 |
12 | from polyfactory.factories import DataclassFactory, TypedDictFactory
13 | from polyfactory.factories.pydantic_factory import ModelFactory
14 |
15 |
16 | class PydanticClass(BaseModel):
17 | field: str
18 |
19 |
20 | class PydanticClassFactory(ModelFactory[PydanticClass]):
21 | __model__ = PydanticClass
22 |
23 |
24 | @pydantic.dataclasses.dataclass
25 | class PydanticDataClass:
26 | field: str
27 |
28 |
29 | class PydanticDataClassFactory(DataclassFactory[PydanticDataClass]):
30 | __model__ = PydanticDataClass
31 |
32 |
33 | @dataclasses.dataclass()
34 | class PythonDataClass:
35 | field: str
36 |
37 |
38 | class PythonDataClassFactory(DataclassFactory[PythonDataClass]):
39 | __model__ = PythonDataClass
40 |
41 |
42 | class TypedDictClass(TypedDict):
43 | field: str
44 |
45 |
46 | class TypedDictClassFactory(TypedDictFactory[TypedDictClass]):
47 | __model__ = TypedDictClass
48 |
--------------------------------------------------------------------------------
/tests/utils/test_deprecation.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import pytest
4 |
5 | from polyfactory.utils.deprecation import check_for_deprecated_parameters
6 |
7 |
8 | def test_parameter_deprecation() -> None:
9 | def my_func(a: int, b: int | None = None) -> None:
10 | check_for_deprecated_parameters("5", parameters=(("b", b),))
11 |
12 | with pytest.warns(
13 | DeprecationWarning,
14 | match="Use of deprecated parameter 'b'. Deprecated in polyfactory 5. This parameter will be removed in the next major version",
15 | ):
16 | my_func(1, 2)
17 |
--------------------------------------------------------------------------------
/tests/utils/test_frozendict.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from polyfactory.utils.types import Frozendict
4 |
5 |
6 | def test_frozendict_immutable() -> None:
7 | instance = Frozendict({"bar": "foo"})
8 | with pytest.raises(TypeError, match="Unable to mutate Frozendict"):
9 | instance["foo"] = "bar"
10 |
11 |
12 | def test_frozendict_hashable() -> None:
13 | assert isinstance(hash(Frozendict()), int)
14 |
--------------------------------------------------------------------------------
/tools/build_docs.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 | import shutil
5 | import subprocess
6 | from contextlib import contextmanager
7 | from pathlib import Path
8 |
9 | REDIRECT_TEMPLATE = """
10 |
11 |
12 |
13 | Page Redirection
14 |
15 |
16 |
17 |
18 |
19 | You are being redirected. If this does not work, click this link
20 |
21 |
22 | """
23 |
24 | parser = argparse.ArgumentParser()
25 | parser.add_argument("output")
26 |
27 |
28 | @contextmanager
29 | def checkout(branch: str) -> None:
30 | subprocess.run(["git", "checkout", branch], check=True) # noqa: S603 S607
31 | yield
32 | subprocess.run(["git", "checkout", "-"], check=True) # noqa: S603 S607
33 |
34 |
35 | def build(output_dir: str) -> None:
36 | subprocess.run(["make", "docs"], check=True) # noqa: S603 S607
37 |
38 | output_dir = Path(output_dir)
39 | output_dir.mkdir()
40 | output_dir.joinpath(".nojekyll").touch(exist_ok=True)
41 | output_dir.joinpath("index.html").write_text(REDIRECT_TEMPLATE.format(target="latest"))
42 |
43 | docs_src_path = Path("docs/_build/html")
44 | shutil.copytree(docs_src_path, output_dir / "latest", dirs_exist_ok=True)
45 |
46 |
47 | def main() -> None:
48 | args = parser.parse_args()
49 | build(output_dir=args.output)
50 |
51 |
52 | if __name__ == "__main__":
53 | main()
54 |
--------------------------------------------------------------------------------
/tools/convert_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CHANGELOG=docs/changelog.rst
4 |
5 | filename="${CHANGELOG%.*}"
6 | echo "Converting $CHANGELOG to $filename.md"
7 | pandoc --wrap=preserve $CHANGELOG -f rst -t markdown -o "$filename".md
8 |
--------------------------------------------------------------------------------
/tools/prepare-release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | bumped_version=$(
6 | uv run git-cliff \
7 | -c pyproject.toml \
8 | --github-token "$(gh auth token)" \
9 | --bumped-version
10 | )
11 |
12 | uv run git-cliff \
13 | -c pyproject.toml \
14 | --github-token "$(gh auth token)" \
15 | --tag "$bumped_version" \
16 | --output docs/changelog.rst
17 |
18 | uvx bump-my-version@1.1.2 bump --new-version "$bumped_version"
19 |
--------------------------------------------------------------------------------
/typos.toml:
--------------------------------------------------------------------------------
1 | [default]
2 | extend-ignore-re = ["(?Rm)^.*(#|//)\\s*codespell:ignore\\s*$"]
3 |
4 | [default.extend-words]
5 | selectin = 'selectin'
6 |
7 | [files]
8 | extend-exclude = ["docs/changelog.rst"]
9 |
--------------------------------------------------------------------------------