├── .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: `![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)`" 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: <title>" 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: <title>" 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 | <svg xmlns="http://www.w3.org/2000/svg" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" 2 | preserveAspectRatio="xMidYMid meet" version="1.0"> 3 | <defs> 4 | <clipPath id="9eb7762d41"> 5 | <path d="M 15.933594 105 L 328 105 L 328 259 L 15.933594 259 Z M 15.933594 105 " clip-rule="nonzero"/> 6 | </clipPath> 7 | <clipPath id="183d3cc178"> 8 | <path d="M 142 78.769531 L 359.433594 78.769531 L 359.433594 296.269531 L 142 296.269531 Z M 142 78.769531 " 9 | clip-rule="nonzero"/> 10 | </clipPath> 11 | </defs> 12 | <g clip-path="url(#9eb7762d41)"> 13 | <path fill="#edb641" 14 | d="M 147.625 240.3125 C 161.5 233.984375 173.554688 227.011719 183.425781 220.550781 C 202.304688 208.203125 226.4375 185.242188 227.761719 183.410156 L 218.917969 177.503906 L 211.257812 172.386719 L 235.503906 171.441406 L 243.296875 171.136719 L 245.414062 163.640625 L 252.007812 140.304688 L 260.402344 163.054688 L 263.097656 170.363281 L 270.890625 170.058594 L 295.136719 169.113281 L 276.078125 184.117188 L 269.953125 188.9375 L 272.652344 196.25 L 281.046875 218.996094 L 260.871094 205.523438 L 254.390625 201.195312 L 248.265625 206.015625 L 229.207031 221.023438 L 232.480469 209.425781 L 235.796875 197.691406 L 236.207031 196.234375 C 213.003906 213.585938 180.546875 230.304688 161.140625 236.488281 C 156.6875 237.90625 152.183594 239.179688 147.625 240.3125 Z M 101.992188 258.078125 C 136.382812 256.734375 177.355469 248 217.675781 222.363281 L 209.90625 249.867188 L 254.910156 214.4375 L 302.539062 246.246094 L 282.71875 192.539062 L 327.71875 157.109375 L 270.46875 159.34375 L 250.648438 105.636719 L 235.085938 160.726562 L 177.835938 162.964844 L 210.980469 185.097656 C 189.164062 204.921875 134.445312 247.195312 61.957031 250.03125 C 47.300781 250.601562 31.914062 249.558594 15.933594 246.394531 C 15.933594 246.394531 52.011719 260.035156 101.992188 258.078125 " 15 | fill-opacity="1" fill-rule="nonzero"/> 16 | </g> 17 | <g clip-path="url(#183d3cc178)"> 18 | <path fill="#edb641" 19 | d="M 250.789062 78.96875 C 190.78125 78.96875 142.140625 127.570312 142.140625 187.519531 C 142.140625 198.875 143.886719 209.816406 147.121094 220.101562 C 151.847656 217.75 156.363281 215.316406 160.660156 212.84375 C 158.394531 204.789062 157.183594 196.296875 157.183594 187.519531 C 157.183594 135.871094 199.089844 93.996094 250.789062 93.996094 C 302.484375 93.996094 344.390625 135.871094 344.390625 187.519531 C 344.390625 239.171875 302.484375 281.042969 250.789062 281.042969 C 222.75 281.042969 197.597656 268.722656 180.441406 249.210938 C 175.453125 251.152344 170.402344 252.917969 165.289062 254.511719 C 185.183594 279.816406 216.082031 296.070312 250.789062 296.070312 C 310.792969 296.070312 359.433594 247.472656 359.433594 187.519531 C 359.433594 127.570312 310.792969 78.96875 250.789062 78.96875 " 20 | fill-opacity="1" fill-rule="nonzero"/> 21 | </g> 22 | <path fill="#edb641" 23 | d="M 92.292969 173.023438 L 98.289062 191.460938 L 117.691406 191.460938 L 101.992188 202.855469 L 107.988281 221.292969 L 92.292969 209.898438 L 76.59375 221.292969 L 82.589844 202.855469 L 66.894531 191.460938 L 86.296875 191.460938 L 92.292969 173.023438 " 24 | fill-opacity="1" fill-rule="nonzero"/> 25 | <path fill="#edb641" 26 | d="M 120.214844 112.25 L 125.390625 128.167969 L 142.140625 128.167969 L 128.589844 138 L 133.765625 153.917969 L 120.214844 144.082031 L 106.664062 153.917969 L 111.839844 138 L 98.289062 128.167969 L 115.039062 128.167969 L 120.214844 112.25 " 27 | fill-opacity="1" fill-rule="nonzero"/> 28 | <path fill="#edb641" 29 | d="M 34.695312 209.136719 L 37.71875 218.421875 L 47.492188 218.421875 L 39.585938 224.160156 L 42.605469 233.449219 L 34.695312 227.707031 L 26.792969 233.449219 L 29.8125 224.160156 L 21.90625 218.421875 L 31.679688 218.421875 L 34.695312 209.136719 " 30 | fill-opacity="1" fill-rule="nonzero"/> 31 | </svg> 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 <python:library/dataclasses>`, :class:`TypedDicts <typing.TypedDict>`, 7 | Pydantic models, :class:`msgspec Struct's <msgspec.Struct>` 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 <https://pypi.org/project/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 <python:library/dataclasses>`, :class:`TypedDicts <typing.TypedDict>`, 41 | `Pydantic <https://docs.pydantic.dev/latest/>`_, `Odmantic <https://art049.github.io/odmantic/>`_, 42 | and `Beanie ODM <https://beanie-odm.dev/>`_ 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 <https://semver.org/>`_, using the ``<major>.<minor>.<patch>`` 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 | ``<major>.<minor>.<patch><release_type><number>``. 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 <polyfactory.factories.base.BaseFactory.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 <polyfactory.factories.base.BaseFactory.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 <polyfactory.decorators.post_generated>` decorator wraps a ``classmethod`` into a 5 | :class:`PostGenerated <polyfactory.fields.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 <polyfactory.pytest_plugin.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 <polyfactory.factories.base.BaseFactory>`. 6 | 7 | These include: 8 | 9 | :class:`DataclassFactory <polyfactory.factories.dataclass_factory.DataclassFactory>` 10 | a base factory for dataclasses 11 | 12 | :class:`TypedDictFactory <polyfactory.factories.typed_dict_factory.TypedDictFactory>` 13 | a base factory for typed-dicts 14 | :class:`ModelFactory <polyfactory.factories.pydantic_factory.ModelFactory>` 15 | a base factory for `pydantic <https://docs.pydantic.dev/>`_ models 16 | 17 | :class:`BeanieDocumentFactory <polyfactory.factories.beanie_odm_factory.BeanieDocumentFactory>` 18 | a base factory for `beanie <https://beanie-odm.dev/>`_ documents 19 | 20 | :class:`OdmanticModelFactory <polyfactory.factories.odmantic_odm_factory.OdmanticModelFactory>` 21 | a base factory for `odmantic <https://art049.github.io/odmantic/>`_ models. 22 | 23 | :class:`MsgspecFactory <polyfactory.factories.msgspec_factory.MsgspecFactory>` 24 | a base factory for `msgspec <https://jcristharif.com/msgspec/>`_ Structs 25 | 26 | :class:`AttrsFactory <polyfactory.factories.attrs_factory.AttrsFactory>` 27 | a base factory for `attrs <https://www.attrs.org/en/stable/index.html>`_ models. 28 | 29 | :class:`SQLAlchemyFactory <polyfactory.factories.sqlalchemy_factory.SQLAlchemyFactory>` 30 | a base factory for `SQLAlchemy <https://www.sqlalchemy.org/>`_ 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 <polyfactory.factories.pydantic_factory.ModelFactory>`, require an additional but optional 35 | dependency, in this case `pydantic <https://docs.pydantic.dev/>`_. 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 | <!DOCTYPE HTML> 11 | <html lang="en-US"> 12 | <head> 13 | <title>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 | --------------------------------------------------------------------------------