Repository: samuelcolvin/pydantic
Pull request #3715 (2022-01-21 09:34:33)

Add Robusta.dev to list of Pydantic users
If possible, we would be honored to be listed on the homepage. We're really excited about how we're utilizing Pydantic to automate Kubernetes troubleshooting. Pydantic makes formerly tedious code simple and elegant.


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3acffa71c..7a42e0820 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,7 +4,7 @@ updates:
 - package-ecosystem: pip
   directory: /
   schedule:
-    interval: weekly
+    interval: monthly
 
 - package-ecosystem: github-actions
   directory: /
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cc81d4bfc..595c863d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,10 +13,10 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
       - name: set up python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.9
 
@@ -40,10 +40,10 @@ jobs:
   docs-build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v3
       with:
         python-version: 3.8
 
@@ -71,10 +71,10 @@ jobs:
       OS: ubuntu
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -140,10 +140,10 @@ jobs:
 
     runs-on: ${{ matrix.os }}-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -173,10 +173,10 @@ jobs:
         mypy-version: ['0.910', '0.920', '0.921']
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: '3.10'
 
@@ -210,9 +210,9 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
-      - uses: actions/setup-python@v1
+      - uses: actions/setup-python@v3
         with:
           python-version: '3.8'
 
@@ -239,13 +239,16 @@ jobs:
     name: test fastAPI
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: '3.10'
 
+    - name: upgrade pip
+      run: pip install -U pip setuptools wheel
+
     - name: install
       run: make install-testing
 
@@ -259,10 +262,10 @@ jobs:
       BENCHMARK_REPEATS: 1
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: '3.8'
 
@@ -292,10 +295,10 @@ jobs:
 
     runs-on: ${{ matrix.os }}-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: set up python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v3
       with:
         python-version: '3.8'
 
@@ -343,10 +346,10 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
       - name: set up python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v3
         with:
           python-version: '3.8'
 
diff --git a/.github/workflows/combine-dependabot.yml b/.github/workflows/combine-dependabot.yml
new file mode 100644
index 000000000..1b141a75f
--- /dev/null
+++ b/.github/workflows/combine-dependabot.yml
@@ -0,0 +1,137 @@
+# 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/.github/workflows/upload-previews.yml b/.github/workflows/upload-previews.yml
index 5325c4939..0bec37843 100644
--- a/.github/workflows/upload-previews.yml
+++ b/.github/workflows/upload-previews.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/setup-python@v1
+      - uses: actions/setup-python@v3
         with:
           python-version: '3.8'
 
diff --git a/.gitignore b/.gitignore
index e0c46182d..55c223952 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,7 @@
 .idea/
 env/
 venv/
-env36/
-env37/
-env38/
-env39/
+env3*/
 Pipfile
 *.lock
 *.py[cod]
diff --git a/changes/3625-read_text-encoding-utf-8.md b/changes/3625-read_text-encoding-utf-8.md
new file mode 100644
index 000000000..a9365e520
--- /dev/null
+++ b/changes/3625-read_text-encoding-utf-8.md
@@ -0,0 +1,1 @@
+Add `read_text(encoding='utf-8')` for `setup.py`
diff --git a/changes/3636-tommilligan.md b/changes/3636-tommilligan.md
new file mode 100644
index 000000000..ec10fce7e
--- /dev/null
+++ b/changes/3636-tommilligan.md
@@ -0,0 +1,1 @@
+fix: clarify that discriminated unions do not support singletons
diff --git a/docs/index.md b/docs/index.md
index 772a6ea99..f06b55583 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -114,6 +114,11 @@ Hundreds of organisations and packages are using *pydantic*, including:
 : trusts *pydantic* (via FastAPI) and [*arq*](https://github.com/samuelcolvin/arq) (Samuel's excellent
   asynchronous task queue) to reliably power multiple mission-critical microservices.
 
+[Robusta.dev](https://robusta.dev/)
+: are using *pydantic* to automate Kubernetes troubleshooting and maintenance. For example, their open source
+  [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 
 [list of dependents on github](https://github.com/samuelcolvin/pydantic/network/dependents).
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 88f06ea3f..8c28433d5 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,12 +1,12 @@
 ansi2html==1.6.0
 flake8==4.0.1
 flake8-quotes==3.3.1
-hypothesis==6.31.6
+hypothesis==6.41.0
 markdown-include==0.6.0
 mdx-truly-sane-lists==1.2
-mkdocs==1.2.3
+mkdocs==1.3.0
 mkdocs-exclude==1.0.2
-mkdocs-material==8.1.3
+mkdocs-material==8.2.8
 sqlalchemy
 orjson
 ujson
diff --git a/docs/usage/settings.md b/docs/usage/settings.md
index 1de49c0a5..3ab582139 100644
--- a/docs/usage/settings.md
+++ b/docs/usage/settings.md
@@ -71,7 +71,7 @@ by treating the environment variable's value as a JSON-encoded string.
 
 Another way to populate nested complex variables is to configure your model with the `env_nested_delimiter`
 config setting, then use an env variable with a name pointing to the nested module fields.
-What it does is simply explodes yor variable into nested models or dicts.
+What it does is simply explodes your variable into nested models or dicts.
 So if you define a variable `FOO__BAR__BAZ=123` it will convert it into `FOO={'BAR': {'BAZ': 123}}`
 If you have multiple variables with the same structure they will be merged.
 
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 3fcd61d31..86a437ba6 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -297,6 +297,12 @@ _(This script is complete, it should run "as is")_
     Using the [Annotated Fields syntax](../schema/#typingannotated-fields) can be handy to regroup
     the `Union` and `discriminator` information. See below for an example!
 
+!!! warning
+    Discriminated unions cannot be used with only a single variant, such as `Union[Cat]`.
+
+    Python changes `Union[T]` into `T` at interpretation time, so it is not possible for `pydantic` to
+    distinguish fields of `Union[T]` from `T`.
+
 #### Nested Discriminated Unions
 
 Only one discriminator can be set for a field but sometimes you want to combine multiple discriminators.
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 6f1699032..e6736f023 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -600,7 +600,7 @@ class ModelField(Representation):
             return
 
         if self.discriminator_key is not None and not is_union(origin):
-            raise TypeError('`discriminator` can only be used with `Union` type')
+            raise TypeError('`discriminator` can only be used with `Union` type with more than one variant')
 
         # add extra check for `collections.abc.Hashable` for python 3.10+ where origin is not `None`
         if origin is None or origin is CollectionsHashable:
diff --git a/pydantic/json.py b/pydantic/json.py
index b732cc0a2..0769228e4 100644
--- a/pydantic/json.py
+++ b/pydantic/json.py
@@ -26,7 +26,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
 
     This is useful when we use ConstrainedDecimal to represent Numeric(x,0)
     where a integer (but not int typed) is used. Encoding this as a float
-    results in failed round-tripping between encode and prase.
+    results in failed round-tripping between encode and parse.
     Our Id type is a prime example of this.
 
     >>> decimal_encoder(Decimal("1.0"))
diff --git a/pydantic/networks.py b/pydantic/networks.py
index 18a042ca9..50a01d42b 100644
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -122,7 +122,7 @@ def int_domain_regex() -> Pattern[str]:
 class AnyUrl(str):
     strip_whitespace = True
     min_length = 1
-    max_length = 2 ** 16
+    max_length = 2**16
     allowed_schemes: Optional[Collection[str]] = None
     tld_required: bool = False
     user_required: bool = False
@@ -386,7 +386,7 @@ def stricturl(
     *,
     strip_whitespace: bool = True,
     min_length: int = 1,
-    max_length: int = 2 ** 16,
+    max_length: int = 2**16,
     tld_required: bool = True,
     host_required: bool = True,
     allowed_schemes: Optional[Collection[str]] = None,
diff --git a/pydantic/types.py b/pydantic/types.py
index e3d6c1277..2d0cc18f8 100644
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -1010,18 +1010,18 @@ class PaymentCardNumber(str):
 
 BYTE_SIZES = {
     'b': 1,
-    'kb': 10 ** 3,
-    'mb': 10 ** 6,
-    'gb': 10 ** 9,
-    'tb': 10 ** 12,
-    'pb': 10 ** 15,
-    'eb': 10 ** 18,
-    'kib': 2 ** 10,
-    'mib': 2 ** 20,
-    'gib': 2 ** 30,
-    'tib': 2 ** 40,
-    'pib': 2 ** 50,
-    'eib': 2 ** 60,
+    'kb': 10**3,
+    'mb': 10**6,
+    'gb': 10**9,
+    'tb': 10**12,
+    'pb': 10**15,
+    'eb': 10**18,
+    'kib': 2**10,
+    'mib': 2**20,
+    'gib': 2**30,
+    'tib': 2**40,
+    'pib': 2**50,
+    'eib': 2**60,
 }
 BYTE_SIZES.update({k.lower()[0]: v for k, v in BYTE_SIZES.items() if 'i' not in k})
 byte_string_re = re.compile(r'^\s*(\d*\.?\d+)\s*(\w+)?', re.IGNORECASE)
diff --git a/requirements.txt b/requirements.txt
index 95aef0852..3beeb0fdb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
 # requirements for compilation and from setup.py so dependabot prompts us to test with latest version of these packages
 
-Cython==0.29.26;sys_platform!='win32'
+Cython==0.29.28;sys_platform!='win32'
 devtools==0.8.0
 email-validator==1.1.3
 dataclasses==0.6; python_version < '3.7'
-typing-extensions==4.0.1
-python-dotenv==0.19.2
+typing-extensions==4.1.1
+python-dotenv==0.20.0
diff --git a/setup.py b/setup.py
index ae91cf9a0..c59056e0e 100644
--- a/setup.py
+++ b/setup.py
@@ -59,12 +59,12 @@ class ReplaceLinks:
 description = 'Data validation and settings management using python type hints'
 THIS_DIR = Path(__file__).resolve().parent
 try:
-    history = (THIS_DIR / 'HISTORY.md').read_text()
+    history = (THIS_DIR / 'HISTORY.md').read_text(encoding='utf-8')
     history = re.sub(r'#(\d+)', r'[#\1](https://github.com/samuelcolvin/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() + '\n\n' + 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.'
 


Test patch:
diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt
index 9a3f0464b..69487560e 100644
--- a/tests/requirements-linting.txt
+++ b/tests/requirements-linting.txt
@@ -1,10 +1,10 @@
-black==21.12b0
+black==22.3.0
 flake8==4.0.1
 flake8-quotes==3.3.1
-hypothesis==6.31.6
+hypothesis==6.41.0
 isort==5.10.1
-mypy==0.930
-pre-commit==2.16.0
+mypy==0.931
+pre-commit==2.17.0
 pycodestyle==2.8.0
 pyflakes==2.4.0
-twine==3.7.1
+twine==4.0.0
diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt
index 1ee135251..e4f808bf8 100644
--- a/tests/requirements-testing.txt
+++ b/tests/requirements-testing.txt
@@ -1,9 +1,9 @@
-coverage==6.2
-hypothesis==6.31.6
+coverage==6.3.2
+hypothesis==6.41.0
 # 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.930
-pytest==6.2.5
+mypy==0.931
+pytest==7.1.1
 pytest-cov==3.0.0
-pytest-mock==3.6.1
+pytest-mock==3.7.0
 pytest-sugar==0.9.4
diff --git a/tests/test_construction.py b/tests/test_construction.py
index 19e912b39..e7b12a0a7 100644
--- a/tests/test_construction.py
+++ b/tests/test_construction.py
@@ -63,8 +63,8 @@ def test_large_any_str():
         a: bytes
         b: str
 
-    content_bytes = b'x' * (2 ** 16 + 1)
-    content_str = 'x' * (2 ** 16 + 1)
+    content_bytes = b'x' * (2**16 + 1)
+    content_str = 'x' * (2**16 + 1)
     m = Model(a=content_bytes, b=content_str)
     assert m.a == content_bytes
     assert m.b == content_str
diff --git a/tests/test_discrimated_union.py b/tests/test_discrimated_union.py
index 120f9d2f1..a4dd501bc 100644
--- a/tests/test_discrimated_union.py
+++ b/tests/test_discrimated_union.py
@@ -11,12 +11,23 @@ from pydantic.generics import GenericModel
 
 
 def test_discriminated_union_only_union():
-    with pytest.raises(TypeError, match='`discriminator` can only be used with `Union` type'):
+    with pytest.raises(
+        TypeError, match='`discriminator` can only be used with `Union` type with more than one variant'
+    ):
 
         class Model(BaseModel):
             x: str = Field(..., discriminator='qwe')
 
 
+def test_discriminated_union_single_variant():
+    with pytest.raises(
+        TypeError, match='`discriminator` can only be used with `Union` type with more than one variant'
+    ):
+
+        class Model(BaseModel):
+            x: Union[str] = Field(..., discriminator='qwe')
+
+
 def test_discriminated_union_invalid_type():
     with pytest.raises(TypeError, match="Type 'str' is not a valid `BaseModel` or `dataclass`"):
 
diff --git a/tests/test_main.py b/tests/test_main.py
index 642b5610b..c00d70688 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -433,13 +433,13 @@ def test_with_declared_hash():
         x: int
 
         def __hash__(self):
-            return self.x ** 2
+            return self.x**2
 
     class Bar(Foo):
         y: int
 
         def __hash__(self):
-            return self.y ** 3
+            return self.y**3
 
     class Buz(Bar):
         z: int
diff --git a/tests/test_networks_ipaddress.py b/tests/test_networks_ipaddress.py
index cd1351b59..c73c51230 100644
--- a/tests/test_networks_ipaddress.py
+++ b/tests/test_networks_ipaddress.py
@@ -114,7 +114,7 @@ def test_ipv6address_success(value):
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 or IPv6 address', 'type': 'value_error.ipvanyaddress'}],
         ),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 or IPv6 address', 'type': 'value_error.ipvanyaddress'}],
         ),
     ],
@@ -141,7 +141,7 @@ def test_ipaddress_fails(value, errors):
         ),
         (-1, [{'loc': ('ipv4',), 'msg': 'value is not a valid IPv4 address', 'type': 'value_error.ipv4address'}]),
         (
-            2 ** 32 + 1,
+            2**32 + 1,
             [{'loc': ('ipv4',), 'msg': 'value is not a valid IPv4 address', 'type': 'value_error.ipv4address'}],
         ),
         (
@@ -172,7 +172,7 @@ def test_ipv4address_fails(value, errors):
         ),
         (-1, [{'loc': ('ipv6',), 'msg': 'value is not a valid IPv6 address', 'type': 'value_error.ipv6address'}]),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ipv6',), 'msg': 'value is not a valid IPv6 address', 'type': 'value_error.ipv6address'}],
         ),
         (
@@ -203,7 +203,7 @@ def test_ipv6address_fails(value, errors):
         ('192.168.0.0/24', IPv4Network),
         ('192.168.128.0/30', IPv4Network),
         ('2001:db00::0/120', IPv6Network),
-        (2 ** 32 - 1, IPv4Network),  # no mask equals to mask /32
+        (2**32 - 1, IPv4Network),  # no mask equals to mask /32
         (20_282_409_603_651_670_423_947_251_286_015, IPv6Network),  # /128
         (b'\xff\xff\xff\xff', IPv4Network),  # /32
         (b'\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', IPv6Network),
@@ -224,7 +224,7 @@ def test_ipnetwork_success(value, cls):
     [
         ('192.168.0.0/24', IPv4Network),
         ('192.168.128.0/30', IPv4Network),
-        (2 ** 32 - 1, IPv4Network),  # no mask equals to mask /32
+        (2**32 - 1, IPv4Network),  # no mask equals to mask /32
         (b'\xff\xff\xff\xff', IPv4Network),  # /32
         (('192.168.0.0', 24), IPv4Network),
         (IPv4Network('192.168.0.0/24'), IPv4Network),
@@ -270,7 +270,7 @@ def test_ip_v6_network_success(value, cls):
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 or IPv6 network', 'type': 'value_error.ipvanynetwork'}],
         ),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 or IPv6 network', 'type': 'value_error.ipvanynetwork'}],
         ),
     ],
@@ -297,7 +297,7 @@ def test_ipnetwork_fails(value, errors):
         ),
         (-1, [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 network', 'type': 'value_error.ipv4network'}]),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 network', 'type': 'value_error.ipv4network'}],
         ),
         (
@@ -328,7 +328,7 @@ def test_ip_v4_network_fails(value, errors):
         ),
         (-1, [{'loc': ('ip',), 'msg': 'value is not a valid IPv6 network', 'type': 'value_error.ipv6network'}]),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv6 network', 'type': 'value_error.ipv6network'}],
         ),
         (
@@ -362,8 +362,8 @@ def test_ip_v6_network_fails(value, errors):
         ('192.168.128.1/30', IPv4Interface),
         ('2001:db00::0/120', IPv6Interface),
         ('2001:db00::1/120', IPv6Interface),
-        (2 ** 32 - 1, IPv4Interface),  # no mask equals to mask /32
-        (2 ** 32 - 1, IPv4Interface),  # so ``strict`` has no effect
+        (2**32 - 1, IPv4Interface),  # no mask equals to mask /32
+        (2**32 - 1, IPv4Interface),  # so ``strict`` has no effect
         (20_282_409_603_651_670_423_947_251_286_015, IPv6Interface),  # /128
         (20_282_409_603_651_670_423_947_251_286_014, IPv6Interface),
         (b'\xff\xff\xff\xff', IPv4Interface),  # /32
@@ -394,8 +394,8 @@ def test_ipinterface_success(value, cls):
         ('192.168.0.1/24', IPv4Interface),
         ('192.168.128.0/30', IPv4Interface),
         ('192.168.128.1/30', IPv4Interface),
-        (2 ** 32 - 1, IPv4Interface),  # no mask equals to mask /32
-        (2 ** 32 - 1, IPv4Interface),  # so ``strict`` has no effect
+        (2**32 - 1, IPv4Interface),  # no mask equals to mask /32
+        (2**32 - 1, IPv4Interface),  # so ``strict`` has no effect
         (b'\xff\xff\xff\xff', IPv4Interface),  # /32
         (b'\xff\xff\xff\xff', IPv4Interface),
         (('192.168.0.0', 24), IPv4Interface),
@@ -467,7 +467,7 @@ def test_ip_v6_interface_success(value, cls):
             ],
         ),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [
                 {
                     'loc': ('ip',),
@@ -500,7 +500,7 @@ def test_ipinterface_fails(value, errors):
         ),
         (-1, [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 interface', 'type': 'value_error.ipv4interface'}]),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv4 interface', 'type': 'value_error.ipv4interface'}],
         ),
     ],
@@ -527,7 +527,7 @@ def test_ip_v4_interface_fails(value, errors):
         ),
         (-1, [{'loc': ('ip',), 'msg': 'value is not a valid IPv6 interface', 'type': 'value_error.ipv6interface'}]),
         (
-            2 ** 128 + 1,
+            2**128 + 1,
             [{'loc': ('ip',), 'msg': 'value is not a valid IPv6 interface', 'type': 'value_error.ipv6interface'}],
         ),
     ],
diff --git a/tests/test_schema.py b/tests/test_schema.py
index ea0290f09..838883109 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -799,7 +799,7 @@ def test_str_constrained_types(field_type, expected_schema):
 @pytest.mark.parametrize(
     'field_type,expected_schema',
     [
-        (AnyUrl, {'title': 'A', 'type': 'string', 'format': 'uri', 'minLength': 1, 'maxLength': 2 ** 16}),
+        (AnyUrl, {'title': 'A', 'type': 'string', 'format': 'uri', 'minLength': 1, 'maxLength': 2**16}),
         (
             stricturl(min_length=5, max_length=10),
             {'title': 'A', 'type': 'string', 'format': 'uri', 'minLength': 5, 'maxLength': 10},
diff --git a/tests/test_types.py b/tests/test_types.py
index a7b7db89f..8e5481719 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1144,7 +1144,7 @@ def test_dict():
         ([1, 2, '3'], [1, 2, '3']),
         ((1, 2, '3'), [1, 2, '3']),
         ({1, 2, '3'}, list({1, 2, '3'})),
-        ((i ** 2 for i in range(5)), [0, 1, 4, 9, 16]),
+        ((i**2 for i in range(5)), [0, 1, 4, 9, 16]),
         ((deque((1, 2, 3)), list(deque((1, 2, 3))))),
     ),
 )
@@ -1184,7 +1184,7 @@ def test_ordered_dict():
         ([1, 2, '3'], (1, 2, '3')),
         ((1, 2, '3'), (1, 2, '3')),
         ({1, 2, '3'}, tuple({1, 2, '3'})),
-        ((i ** 2 for i in range(5)), (0, 1, 4, 9, 16)),
+        ((i**2 for i in range(5)), (0, 1, 4, 9, 16)),
         (deque([1, 2, 3]), (1, 2, 3)),
     ),
 )
@@ -1210,7 +1210,7 @@ def test_tuple_fails(value):
     (
         ([1, 2, '3'], int, (1, 2, 3)),
         ((1, 2, '3'), int, (1, 2, 3)),
-        ((i ** 2 for i in range(5)), int, (0, 1, 4, 9, 16)),
+        ((i**2 for i in range(5)), int, (0, 1, 4, 9, 16)),
         (('a', 'b', 'c'), str, ('a', 'b', 'c')),
     ),
 )
@@ -1250,7 +1250,7 @@ def test_tuple_variable_len_fails(value, cls, exc):
         ({1, 2, 2, '3'}, {1, 2, '3'}),
         ((1, 2, 2, '3'), {1, 2, '3'}),
         ([1, 2, 2, '3'], {1, 2, '3'}),
-        ({i ** 2 for i in range(5)}, {0, 1, 4, 9, 16}),
+        ({i**2 for i in range(5)}, {0, 1, 4, 9, 16}),
     ),
 )
 def test_set_success(value, result):