From f341049b9e5538a125751d75b4e44c1609b53df6 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 6 Sep 2022 17:15:51 +0100 Subject: [PATCH] Remove Cython & Move to `pyproject.toml` (#4473) * Remove Cython * fix CI * fix coverage * fix tests * switching to pypyroject.toml * pre-commit all and use pre-commit for linting * no mypy tests on macos and windows on ci, use flake8-pyproject * fix docs and tests CI * check build is working * drop pytest-cov * window and macos ci with 3.11, reduce filtering * use pip-tools to pin all dependencies * fix docs and fastapi tests * fix test deps for 3.7 * no cache on tests job * revert fastapi changes, fix coverage * fix mypy coverage * test with older mypy * dotenv not required for mypy tests * split testing requirements std and extra * typo * @PrettyWood comments * correct branch name * mypy python_version and pr template --- .github/ISSUE_TEMPLATE/bug.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.yml | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 25 +- .github/dependabot.yml | 5 - .github/workflows/ci.yml | 272 ++++++--------------- .github/workflows/combine-dependabot.yml | 137 ----------- .pre-commit-config.yaml | 9 +- HISTORY.md | 64 ++--- LICENSE | 2 +- MANIFEST.in | 6 - Makefile | 54 +--- README.md | 14 +- docs/blog/pydantic-v2.md | 20 +- docs/hypothesis_plugin.md | 2 +- docs/index.md | 30 +-- docs/install.md | 2 +- docs/mypy_plugin.md | 2 +- docs/pycharm_plugin.md | 2 +- docs/requirements.txt | 15 -- docs/usage/devtools.md | 4 +- docs/usage/model_config.md | 4 +- docs/usage/models.md | 60 ++--- docs/usage/types.md | 3 +- docs/visual_studio_code.md | 8 +- mkdocs.yml | 2 +- pydantic/__init__.py | 3 +- pydantic/config.py | 70 +++--- pydantic/dataclasses.py | 4 +- pydantic/mypy.py | 37 +-- pydantic/typing.py | 6 +- pydantic/utils.py | 18 -- pydantic/version.py | 15 +- pyproject.toml | 143 +++++++++++ requirements.txt | 8 - requirements/all.txt | 5 + requirements/docs.in | 16 ++ requirements/docs.txt | 119 +++++++++ requirements/linting.in | 9 + requirements/linting.txt | 79 ++++++ requirements/pyproject-all.txt | 16 ++ requirements/pyproject-min.txt | 8 + requirements/testing-extra.in | 4 + requirements/testing-extra.txt | 34 +++ requirements/testing.in | 4 + requirements/testing.txt | 45 ++++ setup.cfg | 92 ------- setup.py | 145 ++--------- tests/conftest.py | 8 - tests/mypy/outputs/fail1.txt | 2 +- tests/mypy/outputs/fail2.txt | 2 +- tests/mypy/outputs/fail3.txt | 2 +- tests/mypy/outputs/fail4.txt | 2 +- tests/mypy/test_mypy.py | 10 +- tests/requirements-linting.txt | 11 - tests/requirements-testing.txt | 9 - tests/test_edge_cases.py | 33 +-- tests/test_utils.py | 30 +-- tests/test_version.py | 16 ++ 58 files changed, 787 insertions(+), 970 deletions(-) delete mode 100644 .github/workflows/combine-dependabot.yml delete mode 100644 MANIFEST.in delete mode 100644 docs/requirements.txt create mode 100644 pyproject.toml delete mode 100644 requirements.txt create mode 100644 requirements/all.txt create mode 100644 requirements/docs.in create mode 100644 requirements/docs.txt create mode 100644 requirements/linting.in create mode 100644 requirements/linting.txt create mode 100644 requirements/pyproject-all.txt create mode 100644 requirements/pyproject-min.txt create mode 100644 requirements/testing-extra.in create mode 100644 requirements/testing-extra.txt create mode 100644 requirements/testing.in create mode 100644 requirements/testing.txt delete mode 100644 setup.cfg delete mode 100644 tests/requirements-linting.txt delete mode 100644 tests/requirements-testing.txt diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a5e9350..c297379 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -13,7 +13,7 @@ body: label: Initial Checks description: | Just a few checks to make sure you need to create a bug report. - + _Sorry to sound so draconian 👿; but every second spent replying to issues is time not spent improving pydantic 🙇._ options: - label: I have searched GitHub for a duplicate issue and I'm sure this is something new @@ -23,7 +23,7 @@ body: - label: I have read and followed [the docs](https://pydantic-docs.helpmanual.io) and still think this is a bug required: true - label: > - I am confident that the issue is with pydantic + I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like [FastAPI](https://fastapi.tiangolo.com) or [mypy](https://mypy.readthedocs.io/en/stable)) required: true @@ -60,7 +60,7 @@ body: label: Python, Pydantic & OS Version description: | Which version of Python & Pydantic are you using, and which Operating System? - + Please run the following command and copy the output below: ```bash diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 26273cb..f8e64b8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -13,7 +13,7 @@ body: label: Initial Checks description: | Just a few checks to make sure you need to create a feature request. - + _Sorry to sound so draconian 👿; but every second spent replying to issues is time not spent improving pydantic 🙇._ options: - label: I have searched Google & GitHub for similar requests and couldn't find anything @@ -27,7 +27,7 @@ body: label: Description description: | Please give as much detail as possible about the feature you would like to suggest. 🙏 - + You might like to add: * A demo of how code might look when using the feature * Your use case(s) for the feature diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7dc67e4..1dc917a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,23 +1,14 @@ - - - - +# WARNING!!! -## Change Summary +We're currently in the process of rewriting pydantic in preparation for V2, see +https://pydantic-docs.helpmanual.io/blog/pydantic-v2/. - +As a result, much of the codebase will change significantly over the coming months. -## Related issue number +To avoid wasting your time, please only create Pull Requests if you've got explicit approval by a maintainer. - - +Otherwise, your pull requests may be closed without review. -## Checklist +Thank you for your interest in pydantic, and your patience. :pray: -* [ ] Unit tests for the changes exist -* [ ] Tests pass on CI and coverage remains at 100% -* [ ] Documentation reflects the changes where applicable -* [ ] `changes/-.md` file added describing change - (see [changes/README.md](https://github.com/pydantic/pydantic/blob/main/changes/README.md) for details. - You can [skip this check](https://github.com/pydantic/hooky#change-file-checks) if the change does not need a change file.) -* [ ] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers** +**Note:** if you're making a pull request to fix pydantic v1.10, please make it against the `1.10.X-fixes` branch. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7a42e08..7ca78d1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,6 @@ version: 2 updates: -- package-ecosystem: pip - directory: / - schedule: - interval: monthly - - package-ecosystem: github-actions directory: / schedule: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5370167..3c4ac03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,29 +30,19 @@ jobs: lint ${{ runner.os }} ${{ env.pythonLocation }} - ${{ hashFiles('tests/requirements-linting.txt') }} + ${{ hashFiles('requirements/linting.txt') }} - name: install if: steps.cache.outputs.cache-hit != 'true' - run: | - make install-linting - pip freeze + run: pip install -r requirements/linting.txt - - name: lint - run: make lint - - - name: pyupgrade - run: make pyupgrade - - - name: mypy - run: make mypy + - uses: pre-commit/action@v3.0.0 + with: + extra_args: --all-files - name: make history run: python3 ./changes/make_history.py - - name: check dist - run: make check-dist - - name: install node for pyright uses: actions/setup-node@v3 with: @@ -80,13 +70,12 @@ jobs: docs-build ${{ runner.os }} ${{ env.pythonLocation }} - ${{ hashFiles('setup.py') }} - ${{ hashFiles('requirements.txt') }} - ${{ hashFiles('docs/requirements.txt') }} + ${{ hashFiles('requirements/pyproject-all.txt') }} + ${{ hashFiles('requirements/docs.txt') }} - name: install if: steps.cache.outputs.cache-hit != 'true' - run: make install-docs + run: pip install -r requirements/pyproject-all.txt -r requirements/docs.txt . - name: build site run: make docs @@ -97,75 +86,17 @@ jobs: name: docs path: site - test-linux-compiled: - name: test py${{ matrix.python-version }} on linux compiled - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11.0-rc.1'] - env: - PYTHON: ${{ matrix.python-version }} - OS: ubuntu - - steps: - - uses: actions/checkout@v3 - - - name: set up python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 - id: cache - with: - path: ${{ env.pythonLocation }} - key: > - test-linux-compiled - ${{ runner.os }} - ${{ env.pythonLocation }} - ${{ hashFiles('setup.py') }} - ${{ hashFiles('requirements.txt') }} - ${{ hashFiles('tests/requirements-testing.txt') }} - - - name: install - run: make install-testing - - - name: compile - run: | - make build-trace - python -c "import sys, pydantic; print('compiled:', pydantic.compiled); sys.exit(0 if pydantic.compiled else 1)" - ls -alh - ls -alh pydantic/ - - - run: mkdir coverage - - - name: test - run: make test - env: - COVERAGE_FILE: coverage/.coverage.linux-py${{ matrix.python-version }}-compiled - CONTEXT: linux-py${{ matrix.python-version }}-compiled - - - name: store coverage files - uses: actions/upload-artifact@v3 - with: - name: coverage - path: coverage - - test-not-compiled: + test: name: test py${{ matrix.python-version }} on ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: ['3.7', '3.8', '3.9', '3.10'] - include: - - os: ubuntu + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11.0-rc.1'] env: PYTHON: ${{ matrix.python-version }} OS: ${{ matrix.os }} - COMPILED: no DEPS: yes runs-on: ${{ matrix.os }}-latest @@ -177,40 +108,31 @@ jobs: with: python-version: ${{ matrix.python-version }} - - uses: actions/cache@v3 - id: cache - with: - path: ${{ env.pythonLocation }} - key: > - test-not-compiled - ${{ runner.os }} - ${{ env.pythonLocation }} - ${{ hashFiles('setup.py') }} - ${{ hashFiles('requirements.txt') }} - ${{ hashFiles('tests/requirements-testing.txt') }} + - name: install min deps + run: pip install -r requirements/pyproject-min.txt -r requirements/testing.txt - - name: install - run: make install-testing + - name: install pydantic + run: pip install . - run: pip freeze - run: mkdir coverage - - name: test with deps - run: make test - env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-with-deps - CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps - - - name: uninstall deps - run: pip uninstall -y cython email-validator devtools python-dotenv - - name: test without deps run: make test env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-without-deps CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-without-deps + - name: install extra deps + run: pip install -r requirements/pyproject-all.txt -r requirements/testing-extra.txt + + - name: test with deps + run: make test + env: + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-with-deps + CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps + - name: store coverage files uses: actions/upload-artifact@v3 with: @@ -223,7 +145,7 @@ jobs: strategy: fail-fast: false matrix: - mypy-version: ['0.910', '0.921', '0.931', '0.942', '0.950', '0.960'] + mypy-version: ['0.930', '0.942', '0.950', '0.961'] steps: - uses: actions/checkout@v3 @@ -233,21 +155,30 @@ jobs: with: python-version: '3.10' + - uses: actions/cache@v3 + id: cache + with: + path: ${{ env.pythonLocation }} + key: > + test-mypy + ${{ runner.os }} + ${{ env.pythonLocation }} + ${{ hashFiles('requirements/pyproject-min.txt') }} + ${{ hashFiles('requirements/testing.txt') }} + ${{ matrix.mypy-version }} + - name: install - run: | - make install-testing - pip freeze + if: steps.cache.outputs.cache-hit != 'true' + run: pip install -r requirements/pyproject-min.txt -r requirements/testing.txt - - name: uninstall deps - run: pip uninstall -y mypy tomli toml - - - name: install specific mypy version + - name: install mypy + if: steps.cache.outputs.cache-hit != 'true' run: pip install mypy==${{ matrix.mypy-version }} - run: mkdir coverage - name: run tests - run: pytest --cov=pydantic tests/mypy + run: coverage run -m pytest tests/mypy env: COVERAGE_FILE: coverage/.coverage.linux-py3.10-mypy${{ matrix.mypy-version }} CONTEXT: linux-py3.10-mypy${{ matrix.mypy-version }} @@ -259,7 +190,7 @@ jobs: path: coverage coverage-combine: - needs: [test-linux-compiled, test-not-compiled, test-old-mypy] + needs: [test, test-old-mypy] runs-on: ubuntu-latest steps: @@ -275,7 +206,7 @@ jobs: name: coverage path: coverage - - run: pip install coverage + - run: pip install coverage[toml] - run: ls -la coverage - run: coverage combine coverage @@ -288,87 +219,29 @@ jobs: name: coverage-html path: htmlcov - test-fastapi: - name: test fastAPI - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: set up python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - - name: install - run: make install-testing - - - name: test - run: make test-fastapi - - build: - name: build py3.${{ matrix.python-version }} on ${{ matrix.platform || matrix.os }} - needs: [lint, test-linux-compiled, test-not-compiled, test-old-mypy, test-fastapi] - if: "success() && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main')" - strategy: - fail-fast: false - matrix: - os: [ubuntu , macos , windows] - python-version: ['7', '8', '9', '10', '11'] - include: - - os: ubuntu - platform: linux - - os: windows - ls: dir - - runs-on: ${{ matrix.os }}-latest - steps: - - uses: actions/checkout@v3 - - - name: set up python - uses: actions/setup-python@v4 - with: - python-version: '3.8' - - - name: install - run: pip install -U twine setuptools wheel cibuildwheel - - - name: build sdist - if: matrix.os == 'ubuntu' && matrix.python-version == '9' - run: python setup.py sdist bdist_wheel - env: - SKIP_CYTHON: 1 - - - name: build ${{ matrix.platform || matrix.os }} binaries - run: cibuildwheel --output-dir dist - env: - PIP: 'pip' - CIBW_BUILD: 'cp3${{ matrix.python-version }}-*' - CIBW_SKIP: '*-win32' - CIBW_PLATFORM: '${{ matrix.platform || matrix.os }}' - CIBW_BEFORE_BUILD: 'pip install -U cython' - CIBW_TEST_REQUIRES: 'pytest==6.2.5 pytest-mock==3.6.1' - CIBW_TEST_COMMAND: 'pytest {project}/tests' - CIBW_MANYLINUX_X86_64_IMAGE: 'manylinux2014' - CIBW_MANYLINUX_I686_IMAGE: 'manylinux2014' - CIBW_ARCHS_MACOS: 'x86_64 arm64' - CIBW_TEST_SKIP: '*-macosx_arm64' # see https://cibuildwheel.readthedocs.io/en/stable/faq/#universal2 - - # TODO build windows 32bit binaries - - - name: list dist files - run: | - ${{ matrix.ls || 'ls -lh' }} dist/ - twine check dist/* - - - name: Store dist artifacts - uses: actions/upload-artifact@v3 - with: - name: pypi_files - path: dist +# FastAPI has a version constraint of pydantic<2.0.0, so we can't run tests, we expect them to break for now anyway +# test-fastapi: +# name: test fastAPI +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# +# - name: set up python +# uses: actions/setup-python@v4 +# with: +# python-version: '3.10' +# +# - name: install +# run: | +# pip install -r requirements/pyproject-all.txt +# pip install . +# +# - name: test +# run: make test-fastapi deploy: name: Deploy - needs: build + needs: [lint, docs-build, test, test-old-mypy] # TODO re-add test-fastapi once fixed if: "success() && startsWith(github.ref, 'refs/tags/')" runs-on: ubuntu-latest @@ -380,12 +253,6 @@ jobs: with: python-version: '3.10' - - name: get dist artifacts - uses: actions/download-artifact@v3 - with: - name: pypi_files - path: dist - - name: get docs uses: actions/download-artifact@v3 with: @@ -393,17 +260,18 @@ jobs: path: site - name: install - run: pip install -U twine packaging - - - name: twine check - run: | - twine check dist/* - ls -lh dist + run: pip install -U twine build packaging - name: check tag id: check-tag run: ./tests/check_tag.py + - name: build + run: python -m build + + - run: ls -lh dist + - run: twine check dist/* + - name: upload to pypi run: twine upload dist/* env: diff --git a/.github/workflows/combine-dependabot.yml b/.github/workflows/combine-dependabot.yml deleted file mode 100644 index 1b141a7..0000000 --- a/.github/workflows/combine-dependabot.yml +++ /dev/null @@ -1,137 +0,0 @@ -# from https://github.com/hrvey/combine-prs-workflow/blob/master/combine-prs.yml -name: 'Combine Dependabot PRs' - -on: - workflow_dispatch: - inputs: - branchPrefix: - description: 'Branch prefix to find combinable PRs based on' - required: true - default: 'dependabot/' - mustBeGreen: - description: 'Only combine PRs that are green' - required: true - default: true - combineBranchName: - description: 'Name of the branch to combine PRs into' - required: true - default: 'combine-dependabot-bumps' - ignoreLabel: - description: 'Exclude PRs with this label' - required: true - default: 'nocombine' - -jobs: - combine-prs: - runs-on: ubuntu-latest - - steps: - - uses: actions/github-script@v6 - id: fetch-branch-names - name: Fetch branch names - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { - owner: context.repo.owner, - repo: context.repo.repo - }); - branches = []; - prs = []; - base_branch = null; - for (const pull of pulls) { - const branch = pull['head']['ref']; - console.log('Pull for branch: ' + branch); - if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) { - console.log('Branch matched: ' + branch); - statusOK = true; - if(${{ github.event.inputs.mustBeGreen }}) { - console.log('Checking green status: ' + branch); - const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{ref}/status', { - owner: context.repo.owner, - repo: context.repo.repo, - ref: branch - }); - if(statuses.length > 0) { - const latest_status = statuses[0]['state']; - console.log('Validating status: ' + latest_status); - if(latest_status != 'success') { - console.log('Discarding ' + branch + ' with status ' + latest_status); - statusOK = false; - } - } - } - console.log('Checking labels: ' + branch); - const labels = pull['labels']; - for(const label of labels) { - const labelName = label['name']; - console.log('Checking label: ' + labelName); - if(labelName == '${{ github.event.inputs.ignoreLabel }}') { - console.log('Discarding ' + branch + ' with label ' + labelName); - statusOK = false; - } - } - if (statusOK) { - console.log('Adding branch to array: ' + branch); - branches.push(branch); - prs.push('#' + pull['number'] + ' ' + pull['title']); - base_branch = pull['base']['ref']; - } - } - } - if (branches.length == 0) { - core.setFailed('No PRs/branches matched criteria'); - return; - } - core.setOutput('base-branch', base_branch); - core.setOutput('prs-string', prs.join('\n')); - - combined = branches.join(' ') - console.log('Combined: ' + combined); - return combined - - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # Creates a branch with other PR branches merged together - - name: Created combined branch - env: - BASE_BRANCH: ${{ steps.fetch-branch-names.outputs.base-branch }} - BRANCHES_TO_COMBINE: ${{ steps.fetch-branch-names.outputs.result }} - COMBINE_BRANCH_NAME: ${{ github.event.inputs.combineBranchName }} - run: | - echo "$BRANCHES_TO_COMBINE" - sourcebranches="${BRANCHES_TO_COMBINE%\"}" - sourcebranches="${sourcebranches#\"}" - - basebranch="${BASE_BRANCH%\"}" - basebranch="${basebranch#\"}" - - git config pull.rebase false - git config user.name github-actions - git config user.email github-actions@github.com - - git branch $COMBINE_BRANCH_NAME $basebranch - git checkout $COMBINE_BRANCH_NAME - git pull origin $sourcebranches --no-edit - git push origin $COMBINE_BRANCH_NAME - - # Creates a PR with the new combined branch - - uses: actions/github-script@v6 - name: Create Combined Pull Request - env: - PRS_STRING: ${{ steps.fetch-branch-names.outputs.prs-string }} - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const prString = process.env.PRS_STRING; - const body = 'This PR was created by the Combine PRs action by combining the following PRs:\n' + prString; - await github.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: 'Combined Dependabot Bumps', - head: '${{ github.event.inputs.combineBranchName }}', - base: '${{ steps.fetch-branch-names.outputs.base-branch }}', - body: body - }); diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 391f00b..9580a22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,12 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: check-yaml args: ['--unsafe'] + - id: check-toml - id: end-of-file-fixer + - id: trailing-whitespace - repo: local hooks: @@ -13,13 +15,16 @@ repos: entry: make lint types: [python] language: system + pass_filenames: false - id: mypy name: Mypy entry: make mypy types: [python] language: system + pass_filenames: false - id: pyupgrade name: Pyupgrade - entry: make pyupgrade + entry: pyupgrade --py37-plus types: [python] language: system + exclude: ^docs/.*$ diff --git a/HISTORY.md b/HISTORY.md index 48b9192..19e5ba6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,8 +1,8 @@ ## v1.10.2 (2022-09-05) * **Revert Change:** Revert percent encoding of URL parts which was originally added in #4224, #4470 by @samuelcolvin -* Prevent long (length > `4_300`) strings/bytes as input to int fields, see - [python/cpython#95778](https://github.com/python/cpython/issues/95778) and +* Prevent long (length > `4_300`) strings/bytes as input to int fields, see + [python/cpython#95778](https://github.com/python/cpython/issues/95778) and [CVE-2020-10735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735), #1477 by @samuelcolvin * fix: dataclass wrapper was not always called, #4477 by @PrettyWood * Use `tomllib` on Python 3.11 when parsing `mypy` configuration, #4476 by @hauntsaninja @@ -40,7 +40,7 @@ * fix "extra fields not permitted" error when dataclass with `Extra.forbid` is validated multiple times, #4343 by @detachhead * Add Python 3.9 and 3.10 examples to docs, #4339 by @Bobronium * Discriminated union models now use `oneOf` instead of `anyOf` when generating OpenAPI schema definitions, #4335 by @MaxwellPayne -* Allow type checkers to infer inner type of `Json` type. `Json[list[str]]` will be now inferred as `list[str]`, +* Allow type checkers to infer inner type of `Json` type. `Json[list[str]]` will be now inferred as `list[str]`, `Json[Any]` should be used instead of plain `Json`. Runtime behaviour is not changed, #4332 by @Bobronium * Allow empty string aliases by using a `alias is not None` check, rather than `bool(alias)`, #4253 by @sergeytsaplin @@ -112,23 +112,23 @@ Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/relea ## v1.9.2 (2022-08-11) **Revert Breaking Change**: _v1.9.1_ introduced a breaking change where model fields were -deep copied by default, this release reverts the default behaviour to match _v1.9.0_ and before, +deep copied by default, this release reverts the default behaviour to match _v1.9.0_ and before, while also allow deep-copy behaviour via `copy_on_model_validation = 'deep'`. See #4092 for more information. * Allow for shallow copies of model fields, `Config.copy_on_model_validation` is now a str which must be - `'none'`, `'deep'`, or `'shallow'` corresponding to not copying, deep copy & shallow copy; default `'shallow'`, + `'none'`, `'deep'`, or `'shallow'` corresponding to not copying, deep copy & shallow copy; default `'shallow'`, #4093 by @timkpaine ## v1.9.1 (2022-05-19) Thank you to pydantic's sponsors: -@tiangolo, @stellargraph, @JonasKs, @grillazz, @Mazyod, @kevinalh, @chdsbd, @povilasb, @povilasb, @jina-ai, -@mainframeindustries, @robusta-dev, @SendCloud, @rszamszur, @jodal, @hardbyte, @corleyma, @daddycocoaman, -@Rehket, @jokull, @reillysiemens, @westonsteimel, @primer-io, @koxudaxi, @browniebroke, @stradivari96, +@tiangolo, @stellargraph, @JonasKs, @grillazz, @Mazyod, @kevinalh, @chdsbd, @povilasb, @povilasb, @jina-ai, +@mainframeindustries, @robusta-dev, @SendCloud, @rszamszur, @jodal, @hardbyte, @corleyma, @daddycocoaman, +@Rehket, @jokull, @reillysiemens, @westonsteimel, @primer-io, @koxudaxi, @browniebroke, @stradivari96, @adriangb, @kamalgill, @jqueguiner, @dev-zero, @datarootsio, @RedCarpetUp for their kind support. -* Limit the size of `generics._generic_types_cache` and `generics._assigned_parameters` +* Limit the size of `generics._generic_types_cache` and `generics._assigned_parameters` to avoid unlimited increase in memory usage, #4083 by @samuelcolvin * Add Jupyverse and FPS as Jupyter projects using pydantic, #4082 by @davidbrochart * Speedup `__isinstancecheck__` on pydantic models when the type is not a model, may also avoid memory "leaks", #4081 by @samuelcolvin @@ -152,7 +152,7 @@ for their kind support. Thank you to pydantic's sponsors: @sthagen, @timdrijvers, @toinbis, @koxudaxi, @ginomempin, @primer-io, @and-semakin, @westonsteimel, @reillysiemens, -@es3n1n, @jokull, @JonasKs, @Rehket, @corleyma, @daddycocoaman, @hardbyte, @datarootsio, @jodal, @aminalaee, @rafsaf, +@es3n1n, @jokull, @JonasKs, @Rehket, @corleyma, @daddycocoaman, @hardbyte, @datarootsio, @jodal, @aminalaee, @rafsaf, @jqueguiner, @chdsbd, @kevinalh, @Mazyod, @grillazz, @JonasKs, @simw, @leynier, @xfenix for their kind support. @@ -185,7 +185,7 @@ for their kind support. ### v1.9.0a1 (2021-12-18) Changes -* Add support for `Decimal`-specific validation configurations in `Field()`, additionally to using `condecimal()`, +* Add support for `Decimal`-specific validation configurations in `Field()`, additionally to using `condecimal()`, to allow better support from editors and tooling, #3507 by @tiangolo * Add `arm64` binaries suitable for MacOS with an M1 CPU to PyPI, #3498 by @samuelcolvin * Fix issue where `None` was considered invalid when using a `Union` type containing `Any` or `object`, #3444 by @tharradine @@ -193,7 +193,7 @@ for their kind support. `pydantic.fields.ModelField`) to `__modify_schema__()` if present, #3434 by @jasujm * Fix issue when pydantic fail to parse `typing.ClassVar` string type annotation, #3401 by @uriyyo * Mention Python >= 3.9.2 as an alternative to `typing_extensions.TypedDict`, #3374 by @BvB93 -* Changed the validator method name in the [Custom Errors example](https://pydantic-docs.helpmanual.io/usage/models/#custom-errors) +* Changed the validator method name in the [Custom Errors example](https://pydantic-docs.helpmanual.io/usage/models/#custom-errors) to more accurately describe what the validator is doing; changed from `name_must_contain_space` to ` value_must_equal_bar`, #3327 by @michaelrios28 * Add `AmqpDsn` class, #3254 by @kludex * Always use `Enum` value as default in generated JSON schema, #3190 by @joaommartins @@ -211,19 +211,19 @@ for their kind support. * Make multiple inheritance work when using `PrivateAttr`, #2989 by @hmvp * Parse environment variables as JSON, if they have a `Union` type with a complex subfield, #2936 by @cbartz * Prevent `StrictStr` permitting `Enum` values where the enum inherits from `str`, #2929 by @samuelcolvin -* Make `SecretsSettingsSource` parse values being assigned to fields of complex types when sourced from a secrets file, +* Make `SecretsSettingsSource` parse values being assigned to fields of complex types when sourced from a secrets file, just as when sourced from environment variables, #2917 by @davidmreed * add a dark mode to _pydantic_ documentation, #2913 by @gbdlin -* Make `pydantic-mypy` plugin compatible with `pyproject.toml` configuration, consistent with `mypy` changes. +* Make `pydantic-mypy` plugin compatible with `pyproject.toml` configuration, consistent with `mypy` changes. See the [doc](https://pydantic-docs.helpmanual.io/mypy_plugin/#configuring-the-plugin) for more information, #2908 by @jrwalk * add Python 3.10 support, #2885 by @PrettyWood * Correctly parse generic models with `Json[T]`, #2860 by @geekingfrog * Update contrib docs re: Python version to use for building docs, #2856 by @paxcodes -* Clarify documentation about _pydantic_'s support for custom validation and strict type checking, +* Clarify documentation about _pydantic_'s support for custom validation and strict type checking, despite _pydantic_ being primarily a parsing library, #2855 by @paxcodes * Fix schema generation for `Deque` fields, #2810 by @sergejkozin * fix an edge case when mixing constraints and `Literal`, #2794 by @PrettyWood -* Fix postponed annotation resolution for `NamedTuple` and `TypedDict` when they're used directly as the type of fields +* Fix postponed annotation resolution for `NamedTuple` and `TypedDict` when they're used directly as the type of fields within Pydantic models, #2760 by @jameysharp * Fix bug when `mypy` plugin fails on `construct` method call for `BaseSettings` derived classes, #2753 by @uriyyo * Add function overloading for a `pydantic.create_model` function, #2748 by @uriyyo @@ -246,7 +246,7 @@ for their kind support. and `postgresql+pygresql` schemes for `PostgresDsn`, #2567 by @postgres-asyncpg * Enable the Hypothesis plugin to generate a constrained decimal when the `decimal_places` argument is specified, #2524 by @cwe5590 * Allow `collections.abc.Callable` to be used as type in Python 3.9, #2519 by @daviskirk -* Documentation update how to custom compile pydantic when using pip install, small change in `setup.py` +* Documentation update how to custom compile pydantic when using pip install, small change in `setup.py` to allow for custom CFLAGS when compiling, #2517 by @peterroelants * remove side effect of `default_factory` to run it only once even if `Config.validate_all` is set, #2515 by @PrettyWood * Add lookahead to ip regexes for `AnyUrl` hosts. This allows urls with DNS labels @@ -259,7 +259,7 @@ for their kind support. * Add `PastDate` and `FutureDate` types, #2425 by @Kludex * Support generating schema for `Generic` fields with subtypes, #2375 by @maximberg * fix(encoder): serialize `NameEmail` to str, #2341 by @alecgerona -* add `Config.smart_union` to prevent coercion in `Union` if possible, see +* add `Config.smart_union` to prevent coercion in `Union` if possible, see [the doc](https://pydantic-docs.helpmanual.io/usage/model_config/#smart-union) for more information, #2092 by @PrettyWood * Add ability to use `typing.Counter` as a model field type, #2060 by @uriyyo * Add parameterised subclasses to `__bases__` when constructing new parameterised classes, so that `A <: B => A[int] <: B[int]`, #2007 by @diabolo-dan @@ -276,8 +276,8 @@ for their kind support. A security vulnerability, level "moderate" is fixed in v1.8.2. Please upgrade **ASAP**. See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) -* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` - (or their negative values) does not cause an infinite loop, +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` + (or their negative values) does not cause an infinite loop, see security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) * fix schema generation with Enum by generating a valid name, #2575 by @PrettyWood * fix JSON schema generation with a `Literal` of an enum member, #2536 by @PrettyWood @@ -289,7 +289,7 @@ for their kind support. ## v1.8.1 (2021-03-03) -Bug fixes for regressions and new features from `v1.8` +Bug fixes for regressions and new features from `v1.8` * allow elements of `Config.field` to update elements of a `Field`, #2461 by @samuelcolvin * fix validation with a `BaseModel` field and a custom root type, #2449 by @PrettyWood @@ -303,8 +303,8 @@ Bug fixes for regressions and new features from `v1.8` ## v1.8 (2021-02-26) Thank you to pydantic's sponsors: -@jorgecarleitao, @BCarley, @chdsbd, @tiangolo, @matin, @linusg, @kevinalh, @koxudaxi, @timdrijvers, @mkeen, @meadsteve, -@ginomempin, @primer-io, @and-semakin, @tomthorogood, @AjitZK, @westonsteimel, @Mazyod, @christippett, @CarlosDomingues, +@jorgecarleitao, @BCarley, @chdsbd, @tiangolo, @matin, @linusg, @kevinalh, @koxudaxi, @timdrijvers, @mkeen, @meadsteve, +@ginomempin, @primer-io, @and-semakin, @tomthorogood, @AjitZK, @westonsteimel, @Mazyod, @christippett, @CarlosDomingues, @Kludex, @r-m-n for their kind support. @@ -395,7 +395,7 @@ for their kind support. ## v1.7.4 (2021-05-11) -* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` (or their negative values) does not cause an infinite loop, See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) @@ -434,7 +434,7 @@ for their kind support. ## v1.7 (2020-10-26) Thank you to pydantic's sponsors: -@timdrijvers, @BCarley, @chdsbd, @tiangolo, @matin, @linusg, @kevinalh, @jorgecarleitao, @koxudaxi, @primer-api +@timdrijvers, @BCarley, @chdsbd, @tiangolo, @matin, @linusg, @kevinalh, @jorgecarleitao, @koxudaxi, @primer-api for their kind support. ### Highlights @@ -449,7 +449,7 @@ for their kind support. * **Breaking Change:** remove `__field_defaults__`, add `default_factory` support with `BaseModel.construct`. Use `.get_default()` method on fields in `__fields__` attribute instead, #1732 by @PrettyWood * Rearrange CI to run linting as a separate job, split install recipes for different tasks, #2020 by @samuelcolvin -* Allows subclasses of generic models to make some, or all, of the superclass's type parameters concrete, while +* Allows subclasses of generic models to make some, or all, of the superclass's type parameters concrete, while also defining new type parameters in the subclass, #2005 by @choogeboom * Call validator with the correct `values` parameter type in `BaseModel.__setattr__`, when `validate_assignment = True` in model config, #1999 by @me-ransh @@ -470,17 +470,17 @@ for their kind support. * add basic support of Pattern type in schema generation, #1767 by @PrettyWood * Support custom title, description and default in schema of enums, #1748 by @PrettyWood * Properly represent `Literal` Enums when `use_enum_values` is True, #1747 by @noelevans -* Allows timezone information to be added to strings to be formatted as time objects. Permitted formats are `Z` for UTC +* Allows timezone information to be added to strings to be formatted as time objects. Permitted formats are `Z` for UTC or an offset for absolute positive or negative time shifts. Or the timezone data can be omitted, #1744 by @noelevans * Add stub `__init__` with Python 3.6 signature for `ForwardRef`, #1738 by @sirtelemak * Fix behaviour with forward refs and optional fields in nested models, #1736 by @PrettyWood * add `Enum` and `IntEnum` as valid types for fields, #1735 by @PrettyWood -* Change default value of `__module__` argument of `create_model` from `None` to `'pydantic.main'`. - Set reference of created concrete model to it's module to allow pickling (not applied to models created in +* Change default value of `__module__` argument of `create_model` from `None` to `'pydantic.main'`. + Set reference of created concrete model to it's module to allow pickling (not applied to models created in functions), #1686 by @Bobronium * Add private attributes support, #1679 by @Bobronium * add `config` to `@validate_arguments`, #1663 by @samuelcolvin -* Allow descendant Settings models to override env variable names for the fields defined in parent Settings models with +* Allow descendant Settings models to override env variable names for the fields defined in parent Settings models with `env` in their `Config`. Previously only `env_prefix` configuration option was applicable, #1561 by @ojomio * Support `ref_template` when creating schema `$ref`s, #1479 by @kilo59 * Add a `__call__` stub to `PyObject` so that mypy will know that it is callable, #1352 by @brianmaissy @@ -490,7 +490,7 @@ for their kind support. ## v1.6.2 (2021-05-11) -* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` (or their negative values) does not cause an infinite loop, See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) @@ -559,7 +559,7 @@ Thank you to pydantic's sponsors: @matin, @tiangolo, @chdsbd, @jorgecarleitao, a * Remove `typing_extensions` dependency for Python 3.8, #1342 by @prettywood * Make `SecretStr` and `SecretBytes` initialization idempotent, #1330 by @atheuz * document making secret types dumpable using the json method, #1328 by @atheuz -* Move all testing and build to github actions, add windows and macos binaries, +* Move all testing and build to github actions, add windows and macos binaries, thank you @StephenBrown2 for much help, #1326 by @samuelcolvin * fix card number length check in `PaymentCardNumber`, `PaymentCardBrand` now inherits from `str`, #1317 by @samuelcolvin * Have `BaseModel` inherit from `Representation` to make mypy happy when overriding `__str__`, #1310 by @FuegoFro diff --git a/LICENSE b/LICENSE index 411ce48..f2229e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017, 2018, 2019, 2020, 2021 Samuel Colvin and other contributors +Copyright (c) 2017 - 2022 Samuel Colvin and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 6c7fdca..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include LICENSE -include README.md -include HISTORY.md -graft tests -global-exclude __pycache__ -global-exclude *.py[cod] diff --git a/Makefile b/Makefile index 5b3dc1e..2ac6ad4 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,23 @@ .DEFAULT_GOAL := all sources = pydantic tests docs/build -isort = isort $(sources) -black = black -S -l 120 --target-version py38 $(sources) - -.PHONY: install-linting -install-linting: - pip install -r tests/requirements-linting.txt - pre-commit install - -.PHONY: install-pydantic -install-pydantic: - python -m pip install -U wheel pip - pip install -r requirements.txt - SKIP_CYTHON=1 pip install -e . - -.PHONY: install-testing -install-testing: install-pydantic - pip install -r tests/requirements-testing.txt - -.PHONY: install-docs -install-docs: install-pydantic - pip install -U -r docs/requirements.txt .PHONY: install -install: install-testing install-linting install-docs - @echo 'installed development requirements' - -.PHONY: build-trace -build-trace: - python setup.py build_ext --force --inplace --define CYTHON_TRACE - -.PHONY: build -build: - python setup.py build_ext --inplace +install: + python -m pip install -U pip + pip install -r requirements/all.txt + pip install -e . .PHONY: format format: - pyupgrade --py37-plus --exit-zero-even-if-changed `find $(sources) -name "*.py" -type f` - $(isort) - $(black) + pyupgrade --py37-plus --exit-zero-even-if-changed `find $(sources) -name "*.py" -type f` + isort $(sources) + black $(sources) .PHONY: lint lint: flake8 $(sources) - $(isort) --check-only --df - $(black) --check --diff - -.PHONY: check-dist -check-dist: - python setup.py check -ms - SKIP_CYTHON=1 python setup.py sdist - twine check dist/* + isort $(sources) --check-only --df + black $(sources) --check --diff .PHONY: mypy mypy: @@ -66,7 +33,7 @@ pyright: .PHONY: test test: - pytest --cov=pydantic + coverage run -m pytest --durations=10 .PHONY: testcov testcov: test @@ -107,7 +74,6 @@ clean: rm -rf build rm -rf dist rm -f pydantic/*.c pydantic/*.so - python setup.py clean rm -rf site rm -rf docs/_build rm -rf docs/.changelog.md docs/.version.md docs/.tmp_schema_mappings.html diff --git a/README.md b/README.md index ab27e4d..b4bf706 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,19 @@ [![versions](https://img.shields.io/pypi/pyversions/pydantic.svg)](https://github.com/pydantic/pydantic) [![license](https://img.shields.io/github/license/pydantic/pydantic.svg)](https://github.com/pydantic/pydantic/blob/main/LICENSE) -Data validation and settings management using Python type hints. +Data validation using Python type hints. + +--- + +## Notice + +**This branch relates to development of pydantic V2 which is not yet ready for release.** + +If you're a user of pydantic, you probably want either +[pydantic V1.10 Documentation](https://pydantic-docs.helpmanual.io/) or, +[`1.10.X-fixes` git branch](https://github.com/pydantic/pydantic/tree/1.10.X-fixes). + +--- Fast and extensible, *pydantic* plays nicely with your linters/IDE/brain. Define how data should be in pure, canonical Python 3.7+; validate it with *pydantic*. diff --git a/docs/blog/pydantic-v2.md b/docs/blog/pydantic-v2.md index f9ed30d..d83e8d9 100644 --- a/docs/blog/pydantic-v2.md +++ b/docs/blog/pydantic-v2.md @@ -32,9 +32,9 @@ Here goes... --- Enormous thanks to -[Eric Jolibois](https://github.com/PrettyWood), [Laurence Watson](https://github.com/Rabscuttler), -[Sebastián Ramírez](https://github.com/tiangolo), [Adrian Garcia Badaracco](https://github.com/adriangb), -[Tom Hamilton Stubber](https://github.com/tomhamiltonstubber), [Zac Hatfield-Dodds](https://github.com/Zac-HD), +[Eric Jolibois](https://github.com/PrettyWood), [Laurence Watson](https://github.com/Rabscuttler), +[Sebastián Ramírez](https://github.com/tiangolo), [Adrian Garcia Badaracco](https://github.com/adriangb), +[Tom Hamilton Stubber](https://github.com/tomhamiltonstubber), [Zac Hatfield-Dodds](https://github.com/Zac-HD), [Tom](https://github.com/czotomo) & [Hasan Ramezani](https://github.com/hramezani) for reviewing this blog post, putting up with (and correcting) my horrible typos and making great suggestions that have made this post and Pydantic V2 materially better. @@ -207,7 +207,7 @@ In future direct validation of JSON will also allow: !!! note Pydantic has always had special support for JSON, that is not going to change. - While in theory other formats could be specifically supported, the overheads and development time are + While in theory other formats could be specifically supported, the overheads and development time are significant and I don't think there's another format that's used widely enough to be worth specific logic. Other formats can be parsed to python then validated, similarly when serialising, data can be exported to a python object, then serialised, @@ -215,7 +215,7 @@ In future direct validation of JSON will also allow: ### Validation without a Model :thumbsup: -In pydantic V1 the core of all validation was a pydantic model, this led to a significant performance penalty +In pydantic V1 the core of all validation was a pydantic model, this led to a significant performance penalty and extra complexity when the output data type was not a model. pydantic-core operates on a tree of validators with no "model" type required at the base of that tree. @@ -278,13 +278,13 @@ class MyModel(BaseModel): @validator('timestamp', mode='wrap') def validate_timestamp(cls, v, handler): if v == 'now': - # we don't want to bother with further validation, + # we don't want to bother with further validation, # just return the new value return datetime.now() try: return handler(v) except ValidationError: - # validation failed, in this case we want to + # validation failed, in this case we want to # return a default value return datetime(2000, 1, 1) ``` @@ -366,7 +366,7 @@ from pydantic import BaseModel, EmailStr, validator class User(BaseModel): email: EmailStr home_country: str - + @validator('home_country') def check_home_country(cls, v, context): if v not in context['countries']: @@ -735,7 +735,7 @@ The emoji here is just for variation, I'm not frowning about any of this, these 1. `__root__` custom root models are no longer necessary since validation on any supported data type is allowed without a model -2. `.parse_file()` and `.parse_raw()`, partially replaced with `.model_validate_json()`, +2. `.parse_file()` and `.parse_raw()`, partially replaced with `.model_validate_json()`, see [model methods](#model-namespace-cleanup) 3. `.schema_json()` & `.copy()`, see [model methods](#model-namespace-cleanup) 4. `TypeError` are no longer considered as validation errors, but rather as internal errors, this is to better @@ -759,7 +759,7 @@ The emoji here is just for variation, I'm not frowning about any of this, these * `json_encoders` - see the export "mode" discussion [above](#improvements-to-dumpingserializationexport) * `underscore_attrs_are_private` we should just choose a sensible default * `smart_union` - all unions are now "smart" -9. `dict(model)` functionality should be removed, there's a much clearer distinction now that in 2017 when I +9. `dict(model)` functionality should be removed, there's a much clearer distinction now that in 2017 when I implemented this between a model and a dict ## Features Remaining :neutral_face: diff --git a/docs/hypothesis_plugin.md b/docs/hypothesis_plugin.md index 16d2a79..af24617 100644 --- a/docs/hypothesis_plugin.md +++ b/docs/hypothesis_plugin.md @@ -13,7 +13,7 @@ and [`st.from_type()`](https://hypothesis.readthedocs.io/en/latest/data.html#hyp strategies support them without any user configuration. !!! warning - Please note, while the plugin supports these types, hypothesis will(currently) generate values outside + Please note, while the plugin supports these types, hypothesis will(currently) generate values outside of given args for the constrained function types. diff --git a/docs/index.md b/docs/index.md index d526cea..c267e03 100644 --- a/docs/index.md +++ b/docs/index.md @@ -102,9 +102,9 @@ If validation fails pydantic will raise an error with a breakdown of what was wr So *pydantic* uses some cool new language features, but why should I actually go and use it? **plays nicely with your IDE/linter/brain** -: There's no new schema definition micro-language to learn. If you know how to use Python type hints, - you know how to use *pydantic*. Data structures are just instances of classes you define with type annotations, - so auto-completion, linting, [mypy](usage/mypy.md), IDEs (especially [PyCharm](pycharm_plugin.md)), +: There's no new schema definition micro-language to learn. If you know how to use Python type hints, + you know how to use *pydantic*. Data structures are just instances of classes you define with type annotations, + so auto-completion, linting, [mypy](usage/mypy.md), IDEs (especially [PyCharm](pycharm_plugin.md)), and your intuition should all work properly with your validated data. **dual use** @@ -117,15 +117,15 @@ So *pydantic* uses some cool new language features, but why should I actually go it's generally as fast or faster than most similar libraries. **validate complex structures** -: use of [recursive *pydantic* models](usage/models.md#recursive-models), `typing`'s - [standard types](usage/types.md#standard-library-types) (e.g. `List`, `Tuple`, `Dict` etc.) and +: use of [recursive *pydantic* models](usage/models.md#recursive-models), `typing`'s + [standard types](usage/types.md#standard-library-types) (e.g. `List`, `Tuple`, `Dict` etc.) and [validators](usage/validators.md) allow complex data schemas to be clearly and easily defined, validated, and parsed. **extensible** -: *pydantic* allows [custom data types](usage/types.md#custom-data-types) to be defined or you can extend validation +: *pydantic* allows [custom data types](usage/types.md#custom-data-types) to be defined or you can extend validation with methods on a model decorated with the [`validator`](usage/validators.md) decorator. - + **dataclasses integration** : As well as `BaseModel`, *pydantic* provides a [`dataclass`](usage/dataclasses.md) decorator which creates (almost) vanilla Python dataclasses with input @@ -140,14 +140,14 @@ Hundreds of organisations and packages are using *pydantic*, including: fast to code and ready for production, based on *pydantic* and Starlette. [Project Jupyter](https://jupyter.org/) -: developers of the Jupyter notebook are using *pydantic* +: developers of the Jupyter notebook are using *pydantic* [for subprojects](https://github.com/pydantic/pydantic/issues/773), through the FastAPI-based Jupyter server [Jupyverse](https://github.com/jupyter-server/jupyverse), and for [FPS](https://github.com/jupyter-server/fps)'s configuration management. **Microsoft** -: are using *pydantic* (via FastAPI) for - [numerous services](https://github.com/tiangolo/fastapi/pull/26#issuecomment-463768795), some of which are +: are using *pydantic* (via FastAPI) for + [numerous services](https://github.com/tiangolo/fastapi/pull/26#issuecomment-463768795), some of which are "getting integrated into the core Windows product and some Office products." **Amazon Web Services** @@ -179,7 +179,7 @@ Hundreds of organisations and packages are using *pydantic*, including: [tools to debug and profile Python applications on Kubernetes](https://home.robusta.dev/python/) use *pydantic* models. -For a more comprehensive list of open-source projects using *pydantic* see the +For a more comprehensive list of open-source projects using *pydantic* see the [list of dependents on github](https://github.com/pydantic/pydantic/network/dependents). ## Discussion of Pydantic @@ -190,14 +190,14 @@ Podcasts and videos discussing pydantic. : Michael Kennedy and Samuel Colvin, the creator of *pydantic*, dive into the history of pydantic and its many uses and benefits. [Podcast.\_\_init\_\_](https://www.pythonpodcast.com/pydantic-data-validation-episode-263/){target=_blank} -: Discussion about where *pydantic* came from and ideas for where it might go next with +: Discussion about where *pydantic* came from and ideas for where it might go next with Samuel Colvin the creator of pydantic. [Python Bytes Podcast](https://pythonbytes.fm/episodes/show/157/oh-hai-pandas-hold-my-hand){target=_blank} -: "*This is a sweet simple framework that solves some really nice problems... Data validations and settings management - using Python type annotations, and it's the Python type annotations that makes me really extra happy... It works +: "*This is a sweet simple framework that solves some really nice problems... Data validations and settings management + using Python type annotations, and it's the Python type annotations that makes me really extra happy... It works automatically with all the IDE's you already have.*" --Michael Kennedy [Python pydantic Introduction – Give your data classes super powers](https://www.youtube.com/watch?v=WJmqgJn9TXg){target=_blank} -: a talk by Alexander Hultnér originally for the Python Pizza Conference introducing new users to pydantic and walking +: a talk by Alexander Hultnér originally for the Python Pizza Conference introducing new users to pydantic and walking through the core features of pydantic. diff --git a/docs/install.md b/docs/install.md index d64800c..a78e728 100644 --- a/docs/install.md +++ b/docs/install.md @@ -17,7 +17,7 @@ conda install pydantic -c conda-forge ## Compiled with Cython -*pydantic* can optionally be compiled with [cython](https://cython.org/) which should give a 30-50% performance improvement. +*pydantic* can optionally be compiled with [cython](https://cython.org/) which should give a 30-50% performance improvement. By default `pip install` provides optimized binaries via [PyPI](https://pypi.org/project/pydantic/#files) for Linux, MacOS and 64bit Windows. diff --git a/docs/mypy_plugin.md b/docs/mypy_plugin.md index 729b295..6245577 100644 --- a/docs/mypy_plugin.md +++ b/docs/mypy_plugin.md @@ -90,7 +90,7 @@ To get started, all you need to do is create a `mypy.ini` file with following co plugins = pydantic.mypy ``` -The plugin is compatible with mypy versions `>=0.910`. +The plugin is compatible with mypy versions `>=0.930`. See the [mypy usage](usage/mypy.md) and [plugin configuration](#configuring-the-plugin) docs for more details. diff --git a/docs/pycharm_plugin.md b/docs/pycharm_plugin.md index a18a8e9..6403fab 100644 --- a/docs/pycharm_plugin.md +++ b/docs/pycharm_plugin.md @@ -1,4 +1,4 @@ -While pydantic will work well with any IDE out of the box, a +While pydantic will work well with any IDE out of the box, a [PyCharm plugin](https://plugins.jetbrains.com/plugin/12861-pydantic) offering improved pydantic integration is available on the JetBrains Plugins Repository for PyCharm. You can install the plugin for free from the plugin marketplace diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 2cf41a5..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -autoflake==1.5.3 -ansi2html==1.8.0 -flake8==5.0.4 -flake8-quotes==3.3.1 -hypothesis==6.54.4 -markdown-include==0.7.0 -mdx-truly-sane-lists==1.3 -mkdocs==1.3.1 -mkdocs-exclude==1.0.2 -mkdocs-material==8.4.2 -pyupgrade==2.37.3 -sqlalchemy -orjson -ujson - diff --git a/docs/usage/devtools.md b/docs/usage/devtools.md index 8dd0c7f..3253ba2 100644 --- a/docs/usage/devtools.md +++ b/docs/usage/devtools.md @@ -1,9 +1,9 @@ !!! note **Admission:** I (the primary developer of *pydantic*) also develop python-devtools. - + [python-devtools](https://python-devtools.helpmanual.io/) (`pip install devtools`) provides a number of tools which are useful during Python development, including `debug()` an alternative to `print()` which formats output in a way -which should be easier to read than `print` as well as giving information about which file/line the print statement +which should be easier to read than `print` as well as giving information about which file/line the print statement is on and what value was printed. *pydantic* integrates with *devtools* by implementing the `__pretty__` method on most public classes. diff --git a/docs/usage/model_config.md b/docs/usage/model_config.md index 656cf51..b181eb8 100644 --- a/docs/usage/model_config.md +++ b/docs/usage/model_config.md @@ -112,12 +112,12 @@ not be included in the model schemas. **Note**: this means that attributes on th : whether to treat any underscore non-class var attrs as private, or leave them as is; see [Private model attributes](models.md#private-model-attributes) **`copy_on_model_validation`** -: string literal to control how models instances are processed during validation, +: string literal to control how models instances are processed during validation, with the following means (see [#4093](https://github.com/pydantic/pydantic/pull/4093) for a full discussion of the changes to this field): * `'none'` - models are not copied on validation, they're simply kept "untouched" * `'shallow'` - models are shallow copied, this is the default -* `'deep'` - models are deep copied +* `'deep'` - models are deep copied **`smart_union`** : whether _pydantic_ should try to check all types inside `Union` to prevent undesired coercion; see [the dedicated section](#smart-union) diff --git a/docs/usage/models.md b/docs/usage/models.md index bf5d765..96bf3bd 100644 --- a/docs/usage/models.md +++ b/docs/usage/models.md @@ -1,4 +1,4 @@ -The primary means of defining objects in *pydantic* is via models +The primary means of defining objects in *pydantic* is via models (models are simply classes which inherit from `BaseModel`). You can think of models as similar to types in strictly typed languages, or as the requirements of a single endpoint @@ -27,9 +27,9 @@ class User(BaseModel): id: int name = 'Jane Doe' ``` -`User` here is a model with two fields `id` which is an integer and is required, +`User` here is a model with two fields `id` which is an integer and is required, and `name` which is a string and is not required (it has a default value). The type of `name` is inferred from the -default value, and so a type annotation is not required (however note [this](#field-ordering) warning about field +default value, and so a type annotation is not required (however note [this](#field-ordering) warning about field order when some fields do not have type annotations). ```py user = User(id='123') @@ -65,15 +65,15 @@ This model is mutable so field values can be changed. ### Model properties -The example above only shows the tip of the iceberg of what models can do. +The example above only shows the tip of the iceberg of what models can do. Models possess the following methods and attributes: `dict()` -: returns a dictionary of the model's fields and values; +: returns a dictionary of the model's fields and values; cf. [exporting models](exporting_models.md#modeldict) `json()` -: returns a JSON string representation `dict()`; +: returns a JSON string representation `dict()`; cf. [exporting models](exporting_models.md#modeljson) `copy()` @@ -99,7 +99,7 @@ Models possess the following methods and attributes: : returns a JSON string representation of `schema()`; cf. [schema](schema.md) `construct()` -: a class method for creating models without running validation; +: a class method for creating models without running validation; cf. [Creating models without validation](#creating-models-without-validation) `__fields_set__` @@ -159,7 +159,7 @@ Arbitrary classes are processed by *pydantic* using the `GetterDict` class (see provide a dictionary-like interface to any class. You can customise how this works by setting your own sub-class of `GetterDict` as the value of `Config.getter_dict` (see [config](model_config.md)). -You can also customise class validation using [root_validators](validators.md#root-validators) with `pre=True`. +You can also customise class validation using [root_validators](validators.md#root-validators) with `pre=True`. In this case your validator function will be passed a `GetterDict` instance which you may copy and modify. The `GetterDict` instance will be called for each field with a sentinel as a fallback (if no other default @@ -240,12 +240,12 @@ You can also define your own error classes, which can specify a custom error cod !!! warning To quote the [official `pickle` docs](https://docs.python.org/3/library/pickle.html), "The pickle module is not secure against erroneous or maliciously constructed data. - Never unpickle data received from an untrusted or unauthenticated source." - + Never unpickle data received from an untrusted or unauthenticated source." + !!! info Because it can result in arbitrary code execution, as a security measure, you need to explicitly pass `allow_pickle` to the parsing function in order to load `pickle` data. - + ### Creating models without validation *pydantic* also provides the `construct()` method which allows models to be created **without validation** this @@ -258,11 +258,11 @@ as efficiently as possible (`construct()` is generally around 30x faster than cr {!.tmp_examples/models_construct.md!} -The `_fields_set` keyword argument to `construct()` is optional, but allows you to be more precise about +The `_fields_set` keyword argument to `construct()` is optional, but allows you to be more precise about which fields were originally set and which weren't. If it's omitted `__fields_set__` will just be the keys -of the data provided. +of the data provided. -For example, in the example above, if `_fields_set` was not provided, +For example, in the example above, if `_fields_set` was not provided, `new_user.__fields_set__` would be `{'id', 'age', 'name'}`. ## Generic Models @@ -292,12 +292,12 @@ you would expect mypy to provide if you were to declare the type without using ` Internally, pydantic uses `create_model` to generate a (cached) concrete `BaseModel` at runtime, so there is essentially zero overhead introduced by making use of `GenericModel`. -To inherit from a GenericModel without replacing the `TypeVar` instance, a class must also inherit from +To inherit from a GenericModel without replacing the `TypeVar` instance, a class must also inherit from `typing.Generic`: {!.tmp_examples/models_generics_inheritance.md!} -You can also create a generic subclass of a `GenericModel` that partially or fully replaces the type +You can also create a generic subclass of a `GenericModel` that partially or fully replaces the type parameters in the superclass. {!.tmp_examples/models_generics_inheritance_extend.md!} @@ -311,7 +311,7 @@ Using the same TypeVar in nested models allows you to enforce typing relationshi {!.tmp_examples/models_generics_nested.md!} Pydantic also treats `GenericModel` similarly to how it treats built-in generic types like `List` and `Dict` when it -comes to leaving them unparameterized, or using bounded `TypeVar` instances: +comes to leaving them unparameterized, or using bounded `TypeVar` instances: * If you don't specify parameters before instantiating the generic model, they will be treated as `Any` * You can parametrize models with one or more *bounded* parameters to add subclass checks @@ -331,7 +331,7 @@ Here `StaticFoobarModel` and `DynamicFoobarModel` are identical. !!! warning See the note in [Required Optional Fields](#required-optional-fields) for the distinction between an ellipsis as a - field default and annotation-only fields. + field default and annotation-only fields. See [pydantic/pydantic#1047](https://github.com/pydantic/pydantic/issues/1047) for more details. Fields are defined by either a tuple of the form `(, )` or just a default value. The @@ -356,7 +356,7 @@ Those methods have the exact same keyword arguments as `create_model`. ## Custom Root Types -Pydantic models can be defined with a custom root type by declaring the `__root__` field. +Pydantic models can be defined with a custom root type by declaring the `__root__` field. The root type can be any type supported by pydantic, and is specified by the type hint on the `__root__` field. The root value can be passed to the model `__init__` via the `__root__` keyword argument, or as @@ -371,7 +371,7 @@ the following logic is used: the argument itself is always validated against the custom root type. * For other custom root types, if the dict has precisely one key with the value `__root__`, the corresponding value will be validated against the custom root type. -* Otherwise, the dict itself is validated against the custom root type. +* Otherwise, the dict itself is validated against the custom root type. This is demonstrated in the following example: @@ -380,7 +380,7 @@ This is demonstrated in the following example: !!! warning Calling the `parse_obj` method on a dict with the single key `"__root__"` for non-mapping custom root types is currently supported for backwards compatibility, but is not recommended and may be dropped in a future version. - + If you want to access items in the `__root__` field directly or to iterate over the items, you can implement custom `__iter__` and `__getitem__` functions, as shown in the following example. {!.tmp_examples/models_custom_root_access.md!} @@ -410,7 +410,7 @@ Pydantic models can be used alongside Python's Field order is important in models for the following reasons: -* validation is performed in the order fields are defined; [fields validators](validators.md) +* validation is performed in the order fields are defined; [fields validators](validators.md) can access the values of earlier fields, but not later ones * field order is preserved in the model [schema](schema.md) * field order is preserved in [validation errors](#error-handling) @@ -430,7 +430,7 @@ all fields without an annotation. Within their respective groups, fields remain ## Required fields -To declare a field as required, you may declare it using just an annotation, or you may use an ellipsis (`...`) +To declare a field as required, you may declare it using just an annotation, or you may use an ellipsis (`...`) as the value: {!.tmp_examples/models_required_fields.md!} @@ -488,7 +488,7 @@ using `PrivateAttr`: {!.tmp_examples/private_attributes.md!} -Private attribute names must start with underscore to prevent conflicts with model fields: both `_attr` and `__attr__` +Private attribute names must start with underscore to prevent conflicts with model fields: both `_attr` and `__attr__` are supported. If `Config.underscore_attrs_are_private` is `True`, any non-ClassVar underscore attribute will be treated as private: @@ -503,7 +503,7 @@ logic used to populate pydantic models in a more ad-hoc way. This function behav `BaseModel.parse_obj`, but works with arbitrary pydantic-compatible types. This is especially useful when you want to parse results into a type that is not a direct subclass of `BaseModel`. -For example: +For example: {!.tmp_examples/parse_obj_as.md!} @@ -520,7 +520,7 @@ For example: {!.tmp_examples/models_data_conversion.md!} -This is a deliberate decision of *pydantic*, and in general it's the most useful approach. See +This is a deliberate decision of *pydantic*, and in general it's the most useful approach. See [here](https://github.com/pydantic/pydantic/issues/578) for a longer discussion on the subject. Nevertheless, [strict type checking](types.md#strict-types) is partially supported. @@ -537,14 +537,14 @@ The generated signature will also respect custom `__init__` functions: {!.tmp_examples/models_signature_custom_init.md!} -To be included in the signature, a field's alias or name must be a valid Python identifier. -*pydantic* prefers aliases over names, but may use field names if the alias is not a valid Python identifier. +To be included in the signature, a field's alias or name must be a valid Python identifier. +*pydantic* prefers aliases over names, but may use field names if the alias is not a valid Python identifier. If a field's alias and name are both invalid identifiers, a `**data` argument will be added. In addition, the `**data` argument will always be present in the signature if `Config.extra` is `Extra.allow`. !!! note - Types in the model signature are the same as declared in model annotations, + Types in the model signature are the same as declared in model annotations, not necessarily all the types that can actually be provided to that field. This may be fixed one day once [#1055](https://github.com/pydantic/pydantic/issues/1055) is solved. @@ -555,5 +555,5 @@ In addition, the `**data` argument will always be present in the signature if `C {!.tmp_examples/models_structural_pattern_matching.md!} !!! note - A match-case statement may seem as if it creates a new model, but don't be fooled; + A match-case statement may seem as if it creates a new model, but don't be fooled; it is just syntactic sugar for getting an attribute and either comparing it or declaring and initializing it. diff --git a/docs/usage/types.md b/docs/usage/types.md index 27109f2..003e62f 100644 --- a/docs/usage/types.md +++ b/docs/usage/types.md @@ -619,7 +619,7 @@ For URI/URL validation the following types are available: !!! warning In V1.10.0 and v1.10.1 `stricturl` also took an optional `quote_plus` argument and URL components were percent - encoded in some cases. This feature was removed in v1.10.2, see + encoded in some cases. This feature was removed in v1.10.2, see [#4470](https://github.com/pydantic/pydantic/pull/4470) for explanation and more details. The above types (which all inherit from `AnyUrl`) will attempt to give descriptive errors when invalid URLs are @@ -683,6 +683,7 @@ If further validation is required, these properties can be used by validators to Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good (or at least big) company. + ### Color Type You can use the `Color` data type for storing colors as per diff --git a/docs/visual_studio_code.md b/docs/visual_studio_code.md index 58ba49a..9368ba7 100644 --- a/docs/visual_studio_code.md +++ b/docs/visual_studio_code.md @@ -251,15 +251,15 @@ The specific configuration **`frozen`** (in beta) has a special meaning. It prevents other code from changing a model instance once it's created, keeping it **"frozen"**. -When using the second version to declare `frozen=True` (with **keyword arguments** in the class definition), -Pylance can use it to help you check in your code and **detect errors** when something is trying to set values +When using the second version to declare `frozen=True` (with **keyword arguments** in the class definition), +Pylance can use it to help you check in your code and **detect errors** when something is trying to set values in a model that is "frozen". ![VS Code strict type errors with model](./img/vs_code_08.png) ## BaseSettings and ignoring Pylance/pyright errors -Pylance/pyright does not work well with [`BaseSettings`](./usage/settings.md) - fields in settings classes can be +Pylance/pyright does not work well with [`BaseSettings`](./usage/settings.md) - fields in settings classes can be configured via environment variables and therefore "required" fields do not have to be explicitly set when initialising a settings instance. However, pyright considers these fields as "required" and will therefore show an error when they're not set. @@ -284,7 +284,7 @@ class Knight(BaseModel): title: str = Field(default='Sir Lancelot') # this is okay age: int = Field(23) # this works fine at runtime but will case an error for pyright -lance = Knight() # error: Argument missing for parameter "age" +lance = Knight() # error: Argument missing for parameter "age" ``` Like the issue with `BaseSettings`, this is a limitation of dataclass transforms and cannot be fixed in pydantic. diff --git a/mkdocs.yml b/mkdocs.yml index 6804017..301e4be 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,7 +81,7 @@ markdown_extensions: emoji_index: !!python/name:materialx.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.tabbed: - alternate_style: true + alternate_style: true plugins: - search diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 3bf1418..db13046 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -13,7 +13,7 @@ from .networks import * from .parse import Protocol from .tools import * from .types import * -from .version import VERSION, compiled +from .version import VERSION __version__ = VERSION @@ -126,6 +126,5 @@ __all__ = [ 'PastDate', 'FutureDate', # version - 'compiled', 'VERSION', ] diff --git a/pydantic/config.py b/pydantic/config.py index 74687ca..d5fa764 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -2,11 +2,10 @@ import json from enum import Enum from typing import TYPE_CHECKING, Any, Callable, Dict, ForwardRef, Optional, Tuple, Type, Union -from typing_extensions import Literal, Protocol +from typing_extensions import Literal, Protocol, TypedDict from .typing import AnyArgTCallable, AnyCallable from .utils import GetterDict -from .version import compiled if TYPE_CHECKING: from typing import overload @@ -37,45 +36,34 @@ class Extra(str, Enum): forbid = 'forbid' -# https://github.com/cython/cython/issues/4003 -# Will be fixed with Cython 3 but still in alpha right now -if not compiled: - from typing_extensions import TypedDict - - class ConfigDict(TypedDict, total=False): - title: Optional[str] - anystr_lower: bool - anystr_strip_whitespace: bool - min_anystr_length: int - max_anystr_length: Optional[int] - validate_all: bool - extra: Extra - allow_mutation: bool - frozen: bool - allow_population_by_field_name: bool - use_enum_values: bool - fields: Dict[str, Union[str, Dict[str, str]]] - validate_assignment: bool - error_msg_templates: Dict[str, str] - arbitrary_types_allowed: bool - orm_mode: bool - getter_dict: Type[GetterDict] - alias_generator: Optional[Callable[[str], str]] - keep_untouched: Tuple[type, ...] - schema_extra: Union[Dict[str, object], 'SchemaExtraCallable'] - json_loads: Callable[[str], object] - json_dumps: AnyArgTCallable[str] - json_encoders: Dict[Type[object], AnyCallable] - underscore_attrs_are_private: bool - allow_inf_nan: bool - - # whether or not inherited models as fields should be reconstructed as base model - copy_on_model_validation: bool - # whether dataclass `__post_init__` should be run after validation - post_init_call: Literal['before_validation', 'after_validation'] - -else: - ConfigDict = dict # type: ignore +class ConfigDict(TypedDict, total=False): + title: Optional[str] + anystr_lower: bool + anystr_strip_whitespace: bool + min_anystr_length: int + max_anystr_length: Optional[int] + validate_all: bool + extra: Extra + allow_mutation: bool + frozen: bool + allow_population_by_field_name: bool + use_enum_values: bool + fields: Dict[str, Union[str, Dict[str, str]]] + validate_assignment: bool + error_msg_templates: Dict[str, str] + arbitrary_types_allowed: bool + orm_mode: bool + getter_dict: Type[GetterDict] + alias_generator: Optional[Callable[[str], str]] + keep_untouched: Tuple[type, ...] + schema_extra: Union[Dict[str, object], 'SchemaExtraCallable'] + json_loads: Callable[[str], object] + json_dumps: AnyArgTCallable[str] + json_encoders: Dict[Type[object], AnyCallable] + underscore_attrs_are_private: bool + allow_inf_nan: bool + copy_on_model_validation: Literal['none', 'deep', 'shallow'] + post_init_call: Literal['before_validation', 'after_validation'] class BaseConfig: diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 6833112..799ea27 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -204,7 +204,7 @@ def dataclass( else: dc_cls_doc = cls.__doc__ or '' # needs to be done before generating dataclass if sys.version_info >= (3, 10): - dc_cls = dataclasses.dataclass( + dc_cls = dataclasses.dataclass( # type: ignore[call-overload] cls, init=init, repr=repr, @@ -215,7 +215,7 @@ def dataclass( kw_only=kw_only, ) else: - dc_cls = dataclasses.dataclass( # type: ignore + dc_cls = dataclasses.dataclass( cls, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen ) default_validate_on_init = True diff --git a/pydantic/mypy.py b/pydantic/mypy.py index 6bd9db1..6b8c044 100644 --- a/pydantic/mypy.py +++ b/pydantic/mypy.py @@ -68,7 +68,7 @@ from pydantic.utils import is_valid_field try: from mypy.types import TypeVarDef # type: ignore[attr-defined] except ImportError: # pragma: no cover - # Backward-compatible with TypeVarDef from Mypy 0.910. + # Backward-compatible with TypeVarDef from Mypy 0.930. from mypy.types import TypeVarType as TypeVarDef CONFIGFILE_KEY = 'pydantic-mypy' @@ -164,11 +164,7 @@ class PydanticPlugin(Plugin): # Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter # Pydantic calls the default factory without any argument, so we retrieve the first item if isinstance(default_factory_type, Overloaded): - if MYPY_VERSION_TUPLE > (0, 910): - default_factory_type = default_factory_type.items[0] - else: - # Mypy0.910 exposes the items of overloaded types in a function - default_factory_type = default_factory_type.items()[0] # type: ignore[operator] + default_factory_type = default_factory_type.items[0] if isinstance(default_factory_type, CallableType): ret_type = default_factory_type.ret_type @@ -448,23 +444,18 @@ class PydanticModelTransformer: obj_type = ctx.api.named_type(f'{BUILTINS_NAME}.object') self_tvar_name = '_PydanticBaseModel' # Make sure it does not conflict with other names in the class tvar_fullname = ctx.cls.fullname + '.' + self_tvar_name - tvd = TypeVarDef(self_tvar_name, tvar_fullname, -1, [], obj_type) + # requires mypy>0.910 + self_type = TypeVarDef(self_tvar_name, tvar_fullname, -1, [], obj_type) self_tvar_expr = TypeVarExpr(self_tvar_name, tvar_fullname, [], obj_type) ctx.cls.info.names[self_tvar_name] = SymbolTableNode(MDEF, self_tvar_expr) - # Backward-compatible with TypeVarDef from Mypy 0.910. - if isinstance(tvd, TypeVarType): - self_type = tvd - else: - self_type = TypeVarType(tvd) # type: ignore[call-arg] - add_method( ctx, 'construct', construct_arguments, return_type=self_type, self_type=self_type, - tvar_def=tvd, + tvar_def=self_type, is_classmethod=True, ) @@ -829,22 +820,16 @@ def parse_toml(config_file: str) -> Optional[Dict[str, Any]]: if not config_file.endswith('.toml'): return None - read_mode = 'rb' if sys.version_info >= (3, 11): import tomllib as toml_ else: try: import tomli as toml_ - except ImportError: - # older versions of mypy have toml as a dependency, not tomli - read_mode = 'r' - try: - import toml as toml_ # type: ignore[no-redef] - except ImportError: # pragma: no cover - import warnings + except ImportError: # pragma: no cover + import warnings - warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.') - return None + warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.') + return None - with open(config_file, read_mode) as rf: - return toml_.load(rf) # type: ignore[arg-type] + with open(config_file, 'rb') as rf: + return toml_.load(rf) diff --git a/pydantic/typing.py b/pydantic/typing.py index 5ccf266..ba89066 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -46,10 +46,10 @@ except ImportError: TypingGenericAlias = () try: - from types import UnionType as TypesUnionType # type: ignore + from types import UnionType as TypesUnionType except ImportError: # python < 3.10 does not have UnionType (str | int, byte | bool and so on) - TypesUnionType = () + TypesUnionType = () # type: ignore[misc,assignment] if sys.version_info < (3, 9): @@ -243,7 +243,7 @@ else: def is_union(tp: Optional[Type[Any]]) -> bool: return tp is Union or tp is types.UnionType # noqa: E721 - WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType) + WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType) # type: ignore[attr-defined] if sys.version_info < (3, 9): diff --git a/pydantic/utils.py b/pydantic/utils.py index 1d016c0..88a9a21 100644 --- a/pydantic/utils.py +++ b/pydantic/utils.py @@ -1,5 +1,4 @@ import keyword -import warnings import weakref from collections import OrderedDict, defaultdict, deque from copy import deepcopy @@ -139,23 +138,6 @@ def import_string(dotted_path: str) -> Any: raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e -def truncate(v: Union[str], *, max_len: int = 80) -> str: - """ - Truncate a value and add a unicode ellipsis (three dots) to the end if it was too long - """ - warnings.warn('`truncate` is no-longer used by pydantic and is deprecated', DeprecationWarning) - if isinstance(v, str) and len(v) > (max_len - 2): - # -3 so quote + string + … + quote has correct length - return (v[: (max_len - 3)] + '…').__repr__() - try: - v = v.__repr__() - except TypeError: - v = v.__class__.__repr__(v) # in case v is a type - if len(v) > max_len: - v = v[: max_len - 1] + '…' - return v - - def sequence_like(v: Any) -> bool: return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) diff --git a/pydantic/version.py b/pydantic/version.py index 32c6163..8fc811d 100644 --- a/pydantic/version.py +++ b/pydantic/version.py @@ -1,16 +1,6 @@ -__all__ = 'compiled', 'VERSION', 'version_info' +__all__ = 'VERSION', 'version_info' -VERSION = '1.10.2' - -try: - import cython # type: ignore -except ImportError: - compiled: bool = False -else: # pragma: no cover - try: - compiled = cython.compiled - except AttributeError: - compiled = False +VERSION = '2.0.0.dev0' def version_info() -> str: @@ -29,7 +19,6 @@ def version_info() -> str: info = { 'pydantic version': VERSION, - 'pydantic compiled': compiled, 'install path': Path(__file__).resolve().parent, 'python version': sys.version, 'platform': platform.platform(), diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5e3a511 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,143 @@ +[build-system] +requires = ['hatchling'] +build-backend = 'hatchling.build' + +[tool.hatch.version] +path = 'pydantic/version.py' + +[project] +name = 'pydantic' +description = 'Data validation using Python type hints' +authors = [ + {name = 'Samuel Colvin', email = 's@muelcolvin.com'}, + {name = 'Eric Jolibois', email = 'em.jolibois@gmail.com'}, + {name = 'Hasan Ramezani', email = 'hasan.r67@gmail.com'}, +] +license = {file = 'LICENSE'} +readme = 'README.md' +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: MIT License', + 'Operating System :: Unix', + 'Operating System :: POSIX :: Linux', + 'Environment :: Console', + 'Environment :: MacOS X', + 'Framework :: Hypothesis', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Internet', +] +requires-python = '>=3.7' +dependencies = [ 'typing-extensions>=4.1.0' ] +optional-dependencies = { email = ['email-validator>=1.0.3'], dotenv = ['python-dotenv>=0.10.4'] } +dynamic = ['version'] + +entry-points.hypothesis = {_ = 'pydantic._hypothesis_plugin'} + +[project.urls] +Homepage = 'https://github.com/pydantic/pydantic' +Documentation = 'https://pydantic-docs.helpmanual.io' +Funding = 'https://github.com/sponsors/samuelcolvin' +Source = 'https://github.com/pydantic/pydantic' +Changelog = 'https://pydantic-docs.helpmanual.io/changelog' + +[tool.pytest.ini_options] +testpaths = 'tests' +filterwarnings = [ + 'error', + 'ignore:path is deprecated.*:DeprecationWarning:certifi', +] + +[tool.flake8] +max_line_length = 120 +max_complexity = 14 +inline_quotes = 'single' +multiline_quotes = 'double' +ignore = ['E203', 'W503'] +per_file_ignores = [ + 'docs/examples/schema_unenforced_constraints.py:F811', + 'docs/examples/validation_decorator_async.py:E402', + 'docs/examples/types_constrained.py:F722', +] + +[tool.coverage.run] +source = ['pydantic'] +branch = true +context = '${CONTEXT}' + +[tool.coverage.report] +precision = 2 +exclude_lines = [ + 'pragma: no cover', + 'raise NotImplementedError', + 'raise NotImplemented', + 'if TYPE_CHECKING:', + '@overload', +] + + +[tool.coverage.paths] +source = [ + 'pydantic/', + '/Users/runner/work/pydantic/pydantic/pydantic/', + 'D:\a\pydantic\pydantic\pydantic', +] + +[tool.black] +color = true +line-length = 120 +target-version = ['py310'] +skip-string-normalization = true + +[tool.isort] +line_length = 120 +known_first_party = 'pydantic' +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +combine_as_imports = true + +[tool.mypy] +python_version = '3.10' +show_error_codes = true +follow_imports = 'silent' +strict_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +disallow_any_generics = true +check_untyped_defs = true +no_implicit_reexport = true +warn_unused_configs = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +disallow_untyped_calls = true + +# for strict mypy: (this is the tricky one :-)) +disallow_untyped_defs = true + +# remaining arguments from `mypy --strict` which cause errors +# no_implicit_optional = true +# warn_return_any = true + +# ansi2html and devtools are required to avoid the need to install these packages when running linting, +# they're used in the docs build script +[[tool.mypy.overrides]] +module = [ + 'email_validator.*', + 'dotenv.*', + 'toml.*', + 'ansi2html.*', + 'devtools.*', +] +ignore_missing_imports = true diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 88fbe69..0000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# requirements for compilation and from setup.py so dependabot prompts us to test with latest version of these packages - -Cython==0.29.32;sys_platform!='win32' -devtools==0.9.0 -email-validator==1.2.1 -dataclasses==0.6; python_version < '3.7' -typing-extensions==4.3.0 -python-dotenv==0.20.0 diff --git a/requirements/all.txt b/requirements/all.txt new file mode 100644 index 0000000..d1418b9 --- /dev/null +++ b/requirements/all.txt @@ -0,0 +1,5 @@ +-r ./pyproject-all.txt +-r ./docs.txt +-r ./linting.txt +-r ./testing.txt +-r ./testing-extra.txt diff --git a/requirements/docs.in b/requirements/docs.in new file mode 100644 index 0000000..67bc8ab --- /dev/null +++ b/requirements/docs.in @@ -0,0 +1,16 @@ +autoflake +ansi2html +devtools +flake8 +flake8-quotes +flake8-pyproject +hypothesis +markdown-include +mdx-truly-sane-lists +mkdocs +mkdocs-exclude +mkdocs-material +pyupgrade +orjson +sqlalchemy +ujson diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 0000000..5d0488c --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,119 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=requirements/docs.txt requirements/docs.in +# +ansi2html==1.8.0 + # via -r requirements/docs.in +asttokens==2.0.8 + # via devtools +attrs==22.1.0 + # via hypothesis +autoflake==1.5.3 + # via -r requirements/docs.in +click==8.1.3 + # via mkdocs +devtools==0.9.0 + # via -r requirements/docs.in +exceptiongroup==1.0.0rc9 + # via hypothesis +executing==0.10.0 + # via devtools +flake8==5.0.4 + # via + # -r requirements/docs.in + # flake8-pyproject + # flake8-quotes +flake8-pyproject==1.1.0.post0 + # via -r requirements/docs.in +flake8-quotes==3.3.1 + # via -r requirements/docs.in +ghp-import==2.1.0 + # via mkdocs +greenlet==1.1.3 + # via sqlalchemy +hypothesis==6.54.4 + # via -r requirements/docs.in +importlib-metadata==4.12.0 + # via mkdocs +jinja2==3.1.2 + # via + # mkdocs + # mkdocs-material +markdown==3.3.7 + # via + # markdown-include + # mdx-truly-sane-lists + # mkdocs + # mkdocs-material + # pymdown-extensions +markdown-include==0.7.0 + # via -r requirements/docs.in +markupsafe==2.1.1 + # via jinja2 +mccabe==0.7.0 + # via flake8 +mdx-truly-sane-lists==1.3 + # via -r requirements/docs.in +mergedeep==1.3.4 + # via mkdocs +mkdocs==1.3.1 + # via + # -r requirements/docs.in + # mkdocs-exclude + # mkdocs-material +mkdocs-exclude==1.0.2 + # via -r requirements/docs.in +mkdocs-material==8.4.2 + # via -r requirements/docs.in +mkdocs-material-extensions==1.0.3 + # via mkdocs-material +orjson==3.8.0 + # via -r requirements/docs.in +packaging==21.3 + # via mkdocs +pycodestyle==2.9.1 + # via flake8 +pyflakes==2.5.0 + # via + # autoflake + # flake8 +pygments==2.13.0 + # via mkdocs-material +pymdown-extensions==9.5 + # via mkdocs-material +pyparsing==3.0.9 + # via packaging +python-dateutil==2.8.2 + # via ghp-import +pyupgrade==2.37.3 + # via -r requirements/docs.in +pyyaml==6.0 + # via + # mkdocs + # pyyaml-env-tag +pyyaml-env-tag==0.1 + # via mkdocs +six==1.16.0 + # via + # asttokens + # python-dateutil +sortedcontainers==2.4.0 + # via hypothesis +sqlalchemy==1.4.40 + # via -r requirements/docs.in +tokenize-rt==4.2.1 + # via pyupgrade +toml==0.10.2 + # via autoflake +tomli==2.0.1 + # via flake8-pyproject +typing-extensions==4.3.0 + # via -r requirements/docs.in +ujson==5.4.0 + # via -r requirements/docs.in +watchdog==2.1.9 + # via mkdocs +zipp==3.8.1 + # via importlib-metadata diff --git a/requirements/linting.in b/requirements/linting.in new file mode 100644 index 0000000..5618e18 --- /dev/null +++ b/requirements/linting.in @@ -0,0 +1,9 @@ +black +flake8 +flake8-quotes +flake8-pyproject +hypothesis +isort +pyupgrade +mypy +pre-commit diff --git a/requirements/linting.txt b/requirements/linting.txt new file mode 100644 index 0000000..d5cc3f2 --- /dev/null +++ b/requirements/linting.txt @@ -0,0 +1,79 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=requirements/linting.txt requirements/linting.in +# +attrs==22.1.0 + # via hypothesis +black==22.8.0 + # via -r requirements/linting.in +cfgv==3.3.1 + # via pre-commit +click==8.1.3 + # via black +distlib==0.3.6 + # via virtualenv +exceptiongroup==1.0.0rc9 + # via hypothesis +filelock==3.8.0 + # via virtualenv +flake8==5.0.4 + # via + # -r requirements/linting.in + # flake8-pyproject + # flake8-quotes +flake8-pyproject==1.1.0.post0 + # via -r requirements/linting.in +flake8-quotes==3.3.1 + # via -r requirements/linting.in +hypothesis==6.54.4 + # via -r requirements/linting.in +identify==2.5.3 + # via pre-commit +isort==5.10.1 + # via -r requirements/linting.in +mccabe==0.7.0 + # via flake8 +mypy==0.971 + # via -r requirements/linting.in +mypy-extensions==0.4.3 + # via + # black + # mypy +nodeenv==1.7.0 + # via pre-commit +pathspec==0.10.1 + # via black +platformdirs==2.5.2 + # via + # black + # virtualenv +pre-commit==2.20.0 + # via -r requirements/linting.in +pycodestyle==2.9.1 + # via flake8 +pyflakes==2.5.0 + # via flake8 +pyupgrade==2.37.3 + # via -r requirements/linting.in +pyyaml==6.0 + # via pre-commit +sortedcontainers==2.4.0 + # via hypothesis +tokenize-rt==4.2.1 + # via pyupgrade +toml==0.10.2 + # via pre-commit +tomli==2.0.1 + # via + # black + # flake8-pyproject + # mypy +typing-extensions==4.3.0 + # via mypy +virtualenv==20.16.4 + # via pre-commit + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/pyproject-all.txt b/requirements/pyproject-all.txt new file mode 100644 index 0000000..5e35901 --- /dev/null +++ b/requirements/pyproject-all.txt @@ -0,0 +1,16 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --extra=email,dotenv --output-file=requirements/pyproject-all.txt pyproject.toml +# +dnspython==2.2.1 + # via email-validator +email-validator==1.2.1 + # via pydantic (pyproject.toml) +idna==3.3 + # via email-validator +python-dotenv==0.20.0 + # via pydantic (pyproject.toml) +typing-extensions==4.3.0 + # via pydantic (pyproject.toml) diff --git a/requirements/pyproject-min.txt b/requirements/pyproject-min.txt new file mode 100644 index 0000000..c31dc42 --- /dev/null +++ b/requirements/pyproject-min.txt @@ -0,0 +1,8 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=requirements/pyproject-min.txt pyproject.toml +# +typing-extensions==4.3.0 + # via pydantic (pyproject.toml) diff --git a/requirements/testing-extra.in b/requirements/testing-extra.in new file mode 100644 index 0000000..598f936 --- /dev/null +++ b/requirements/testing-extra.in @@ -0,0 +1,4 @@ +devtools +hypothesis +mypy +typed-ast;python_version<'3.8' diff --git a/requirements/testing-extra.txt b/requirements/testing-extra.txt new file mode 100644 index 0000000..2503fdd --- /dev/null +++ b/requirements/testing-extra.txt @@ -0,0 +1,34 @@ +# +# This file is autogenerated by pip-compile with python 3.7 +# To update, run: +# +# pip-compile --output-file=requirements/testing-extra.txt requirements/testing-extra.in +# +asttokens==2.0.8 + # via devtools +attrs==22.1.0 + # via hypothesis +devtools==0.9.0 + # via -r requirements/testing-extra.in +exceptiongroup==1.0.0rc9 + # via hypothesis +executing==0.10.0 + # via devtools +hypothesis==6.54.4 + # via -r requirements/testing-extra.in +mypy==0.971 + # via -r requirements/testing-extra.in +mypy-extensions==0.4.3 + # via mypy +six==1.16.0 + # via asttokens +sortedcontainers==2.4.0 + # via hypothesis +tomli==2.0.1 + # via mypy +typed-ast==1.5.4 ; python_version < "3.8" + # via + # -r requirements/testing-extra.in + # mypy +typing-extensions==4.3.0 + # via mypy diff --git a/requirements/testing.in b/requirements/testing.in new file mode 100644 index 0000000..b7f150a --- /dev/null +++ b/requirements/testing.in @@ -0,0 +1,4 @@ +coverage[toml] +pytest +pytest-mock +pytest-sugar diff --git a/requirements/testing.txt b/requirements/testing.txt new file mode 100644 index 0000000..f76477d --- /dev/null +++ b/requirements/testing.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with python 3.7 +# To update, run: +# +# pip-compile --output-file=requirements/testing.txt requirements/testing.in +# +attrs==22.1.0 + # via pytest +coverage[toml]==6.4.4 + # via -r requirements/testing.in +importlib-metadata==4.12.0 + # via + # pluggy + # pytest +iniconfig==1.1.1 + # via pytest +packaging==21.3 + # via + # pytest + # pytest-sugar +pluggy==1.0.0 + # via pytest +py==1.11.0 + # via pytest +pyparsing==3.0.9 + # via packaging +pytest==7.1.3 + # via + # -r requirements/testing.in + # pytest-mock + # pytest-sugar +pytest-mock==3.8.2 + # via -r requirements/testing.in +pytest-sugar==0.9.5 + # via -r requirements/testing.in +termcolor==1.1.0 + # via pytest-sugar +tomli==2.0.1 + # via + # coverage + # pytest +typing-extensions==4.3.0 + # via importlib-metadata +zipp==3.8.1 + # via importlib-metadata diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 505479b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,92 +0,0 @@ -[tool:pytest] -testpaths = tests -addopts = -p no:hypothesispytest -filterwarnings = - error - ignore::DeprecationWarning:distutils - ignore::DeprecationWarning:Cython - # for Python 3.10+: mypy still relies on distutils on windows. We hence ignore those warnings - ignore:The distutils package is deprecated and slated for removal in Python 3.12:DeprecationWarning - ignore:The distutils.sysconfig module is deprecated, use sysconfig instead:DeprecationWarning - # for Python 3.11 - ignore:path is deprecated.*:DeprecationWarning:certifi - ignore:module 'sre_constants' is deprecated:DeprecationWarning:pkg_resources - -[flake8] -max-line-length = 120 -max-complexity = 14 -inline-quotes = single -multiline-quotes = double -ignore = E203, W503 -per-file-ignores = - docs/examples/schema_unenforced_constraints.py: F811 - docs/examples/validation_decorator_async.py: E402 - docs/examples/types_constrained.py: F722 - -[coverage:run] -source = pydantic -branch = True -context = ${CONTEXT} - -[coverage:report] -precision = 2 -exclude_lines = - pragma: no cover - raise NotImplementedError - raise NotImplemented - if TYPE_CHECKING: - @overload - -[coverage:paths] -source = - pydantic/ - /Users/runner/work/pydantic/pydantic/pydantic/ - D:\a\pydantic\pydantic\pydantic - -[isort] -line_length=120 -known_first_party=pydantic -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -combine_as_imports=True - -[mypy] -python_version = 3.9 -show_error_codes = True -follow_imports = silent -strict_optional = True -warn_redundant_casts = True -warn_unused_ignores = True -disallow_any_generics = True -check_untyped_defs = True -no_implicit_reexport = True -warn_unused_configs = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -disallow_untyped_calls = True - -# for strict mypy: (this is the tricky one :-)) -disallow_untyped_defs = True - -# remaining arguments from `mypy --strict` which cause errors -;no_implicit_optional = True -;warn_return_any = True - -[mypy-email_validator] -ignore_missing_imports = true - -[mypy-dotenv] -ignore_missing_imports = true - -[mypy-toml] -ignore_missing_imports = true - -# ansi2html and devtools are required to avoid the need to install these packages when running linting, -# they're used in the docs build script -[mypy-ansi2html] -ignore_missing_imports = true - -[mypy-devtools] -ignore_missing_imports = true diff --git a/setup.py b/setup.py index d677cd7..5d578b7 100644 --- a/setup.py +++ b/setup.py @@ -1,141 +1,26 @@ -import os -import re import sys -from importlib.machinery import SourceFileLoader -from pathlib import Path -from setuptools import setup - -if os.name == 'nt': - from setuptools.command import build_ext - - def get_export_symbols(self, ext): - """ - Slightly modified from: - https://github.com/python/cpython/blob/8849e5962ba481d5d414b3467a256aba2134b4da\ - /Lib/distutils/command/build_ext.py#L686-L703 - """ - # Patch from: https://bugs.python.org/issue35893 - parts = ext.name.split('.') - if parts[-1] == '__init__': - suffix = parts[-2] - else: - suffix = parts[-1] - - # from here on unchanged - try: - # Unicode module name support as defined in PEP-489 - # https://www.python.org/dev/peps/pep-0489/#export-hook-name - suffix.encode('ascii') - except UnicodeEncodeError: - suffix = 'U' + suffix.encode('punycode').replace(b'-', b'_').decode('ascii') - - initfunc_name = 'PyInit_' + suffix - if initfunc_name not in ext.export_symbols: - ext.export_symbols.append(initfunc_name) - return ext.export_symbols - - build_ext.build_ext.get_export_symbols = get_export_symbols +sys.stderr.write( + """ +=============================== +Unsupported installation method +=============================== +pydantic no longer supports installation with `python setup.py install`. +Please use `python -m pip install .` instead. +""" +) +sys.exit(1) -class ReplaceLinks: - def __init__(self): - self.links = set() - - def replace_issues(self, m): - id = m.group(1) - self.links.add(f'.. _#{id}: https://github.com/pydantic/pydantic/issues/{id}') - return f'`#{id}`_' - - def replace_users(self, m): - name = m.group(2) - self.links.add(f'.. _@{name}: https://github.com/{name}') - return f'{m.group(1)}`@{name}`_' - - def extra(self): - return '\n\n' + '\n'.join(sorted(self.links)) + '\n' - - -description = 'Data validation and settings management using python type hints' -THIS_DIR = Path(__file__).resolve().parent -try: - history = (THIS_DIR / 'HISTORY.md').read_text(encoding='utf-8') - history = re.sub(r'#(\d+)', r'[#\1](https://github.com/pydantic/pydantic/issues/\1)', history) - history = re.sub(r'( +)@([\w\-]+)', r'\1[@\2](https://github.com/\2)', history, flags=re.I) - history = re.sub('@@', '@', history) - - long_description = (THIS_DIR / 'README.md').read_text(encoding='utf-8') + '\n\n' + history -except FileNotFoundError: - long_description = description + '.\n\nSee https://pydantic-docs.helpmanual.io/ for documentation.' - -# avoid loading the package before requirements are installed: -version = SourceFileLoader('version', 'pydantic/version.py').load_module() - -ext_modules = None -if not any(arg in sys.argv for arg in ['clean', 'check']) and 'SKIP_CYTHON' not in os.environ: - try: - from Cython.Build import cythonize - except ImportError: - pass - else: - # For cython test coverage install with `make build-trace` - compiler_directives = {} - if 'CYTHON_TRACE' in sys.argv: - compiler_directives['linetrace'] = True - # Set CFLAG to all optimizations (-O3) - # Any additional CFLAGS will be appended. Only the last optimization flag will have effect - os.environ['CFLAGS'] = '-O3 ' + os.environ.get('CFLAGS', '') - ext_modules = cythonize( - 'pydantic/*.py', - exclude=['pydantic/generics.py'], - nthreads=int(os.getenv('CYTHON_NTHREADS', 0)), - language_level=3, - compiler_directives=compiler_directives, - ) +# The below code will never execute, however GitHub is particularly +# picky about where it finds Python packaging metadata. +# See: https://github.com/github/feedback/discussions/6456 +# +# To be removed once GitHub catches up. setup( name='pydantic', - version=str(version.VERSION), - description=description, - long_description=long_description, - long_description_content_type='text/markdown', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: MIT License', - 'Operating System :: Unix', - 'Operating System :: POSIX :: Linux', - 'Environment :: Console', - 'Environment :: MacOS X', - 'Framework :: Hypothesis', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Internet', - ], - author='Samuel Colvin', - author_email='s@muelcolvin.com', - url='https://github.com/pydantic/pydantic', - license='MIT', - packages=['pydantic'], - package_data={'pydantic': ['py.typed']}, - python_requires='>=3.7', - zip_safe=False, # https://mypy.readthedocs.io/en/latest/installed_packages.html install_requires=[ 'typing-extensions>=4.1.0' ], - extras_require={ - 'email': ['email-validator>=1.0.3'], - 'dotenv': ['python-dotenv>=0.10.4'], - }, - ext_modules=ext_modules, - entry_points={'hypothesis': ['_ = pydantic._hypothesis_plugin']}, ) diff --git a/tests/conftest.py b/tests/conftest.py index dafdf2a..0d1c6c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,14 +9,6 @@ from types import FunctionType import pytest from _pytest.assertion.rewrite import AssertionRewritingHook -# See https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov -try: - from hypothesis import given # noqa -except ImportError: - pytest_plugins = [] -else: - pytest_plugins = ['hypothesis.extra.pytestplugin'] - def _extract_source_code_from_function(function): if function.__code__.co_argcount: diff --git a/tests/mypy/outputs/fail1.txt b/tests/mypy/outputs/fail1.txt index edeb7d7..68784bb 100644 --- a/tests/mypy/outputs/fail1.txt +++ b/tests/mypy/outputs/fail1.txt @@ -1,2 +1,2 @@ 22: error: Unsupported operand types for + ("int" and "str") [operator] -23: error: Unsupported operand types for + ("int" and "str") [operator] \ No newline at end of file +23: error: Unsupported operand types for + ("int" and "str") [operator] diff --git a/tests/mypy/outputs/fail2.txt b/tests/mypy/outputs/fail2.txt index 1f08383..cfd2c15 100644 --- a/tests/mypy/outputs/fail2.txt +++ b/tests/mypy/outputs/fail2.txt @@ -1 +1 @@ -20: error: "Model" has no attribute "foobar" [attr-defined] \ No newline at end of file +20: error: "Model" has no attribute "foobar" [attr-defined] diff --git a/tests/mypy/outputs/fail3.txt b/tests/mypy/outputs/fail3.txt index 09ed7e6..5516d20 100644 --- a/tests/mypy/outputs/fail3.txt +++ b/tests/mypy/outputs/fail3.txt @@ -1 +1 @@ -22: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int" [arg-type] \ No newline at end of file +22: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int" [arg-type] diff --git a/tests/mypy/outputs/fail4.txt b/tests/mypy/outputs/fail4.txt index 2e64158..ce9effd 100644 --- a/tests/mypy/outputs/fail4.txt +++ b/tests/mypy/outputs/fail4.txt @@ -5,4 +5,4 @@ 14: error: Argument 2 to "foo" has incompatible type "int"; expected "str" [arg-type] 15: error: Unexpected keyword argument "d" for "foo" [call-arg] 17: error: "Callable[[int, DefaultNamedArg(str, 'c')], str]" has no attribute "raw_function" [attr-defined] -26: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] \ No newline at end of file +26: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] diff --git a/tests/mypy/test_mypy.py b/tests/mypy/test_mypy.py index fb1a2bd..d12c873 100644 --- a/tests/mypy/test_mypy.py +++ b/tests/mypy/test_mypy.py @@ -1,6 +1,7 @@ import importlib import os import re +import sys from pathlib import Path import pytest @@ -16,10 +17,7 @@ except ImportError: parse_mypy_version = lambda _: (0,) # noqa: E731 -try: - import dotenv -except ImportError: - dotenv = None +pytestmark = pytest.mark.skipif(sys.platform != 'linux' and 'CI' in os.environ, reason='only run on linux when on CI') # This ensures mypy can find the test files, no matter where tests are run from: os.chdir(Path(__file__).parent.parent.parent) @@ -54,7 +52,7 @@ cases = [ executable_modules = list({fname[:-3] for _, fname, out_fname in cases if out_fname is None}) -@pytest.mark.skipif(not (dotenv and mypy_api), reason='dotenv or mypy are not installed') +@pytest.mark.skipif(not mypy_api, reason='mypy is not installed') @pytest.mark.parametrize('config_filename,python_filename,output_filename', cases) def test_mypy_results(config_filename: str, python_filename: str, output_filename: str) -> None: full_config_filename = f'tests/mypy/configs/{config_filename}' @@ -96,7 +94,7 @@ def test_mypy_results(config_filename: str, python_filename: str, output_filenam assert actual_out == expected_out, actual_out -@pytest.mark.skipif(not (dotenv and mypy_api), reason='dotenv or mypy are not installed') +@pytest.mark.skipif(not mypy_api, reason='mypy is not installed') def test_bad_toml_config() -> None: full_config_filename = 'tests/mypy/configs/pyproject-plugin-bad-param.toml' full_filename = 'tests/mypy/modules/success.py' diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt deleted file mode 100644 index 9b250c5..0000000 --- a/tests/requirements-linting.txt +++ /dev/null @@ -1,11 +0,0 @@ -black==22.8.0 -flake8==5.0.4 -flake8-quotes==3.3.1 -hypothesis==6.54.4 -isort==5.10.1 -pyupgrade==2.37.3 -mypy==0.971 -pre-commit==2.20.0 -pycodestyle==2.9.1 -pyflakes==2.5.0 -twine==4.0.1 diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt deleted file mode 100644 index f027620..0000000 --- a/tests/requirements-testing.txt +++ /dev/null @@ -1,9 +0,0 @@ -coverage==6.4.4 -hypothesis==6.54.4 -# pin importlib-metadata as upper versions need typing-extensions to work if on Python < 3.8 -importlib-metadata==3.1.0;python_version<"3.8" -mypy==0.971 -pytest==7.1.2 -pytest-cov==3.0.0 -pytest-mock==3.8.2 -pytest-sugar==0.9.5 diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 799dd0b..8c5e8c3 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -14,7 +14,6 @@ from pydantic import ( NoneStrBytes, StrBytes, ValidationError, - compiled, constr, errors, validate_model, @@ -22,11 +21,6 @@ from pydantic import ( ) from pydantic.fields import Field -try: - import cython -except ImportError: - cython = None - def test_str_bytes(): class Model(BaseModel): @@ -244,9 +238,7 @@ def test_tuple_more(): (dict, frozenset, list, set, tuple, type), ], ) -@pytest.mark.skipif( - sys.version_info < (3, 9) or compiled, reason='PEP585 generics only supported for python 3.9 and above' -) +@pytest.mark.skipif(sys.version_info < (3, 9), reason='PEP585 generics only supported for python 3.9 and above') def test_pep585_generic_types(dict_cls, frozenset_cls, list_cls, set_cls, tuple_cls, type_cls): class Type1: pass @@ -1874,29 +1866,6 @@ def test_default_factory_validator_child(): assert Child(foo=['a', 'b']).foo == ['a-1', 'b-1'] -@pytest.mark.skipif(cython is None, reason='cython not installed') -def test_cython_function_untouched(): - Model = cython.inline( - # language=Python - """ -from pydantic import BaseModel - -class Model(BaseModel): - a = 0.0 - b = 10 - - def get_double_a(self) -> float: - return self.a + self.b - -return Model -""" - ) - model = Model(a=10.2) - assert model.a == 10.2 - assert model.b == 10 - return model.get_double_a() == 20.2 - - def test_resolve_annotations_module_missing(tmp_path): # see https://github.com/pydantic/pydantic/issues/2363 file_path = tmp_path / 'module_to_load.py' diff --git a/tests/test_utils.py b/tests/test_utils.py index 0fbf007..285b8af 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,17 +1,14 @@ import collections.abc import os import pickle -import re -import string import sys from copy import copy, deepcopy from typing import Callable, Dict, ForwardRef, List, NewType, Tuple, TypeVar, Union import pytest -from pkg_resources import safe_version from typing_extensions import Annotated, Literal -from pydantic import VERSION, BaseModel, ConstrainedList, conlist +from pydantic import BaseModel, ConstrainedList, conlist from pydantic.color import Color from pydantic.dataclasses import dataclass from pydantic.fields import Undefined @@ -37,10 +34,8 @@ from pydantic.utils import ( path_type, smart_deepcopy, to_lower_camel, - truncate, unique_list, ) -from pydantic.version import version_info try: import devtools @@ -95,19 +90,6 @@ def test_lenient_issubclass_is_lenient(): assert lenient_issubclass('a', 'a') is False -@pytest.mark.parametrize( - 'input_value,output', - [ - (object, ""), - (string.ascii_lowercase, "'abcdefghijklmnopq…'"), - (list(range(20)), '[0, 1, 2, 3, 4, 5, …'), - ], -) -def test_truncate(input_value, output): - with pytest.warns(DeprecationWarning, match='`truncate` is no-longer used by pydantic and is deprecated'): - assert truncate(input_value, max_len=20) == output - - @pytest.mark.parametrize( 'input_value,output', [ @@ -372,16 +354,6 @@ def test_get_model(): get_model(C) -def test_version_info(): - s = version_info() - assert re.match(' *pydantic version: ', s) - assert s.count('\n') == 5 - - -def test_standard_version(): - assert safe_version(VERSION) == VERSION - - def test_class_attribute(): class Foo: attr = ClassAttribute('attr', 'foo') diff --git a/tests/test_version.py b/tests/test_version.py index ef7c928..e03530f 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,4 +1,20 @@ +import re + +from packaging.version import parse as parse_version + import pydantic +from pydantic.version import version_info + + +def test_version_info(): + s = version_info() + assert re.match(' *pydantic version: ', s) + assert s.count('\n') == 4 + + +def test_standard_version(): + v = parse_version(pydantic.VERSION) + assert str(v) == pydantic.VERSION def test_version_attribute_is_present():