diff --git a/.github/workflows/federation-compatibility.yml b/.github/workflows/federation-compatibility.yml
index e9c74d2158..5ba4c34453 100644
--- a/.github/workflows/federation-compatibility.yml
+++ b/.github/workflows/federation-compatibility.yml
@@ -28,20 +28,20 @@ jobs:
- uses: actions/setup-python@v4
id: setup-python
with:
- python-version: "3.10"
+ python-version: "3.12"
cache: "poetry"
- - run: poetry env use python3.10
+ - run: poetry env use python3.12
- run: poetry install
- name: export schema
run: poetry run strawberry export-schema schema:schema > schema.graphql
working-directory: federation-compatibility
- - uses: apollographql/federation-subgraph-compatibility@v1
+ - uses: apollographql/federation-subgraph-compatibility@v2
with:
compose: 'federation-compatibility/docker-compose.yml'
schema: 'federation-compatibility/schema.graphql'
port: 4001
token: ${{ secrets.BOT_TOKEN }}
- failOnWarning: true
+ failOnWarning: false
failOnRequired: true
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
new file mode 100644
index 0000000000..77ad2f05b2
--- /dev/null
+++ b/.github/workflows/issue-manager.yml
@@ -0,0 +1,30 @@
+name: Issue Manager
+
+on:
+ schedule:
+ - cron: "0 0 * * *"
+ issue_comment:
+ types:
+ - created
+ issues:
+ types:
+ - labeled
+ pull_request_target:
+ types:
+ - labeled
+ workflow_dispatch:
+
+jobs:
+ issue-manager:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: tiangolo/issue-manager@0.5.0
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ config: >
+ {
+ "info-needed": {
+ "delay": "P14D",
+ "message": "Hi, this issue requires extra info to be actionable. We're closing this issue because it has not been actionable for a while now. Feel free to provide the requested information and we'll happily open it again! ๐"
+ }
+ }
diff --git a/.github/workflows/ok-to-preview.yml b/.github/workflows/ok-to-preview.yml
index 1ae26397df..fbc1985c43 100644
--- a/.github/workflows/ok-to-preview.yml
+++ b/.github/workflows/ok-to-preview.yml
@@ -31,7 +31,7 @@ jobs:
event_data = json.load(f)
links = [
- "https://beta.strawberry.rocks/docs/pr/{pr_number}/{path}".format(
+ "https://strawberry.rocks/docs/pr/{pr_number}/{path}".format(
pr_number=event_data["number"],
path=file.replace(".md", "").replace("docs/", "")
)
diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml
index 62c7882fb0..06d75d4176 100644
--- a/.github/workflows/release-check.yml
+++ b/.github/workflows/release-check.yml
@@ -89,7 +89,7 @@ jobs:
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Extract tweet message and changelog
id: extract
- uses: strawberry-graphql/tweet-actions/read-tweet@v5
+ uses: strawberry-graphql/tweet-actions/read-tweet@v6
with:
changelog: ${{ needs.release-file-check.outputs.changelog }}
version: "(next)"
@@ -102,7 +102,7 @@ jobs:
if: ${{ needs.read-tweet-md.outputs.tweet != '' }}
steps:
- name: Validate tweet
- uses: strawberry-graphql/tweet-actions/validate-tweet@v5
+ uses: strawberry-graphql/tweet-actions/validate-tweet@v6
with:
tweet: ${{ needs.read-tweet-md.outputs.tweet }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 399b90dc2d..d31543c120 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -162,7 +162,7 @@ jobs:
- uses: actions/checkout@v1
- name: Extract tweet message and changelog
id: extract
- uses: strawberry-graphql/tweet-actions/read-tweet@v5
+ uses: strawberry-graphql/tweet-actions/read-tweet@v6
with:
changelog: ${{ needs.release-file-check.outputs.changelog }}
version: ${{ needs.release.outputs.version }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d4f2002b7b..71219aab13 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -23,10 +23,8 @@ jobs:
outputs:
sessions: ${{ steps.set-matrix.outputs.sessions }}
steps:
- - uses: actions/checkout@v3
- - uses: wntrblm/nox@main
- - run: pipx install poetry
- - run: pipx inject nox nox-poetry
+ - uses: actions/checkout@v4
+ - run: pip install poetry nox nox-poetry
- id: set-matrix
shell: bash
run: |
@@ -50,36 +48,44 @@ jobs:
session: ${{ fromJson(needs.generate-jobs-tests.outputs.sessions) }}
steps:
- - uses: actions/checkout@v3
- - uses: wntrblm/nox@main
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
- python-versions: "3.8, 3.9, 3.10, 3.11, 3.12"
+ python-version: |
+ 3.8
+ 3.9
+ 3.10
+ 3.11
+ 3.12
- name: Pip and nox cache
id: cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.cache
~/.nox
.nox
- key: ${{ runner.os }}-nox-${{ matrix.session.session }}-${{
- hashFiles('**/poetry.lock') }}-${{ hashFiles('**/noxfile.py') }}
- restore-keys: |
- ${{ runner.os }}-nox-${{ matrix.session.session }}-
- ${{ runner.os }}-nox-
+ key:
+ ${{ runner.os }}-nox-${{ matrix.session.session }}-${{
+ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/noxfile.py') }}-4
- - run: pipx install coverage
- - run: pipx install poetry
- - run: pipx inject nox nox-poetry
+ - run: pip install poetry nox nox-poetry
- run: nox -r -t tests -s "${{ matrix.session.session }}"
-
- - name: coverage xml
- run: coverage xml -i
+ - uses: actions/upload-artifact@v4
if: ${{ always() }}
+ with:
+ name: coverage-${{ matrix.session.session }}
+ path: coverage.xml
- - uses: codecov/codecov-action@v3
- if: ${{ always() }}
+ upload-coverage:
+ name: ๐ Upload Coverage
+ needs: [unit-tests]
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/download-artifact@v4
+ - uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
@@ -92,9 +98,9 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- run: pipx install poetry
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
id: setup-python
with:
python-version: "3.12"
@@ -106,7 +112,7 @@ jobs:
if: steps.setup-python.outputs.cache-hit != 'true'
- name: Run benchmarks
- uses: CodSpeedHQ/action@v1
+ uses: CodSpeedHQ/action@v2
with:
token: ${{ secrets.CODSPEED_TOKEN }}
run: poetry run pytest tests/benchmarks --codspeed
@@ -118,27 +124,33 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v3
- - uses: wntrblm/nox@main
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
- python-versions: "3.8, 3.9, 3.10, 3.11, 3.12"
+ python-version: |
+ 3.8
+ 3.9
+ 3.10
+ 3.11
+ 3.12
- name: Pip and nox cache
id: cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.cache
~/.nox
.nox
- key: ${{ runner.os }}-nox-lint-${{ matrix.session.session }}-${{
+ key:
+ ${{ runner.os }}-nox-lint-${{ matrix.session.session }}-${{
hashFiles('**/poetry.lock') }}-${{ hashFiles('**/noxfile.py') }}
restore-keys: |
${{ runner.os }}-lint-nox-${{ matrix.session.session }}-
${{ runner.os }}-lint-nox-
- - run: pipx install poetry
- - run: pipx inject nox nox-poetry
+ - run: pip install poetry
+ - run: pip install nox nox-poetry
- run: nox -r -t lint
unit-tests-on-windows:
@@ -146,10 +158,10 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- run: pipx install poetry
- run: pipx install coverage
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v5
id: setup-python
with:
python-version: "3.11"
@@ -170,7 +182,7 @@ jobs:
run: coverage xml -i
if: ${{ always() }}
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v4
if: ${{ always() }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 0b1023af7d..a7f729a098 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,3 @@
-.DS_Store
-
-
# Created by https://www.gitignore.io/api/macos,linux,python,windows
# Edit at https://www.gitignore.io/?templates=macos,linux,python,windows
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 074cabe31f..8866d3cb5e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.2.1
+ rev: v0.4.10
hooks:
- id: ruff-format
exclude: ^tests/\w+/snapshots/
@@ -20,7 +20,7 @@ repos:
files: '^docs/.*\.mdx?$'
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
+ rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
@@ -35,3 +35,4 @@ repos:
hooks:
- id: blacken-docs
args: [--skip-errors]
+ files: '\.(rst|md|markdown|py|mdx)$'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2df76e27a1..3bca76bd6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,826 @@
CHANGELOG
=========
+0.237.3 - 2024-07-31
+--------------------
+
+This release fixes the type of the ASGI request handler's `scope` argument, making type checkers ever so slightly happier.
+
+Contributed by [Jonathan Ehwald](https://github.com/DoctorJohn) via [PR #3581](https://github.com/strawberry-graphql/strawberry/pull/3581/)
+
+
+0.237.2 - 2024-07-26
+--------------------
+
+This release makes the ASGI and FastAPI integrations share their HTTP request adapter code, making Strawberry ever so slightly smaller and easier to maintain.
+
+Contributed by [Jonathan Ehwald](https://github.com/DoctorJohn) via [PR #3582](https://github.com/strawberry-graphql/strawberry/pull/3582/)
+
+
+0.237.1 - 2024-07-24
+--------------------
+
+This release adds support for GraphQL-core v3.3 (which has not yet been
+released). Note that we continue to support GraphQL-core v3.2 as well.
+
+Contributed by [ื ืืจ](https://github.com/nrbnlulu) via [PR #3570](https://github.com/strawberry-graphql/strawberry/pull/3570/)
+
+
+0.237.0 - 2024-07-24
+--------------------
+
+This release ensures using pydantic 2.8.0 doesn't break when using experimental
+pydantic_type and running mypy.
+
+Contributed by [Martin Roy](https://github.com/lindycoder) via [PR #3562](https://github.com/strawberry-graphql/strawberry/pull/3562/)
+
+
+0.236.2 - 2024-07-23
+--------------------
+
+Update federation entity resolver exception handling to set the result to the original error instead of a `GraphQLError`, which obscured the original message and meta-fields.
+
+Contributed by [Bradley Oesch](https://github.com/bradleyoesch) via [PR #3144](https://github.com/strawberry-graphql/strawberry/pull/3144/)
+
+
+0.236.1 - 2024-07-23
+--------------------
+
+This release fixes an issue where optional lazy types using `| None` were
+failing to be correctly resolved inside modules using future annotations, e.g.
+
+```python
+from __future__ import annotations
+
+from typing import Annotated, TYPE_CHECKING
+
+import strawberry
+
+if TYPE_CHECKING:
+ from types import Group
+
+
+@strawberry.type
+class Person:
+ group: Annotated["Group", strawberry.lazy("types.group")] | None
+```
+
+This should now work as expected.
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3576](https://github.com/strawberry-graphql/strawberry/pull/3576/)
+
+
+0.236.0 - 2024-07-17
+--------------------
+
+This release changes some of the internals of Strawberry, it shouldn't
+be affecting most of the users, but since we have changed the structure
+of the code you might need to update your imports.
+
+Thankfully we also provide a codemod for this, you can run it with:
+
+```bash
+strawberry upgrade update-imports
+```
+
+This release also includes additional documentation to some of
+the classes, methods and functions, this is in preparation for
+having the API reference in the documentation โจ
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3546](https://github.com/strawberry-graphql/strawberry/pull/3546/)
+
+
+0.235.2 - 2024-07-08
+--------------------
+
+This release removes an unnecessary check from our internal GET query parsing logic making it simpler and (insignificantly) faster.
+
+Contributed by [Jonathan Ehwald](https://github.com/DoctorJohn) via [PR #3558](https://github.com/strawberry-graphql/strawberry/pull/3558/)
+
+
+0.235.1 - 2024-06-26
+--------------------
+
+This release improves the performance when returning a lot of data, especially
+when using generic inputs (where we got a 7x speedup in our benchmark!).
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3549](https://github.com/strawberry-graphql/strawberry/pull/3549/)
+
+
+0.235.0 - 2024-06-10
+--------------------
+
+This release adds a new configuration to disable field suggestions in the error
+response.
+
+```python
+@strawberry.type
+class Query:
+ name: str
+
+
+schema = strawberry.Schema(
+ query=Query, config=StrawberryConfig(disable_field_suggestions=True)
+)
+```
+
+Trying to query `{ nam }` will not suggest to query `name` instead.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3537](https://github.com/strawberry-graphql/strawberry/pull/3537/)
+
+
+0.234.3 - 2024-06-10
+--------------------
+
+Fixes a bug where pydantic models as the default value for an input did not print the proper schema.
+See [this issue](https://github.com/strawberry-graphql/strawberry/issues/3285).
+
+Contributed by [ppease](https://github.com/ppease) via [PR #3499](https://github.com/strawberry-graphql/strawberry/pull/3499/)
+
+
+0.234.2 - 2024-06-07
+--------------------
+
+This release fixes an issue when trying to retrieve specialized type vars from a
+generic type that has been aliased to a name, in cases like:
+
+```python
+@strawberry.type
+class Fruit(Generic[T]): ...
+
+
+SpecializedFruit = Fruit[str]
+```
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3535](https://github.com/strawberry-graphql/strawberry/pull/3535/)
+
+
+0.234.1 - 2024-06-06
+--------------------
+
+Improved error message when supplying GlobalID with invalid or unknown type name component
+
+Contributed by [Take Weiland](https://github.com/diesieben07) via [PR #3533](https://github.com/strawberry-graphql/strawberry/pull/3533/)
+
+
+0.234.0 - 2024-06-01
+--------------------
+
+This release separates the `relay.ListConnection` logic that calculates the
+slice of the nodes into a separate function.
+
+This allows for easier reuse of that logic for other places/libraries.
+
+The new function lives in the `strawberry.relay.utils` and can be used by
+calling `SliceMetadata.from_arguments`.
+
+This has no implications to end users.
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3530](https://github.com/strawberry-graphql/strawberry/pull/3530/)
+
+
+0.233.3 - 2024-05-31
+--------------------
+
+This release fixes a typing issue where trying to type a `root` argument with
+`strawberry.Parent` would fail, like in the following example:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class SomeType:
+ @strawberry.field
+ def hello(self, root: strawberry.Parent[str]) -> str:
+ return "world"
+```
+
+This should now work as intended.
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3529](https://github.com/strawberry-graphql/strawberry/pull/3529/)
+
+
+0.233.2 - 2024-05-31
+--------------------
+
+This release fixes an introspection issue when requesting `isOneOf` on built-in
+scalars, like `String`.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3528](https://github.com/strawberry-graphql/strawberry/pull/3528/)
+
+
+0.233.1 - 2024-05-30
+--------------------
+
+This release exposes `get_arguments` in the schema_converter module to allow
+integrations, such as strawberry-django, to reuse that functionality if needed.
+
+This is an internal change with no impact for end users.
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3527](https://github.com/strawberry-graphql/strawberry/pull/3527/)
+
+
+0.233.0 - 2024-05-29
+--------------------
+
+This release refactors our Federation integration to create types using
+Strawberry directly, instead of using low level types from GraphQL-core.
+
+The only user facing change is that now the `info` object passed to the
+`resolve_reference` function is the `strawberry.Info` object instead of the one
+coming coming from GraphQL-core. This is a **breaking change** for users that
+were using the `info` object directly.
+
+If you need to access the original `info` object you can do so by accessing the
+`_raw_info` attribute.
+
+```python
+import strawberry
+
+
+@strawberry.federation.type(keys=["upc"])
+class Product:
+ upc: str
+
+ @classmethod
+ def resolve_reference(cls, info: strawberry.Info, upc: str) -> "Product":
+ # Access the original info object
+ original_info = info._raw_info
+
+ return Product(upc=upc)
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3525](https://github.com/strawberry-graphql/strawberry/pull/3525/)
+
+
+0.232.2 - 2024-05-28
+--------------------
+
+This release fixes an issue that would prevent using lazy aliased connections to
+annotate a connection field.
+
+For example, this should now work correctly:
+
+```python
+# types.py
+
+
+@strawberry.type
+class Fruit: ...
+
+
+FruitConnection: TypeAlias = ListConnection[Fruit]
+```
+
+```python
+# schema.py
+
+
+@strawberry.type
+class Query:
+ fruits: Annotated["FruitConnection", strawberry.lazy("types")] = (
+ strawberry.connection()
+ )
+```
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3524](https://github.com/strawberry-graphql/strawberry/pull/3524/)
+
+
+0.232.1 - 2024-05-27
+--------------------
+
+This release fixes an issue where mypy would complain when using a typed async
+resolver with `strawberry.field(resolver=...)`.
+
+Now the code will type check correctly. We also updated our test suite to make
+we catch similar issues in the future.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3516](https://github.com/strawberry-graphql/strawberry/pull/3516/)
+
+
+0.232.0 - 2024-05-25
+--------------------
+
+This release improves type checking for async resolver functions when used as
+`strawberry.field(resolver=resolver_func)`.
+
+Now doing this will raise a type error:
+
+```python
+import strawberry
+
+
+def some_resolver() -> int:
+ return 0
+
+
+@strawberry.type
+class User:
+ # Note the field being typed as str instead of int
+ name: str = strawberry.field(resolver=some_resolver)
+```
+
+Contributed by [Bryan Ricker](https://github.com/bricker) via [PR #3241](https://github.com/strawberry-graphql/strawberry/pull/3241/)
+
+
+0.231.1 - 2024-05-25
+--------------------
+
+Fixes an issue where lazy annotations raised an error when used together with a List
+
+Contributed by [jeich](https://github.com/jeich) via [PR #3388](https://github.com/strawberry-graphql/strawberry/pull/3388/)
+
+
+0.231.0 - 2024-05-25
+--------------------
+
+When calling the CLI without all the necessary dependencies installed,
+a `MissingOptionalDependenciesError` will be raised instead of a
+`ModuleNotFoundError`. This new exception will provide a more helpful
+hint regarding how to fix the problem.
+
+Contributed by [Ethan Henderson](https://github.com/parafoxia) via [PR #3511](https://github.com/strawberry-graphql/strawberry/pull/3511/)
+
+
+0.230.0 - 2024-05-22
+--------------------
+
+This release adds support for `@oneOf` on input types! ๐ You can use
+`one_of=True` on input types to create an input type that should only have one
+of the fields set.
+
+```python
+import strawberry
+
+
+@strawberry.input(one_of=True)
+class ExampleInputTagged:
+ a: str | None = strawberry.UNSET
+ b: int | None = strawberry.UNSET
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3429](https://github.com/strawberry-graphql/strawberry/pull/3429/)
+
+
+0.229.2 - 2024-05-22
+--------------------
+
+This release fixes an issue when using `Annotated` + `strawberry.lazy` +
+deferred annotations such as:
+
+```python
+from __future__ import annotations
+import strawberry
+from typing import Annotated
+
+
+@strawberry.type
+class Query:
+ a: Annotated["datetime", strawberry.lazy("datetime")]
+
+
+schema = strawberry.Schema(Query)
+```
+
+Before this would only work if `datetime` was not inside quotes. Now it should
+work as expected!
+
+Contributed by [Thiago Bellini Ribeiro](https://github.com/bellini666) via [PR #3507](https://github.com/strawberry-graphql/strawberry/pull/3507/)
+
+
+0.229.1 - 2024-05-15
+--------------------
+
+This release fixes a regression from 0.229.0 where using a generic interface
+inside a union would return an error.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3502](https://github.com/strawberry-graphql/strawberry/pull/3502/)
+
+
+0.229.0 - 2024-05-12
+--------------------
+
+This release improves our support for generic types, now using the same the same
+generic multiple times with a list inside an interface or union is supported,
+for example the following will work:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class BlockRow[T]:
+ items: list[T]
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def blocks(self) -> list[BlockRow[str] | BlockRow[int]]:
+ return [
+ BlockRow(items=["a", "b", "c"]),
+ BlockRow(items=[1, 2, 3, 4]),
+ ]
+
+
+schema = strawberry.Schema(query=Query)
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3463](https://github.com/strawberry-graphql/strawberry/pull/3463/)
+
+
+0.228.0 - 2024-05-12
+--------------------
+
+This releases updates the JSON scalar definition to have the updated `specifiedBy` URL.
+
+The release is marked as minor because it will change the generated schema if you're using the JSON scalar.
+
+Contributed by [Egor](https://github.com/Birdi7) via [PR #3478](https://github.com/strawberry-graphql/strawberry/pull/3478/)
+
+
+0.227.7 - 2024-05-12
+--------------------
+
+This releases updates the `field-extensions` documentation's `StrawberryField` stability warning to include stable features.
+
+The release is marked as patch because it only changes documentation.
+
+Contributed by [Ray Sy](https://github.com/fireteam99) via [PR #3496](https://github.com/strawberry-graphql/strawberry/pull/3496/)
+
+
+0.227.6 - 2024-05-11
+--------------------
+
+Fix `AssertionError` caused by the `DatadogTracingExtension` whenever the query is unavailable.
+
+The bug in question was reported by issue [#3150](https://github.com/strawberry-graphql/strawberry/issues/3150).
+The datadog extension would throw an `AssertionError` whenever there was no query available. This could happen if,
+for example, a user POSTed something to `/graphql` with a JSON that doesn't contain a `query` field as per the
+GraphQL spec.
+
+The fix consists of adding `query_missing` to the `operation_type` tag, and also adding `query_missing` to the resource name.
+It also makes it easier to look for logs of users making invalid queries by searching for `query_missing` in Datadog.
+
+Contributed by [Lucas Valente](https://github.com/serramatutu) via [PR #3483](https://github.com/strawberry-graphql/strawberry/pull/3483/)
+
+
+0.227.5 - 2024-05-11
+--------------------
+
+**Deprecations:** This release deprecates the `Starlite` integration in favour of the `LiteStar` integration.
+Refer to the [LiteStar](./litestar.md) integration for more information.
+LiteStar is a [renamed](https://litestar.dev/about/organization.html#litestar-and-starlite) and upgraded version of Starlite.
+
+Before:
+
+```python
+from strawberry.starlite import make_graphql_controller
+```
+
+After:
+
+```python
+from strawberry.litestar import make_graphql_controller
+```
+
+Contributed by [Egor](https://github.com/Birdi7) via [PR #3492](https://github.com/strawberry-graphql/strawberry/pull/3492/)
+
+
+0.227.4 - 2024-05-09
+--------------------
+
+This release fixes a bug in release 0.227.3 where FragmentSpread nodes
+were not resolving edges.
+
+Contributed by [Eric Uriostigue](https://github.com/euriostigue) via [PR #3487](https://github.com/strawberry-graphql/strawberry/pull/3487/)
+
+
+0.227.3 - 2024-05-01
+--------------------
+
+This release adds an optimization to `ListConnection` such that only queries with
+`edges` or `pageInfo` in their selected fields triggers `resolve_edges`.
+
+This change is particularly useful for the `strawberry-django` extension's
+`ListConnectionWithTotalCount` and the only selected field is `totalCount`. An
+extraneous SQL query is prevented with this optimization.
+
+Contributed by [Eric Uriostigue](https://github.com/euriostigue) via [PR #3480](https://github.com/strawberry-graphql/strawberry/pull/3480/)
+
+
+0.227.2 - 2024-04-21
+--------------------
+
+This release fixes a minor issue where the docstring for the relay util `to_base64` described the return type incorrectly.
+
+Contributed by [Gavin Bannerman](https://github.com/gbannerman) via [PR #3467](https://github.com/strawberry-graphql/strawberry/pull/3467/)
+
+
+0.227.1 - 2024-04-20
+--------------------
+
+This release fixes an issue where annotations on `@strawberry.type`s were overridden
+by our code. With release all annotations should be preserved.
+
+This is useful for libraries that use annotations to introspect Strawberry types.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3003](https://github.com/strawberry-graphql/strawberry/pull/3003/)
+
+
+0.227.0 - 2024-04-19
+--------------------
+
+This release improves the schema codegen, making it more robust and easier to
+use.
+
+It does this by introducing a directed acyclic graph for the schema codegen,
+which should reduce the amount of edits needed to make the generated code work,
+since it will be able to generate the code in the correct order (based on the
+dependencies of each type).
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3116](https://github.com/strawberry-graphql/strawberry/pull/3116/)
+
+
+0.226.2 - 2024-04-19
+--------------------
+
+This release updates our Mypy plugin to add support for Pydantic >= 2.7.0
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3462](https://github.com/strawberry-graphql/strawberry/pull/3462/)
+
+
+0.226.1 - 2024-04-19
+--------------------
+
+This releases fixes a bug in the mypy plugin where the `from_pydantic` method
+was not correctly typed.
+
+Contributed by [Corentin-Br](https://github.com/Corentin-Br) via [PR #3368](https://github.com/strawberry-graphql/strawberry/pull/3368/)
+
+
+0.226.0 - 2024-04-17
+--------------------
+
+Starting with this release, any error raised from within schema
+extensions will abort the operation and is returned to the client.
+
+This corresponds to the way we already handle field extension errors
+and resolver errors.
+
+This is particular useful for schema extensions performing checks early
+in the request lifecycle, for example:
+
+```python
+class MaxQueryLengthExtension(SchemaExtension):
+ MAX_QUERY_LENGTH = 8192
+
+ async def on_operation(self):
+ if len(self.execution_context.query) > self.MAX_QUERY_LENGTH:
+ raise StrawberryGraphQLError(message="Query too large")
+ yield
+```
+
+Contributed by [Jonathan Ehwald](https://github.com/DoctorJohn) via [PR #3217](https://github.com/strawberry-graphql/strawberry/pull/3217/)
+
+
+0.225.1 - 2024-04-15
+--------------------
+
+This change fixes GET request queries returning a 400 if a content_type header is supplied
+
+Contributed by [Nathan John](https://github.com/vethan) via [PR #3452](https://github.com/strawberry-graphql/strawberry/pull/3452/)
+
+
+0.225.0 - 2024-04-14
+--------------------
+
+This release adds support for using FastAPI APIRouter arguments in GraphQLRouter.
+
+Now you have the opportunity to specify parameters such as `tags`, `route_class`,
+`deprecated`, `include_in_schema`, etc:
+
+```python
+import strawberry
+
+from fastapi import FastAPI
+from strawberry.fastapi import GraphQLRouter
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def hello(self) -> str:
+ return "Hello World"
+
+
+schema = strawberry.Schema(Query)
+
+graphql_app = GraphQLRouter(schema, tags=["graphql"])
+
+app = FastAPI()
+app.include_router(graphql_app, prefix="/graphql")
+```
+
+Contributed by [Nikita Paramonov](https://github.com/nparamonov) via [PR #3442](https://github.com/strawberry-graphql/strawberry/pull/3442/)
+
+
+0.224.2 - 2024-04-13
+--------------------
+
+This releases fixes a bug where schema extensions where not running a LIFO order.
+
+Contributed by [ื ืืจ](https://github.com/nrbnlulu) via [PR #3416](https://github.com/strawberry-graphql/strawberry/pull/3416/)
+
+
+0.224.1 - 2024-03-30
+--------------------
+
+This release fixes a deprecation warning when using the Apollo Tracing
+Extension.
+
+Contributed by [A. Coady](https://github.com/coady) via [PR #3410](https://github.com/strawberry-graphql/strawberry/pull/3410/)
+
+
+0.224.0 - 2024-03-30
+--------------------
+
+This release adds support for using both Pydantic v1 and v2, when importing from
+`pydantic.v1`.
+
+This is automatically detected and the correct version is used.
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3426](https://github.com/strawberry-graphql/strawberry/pull/3426/)
+
+
+0.223.0 - 2024-03-29
+--------------------
+
+This release adds support for Apollo Federation in the schema codegen. Now you
+can convert a schema like this:
+
+```graphql
+extend schema
+ @link(url: "https://specs.apollo.dev/federation/v2.3",
+ import: ["@key", "@shareable"])
+
+type Query {
+ me: User
+}
+
+type User @key(fields: "id") {
+ id: ID!
+ username: String! @shareable
+}
+```
+
+to a Strawberry powered schema like this:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class Query:
+ me: User | None
+
+
+@strawberry.federation.type(keys=["id"])
+class User:
+ id: strawberry.ID
+ username: str = strawberry.federation.field(shareable=True)
+
+
+schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+```
+
+By running the following command:
+
+```bash
+strawberry schema-codegen example.graphql
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3417](https://github.com/strawberry-graphql/strawberry/pull/3417/)
+
+
+0.222.0 - 2024-03-27
+--------------------
+
+This release adds support for Apollo Federation v2.7 which includes the `@authenticated`, `@requiresScopes`, `@policy` directives, as well as the `label` argument for `@override`.
+As usual, we have first class support for them in the `strawberry.federation` namespace, here's an example:
+
+```python
+from strawberry.federation.schema_directives import Override
+
+
+@strawberry.federation.type(
+ authenticated=True,
+ policy=[["client", "poweruser"], ["admin"]],
+ requires_scopes=[["client", "poweruser"], ["admin"]],
+)
+class Product:
+ upc: str = strawberry.federation.field(
+ override=Override(override_from="mySubGraph", label="percent(1)")
+ )
+```
+
+Contributed by [Tyger Taco](https://github.com/TygerTaco) via [PR #3420](https://github.com/strawberry-graphql/strawberry/pull/3420/)
+
+
+0.221.1 - 2024-03-21
+--------------------
+
+This release properly allows passing one argument to the `Info` class.
+
+This is now fully supported:
+
+```python
+import strawberry
+
+from typing import TypedDict
+
+
+class Context(TypedDict):
+ user_id: str
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def info(self, info: strawberry.Info[Context]) -> str:
+ return info.context["user_id"]
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3419](https://github.com/strawberry-graphql/strawberry/pull/3419/)
+
+
+0.221.0 - 2024-03-21
+--------------------
+
+This release improves the `Info` type, by adding support for default TypeVars
+and by exporting it from the main module. This makes it easier to use `Info` in
+your own code, without having to import it from `strawberry.types.info`.
+
+### New export
+
+By exporting `Info` from the main module, now you can do the follwing:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def info(self, info: strawberry.Info) -> str:
+ # do something with info
+ return "hello"
+```
+
+### Default TypeVars
+
+The `Info` type now has default TypeVars, so you can use it without having to
+specify the type arguments, like we did in the example above. Make sure to use
+the latest version of Mypy or Pyright for this. It also means that you can only
+pass one value to it if you only care about the context type:
+
+```python
+import strawberry
+
+from .context import Context
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def info(self, info: strawberry.Info[Context]) -> str:
+ return info.context.user_id
+```
+
+Contributed by [Patrick Arminio](https://github.com/patrick91) via [PR #3418](https://github.com/strawberry-graphql/strawberry/pull/3418/)
+
+
+0.220.0 - 2024-03-08
+--------------------
+
+This release adds support to allow passing `connection_params` as dictionary to `GraphQLWebsocketCommunicator` class when testing [channels integration](https://strawberry.rocks/docs/integrations/channels#testing)
+
+
+### Example
+
+
+```python
+GraphQLWebsocketCommunicator(
+ application=application,
+ path="/graphql",
+ connection_params={"username": "strawberry"},
+)
+```
+
+Contributed by [selvarajrajkanna](https://github.com/selvarajrajkanna) via [PR #3403](https://github.com/strawberry-graphql/strawberry/pull/3403/)
+
+
0.219.2 - 2024-02-06
--------------------
@@ -39,7 +859,7 @@ def custom_context_getter(request: Request):
@strawberry.type
class Query:
@strawberry.field
- def hello(self, info: Info[object, None]) -> str:
+ def hello(self, info: strawberry.Info[object, None]) -> str:
return info.context["custom"]
@@ -610,7 +1430,7 @@ class User:
@strawberry.field
@staticmethod
async def name(parent: strawberry.Parent[UserRow]) -> str:
- return f"User Number {parent.id}"
+ return f"User Number {parent.id_}"
@strawberry.type
@@ -1172,7 +1992,7 @@ class MyDataType:
class Subscription:
@strawberry.subscription
async def my_data_subscription(
- self, info: Info, groups: list[str]
+ self, info: strawberry.Info, groups: list[str]
) -> AsyncGenerator[MyDataType | None, None]:
yield None
async for message in info.context["ws"].channel_listen(
@@ -1187,7 +2007,7 @@ class Subscription:
class Subscription:
@strawberry.subscription
async def my_data_subscription(
- self, info: Info, groups: list[str]
+ self, info: strawberry.Info, groups: list[str]
) -> AsyncGenerator[MyDataType | None, None]:
async with info.context["ws"].listen_to_channel("my_data", groups=groups) as cm:
yield None
@@ -1220,7 +2040,7 @@ class Query:
@strawberry.field
def get_testing(
self,
- info: Info[None, None],
+ info: strawberry.Info,
id_: Annotated[uuid.UUID, strawberry.argument(name="id")],
) -> str | None:
return None
@@ -1674,7 +2494,7 @@ class Mutation:
@strawberry.mutation(extensions=[InputMutationExtension()])
def update_fruit_weight(
self,
- info: Info,
+ info: strawberry.Info,
id: strawberry.ID,
weight: Annotated[
float,
@@ -2168,7 +2988,9 @@ class MyInput:
class MyFieldExtension(FieldExtension):
- def resolve(self, next_: Callable[..., Any], source: Any, info: Info, **kwargs):
+ def resolve(
+ self, next_: Callable[..., Any], source: Any, info: strawberry.Info, **kwargs
+ ):
# kwargs["my_input"] is instance of MyInput
...
@@ -2457,7 +3279,7 @@ def custom_context_getter(request: Request):
@strawberry.type
class Query:
@strawberry.field
- def hello(self, info: Info[object, None]) -> str:
+ def hello(self, info: strawberry.Info[object, None]) -> str:
return info.context["custom"]
@@ -2660,7 +3482,11 @@ from strawberry.extensions import FieldExtension
class UpperCaseExtension(FieldExtension):
async def resolve_async(
- self, next: Callable[..., Awaitable[Any]], source: Any, info: Info, **kwargs
+ self,
+ next: Callable[..., Awaitable[Any]],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs
):
result = await next(source, info, **kwargs)
return str(result).upper()
@@ -3460,7 +4286,7 @@ the original type was already used with that generic in the schema.
Example:
-```python3
+```python
@strawberry.type
class Query:
regular: Edge[User]
@@ -4324,11 +5150,11 @@ will print the following SDL:
directive @specifiedBy(name: String!) on SCALAR
"""
-The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
+The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
scalar JSON
@specifiedBy(
- url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf"
+ url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
)
type Query {
@@ -4953,7 +5779,7 @@ and here's an example of how the new syntax works:
from strawberry.types import Info
-def some_resolver(info: Info) -> str:
+def some_resolver(info: strawberry.Info) -> str:
return info.context.get("some_key", "default")
@@ -4990,7 +5816,7 @@ class Query:
locations=[DirectiveLocation.FIELD],
description="Add frosting with ``value`` to a cake.",
)
-def add_frosting(value: str, v: DirectiveValue[Cake], my_info: Info):
+def add_frosting(value: str, v: DirectiveValue[Cake], my_info: strawberry.Info):
# Arbitrary argument name when using `DirectiveValue` is supported!
assert isinstance(v, Cake)
if (
@@ -5783,7 +6609,7 @@ Added the response object to `get_context` on the `flask` view. This means that
```python
@strawberry.field
-def response_check(self, info: Info) -> bool:
+def response_check(self, info: strawberry.Info) -> bool:
response: Response = info.context["response"]
response.status_code = 401
@@ -7439,7 +8265,7 @@ from starlette.background import BackgroundTask
@strawberry.mutation
-def create_flavour(self, info: Info) -> str:
+def create_flavour(self, info: strawberry.Info) -> str:
info.context["response"].background = BackgroundTask(...)
```
@@ -8531,7 +9357,7 @@ This release updates get_context in the django integration to also receive a tem
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
info.context.response.status_code = 418
return "ABC"
diff --git a/docs/README.md b/docs/README.md
index 5a65aeb236..7114bceb13 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,3 +1,7 @@
+---
+title: Strawberry docs
+---
+
# Strawberry docs
- [Getting started](./index.md)
@@ -39,10 +43,11 @@
## [Extensions](./extensions)
-## [Errors](./errors/)
+## [Errors](./errors)
## Guides
+- [Accessing parent data](./guides/accessing-parent-data.md)
- [Authentication](./guides/authentication.md)
- [DataLoaders](./guides/dataloaders.md)
- [Dealing with errors](./guides/errors.md)
@@ -57,7 +62,7 @@
- [Implementing Cursor Pagination](./guides/pagination/cursor-based.md)
- [Implementing the Connection specification](./guides/pagination/connections.md)
- [Permissions](./guides/permissions.md)
-- [Builtin server](./guides/server.md)
+- [Built-in server](./guides/server.md)
- [Tools](./guides/tools.md)
- [Schema export](./guides/schema-export.md)
- [Convert to dictionary](./guides/convert-to-dictionary.md)
diff --git a/docs/_test.md b/docs/_test.md
index 1fe4f704fc..06eb1a239c 100644
--- a/docs/_test.md
+++ b/docs/_test.md
@@ -57,20 +57,27 @@ class X:
You can show two different code blocks next to each other (useful when comparing
the GraphQL schema against the Python definition):
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class Query:
@strawberry.field
def ping(self) -> str:
return "pong"
----
+```
+
+```graphql
type Query {
- ping: String!
+ ping: String!
}
```
+
+
or when showing the request and response to a query:
```graphql+response
diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md
index 520d2f222e..772c9ef8c5 100644
--- a/docs/breaking-changes.md
+++ b/docs/breaking-changes.md
@@ -4,6 +4,8 @@ title: List of breaking changes and deprecations
# List of breaking changes and deprecations
+- [Version 0.236.0 - 17 July 2024](./breaking-changes/0.236.0.md)
+- [Version 0.233.0 - 29 May 2024](./breaking-changes/0.233.0.md)
- [Version 0.217.0 - 18 December 2023](./breaking-changes/0.217.0.md)
- [Version 0.213.0 - 8 November 2023](./breaking-changes/0.213.0.md)
- [Version 0.180.0 - 31 May 2023](./breaking-changes/0.180.0.md)
diff --git a/docs/breaking-changes/0.146.0.md b/docs/breaking-changes/0.146.0.md
index 71b6116b11..fc7aab5f64 100644
--- a/docs/breaking-changes/0.146.0.md
+++ b/docs/breaking-changes/0.146.0.md
@@ -1,5 +1,6 @@
---
title: 0.146.0 Breaking Changes
+slug: breaking-changes/0.146.0
---
# v0.146.0 Breaking Changes - 5 December 2022
diff --git a/docs/breaking-changes/0.159.0.md b/docs/breaking-changes/0.159.0.md
index 78a8aef030..1e31219b8a 100644
--- a/docs/breaking-changes/0.159.0.md
+++ b/docs/breaking-changes/0.159.0.md
@@ -1,5 +1,6 @@
---
title: 0.159.0 Deprecations
+slug: breaking-changes/0.159.0
---
# v0.159.0 Introduces changes how extension hooks are defined
diff --git a/docs/breaking-changes/0.169.0.md b/docs/breaking-changes/0.169.0.md
index a0cef053a9..8b8a4d486d 100644
--- a/docs/breaking-changes/0.169.0.md
+++ b/docs/breaking-changes/0.169.0.md
@@ -1,5 +1,6 @@
---
title: 0.169.0 Breaking changes
+slug: breaking-changes/0.169.0
---
# v0.169.0 Introduces a couple of breaking changes in the HTTP integrations
diff --git a/docs/breaking-changes/0.180.0.md b/docs/breaking-changes/0.180.0.md
index 2b9e80f46b..2039c13bb9 100644
--- a/docs/breaking-changes/0.180.0.md
+++ b/docs/breaking-changes/0.180.0.md
@@ -1,5 +1,6 @@
---
title: 0.180.0 Breaking changes
+slug: breaking-changes/0.180.0
---
# v0.180.0 introduces a breaking change for the Django Channels HTTP integration
diff --git a/docs/breaking-changes/0.213.0.md b/docs/breaking-changes/0.213.0.md
index f31f4d6f86..5a54fbfe04 100644
--- a/docs/breaking-changes/0.213.0.md
+++ b/docs/breaking-changes/0.213.0.md
@@ -1,5 +1,6 @@
---
title: 0.213.0 Deprecation
+slug: breaking-changes/0.213.0
---
# v0.213.0 introduces a deprecation for `graphiql` parameter
diff --git a/docs/breaking-changes/0.217.0.md b/docs/breaking-changes/0.217.0.md
index b76410008c..7f3450c446 100644
--- a/docs/breaking-changes/0.217.0.md
+++ b/docs/breaking-changes/0.217.0.md
@@ -1,5 +1,6 @@
---
title: 0.217 Breaking Changes
+slug: breaking-changes/0.217.0
---
# v0.217.0 changes how kwargs are passed to `has_permission` method
diff --git a/docs/breaking-changes/0.233.0.md b/docs/breaking-changes/0.233.0.md
new file mode 100644
index 0000000000..4dc261245c
--- /dev/null
+++ b/docs/breaking-changes/0.233.0.md
@@ -0,0 +1,29 @@
+---
+title: 0.233.0 Breaking Changes
+slug: breaking-changes/0.233.0
+---
+
+# v0.233.0 changes the `info` argument in `resolve_reference` in Federation
+
+In this release we have updated the `info` object passed to the
+`resolve_reference` function in Federation to be a `strawberry.Info` object
+instead of the one coming from GraphQL-core.
+
+If you need to access the original `info` object you can do so by accessing the
+`_raw_info` attribute.
+
+```python
+import strawberry
+
+
+@strawberry.federation.type(keys=["upc"])
+class Product:
+ upc: str
+
+ @classmethod
+ def resolve_reference(cls, info: strawberry.Info, upc: str) -> "Product":
+ # Access the original info object
+ original_info = info._raw_info
+
+ return Product(upc=upc)
+```
diff --git a/docs/breaking-changes/0.236.0.md b/docs/breaking-changes/0.236.0.md
new file mode 100644
index 0000000000..b2ed3a3216
--- /dev/null
+++ b/docs/breaking-changes/0.236.0.md
@@ -0,0 +1,19 @@
+---
+title: 0.236.0 Breaking Changes
+slug: breaking-changes/0.236.0
+---
+
+# v0.236.0 changes some of the imports
+
+This release changes the location of some files in the codebase, this is to make
+the codebase more organized and easier to navigate.
+
+Technically most of these changes should not affect you, but if you were
+importing some of the files directly you will need to update the imports.
+
+We created a codemod to help you with that, feel free to try and submit bugs if
+we missed something.
+
+```bash
+strawberry upgrade update-imports
+```
diff --git a/docs/concepts/typings.md b/docs/concepts/typings.md
index 782625e549..e824d8fdb8 100644
--- a/docs/concepts/typings.md
+++ b/docs/concepts/typings.md
@@ -60,7 +60,8 @@ the following:
```python
import datetime
-from typing import List, Union, Optional
+import decimal
+from typing import List, Optional
import strawberry
diff --git a/docs/editors/vscode.md b/docs/editors/vscode.md
index 1b6a36f5f7..6075166347 100644
--- a/docs/editors/vscode.md
+++ b/docs/editors/vscode.md
@@ -34,4 +34,4 @@ checking mode. At the moment strict mode is not supported.
Once you have configured the settings, you can restart VS Code and you should be
getting type checking errors in vscode.
-
+
diff --git a/docs/errors/private-strawberry-field.md b/docs/errors/private-strawberry-field.md
index 72cbb93e49..40a4721289 100644
--- a/docs/errors/private-strawberry-field.md
+++ b/docs/errors/private-strawberry-field.md
@@ -28,11 +28,11 @@ the GraphQL schema, so using `strawberry.field` on that field won't be useful,
since it is meant to be used to change information about a field that is exposed
in the GraphQL schema.
-
+
+ This makes sense, but now we don't have a way to do something like:
+ strawberry.Private[list[str]] = strawberry.field(default_factory=list)
+ (workaround is to use dataclasses.field, explained below)
+
## How to fix this error
diff --git a/docs/errors/relay-wrong-resolver-annotation.md b/docs/errors/relay-wrong-resolver-annotation.md
index 070a1c61cb..80c732dd95 100644
--- a/docs/errors/relay-wrong-resolver-annotation.md
+++ b/docs/errors/relay-wrong-resolver-annotation.md
@@ -67,10 +67,8 @@ class Query:
```
-
-Note that if you are returning a type different than the connection type, you
-will need to subclass the connection type and override its `resolve_node` method
-to convert it to the correct type, as explained in the
-[relay guide](../guides/relay).
-
-
+ Note that if you are returning a type different than the connection type, you
+ will need to subclass the connection type and override its `resolve_node`
+ method to convert it to the correct type, as explained in the [relay
+ guide](../guides/relay).
+
diff --git a/docs/errors/scalar-already-registered.md b/docs/errors/scalar-already-registered.md
index 0835801684..a7a283bf1d 100644
--- a/docs/errors/scalar-already-registered.md
+++ b/docs/errors/scalar-already-registered.md
@@ -36,10 +36,10 @@ strawberry.Schema(Query)
This happens because different types in Strawberry (and GraphQL) cannot have the
same name.
-
+
+ This error might happen also when trying to defined a scalar with the same
+ name as a type.
+
## How to fix this error
diff --git a/docs/extensions/_template.md b/docs/extensions/_template.md
index be032724ab..b1f77fb320 100644
--- a/docs/extensions/_template.md
+++ b/docs/extensions/_template.md
@@ -1,5 +1,5 @@
---
-title: ExtensionName
+title: Extension Name
summary: A summary of the extension.
tags: comma,separated,list,of,tags
---
diff --git a/docs/extensions/add-validation-rules.md b/docs/extensions/add-validation-rules.md
index 7bc785551c..5389b190c9 100644
--- a/docs/extensions/add-validation-rules.md
+++ b/docs/extensions/add-validation-rules.md
@@ -1,5 +1,5 @@
---
-title: AddValidationRules
+title: Add Validation Rules
summary: Add GraphQL validation rules.
tags: validation,security
---
diff --git a/docs/extensions/apollo-tracing.md b/docs/extensions/apollo-tracing.md
index 89f1254d0f..f7e2356850 100644
--- a/docs/extensions/apollo-tracing.md
+++ b/docs/extensions/apollo-tracing.md
@@ -1,5 +1,5 @@
---
-title: ApolloTracingExtension
+title: Apollo Tracing
summary: Add Apollo tracing to your GraphQL server.
tags: tracing
---
diff --git a/docs/extensions/datadog.md b/docs/extensions/datadog.md
index 3f57e1494c..ac647a2d33 100644
--- a/docs/extensions/datadog.md
+++ b/docs/extensions/datadog.md
@@ -1,5 +1,5 @@
---
-title: DatadogExtension
+title: Datadog
summary: Add Datadog tracing to your GraphQL server.
tags: tracing
---
diff --git a/docs/extensions/disable-validation.md b/docs/extensions/disable-validation.md
index 1708ebded9..2aa7f20179 100644
--- a/docs/extensions/disable-validation.md
+++ b/docs/extensions/disable-validation.md
@@ -1,5 +1,5 @@
---
-title: DisableValidation
+title: Disable Validation
summary: Disable all query validation.
tags: performance,validation
---
diff --git a/docs/extensions/mask-errors.md b/docs/extensions/mask-errors.md
index 21491fb3ba..08eed28b2f 100644
--- a/docs/extensions/mask-errors.md
+++ b/docs/extensions/mask-errors.md
@@ -1,5 +1,5 @@
---
-title: MaskErrors
+title: Mask Errors
summary: Hide error messages from the client.
tags: security
---
diff --git a/docs/extensions/max-aliases-limiter.md b/docs/extensions/max-aliases-limiter.md
index 5fc8140d14..98bb52bafc 100644
--- a/docs/extensions/max-aliases-limiter.md
+++ b/docs/extensions/max-aliases-limiter.md
@@ -1,5 +1,5 @@
---
-title: MaxAliasesLimiter
+title: Max Aliases Limiter
summary:
Add a validator to limit the maximum number of aliases in a GraphQL document.
tags: security
diff --git a/docs/extensions/max-tokens-limiter.md b/docs/extensions/max-tokens-limiter.md
index f06d82a1e6..b3bbb725f0 100644
--- a/docs/extensions/max-tokens-limiter.md
+++ b/docs/extensions/max-tokens-limiter.md
@@ -1,5 +1,5 @@
---
-title: MaxTokensLimiter
+title: Max Tokens Limiter
summary:
Add a validator to limit the maximum number of tokens in a GraphQL document.
tags: security
diff --git a/docs/extensions/opentelemetry.md b/docs/extensions/opentelemetry.md
index 89c2f63377..5d0545839d 100644
--- a/docs/extensions/opentelemetry.md
+++ b/docs/extensions/opentelemetry.md
@@ -1,5 +1,5 @@
---
-title: OpenTelemetryExtension
+title: Open Telemetry
summary: Add Open Telemetry tracing to your GraphQL server.
tags: tracing
---
diff --git a/docs/extensions/parser-cache.md b/docs/extensions/parser-cache.md
index a80167157d..1096549981 100644
--- a/docs/extensions/parser-cache.md
+++ b/docs/extensions/parser-cache.md
@@ -1,5 +1,5 @@
---
-title: ParserCache
+title: Parser Cache
summary: Add in memory caching to the parsing step of query execution.
tags: performance,caching,parsing
---
diff --git a/docs/extensions/query-depth-limiter.md b/docs/extensions/query-depth-limiter.md
index 2fd806c9ad..3a4d753247 100644
--- a/docs/extensions/query-depth-limiter.md
+++ b/docs/extensions/query-depth-limiter.md
@@ -1,5 +1,5 @@
---
-title: QueryDepthLimiter
+title: Query Depth Limiter
summary: Add a validator to limit the query depth of GraphQL operations.
tags: security
---
diff --git a/docs/extensions/sentry-tracing.md b/docs/extensions/sentry-tracing.md
index d9eedb050a..52b70d3582 100644
--- a/docs/extensions/sentry-tracing.md
+++ b/docs/extensions/sentry-tracing.md
@@ -1,5 +1,5 @@
---
-title: SentryTracingExtension
+title: Sentry Tracing
summary: Add Sentry tracing to your GraphQL server.
tags: tracing
---
diff --git a/docs/extensions/validation-cache.md b/docs/extensions/validation-cache.md
index 0f4c7b34fc..0b29c6dfdf 100644
--- a/docs/extensions/validation-cache.md
+++ b/docs/extensions/validation-cache.md
@@ -1,5 +1,5 @@
---
-title: ValidationCache
+title: Validation Cache
summary: Add in memory caching to the validation step of query execution.
tags: performance,caching,validation
---
diff --git a/docs/general/mutations.md b/docs/general/mutations.md
index ea7d065214..430ae24e30 100644
--- a/docs/general/mutations.md
+++ b/docs/general/mutations.md
@@ -69,18 +69,23 @@ It is also possible to write a mutation that doesn't return anything.
This is mapped to a `Void` GraphQL scalar, and always returns `null`
-```python+schema
+
+```python
@strawberry.type
class Mutation:
@strawberry.mutation
def restart() -> None:
- print(f'Restarting the server')
----
+ print(f"Restarting the server")
+```
+
+```graphql
type Mutation {
restart: Void
}
```
+
+
Mutations with void-result go against
@@ -88,7 +93,7 @@ Mutations with void-result go against
-### The Input Mutation Extension
+## The input mutation extension
It is usually useful to use a pattern of defining a mutation that receives a
single [input type](../types/input-types) argument called `input`.
@@ -108,7 +113,7 @@ class Mutation:
@strawberry.mutation(extensions=[InputMutationExtension()])
def update_fruit_weight(
self,
- info: Info,
+ info: strawberry.Info,
id: strawberry.ID,
weight: Annotated[
float,
@@ -166,12 +171,10 @@ import strawberry
@strawberry.type
class FruitMutations:
@strawberry.mutation
- def add(self, info, input: AddFruitInput) -> Fruit:
- # ...
+ def add(self, info, input: AddFruitInput) -> Fruit: ...
@strawberry.mutation
- def update_weight(self, info, input: UpdateFruitWeightInput) -> Fruit:
- # ...
+ def update_weight(self, info, input: UpdateFruitWeightInput) -> Fruit: ...
@strawberry.type
@@ -211,4 +214,5 @@ For more details, see
[Apollo's guide on Namespaces for serial mutations](https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern/#namespaces-for-serial-mutations)
and
[Rapid API's Interactive Guide to GraphQL Queries: Aliases and Variables](https://rapidapi.com/guides/graphql-aliases-variables).
+
diff --git a/docs/general/schema-basics.md b/docs/general/schema-basics.md
index fbd4e9b25d..813c179b02 100644
--- a/docs/general/schema-basics.md
+++ b/docs/general/schema-basics.md
@@ -49,8 +49,6 @@ The `!` sign specifies that a field is non-nullable.
Notice that the schema doesnโt specify how to get the data. That comes later
when defining the resolvers.
-
-
## Code first approach
As mentioned Strawberry uses a code first approach. The previous schema would
@@ -70,7 +68,7 @@ class Book:
@strawberry.type
class Author:
name: str
- books: typing.List["Book"]
+ books: typing.List[Book]
```
As you can see the code maps almost one to one with the schema, thanks to
@@ -167,7 +165,7 @@ class Book:
author: "Author" = strawberry.field(resolver=get_author_for_book)
-def get_books_for_author(root):
+def get_books_for_author(root) -> typing.List[Book]:
return [Book(title="Jurassic Park")]
@@ -298,7 +296,7 @@ the following:
```python
@strawberry.type
class Mutation:
- @strawberry.field
+ @strawberry.mutation
def add_book(self, title: str, author: str) -> Book: ...
```
@@ -359,7 +357,7 @@ Consider our previous mutation to add a book:
```python
@strawberry.type
class Mutation:
- @strawberry.field
+ @strawberry.mutation
def add_book(self, title: str, author: str) -> Book: ...
```
@@ -379,7 +377,7 @@ class AddBookInput:
@strawberry.type
class Mutation:
- @strawberry.field
+ @strawberry.mutation
def add_book(self, book: AddBookInput) -> Book: ...
```
diff --git a/docs/general/subscriptions.md b/docs/general/subscriptions.md
index 6879ed5978..7d4161bdc4 100644
--- a/docs/general/subscriptions.md
+++ b/docs/general/subscriptions.md
@@ -60,7 +60,7 @@ subscription {
In this example, the data looks like this as it passes over the websocket:
-
+![A view of the data that's been passed via websocket](../images/subscriptions-count-websocket.png)
This is a very short example of what is possible. Like with queries and
mutations the subscription can return any GraphQL type, not only scalars as
@@ -113,7 +113,6 @@ import asyncio
from typing import AsyncGenerator
import strawberry
-from strawberry.types import Info
from .auth import authenticate_token
@@ -128,7 +127,9 @@ class Query:
@strawberry.type
class Subscription:
@strawberry.subscription
- async def count(self, info: Info, target: int = 100) -> AsyncGenerator[int, None]:
+ async def count(
+ self, info: strawberry.Info, target: int = 100
+ ) -> AsyncGenerator[int, None]:
connection_params: dict = info.context.get("connection_params")
token: str = connection_params.get(
"authToken"
@@ -331,8 +332,8 @@ application = GraphQLProtocolTypeRouter(
)
```
-Note: Check the [channels integraton](/docs/integrations/channels.md) page for
-more information regarding it.
+Note: Check the [channels integraton](../integrations/channels.md) page for more
+information regarding it.
#### FastAPI
diff --git a/docs/general/why.md b/docs/general/why.md
index 12a73462aa..374c7b8a30 100644
--- a/docs/general/why.md
+++ b/docs/general/why.md
@@ -27,18 +27,24 @@ GraphQL APIs while also helping finding bugs when using type checkers like MyPy.
Here's a basic example of a type and how it compares to the equivalent type in
GraphQL:
-```python+schema
+
+
+```python
@strawberry.type
class User:
id: strawberry.ID
name: str
----
+```
+
+```graphql
type User {
id: ID!
name: String!
}
```
+
+
As you can see the code is very similar to what you would write using the
GraphQL SDL. Thanks to this, we think Strawberry hits a perfect middle ground
between code first and schema first.
diff --git a/docs/guides/accessing-parent-data.md b/docs/guides/accessing-parent-data.md
new file mode 100644
index 0000000000..2a78f426d9
--- /dev/null
+++ b/docs/guides/accessing-parent-data.md
@@ -0,0 +1,221 @@
+---
+title: Accessing parent's data in resolvers
+---
+
+# Accessing parent's data in resolvers
+
+It is quite common to want to be able to access the data from the field's parent
+in a resolver. For example let's say that we want to define a `fullName` field
+on our `User`. This would be our code:
+
+
+
+```python
+import strawberry
+
+
+@strawberry.type
+class User:
+ first_name: str
+ last_name: str
+ full_name: str
+```
+
+```graphql
+type User {
+ firstName: String!
+ lastName: String!
+ fullName: String!
+}
+```
+
+
+
+In this case `full_name` will need to access the `first_name` and `last_name`
+fields, and depending on whether we define the resolver as a function or as a
+method, we'll have a few options! Let's start with the defining a resolver as a
+function.
+
+## Accessing parent's data in function resolvers
+
+```python
+import strawberry
+
+
+def get_full_name() -> str: ...
+
+
+@strawberry.type
+class User:
+ first_name: str
+ last_name: str
+ full_name: str = strawberry.field(resolver=get_full_name)
+```
+
+Our resolver is a function with no arguments, in order to tell Strawberry to
+pass us the parent of the field, we need to add a new argument with type
+`strawberry.Parent[ParentType]`, like so:
+
+```python
+def get_full_name(parent: strawberry.Parent[User]) -> str:
+ return f"{parent.first_name} {parent.last_name}"
+```
+
+`strawberry.Parent` tells Strawberry to pass the parent value of the field, in
+this case it would be the `User`.
+
+> **Note:** `strawberry.Parent` accepts a type argument, which will then be used
+> by your type checker to check your code!
+
+### Using root
+
+Historically Strawberry only supported passing the parent value by adding a
+parameter called `root`:
+
+```python
+def get_full_name(root: User) -> str:
+ return f"{root.first_name} {root.last_name}"
+```
+
+This is still supported, but we recommend using `strawberry.Parent`, since it
+follows Strawberry's philosophy of using type annotations. Also, with
+`strawberry.Parent` your argument can have any name, for example this will still
+work:
+
+```python
+def get_full_name(user: strawberry.Parent[User]) -> str:
+ return f"{user.first_name} {user.last_name}"
+```
+
+## Accessing parent's data in a method resolver
+
+Both options also work when defining a method resolver, so we can still use
+`strawberry.Parent` in a resolver defined as a method:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class User:
+ first_name: str
+ last_name: str
+
+ @strawberry.field
+ def full_name(self, parent: strawberry.Parent[User]) -> str:
+ return f"{parent.first_name} {parent.last_name}"
+```
+
+But, here's where things get more interesting. If this was a pure Python class,
+we would use `self` directly, right? Turns out that Strawberry also supports
+this!
+
+Let's update our resolver:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class User:
+ first_name: str
+ last_name: str
+
+ @strawberry.field
+ def full_name(self) -> str:
+ return f"{self.first_name} {self.last_name}"
+```
+
+Much better, no? `self` on resolver methods is pretty convenient, and it works
+like it should in Python, but there might be cases where it doesn't properly
+follow Python's semantics. This is because under the hood resolvers are actually
+called as if they were static methods by Strawberry.
+
+Let's see a simplified version of what happens when you request the `full_name`
+field, to do that we also need a field that allows to fetch a user:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def user(self) -> User:
+ return User(first_name="Albert", last_name="Heijn")
+```
+
+When we do a query like this:
+
+```graphql
+{
+ user {
+ fullName
+ }
+}
+```
+
+We are pretty much asking to call the `user` function on the `Query` class, and
+then call the `full_name` function on the `User` class, similar to this code:
+
+```python
+user = Query().user()
+
+full_name = user.full_name()
+```
+
+While this might work for this case, it won't work in other cases, like when
+returning a different type, for example when fetching the user from a database:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def user(self) -> User:
+ # let's assume UserModel fetches data from the db and it
+ # also has `first_name` and `last_name`
+ user = UserModel.objects.first()
+
+ return user
+```
+
+In this case our pseudo code would break, since `UserModel` doesn't have a
+`full_name` function! But it does work when using Strawberry (provided that the
+`UserModel` has both `first_name` and `last_name` fields).
+
+As mentioned, this is because Strawberry class the resolvers as if they were
+plain functions (not bound to the class), similar to this:
+
+```python
+# note, we are not instantiating the Query any more!
+user = Query.user() # note: this is a `UserModel` now
+
+full_name = User.full_name(user)
+```
+
+You're probably thinking of `staticmethod`s and that's pretty much what we are
+dealing with now! If you want to keep the resolver as a method on your class but
+also want to remove some of the magic around `self`, you can use the
+`@staticmethod` decorator in combination with `strawberry.Parent`:
+
+```python
+import strawberry
+
+
+@strawberry.type
+class User:
+ first_name: str
+ last_name: str
+
+ @strawberry.field
+ @staticmethod
+ def full_name(parent: strawberry.Parent[User]) -> str:
+ return f"{parent.first_name} {parent.last_name}"
+```
+
+Combining `@staticmethod` with `strawberry.Parent` is a good way to make sure
+that your code is clear and that you are aware of what's happening under the
+hood, and it will keep your linters and type checkers happy!
diff --git a/docs/guides/authentication.md b/docs/guides/authentication.md
index 4aa3188b90..e7a7f85a57 100644
--- a/docs/guides/authentication.md
+++ b/docs/guides/authentication.md
@@ -67,8 +67,6 @@ from functools import cached_property
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import BaseContext, GraphQLRouter
-from strawberry.types import Info as _Info
-from strawberry.types.info import RootValueType
@strawberry.type
@@ -85,13 +83,10 @@ class Context(BaseContext):
return authorization_service.authorize(authorization)
-Info = _Info[Context, RootValueType]
-
-
@strawberry.type
class Query:
@strawberry.field
- def get_authenticated_user(self, info: Info) -> User | None:
+ def get_authenticated_user(self, info: strawberry.Info[Context]) -> User | None:
return info.context.user
diff --git a/docs/guides/custom-extensions.md b/docs/guides/custom-extensions.md
index 453ca449eb..22cc8c1f97 100644
--- a/docs/guides/custom-extensions.md
+++ b/docs/guides/custom-extensions.md
@@ -39,12 +39,11 @@ check out [field extensions](field-extensions.md).
Note that `resolve` can also be implemented asynchronously.
```python
-from strawberry.types import Info
from strawberry.extensions import SchemaExtension
class MyExtension(SchemaExtension):
- def resolve(self, _next, root, info: Info, *args, **kwargs):
+ def resolve(self, _next, root, info: strawberry.Info, *args, **kwargs):
return _next(root, info, *args, **kwargs)
```
diff --git a/docs/guides/dataloaders.md b/docs/guides/dataloaders.md
index 0d443c4b46..4d27f64b1f 100644
--- a/docs/guides/dataloaders.md
+++ b/docs/guides/dataloaders.md
@@ -197,7 +197,9 @@ the dataloader, in order to avoid reloading data afterwards.
For example:
-```python+graphql
+
+
+```python
@strawberry.type
class Person:
id: strawberry.ID
@@ -205,7 +207,8 @@ class Person:
@strawberry.field
async def friends(self) -> List[Person]:
- return await loader.load_many(self.friends_ids)
+ return await loader.load_many(self.friends_ids)
+
@strawberry.type
class Query:
@@ -220,7 +223,9 @@ class Query:
loader.prime_many({person.id: person for person in people})
return people
----
+```
+
+```graphql
{
getAllPeople {
id
@@ -231,6 +236,8 @@ class Query:
}
```
+
+
### Custom Cache
DataLoader's default cache is per-request and it caches data in memory. This
@@ -250,7 +257,6 @@ provided.
from typing import List, Union, Any, Optional
import strawberry
-from strawberry.types import Info
from strawberry.asgi import GraphQL
from strawberry.dataloader import DataLoader, AbstractCache
@@ -296,7 +302,7 @@ class MyGraphQL(GraphQL):
@strawberry.type
class Query:
@strawberry.field
- async def get_user(self, info: Info, id: strawberry.ID) -> User:
+ async def get_user(self, info: strawberry.Info, id: strawberry.ID) -> User:
return await info.context["user_loader"].load(id)
@@ -342,7 +348,9 @@ that allows to fetch a single user by id.
We can use this query by doing the following request:
-```graphql+response
+
+
+```graphql
{
first: getUser(id: 1) {
id
@@ -351,7 +359,9 @@ We can use this query by doing the following request:
id
}
}
----
+```
+
+```json
{
"data": {
"first": {
@@ -364,6 +374,8 @@ We can use this query by doing the following request:
}
```
+
+
Even if this query is fetching two users, it still results in one call to
`load_users`.
@@ -383,7 +395,6 @@ example of this using our ASGI view:
from typing import List, Union, Any, Optional
import strawberry
-from strawberry.types import Info
from strawberry.asgi import GraphQL
from strawberry.dataloader import DataLoader
@@ -411,7 +422,7 @@ class MyGraphQL(GraphQL):
@strawberry.type
class Query:
@strawberry.field
- async def get_user(self, info: Info, id: strawberry.ID) -> User:
+ async def get_user(self, info: strawberry.Info, id: strawberry.ID) -> User:
return await info.context["user_loader"].load(id)
diff --git a/docs/guides/errors.md b/docs/guides/errors.md
index bd8fdb5fab..7ec2b6a506 100644
--- a/docs/guides/errors.md
+++ b/docs/guides/errors.md
@@ -20,11 +20,15 @@ GraphQL is strongly typed and so Strawberry validates all queries before
executing them. If a query is invalid it isnโt executed and instead the response
contains an `errors` list:
-```graphql+response
+
+
+```graphql
{
hi
}
----
+```
+
+```json
{
"data": null,
"errors": [
@@ -42,6 +46,8 @@ contains an `errors` list:
}
```
+
+
Each error has a message, line, column and path to help you identify what part
of the query caused the error.
@@ -69,11 +75,15 @@ class Query:
schema = strawberry.Schema(query=Query)
```
-```graphql+response
+
+
+```graphql
{
hello
}
----
+```
+
+```json
{
"data": null,
"errors": [
@@ -85,14 +95,14 @@ schema = strawberry.Schema(query=Query)
"column": 3
}
],
- "path": [
- "hello"
- ]
+ "path": ["hello"]
}
]
}
```
+
+
Each error has a message, line, column and path to help you identify what part
of the query caused the error.
@@ -121,13 +131,17 @@ class Query:
schema = strawberry.Schema(query=Query)
```
-```graphql+response
+
+
+```graphql
{
user {
name
}
}
----
+```
+
+```json
{
"data": null,
"errors": [
@@ -139,14 +153,14 @@ schema = strawberry.Schema(query=Query)
"column": 2
}
],
- "path": [
- "user"
- ]
+ "path": ["user"]
}
]
}
```
+
+
## Expected errors
If an error is expected then it is often best to express it in the schema. This
diff --git a/docs/guides/federation.md b/docs/guides/federation.md
index 55fbffdf6d..08e8f3d270 100644
--- a/docs/guides/federation.md
+++ b/docs/guides/federation.md
@@ -326,7 +326,8 @@ Strawberry and Federation. The repo is available here:
## Federated schema directives
Strawberry provides implementations for
-[Apollo federation-specific GraphQL directives](https://www.apollographql.com/docs/federation/federated-types/federated-directives/).
+[Apollo federation-specific GraphQL directives](https://www.apollographql.com/docs/federation/federated-types/federated-directives/)
+up to federation spec v2.7.
Some of these directives may not be necessary to directly include in your code,
and are accessed through other means.
@@ -348,6 +349,9 @@ Other directives you may need to specifically include when relevant.
- `@requires`
- `@shareable`
- `@tag`
+- `@authenticated`
+- `@requiresScopes`
+- `@policy`
For example, adding the following directives:
@@ -372,7 +376,7 @@ Will result in the following GraphQL schema:
```graphql
schema
@link(
- url: "https://specs.apollo.dev/federation/v2.3"
+ url: "https://specs.apollo.dev/federation/v2.7"
import: ["@key", "@inaccessible", "@shareable", "@tag"]
) {
query: Query
diff --git a/docs/guides/field-extensions.md b/docs/guides/field-extensions.md
index f299733e94..2921ceac39 100644
--- a/docs/guides/field-extensions.md
+++ b/docs/guides/field-extensions.md
@@ -22,7 +22,9 @@ from strawberry.extensions import FieldExtension
class UpperCaseExtension(FieldExtension):
- def resolve(self, next_: Callable[..., Any], source: Any, info: Info, **kwargs):
+ def resolve(
+ self, next_: Callable[..., Any], source: Any, info: strawberry.Info, **kwargs
+ ):
result = next_(source, info, **kwargs)
return str(result).upper()
@@ -40,22 +42,29 @@ will be called instead of the resolver and receives the resolver function as the
`next` argument. Therefore, it is important to not modify any arguments that are
passed to `next` in an incompatible way.
-```graphql+response
+
+
+```graphql
query {
- string
+ string
}
----
+```
+
+```json
{
"string": "THIS IS A TEST!!"
}
```
+
+
## Modifying the field
-The `StrawberryField` API is not stable and might change in the future without
-warning.
+Most of the `StrawberryField` API is not stable and might change in the future
+without warning. Stable features include: `StrawberryField.type`,
+`StrawberryField.python_name`, and `StrawberryField.arguments`.
@@ -70,7 +79,7 @@ import time
import strawberry
from strawberry.extensions import FieldExtension
from strawberry.schema_directive import Location
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
@strawberry.schema_directive(locations=[Location.FIELD_DEFINITION])
@@ -88,7 +97,7 @@ class CachingExtension(FieldExtension):
field.directives.append(Cached(time=self.caching_time))
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs
+ self, next_: Callable[..., Any], source: Any, info: strawberry.Info, **kwargs
) -> Any:
current_time = time.time()
if self.last_cached + self.caching_time > current_time:
@@ -97,19 +106,25 @@ class CachingExtension(FieldExtension):
return self.cached_result
```
-```python+schema
+
+
+```python
@strawberry.type
class Client:
@strawberry.field(extensions=[CachingExtensions(caching_time=200)])
def analyzed_hours(self, info) -> int:
return do_expensive_computation()
----
+```
+
+```graphql
type Client {
analyzedHours: Int! @Cached(time=200)
}
```
+
+
## Combining multiple field extensions
When chaining multiple field extensions, the last extension in the list is
@@ -164,12 +179,18 @@ from strawberry.extensions import FieldExtension
class UpperCaseExtension(FieldExtension):
- def resolve(self, next: Callable[..., Any], source: Any, info: Info, **kwargs):
+ def resolve(
+ self, next: Callable[..., Any], source: Any, info: strawberry.Info, **kwargs
+ ):
result = next(source, info, **kwargs)
return str(result).upper()
async def resolve_async(
- self, next: Callable[..., Awaitable[Any]], source: Any, info: Info, **kwargs
+ self,
+ next: Callable[..., Awaitable[Any]],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs
):
result = await next(source, info, **kwargs)
return str(result).upper()
diff --git a/docs/guides/file-upload.md b/docs/guides/file-upload.md
index f0b5da537c..c9c556b9bc 100644
--- a/docs/guides/file-upload.md
+++ b/docs/guides/file-upload.md
@@ -23,6 +23,21 @@ runtime depends on the integration:
| [Sanic](/docs/integrations/sanic) | [`sanic.request.File`](https://sanic.readthedocs.io/en/stable/sanic/api/core.html#sanic.request.File) |
| [Starlette](/docs/integrations/starlette) | [`starlette.datastructures.UploadFile`](https://www.starlette.io/requests/#request-files) |
+In order to have the correct runtime type in resolver type annotations you can
+set a scalar override based on the integrations above. For example with
+Starlette:
+
+```python
+import strawberry
+from starlette.datastructures import UploadFile
+from strawberry.file_uploads import Upload
+
+schema = strawberry.Schema(
+ ...
+ scalar_overrides={UploadFile: Upload}
+)
+```
+
## ASGI / FastAPI / Starlette
Since these integrations use asyncio for communication, the resolver _must_ be
diff --git a/docs/guides/pagination/connections.md b/docs/guides/pagination/connections.md
index 3996902645..37c6a89f3a 100644
--- a/docs/guides/pagination/connections.md
+++ b/docs/guides/pagination/connections.md
@@ -13,7 +13,9 @@ building GraphQL APIs which use a cursor based connection pattern.
By the end of this tutorial, we should be able to return a connection of users
when requested.
-```graphql+response
+
+
+```graphql
query getUsers {
getUsers(first: 2) {
users {
@@ -33,7 +35,9 @@ query getUsers {
}
}
}
----
+```
+
+```json
{
"data": {
"getUsers": {
@@ -60,14 +64,16 @@ query getUsers {
]
},
"pageInfo": {
- "endCursor": "dXNlcjoz",
- "hasNextPage": true
+ "endCursor": "dXNlcjoz",
+ "hasNextPage": true
}
}
}
}
```
+
+
## Connections
A Connection represents a paginated relationship between two entities. This
diff --git a/docs/guides/pagination/cursor-based.md b/docs/guides/pagination/cursor-based.md
index 3620c050c0..06a95a661c 100644
--- a/docs/guides/pagination/cursor-based.md
+++ b/docs/guides/pagination/cursor-based.md
@@ -9,7 +9,9 @@ Make sure to check our introduction to pagination [here](./overview.md)!
Let us implement cursor based pagination in GraphQL. By the end of this
tutorial, we should be able to return a paginated list of users when requested.
-```graphql+response
+
+
+```graphql
query getUsers {
getUsers(limit: 2) {
users {
@@ -23,7 +25,9 @@ query getUsers {
}
}
}
----
+```
+
+```json
{
"data": {
"getUsers": {
@@ -42,13 +46,15 @@ query getUsers {
}
],
"pageMeta": {
- "nextCursor": "dXNlcjoz",
+ "nextCursor": "dXNlcjoz"
}
}
}
}
```
+
+
The server needs to return a `cursor` along with the sliced user data, so that
our client can know what to query for next. The client could also provide a
`limit` value, to specify how much users it wants at a time.
@@ -66,11 +72,8 @@ import strawberry
@strawberry.type
class User:
id: str = strawberry.field(description="ID of the user.")
-
name: str = strawberry.field(description="The name of the user.")
-
occupation: str = strawberry.field(description="The occupation of the user.")
-
age: int = strawberry.field(description="The age of the user.")
@staticmethod
@@ -143,11 +146,8 @@ user_data = [
@strawberry.type
class User:
id: str = strawberry.field(description="ID of the user.")
-
name: str = strawberry.field(description="The name of the user.")
-
occupation: str = strawberry.field(description="The occupation of the user.")
-
age: int = strawberry.field(description="The age of the user.")
@staticmethod
@@ -167,7 +167,6 @@ class PageMeta:
@strawberry.type
class UserResponse:
users: List[User] = strawberry.field(description="The list of users.")
-
page_meta: PageMeta = strawberry.field(description="Metadata to aid in pagination.")
@@ -258,11 +257,8 @@ def decode_user_cursor(cursor: str) -> int:
@strawberry.type
class User:
id: str = strawberry.field(description="ID of the user.")
-
name: str = strawberry.field(description="The name of the user.")
-
occupation: str = strawberry.field(description="The occupation of the user.")
-
age: int = strawberry.field(description="The age of the user.")
@staticmethod
@@ -282,7 +278,6 @@ class PageMeta:
@strawberry.type
class UserResponse:
users: List[User] = strawberry.field(description="The list of users.")
-
page_meta: PageMeta = strawberry.field(description="Metadata to aid in pagination.")
@@ -363,11 +358,8 @@ def decode_user_cursor(cursor: str) -> int:
@strawberry.type
class User:
id: str = strawberry.field(description="ID of the user.")
-
name: str = strawberry.field(description="The name of the user.")
-
occupation: str = strawberry.field(description="The occupation of the user.")
-
age: int = strawberry.field(description="The age of the user.")
@staticmethod
@@ -387,7 +379,6 @@ class PageMeta:
@strawberry.type
class UserResponse:
users: List[User] = strawberry.field(description="The list of users.")
-
page_meta: PageMeta = strawberry.field(description="Metadata to aid in pagination.")
diff --git a/docs/guides/permissions.md b/docs/guides/permissions.md
index 908931f30b..d38f9e3492 100644
--- a/docs/guides/permissions.md
+++ b/docs/guides/permissions.md
@@ -13,14 +13,15 @@ like this:
import typing
import strawberry
from strawberry.permission import BasePermission
-from strawberry.types import Info
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
# This method can also be async!
- def has_permission(self, source: typing.Any, info: Info, **kwargs) -> bool:
+ def has_permission(
+ self, source: typing.Any, info: strawberry.Info, **kwargs
+ ) -> bool:
return False
@@ -77,13 +78,14 @@ from myauth import authenticate_header, authenticate_query_param
from starlette.requests import Request
from starlette.websockets import WebSocket
from strawberry.permission import BasePermission
-from strawberry.types import Info
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
- def has_permission(self, source: typing.Any, info: Info, **kwargs) -> bool:
+ def has_permission(
+ self, source: typing.Any, info: strawberry.Info, **kwargs
+ ) -> bool:
request: typing.Union[Request, WebSocket] = info.context["request"]
if "Authorization" in request.headers:
@@ -124,7 +126,6 @@ as specified in the
import typing
from strawberry.permission import BasePermission
-from strawberry.types import Info
from your_business_logic import GQLNotImplementedError
@@ -134,7 +135,9 @@ class IsAuthenticated(BasePermission):
error_class = GQLNotImplementedError
error_extensions = {"code": "UNAUTHORIZED"}
- def has_permission(self, source: typing.Any, info: Info, **kwargs) -> bool:
+ def has_permission(
+ self, source: typing.Any, info: strawberry.Info, **kwargs
+ ) -> bool:
return False
```
@@ -178,8 +181,8 @@ client. To return `None` or `[]` instead of raising an error, the
`fail_silently ` keyword argument on `PermissionExtension` can be set to `True`:
-Note that this will only work if the field returns a type that
-is nullable or a list, e.g. `Optional[str]` or `List[str]`.
+ Note that this will only work if the field returns a type that is nullable or
+ a list, e.g. `Optional[str]` or `List[str]`.
```python
diff --git a/docs/guides/relay.md b/docs/guides/relay.md
index bd991a2fd8..394583abcf 100644
--- a/docs/guides/relay.md
+++ b/docs/guides/relay.md
@@ -48,7 +48,7 @@ class Fruit(relay.Node):
def resolve_nodes(
cls,
*,
- info: Info,
+ info: strawberry.Info,
node_ids: Iterable[str],
required: bool = False,
):
@@ -194,7 +194,7 @@ The connection resolver for `relay.ListConnection` should return one of those:
As demonstrated above, the `Node` field can be used to retrieve/refetch any
object in the schema that implements the `Node` interface.
-It can be defined in in the `Query` objects in 4 ways:
+It can be defined in the `Query` objects in 4 ways:
- `node: Node`: This will define a field that accepts a `GlobalID!` and returns
a `Node` instance. This is the most basic way to define it.
@@ -330,7 +330,7 @@ class Query:
@relay.connection(relay.ListConnection[Fruit])
def fruits_with_filter(
self,
- info: Info,
+ info: strawberry.Info,
name_endswith: str,
) -> Iterable[Fruit]:
for f in fruits.values():
@@ -379,7 +379,7 @@ class Fruit(relay.Node):
@strawberry.type
class FruitDBConnection(relay.ListConnection[Fruit]):
@classmethod
- def resolve_node(cls, node: FruitDB, *, info: Info, **kwargs) -> Fruit:
+ def resolve_node(cls, node: FruitDB, *, info: strawberry.Info, **kwargs) -> Fruit:
return Fruit(
code=node.code,
name=node.name,
@@ -392,7 +392,7 @@ class Query:
@relay.connection(FruitDBConnection)
def fruits_with_filter(
self,
- info: Info,
+ info: strawberry.Info,
name_endswith: str,
) -> Iterable[models.Fruit]:
return models.Fruit.objects.filter(name__endswith=name_endswith)
@@ -421,7 +421,7 @@ class Mutation:
@strawberry.mutation
async def update_fruit_weight(
self,
- info: Info,
+ info: strawberry.Info,
id: relay.GlobalID,
weight: float,
) -> Fruit:
@@ -433,7 +433,7 @@ class Mutation:
@strawberry.mutation
def update_fruit_weight_sync(
self,
- info: Info,
+ info: strawberry.Info,
id: relay.GlobalID,
weight: float,
) -> Fruit:
diff --git a/docs/guides/tools.md b/docs/guides/tools.md
index 52fe7d373b..5b3d2c02ad 100644
--- a/docs/guides/tools.md
+++ b/docs/guides/tools.md
@@ -27,36 +27,47 @@ def create_type(
Example:
-```python+schema
+
+
+```python
import strawberry
from strawberry.tools import create_type
+
@strawberry.field
def hello(info) -> str:
return "World"
+
def get_name(info) -> str:
return info.context.user.name
+
my_name = strawberry.field(name="myName", resolver=get_name)
Query = create_type("Query", [hello, my_name])
schema = strawberry.Schema(query=Query)
----
+```
+
+```graphql
type Query {
hello: String!
myName: String!
}
```
+
+
---
### `merge_types`
Merge multiple Strawberry types into one. Example:
-```python+schema
+
+
+```python
import strawberry
from strawberry.tools import merge_types
@@ -64,22 +75,23 @@ from strawberry.tools import merge_types
@strawberry.type
class QueryA:
@strawberry.field
- def perform_a(self) -> str:
- ...
+ def perform_a(self) -> str: ...
@strawberry.type
class QueryB:
@strawberry.field
- def perform_b(self) -> str:
- ...
+ def perform_b(self) -> str: ...
ComboQuery = merge_types("ComboQuery", (QueryB, QueryA))
-schema = strawberry.Schema(query=ComboQuery)
----
+```
+
+```graphql
type ComboQuery {
performB: String!
performA: String!
}
```
+
+
diff --git a/docs/index.md b/docs/index.md
index 7f384c5cbf..9c30f46299 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -181,6 +181,6 @@ Well done! You just created your first GraphQL API using Strawberry ๐!
Check out the following resources to learn more about GraphQL and Strawberry.
-- [Schema Basics](/docs/general/schema-basics.md)
-- [Resolvers](/docs/types/resolvers.md)
-- [Deployment](/docs/operations/deployment.md)
+- [Schema Basics](./general/schema-basics.md)
+- [Resolvers](./types/resolvers.md)
+- [Deployment](./operations/deployment.md)
diff --git a/docs/integrations/aiohttp.md b/docs/integrations/aiohttp.md
index ebae48abab..cb622234fd 100644
--- a/docs/integrations/aiohttp.md
+++ b/docs/integrations/aiohttp.md
@@ -69,7 +69,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
diff --git a/docs/integrations/asgi.md b/docs/integrations/asgi.md
index 2f6543c422..eaad8a52ff 100644
--- a/docs/integrations/asgi.md
+++ b/docs/integrations/asgi.md
@@ -64,7 +64,7 @@ class MyGraphQL(GraphQL):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
@@ -87,7 +87,7 @@ the `Info` object.
@strawberry.type
class Mutation:
@strawberry.mutation
- def login(self, info: Info) -> bool:
+ def login(self, info: strawberry.Info) -> bool:
token = do_login()
info.context["response"].set_cookie(key="token", value=token)
return True
@@ -108,7 +108,7 @@ async def notify_new_flavour(name: str): ...
@strawberry.type
class Mutation:
@strawberry.mutation
- def create_flavour(self, name: str, info: Info) -> bool:
+ def create_flavour(self, name: str, info: strawberry.Info) -> bool:
info.context["response"].background = BackgroundTask(notify_new_flavour, name)
```
diff --git a/docs/integrations/chalice.md b/docs/integrations/chalice.md
index dbfd68f8af..8c5671c33d 100644
--- a/docs/integrations/chalice.md
+++ b/docs/integrations/chalice.md
@@ -93,7 +93,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
diff --git a/docs/integrations/channels.md b/docs/integrations/channels.md
index 775fdd00c3..4fa4ae716f 100644
--- a/docs/integrations/channels.md
+++ b/docs/integrations/channels.md
@@ -112,15 +112,13 @@ import threading
from typing import AsyncGenerator, List
-from strawberry.types import Info
-
@strawberry.type
class Subscription:
@strawberry.subscription
async def join_chat_rooms(
self,
- info: Info,
+ info: strawberry.Info,
rooms: List[ChatRoom],
user: str,
) -> AsyncGenerator[ChatRoomMessage, None]:
@@ -200,7 +198,7 @@ class Mutation:
@strawberry.mutation
async def send_chat_message(
self,
- info: Info,
+ info: strawberry.Info,
room: ChatRoom,
message: str,
) -> None:
@@ -387,7 +385,7 @@ class Subscription:
@strawberry.subscription
async def join_chat_rooms(
self,
- info: Info,
+ info: strawberry.Info,
rooms: List[ChatRoom],
user: str,
) -> AsyncGenerator[ChatRoomMessage | None, None]:
@@ -498,6 +496,20 @@ The HTTP and WebSockets protocol are handled by different base classes. HTTP
uses `GraphQLHTTPConsumer` and WebSockets uses `GraphQLWSConsumer`. Both of them
can be extended:
+### Passing connection params
+
+Connection parameters can be passed using the `connection_params` parameter of
+the `GraphQLWebsocketCommunicator` class. This is particularily useful to test
+websocket authentication.
+
+```python
+GraphQLWebsocketCommunicator(
+ application=application,
+ path="/graphql",
+ connection_params={"token": "strawberry"},
+)
+```
+
## GraphQLHTTPConsumer (HTTP)
### Options
diff --git a/docs/integrations/django.md b/docs/integrations/django.md
index d557bc1e4d..2a4e75266a 100644
--- a/docs/integrations/django.md
+++ b/docs/integrations/django.md
@@ -73,7 +73,7 @@ resolver. You can return anything here, by default we return a
@strawberry.type
class Query:
@strawberry.field
- def user(self, info: Info) -> str:
+ def user(self, info: strawberry.Info) -> str:
return str(info.context.request.user)
```
@@ -88,7 +88,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
@@ -205,7 +205,7 @@ class MyGraphQLView(AsyncGraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
@@ -280,5 +280,4 @@ class MyGraphQLView(AsyncGraphQLView):
Subscriptions run over websockets and thus depend on
[channels](https://channels.readthedocs.io/). Take a look at our
-[channels integraton](/docs/integrations/channels.md) page for more information
-regarding it.
+[channels integraton](./channels.md) page for more information regarding it.
diff --git a/docs/integrations/fastapi.md b/docs/integrations/fastapi.md
index 0def346e8a..1fa144b974 100644
--- a/docs/integrations/fastapi.md
+++ b/docs/integrations/fastapi.md
@@ -81,7 +81,6 @@ For dictionary-based custom contexts, an example might look like the following.
import strawberry
from fastapi import FastAPI, Depends, Request, WebSocket, BackgroundTasks
-from strawberry.types import Info
from strawberry.fastapi import GraphQLRouter
@@ -100,7 +99,7 @@ async def get_context(
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return f"Hello {info.context['custom_value']}"
@@ -129,7 +128,6 @@ For class-based custom contexts, an example might look like the following.
import strawberry
from fastapi import FastAPI, Depends, Request, WebSocket, BackgroundTasks
-from strawberry.types import Info
from strawberry.fastapi import BaseContext, GraphQLRouter
@@ -152,7 +150,7 @@ async def get_context(
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return f"Hello {info.context.name}, {info.context.greeting}"
@@ -186,7 +184,6 @@ can be added via the context:
import strawberry
from fastapi import FastAPI, BackgroundTasks
-from strawberry.types import Info
from strawberry.fastapi import GraphQLRouter
@@ -204,7 +201,7 @@ class Query:
@strawberry.type
class Mutation:
@strawberry.mutation
- def create_flavour(self, name: str, info: Info) -> bool:
+ def create_flavour(self, name: str, info: strawberry.Info) -> bool:
info.context["background_tasks"].add_task(notify_new_flavour, name)
return True
@@ -293,9 +290,10 @@ tweaked based on your needs.
`encode_json` allows to customize the encoding of the JSON response. By default
we use `json.dumps` but you can override this method to use a different encoder.
+For example, the `orjson` library from pypi has blazing fast speeds.
```python
class MyGraphQLRouter(GraphQLRouter):
- def encode_json(self, data: GraphQLHTTPResponse) -> str:
- return json.dumps(data, indent=2)
+ def encode_json(self, data: GraphQLHTTPResponse) -> bytes:
+ return orjson.dumps(data)
```
diff --git a/docs/integrations/flask.md b/docs/integrations/flask.md
index f71c383ec5..9fe4e17ac3 100644
--- a/docs/integrations/flask.md
+++ b/docs/integrations/flask.md
@@ -75,7 +75,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
diff --git a/docs/integrations/litestar.md b/docs/integrations/litestar.md
index 05aba23ae2..0ba7c1ded8 100644
--- a/docs/integrations/litestar.md
+++ b/docs/integrations/litestar.md
@@ -81,7 +81,7 @@ async def custom_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def hello(self, info: Info[dict, None]) -> str:
+ def hello(self, info: strawberry.Info[dict, None]) -> str:
return info.context["custom"]
@@ -116,7 +116,7 @@ async def custom_context_getter(request: Request, db_session: AsyncSession):
@strawberry.type
class Query:
@strawberry.field
- async def hello(self, info: Info[dict, None]) -> str:
+ async def hello(self, info: strawberry.Info[dict, None]) -> str:
session: AsyncSession = info.context["session"]
user: User = info.context["user"]
@@ -164,7 +164,7 @@ async def custom_context_getter(
@strawberry.type
class Query:
@strawberry.field
- async def hello(self, info: Info[CustomContext, None]) -> str:
+ async def hello(self, info: strawberry.Info[CustomContext, None]) -> str:
session: AsyncSession = info.context.session
user: User = info.context.user
@@ -244,7 +244,7 @@ async def custom_context_getter(
@strawberry.type
class Query:
@strawberry.field
- async def hello(self, info: Info[CustomHTTPContextType, None]) -> str:
+ async def hello(self, info: strawberry.Info[CustomHTTPContextType, None]) -> str:
session: AsyncSession = info.context.session
user: User = info.context.user
@@ -257,7 +257,7 @@ class Query:
class Subscription:
@strawberry.subscription
async def count(
- self, info: Info[CustomWSContextType, None], target: int = 100
+ self, info: strawberry.Info[CustomWSContextType, None], target: int = 100
) -> AsyncGenerator[int, None]:
import devtools
diff --git a/docs/integrations/pydantic.md b/docs/integrations/pydantic.md
index e9cabb0aaf..9c52c54d89 100644
--- a/docs/integrations/pydantic.md
+++ b/docs/integrations/pydantic.md
@@ -129,24 +129,28 @@ In addition to object types and input types, Strawberry allows you to create
"error types". You can use these error types to have a typed representation of
Pydantic errors in GraphQL. Let's see an example:
-```python+schema
+
+
+```python
from pydantic import BaseModel, constr
import strawberry
+
class User(BaseModel):
id: int
name: constr(min_length=2)
signup_ts: Optional[datetime] = None
friends: List[int] = []
+
@strawberry.experimental.pydantic.error_type(model=User)
class UserError:
id: strawberry.auto
name: strawberry.auto
friends: strawberry.auto
+```
----
-
+```graphql
type UserError {
id: [String!]
name: [String!]
@@ -154,6 +158,8 @@ type UserError {
}
```
+
+
where each field will hold a list of error messages
### Extending types
@@ -161,31 +167,37 @@ where each field will hold a list of error messages
You can use the usual Strawberry syntax to add additional new fields to the
GraphQL type that aren't defined in the pydantic model
-```python+schema
+
+
+```python
import strawberry
from pydantic import BaseModel
from .models import User
+
class User(BaseModel):
id: int
name: str
+
@strawberry.experimental.pydantic.type(model=User)
class User:
id: strawberry.auto
name: strawberry.auto
age: int
+```
----
-
+```graphql
type User {
- id: Int!
- name: String!
- age: Int!
+ id: Int!
+ name: String!
+ age: Int!
}
```
+
+
### Converting types
The generated types won't run any pydantic validation. This is to prevent
@@ -284,16 +296,20 @@ Strawberry supports
Note that constraint is not enforced in the graphql type. Thus, we recommend
always working on the pydantic type such that the validation is enforced.
-```python+schema
+
+
+```python
from pydantic import BaseModel, conlist
import strawberry
+
class Example(BaseModel):
friends: conlist(str, min_items=1)
+
@strawberry.experimental.pydantic.input(model=Example, all_fields=True)
-class ExampleGQL:
- ...
+class ExampleGQL: ...
+
@strawberry.type
class Query:
@@ -305,9 +321,11 @@ class Query:
# an error if friends is empty
print(example.to_pydantic().friends)
+
schema = strawberry.Schema(query=Query)
+```
----
+```graphql
input ExampleGQL {
friends: [String!]!
}
@@ -317,6 +335,8 @@ type Query {
}
```
+
+
### Classes with `__get_validators__`
Pydantic BaseModels may define a custom type with
diff --git a/docs/integrations/quart.md b/docs/integrations/quart.md
index ec9edcda4c..05eb0cd994 100644
--- a/docs/integrations/quart.md
+++ b/docs/integrations/quart.md
@@ -59,7 +59,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
diff --git a/docs/integrations/sanic.md b/docs/integrations/sanic.md
index 690969e0a0..1701a74a83 100644
--- a/docs/integrations/sanic.md
+++ b/docs/integrations/sanic.md
@@ -56,7 +56,7 @@ class MyGraphQLView(GraphQLView):
@strawberry.type
class Query:
@strawberry.field
- def example(self, info: Info) -> str:
+ def example(self, info: strawberry.Info) -> str:
return str(info.context["example"])
```
diff --git a/docs/integrations/starlite.md b/docs/integrations/starlite.md
index 9b09b71f6e..51b28b2cd2 100644
--- a/docs/integrations/starlite.md
+++ b/docs/integrations/starlite.md
@@ -4,6 +4,16 @@ title: Starlite
# Starlite
+## Deprecation Notice
+
+This integration has been deprecated in favor of the `Litestar` integration.
+Refer to the [Litestar](./litestar.md) integration for more information.
+Litestar is a
+[renamed](https://litestar.dev/about/organization.html#litestar-and-starlite)
+and upgraded version of Starlite.
+
+## How to use
+
Strawberry comes with an integration for
[Starlite](https://starliteproject.dev/) by providing a
`make_graphql_controller` function that can be used to create a GraphQL
@@ -79,7 +89,7 @@ def custom_context_getter(request: Request):
@strawberry.type
class Query:
@strawberry.field
- def hello(self, info: Info[object, None]) -> str:
+ def hello(self, info: strawberry.Info[object, None]) -> str:
return info.context["custom"]
diff --git a/docs/types/generics.md b/docs/types/generics.md
index e7cb49e4eb..9e4e424793 100644
--- a/docs/types/generics.md
+++ b/docs/types/generics.md
@@ -32,17 +32,23 @@ class Page(Generic[T]):
This example defines a generic type `Page` that can be used to represent a page
of any type. For example, we can create a page of `User` objects:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class User:
name: str
+
@strawberry.type
class Query:
users: Page[User]
----
+```
+
+```graphql
type Query {
users: UserPage!
}
@@ -57,25 +63,32 @@ type UserPage {
}
```
+
+
It is also possible to use a specialized generic type directly. For example, the
same example above could be written like this:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class User:
name: str
@strawberry.type
-class UserPage(Page[User]):
- ...
+class UserPage(Page[User]): ...
+
@strawberry.type
class Query:
users: UserPage
----
+```
+
+```graphql
type Query {
users: UserPage!
}
@@ -90,6 +103,8 @@ type UserPage {
}
```
+
+
# Input and Argument Types
Arguments to queries and mutations can also be made generic by creating Generic
@@ -97,37 +112,47 @@ Input types. Here we'll define an input type that can serve as a collection of
anything, then create a specialization by using as a filled-in argument on a
mutation.
-```python+schema
+
+
+```python
import strawberry
from typing import Generic, List, Optional, TypeVar
T = TypeVar("T")
+
@strawberry.input
class CollectionInput(Generic[T]):
values: List[T]
+
@strawberry.input
class PostInput:
name: str
+
@strawberry.type
class Post:
id: int
name: str
+
@strawberry.type
class Mutation:
@strawberry.mutation
def add_posts(self, posts: CollectionInput[PostInput]) -> bool:
return True
+
@strawberry.type
class Query:
most_recent_post: Optional[Post] = None
+
schema = strawberry.Schema(query=Query, mutation=Mutation)
----
+```
+
+```graphql
input PostInputCollectionInput {
values: [PostInput!]!
}
@@ -150,6 +175,8 @@ type Mutation {
}
```
+
+
> **Note**: Pay attention to the fact that both `CollectionInput` and
> `PostInput` are Input types. Providing `posts: CollectionInput[Post]` to
> `add_posts` (i.e. using the non-input `Post` type) would have resulted in an
@@ -165,18 +192,22 @@ type Mutation {
Using multiple specializations of a Generic type will work as expected. Here we
define a `Point2D` type and then specialize it for both `int`s and `float`s.
-```python+schema
+
+
+```python
from typing import Generic, TypeVar
import strawberry
-T = TypeVar('T')
+T = TypeVar("T")
+
@strawberry.input
class Point2D(Generic[T]):
x: T
y: T
+
@strawberry.type
class Mutation:
@strawberry.mutation
@@ -186,7 +217,9 @@ class Mutation:
@strawberry.mutation
def store_line_int(self, a: Point2D[int], b: Point2D[int]) -> bool:
return True
----
+```
+
+```graphql
type Mutation {
storeLineFloat(a: FloatPoint2D!, b: FloatPoint2D!): Boolean!
storeLineInt(a: IntPoint2D!, b: IntPoint2D!): Boolean!
@@ -203,6 +236,8 @@ input IntPoint2D {
}
```
+
+
# Variadic Generics
Variadic Generics, introduced in [PEP-646][pep-646], are currently unsupported.
diff --git a/docs/types/input-types.md b/docs/types/input-types.md
index 930238a087..d3880ae393 100644
--- a/docs/types/input-types.md
+++ b/docs/types/input-types.md
@@ -22,22 +22,27 @@ This is how the
In Strawberry, you can define input types by using the `@strawberry.input`
decorator, like this:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.input
class Point2D:
x: float
y: float
+```
----
-
+```graphql
input Point2D {
x: Float!
y: Float!
}
```
+
+
Then you can use input types as argument for your fields or mutations:
```python
@@ -55,16 +60,21 @@ If you want to include optional arguments, you need to provide them with a
default. For example if we want to expand on the above example to allow optional
labeling of our point we could do:
-```python+schema
+
+
+```python
import strawberry
from typing import Optional
+
@strawberry.input
class Point2D:
x: float
y: float
label: Optional[str] = None
----
+```
+
+```graphql
type Point2D {
x: Float!
y: Float!
@@ -72,26 +82,35 @@ type Point2D {
}
```
+
+
Alternatively you can also use `strawberry.UNSET` instead of the `None` default
value, which will make the field optional in the schema:
-```python+schema
+
+
+```python
import strawberry
from typing import Optional
+
@strawberry.input
class Point2D:
x: float
y: float
label: Optional[str] = strawberry.UNSET
----
+```
+
+```graphql
type Point2D {
- x: Float!
- y: Float!
- label: String
+ x: Float!
+ y: Float!
+ label: String
}
```
+
+
## API
`@strawberry.input(name: str = None, description: str = None)`
@@ -102,3 +121,73 @@ Creates an input type from a class definition.
generated by camel-casing the name of the class.
- `description`: this is the GraphQL description that will be returned when
introspecting the schema or when navigating the schema using GraphiQL.
+
+## One Of Input Types
+
+Strawberry also supports defining input types that can have only one field set.
+This is based on the
+[OneOf Input Objects RFC](https://github.com/graphql/graphql-spec/pull/825)
+
+To define a one of input type you can use the `one_of` flag on the
+`@strawberry.input` decorator:
+
+
+
+```python
+import strawberry
+
+
+@strawberry.input(one_of=True)
+class SearchBy:
+ name: str | None = strawberry.UNSET
+ email: str | None = strawberry.UNSET
+```
+
+```graphql
+input SearchBy @oneOf {
+ name: String
+ email: String
+}
+```
+
+
+
+## Deprecating fields
+
+Fields can be deprecated using the argument `deprecation_reason`.
+
+
+
+This does not prevent the field from being used, it's only for documentation.
+See:
+[GraphQL field deprecation](https://spec.graphql.org/June2018/#sec-Field-Deprecation).
+
+
+
+
+
+```python
+import strawberry
+from typing import Optional
+
+
+@strawberry.input
+class Point2D:
+ x: float
+ y: float
+ z: Optional[float] = strawberry.field(
+ deprecation_reason="3D coordinates are deprecated"
+ )
+ label: Optional[str] = strawberry.UNSET
+```
+
+```graphql
+input Point2D {
+ x: Float!
+ y: Float!
+ z: Float @deprecated(reason: "3D coordinates are deprecated")
+ label: String
+}
+```
+
+
diff --git a/docs/types/interfaces.md b/docs/types/interfaces.md
index d3a0fb4d1e..6248de0f18 100644
--- a/docs/types/interfaces.md
+++ b/docs/types/interfaces.md
@@ -77,18 +77,25 @@ donโt have fields in common, use a [Union](/docs/types/union) instead.
Interfaces are defined using the `@strawberry.interface` decorator:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.interface
class Customer:
name: str
----
+```
+
+```graphql
interface Customer {
name: String!
}
```
+
+
Interface classes should never be instantiated directly.
@@ -130,24 +137,31 @@ schema = strawberry.Schema(query=Query, types=[Individual, Company])
Interfaces can also implement other interfaces:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.interface
class Error:
message: str
+
@strawberry.interface
class FieldError(Error):
message: str
field: str
+
@strawberry.type
class PasswordTooShort(FieldError):
message: str
field: str
min_length: int
----
+```
+
+```graphql
interface Error {
message: String!
}
@@ -164,6 +178,8 @@ type PasswordTooShort implements FieldError & Error {
}
```
+
+
## Implementing fields
Interfaces can provide field implementations as well. For example:
diff --git a/docs/types/object-types.md b/docs/types/object-types.md
index 2000c74b72..8471bd9a4c 100644
--- a/docs/types/object-types.md
+++ b/docs/types/object-types.md
@@ -28,46 +28,55 @@ schema (also referred as root types).
- `Subscription` is the entry point for all the subscriptions.
For a walk-through on how to define schemas, read the
-[schema basics](/docs/general/schema-basics.md).
+[schema basics](../general/schema-basics.md).
## Defining object types
In Strawberry, you can define object types by using the `@strawberry.type`
decorator, like this:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class Character:
name: str
age: int
+```
----
-
+```graphql
type Character {
name: String!
age: int!
}
```
+
+
You can also refer to other types, like this:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class Character:
name: str
age: int
+
@strawberry.type
class Book:
title: str
main_character: Character
+```
----
-
+```graphql
type Character {
name: String!
age: Int!
@@ -79,6 +88,8 @@ type Book {
}
```
+
+
## API
`@strawberry.type(name: str = None, description: str = None)`
diff --git a/docs/types/private.md b/docs/types/private.md
index 43fe67aa33..56c989e147 100644
--- a/docs/types/private.md
+++ b/docs/types/private.md
@@ -55,7 +55,9 @@ class Query:
Queries can then select the fields and formats desired, but formatting only
happens as requested:
-```graphql+json
+
+
+```graphql
{
now {
format(template: "{my.year}")
@@ -63,9 +65,9 @@ happens as requested:
repr
}
}
+```
----
-
+```json
{
"data": {
"now": {
@@ -76,3 +78,5 @@ happens as requested:
}
}
```
+
+
diff --git a/docs/types/resolvers.md b/docs/types/resolvers.md
index 1040cd18b7..5398828865 100644
--- a/docs/types/resolvers.md
+++ b/docs/types/resolvers.md
@@ -7,9 +7,12 @@ title: Resolvers
When defining a GraphQL schema, you usually start with the definition of the
schema for your API, for example, let's take a look at this schema:
-```python+schema
+
+
+```python
import strawberry
+
@strawberry.type
class User:
name: str
@@ -18,16 +21,20 @@ class User:
@strawberry.type
class Query:
last_user: User
----
+```
+
+```graphql
type User {
- name: String!
+ name: String!
}
type Query {
- lastUser: User!
+ lastUser: User!
}
```
+
+
We have defined a `User` type and a `Query` type. Next, to define how the data
is returned from our server, we will attach resolvers to our fields.
@@ -50,13 +57,17 @@ class Query:
Now when Strawberry executes the following query, it will call the
`get_last_user` function to fetch the data for the `lastUser` field:
-```graphql+response
+
+
+```graphql
{
lastUser {
name
}
}
----
+```
+
+```json
{
"data": {
"lastUser": {
@@ -66,6 +77,8 @@ Now when Strawberry executes the following query, it will call the
}
```
+
+
## Defining resolvers as methods
The other way to define a resolver is to use `strawberry.field` as a decorator,
@@ -79,16 +92,14 @@ class Query:
return User(name="Marco")
```
-this is useful when you want to colocate resolvers and types or when you have
+This is useful when you want to co-locate resolvers and types or when you have
very small resolvers.
-The _self_ argument is a bit special here, when executing a GraphQL query, in
-case of resolvers defined with a decorator, the _self_ argument corresponds to
-the _root_ value of that field. In this example the _root_ value is the value
-`Query` type, which is usually `None`. You can change the _root_ value when
-calling the `execute` method on a `Schema`. More on _root_ values below.
+If you're curious how the `self` parameter works in the resolver, you can read
+more about it in the
+[accessing parent data guide](../guides/accessing-parent-data.md).
@@ -98,7 +109,9 @@ Fields can also have arguments; in Strawberry the arguments for a field are
defined on the resolver, as you would normally do in a Python function. Let's
define a field on a Query that returns a user by ID:
-```python+schema
+
+
+```python
import strawberry
@@ -113,26 +126,33 @@ class Query:
def user(self, id: strawberry.ID) -> User:
# here you'd use the `id` to get the user from the database
return User(name="Marco")
----
+```
+
+```graphql
type User {
- name: String!
+ name: String!
}
type Query {
- user(id: ID!): User!
+ user(id: ID!): User!
}
```
+
+
### Optional arguments
Optional or nullable arguments can be expressed using `Optional`. If you need to
differentiate between `null` (maps to `None` in Python) and no arguments being
passed, you can use `UNSET`:
-```python+schema
+
+
+```python
from typing import Optional
import strawberry
+
@strawberry.type
class Query:
@strawberry.field
@@ -148,22 +168,30 @@ class Query:
if name is None:
return "Name was null!"
return f"Hello {name}!"
----
+```
+
+```graphql
type Query {
- hello(name: String = null): String!
- greet(name: String): String!
+ hello(name: String = null): String!
+ greet(name: String): String!
}
```
+
+
Like this you will get the following responses:
-```graphql+response
+
+
+```graphql
{
unset: greet
null: greet(name: null)
name: greet(name: "Dominique")
}
----
+```
+
+```json
{
"data": {
"unset": "Name was not set!",
@@ -173,104 +201,13 @@ Like this you will get the following responses:
}
```
-## Accessing field's parent's data
-
-It is quite common to want to be able to access the data from the field's parent
-in a resolver. For example let's say that we want to define a `fullName` field
-on our `User`. We can define a new field with a resolver that combines its first
-and last names:
-
-```python+schema
-import strawberry
-
-
-@strawberry.type
-class User:
- first_name: str
- last_name: str
-
- @strawberry.field
- def full_name(self) -> str:
- return f"{self.first_name} {self.last_name}"
----
-type User {
- firstName: String!
- lastName: String!
- fullName: String!
-}
-```
-
-In the case of a decorated resolver you can use the _self_ parameter as you
-would do in a method on a normal Python class[^1].
-
-For resolvers defined as normal Python functions, you can use the special `root`
-parameter, when added to arguments of the function, Strawberry will pass to it
-the value of the parent:
-
-```python
-import strawberry
-
-
-def full_name(root: "User") -> str:
- return f"{root.first_name} {root.last_name}"
-
-
-@strawberry.type
-class User:
- first_name: str
- last_name: str
- full_name: str = strawberry.field(resolver=full_name)
-```
-
-For either decorated resolvers or Python functions, there is a third option
-which is to annotate one of the parameters with the type `Parent`. This is
-particularly useful if the parent resolver returns a type other than the
-strawberry type that the resolver is defined within, as it allows you to specify
-the type of the parent object. This comes up particularly often for resolvers
-that return ORMs:
-
-```python
-import dataclass
-
-import strawberry
-
-
-@dataclass
-class UserRow:
- id_: str
-
-
-@strawberry.type
-class User:
- @strawberry.field
- @staticmethod
- async def name(parent: strawberry.Parent[UserRow]) -> str:
- return f"User Number {parent.id}"
-
-
-@strawberry.type
-class Query:
- @strawberry.field
- def user(self) -> User:
- # Even though this method is annotated as returning type `User`,
- # which strawberry uses to define the GraphQL schema, we're
- # not actually required to return an object of that type. Whatever
- # object we do return will be passed on to child resolvers that
- # request it via the `self`, `root`, or `strawberry.Parent` parameter.
- # In this case, we return our ORM directly.
- #
- # Put differently, the GraphQL schema and associated resolvers come
- # from the type annotations, but the actual object passed to the
- # resolvers via `self`, `root`, or `strawberry.Parent` is whatever
- # the parent resolvers return, regardless of type.
- return UserRow(id_="1234")
-```
+
## Accessing execution information
Sometimes it is useful to access the information for the current execution
context. Strawberry allows to declare a parameter of type `Info` that will be
-automatically passed to the resolver. This parameter containes the information
+automatically passed to the resolver. This parameter contains the information
for the current execution context.
```python
@@ -278,7 +215,7 @@ import strawberry
from strawberry.types import Info
-def full_name(root: "User", info: Info) -> str:
+def full_name(root: "User", info: strawberry.Info) -> str:
return f"{root.first_name} {root.last_name} {info.field_name}"
@@ -313,8 +250,3 @@ Info objects contain information for the current execution context:
| path | `Path` | The path for the current field |
| selected_fields | `List[SelectedField]` | Additional information related to the current field |
| schema | `Schema` | The Strawberry schema instance |
-
-[^1]:
- see
- [this discussion](https://github.com/strawberry-graphql/strawberry/discussions/515)
- for more context around the self parameter.
diff --git a/docs/types/scalars.md b/docs/types/scalars.md
index b1d97f818d..ad9e6c26b5 100644
--- a/docs/types/scalars.md
+++ b/docs/types/scalars.md
@@ -5,16 +5,20 @@ title: Scalars
# Scalars
Scalar types represent concrete values at the leaves of a query. For example in
-the following query the name field will resolve to a scalar type (in this case
+the following query the `name` field will resolve to a scalar type (in this case
it's a `String` type):
-```graphql+response
+
+
+```graphql "name"
{
user {
name
}
}
----
+```
+
+```json '"name": "Patrick"'
{
"data": {
"user": {
@@ -24,6 +28,8 @@ it's a `String` type):
}
```
+
+
There are several built-in scalars, and you can define custom scalars too.
([Enums](/docs/types/enums) are also leaf values.) The built in scalars are:
@@ -45,15 +51,25 @@ There are several built-in scalars, and you can define custom scalars too.
- `UUID`, a [UUID](https://docs.python.org/3/library/uuid.html#uuid.UUID) value
serialized as a string
- `Void`, always null, maps to Pythonโs `None`
+- `JSON`, a JSON value as specified in
+ [ECMA-404](https://ecma-international.org/publications-and-standards/standards/ecma-404/)
+ standard, maps to Pythonโs `dict`
+- `Base16`, `Base32`, `Base64`, represents hexadecimal strings encoded with
+ `Base16`/`Base32`/`Base64`. As specified in
+ [RFC4648](https://datatracker.ietf.org/doc/html/rfc4648.html). Maps to
+ Pythonโs `str`
Fields can return built-in scalars by using the Python equivalent:
-```python+schema
+
+
+```python
import datetime
import decimal
import uuid
import strawberry
+
@strawberry.type
class Product:
id: uuid.UUID
@@ -65,7 +81,9 @@ class Product:
created_at: datetime.datetime
price: decimal.Decimal
void: None
----
+```
+
+```graphql
type Product {
id: UUID!
name: String!
@@ -79,9 +97,11 @@ type Product {
}
```
+
+
Scalar types can also be used as inputs:
-```python
+```python 'date_input: datetime.date'
import datetime
import strawberry
@@ -168,11 +188,15 @@ class Query:
return {"hello": {"a": 1}, "someNumbers": [1, 2, 3]}
```
-```graphql+response
+
+
+```graphql
query ExampleDataQuery {
data
}
----
+```
+
+```json
{
"data": {
"hello": {
@@ -183,6 +207,8 @@ query ExampleDataQuery {
}
```
+
+
The `JSON` scalar type is available in `strawberry.scalars`
@@ -293,10 +319,12 @@ You can adapt your schema to automatically use this scalar for all integers by
using the `scalar_overrides` parameter
-Only use this override if you expect most of your integers to be 64-bit. Since most GraphQL schemas
-follow standardized design patterns and most clients require additional effort to handle all numbers
-as strings, it makes more sense to reserve BigInt for numbers that actually exceed the 32-bit limit.
-You can achieve this by annotating `BigInt` instead of `int` in your resolvers handling large python integers.
+ Only use this override if you expect most of your integers to be 64-bit. Since
+ most GraphQL schemas follow standardized design patterns and most clients
+ require additional effort to handle all numbers as strings, it makes more
+ sense to reserve BigInt for numbers that actually exceed the 32-bit limit. You
+ can achieve this by annotating `BigInt` instead of `int` in your resolvers
+ handling large python integers.
```python
diff --git a/docs/types/schema-configurations.md b/docs/types/schema-configurations.md
index 8a6811dc7f..099153238e 100644
--- a/docs/types/schema-configurations.md
+++ b/docs/types/schema-configurations.md
@@ -5,8 +5,7 @@ title: Schema Configurations
# Schema Configurations
Strawberry allows to customise how the schema is generated by passing
-configurations. At the moment we only allow to disable auto camel casing of
-fields and arguments names.
+configurations.
To customise the schema you can create an instance of `StrawberryConfig`, as
shown in the example below:
@@ -33,3 +32,76 @@ type Query {
example_field: String!
}
```
+
+## Available configurations
+
+Here's a list of the available configurations:
+
+### auto_camel_case
+
+By default Strawberry will convert the field names to camel case, so a field
+like `example_field` will be converted to `exampleField`. You can disable this
+feature by setting `auto_camel_case` to `False`.
+
+```python
+schema = strawberry.Schema(query=Query, config=StrawberryConfig(auto_camel_case=False))
+```
+
+### default_resolver
+
+By default Strawberry will use the `getattr` function as the default resolver.
+You can customise this by setting the `default_resolver` configuration.
+
+This can be useful in cases you want to allow returning a dictionary from a
+resolver.
+
+```python
+import strawberry
+
+from strawberry.schema.config import StrawberryConfig
+
+
+def custom_resolver(obj, field):
+ try:
+ return obj[field]
+ except (KeyError, TypeError):
+ return getattr(obj, field)
+
+
+@strawberry.type
+class User:
+ name: str
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def user(self, info) -> User: # this won't type check, but will work at runtime
+ return {"name": "Patrick"}
+
+
+schema = strawberry.Schema(
+ query=Query, config=StrawberryConfig(default_resolver=custom_resolver)
+)
+```
+
+### relay_max_results
+
+By default Strawberry's max limit for relay connections is 100. You can
+customise this by setting the `relay_max_results` configuration.
+
+```python
+schema = strawberry.Schema(query=Query, config=StrawberryConfig(relay_max_results=50))
+```
+
+### disable_field_suggestions
+
+By default Strawberry will suggest fields when a field is not found in the
+schema. You can disable this feature by setting `disable_field_suggestions` to
+`True`.
+
+```python
+schema = strawberry.Schema(
+ query=Query, config=StrawberryConfig(disable_field_suggestions=True)
+)
+```
diff --git a/docs/types/schema.md b/docs/types/schema.md
index 8330e18239..f82f4fe7cb 100644
--- a/docs/types/schema.md
+++ b/docs/types/schema.md
@@ -272,3 +272,40 @@ The `get_fields` method is only called once when creating the schema, this is
not intended to be used to dynamically customise the schema.
+
+## Deprecating fields
+
+Fields can be deprecated using the argument `deprecation_reason`.
+
+
+
+This does not prevent the field from being used, it's only for documentation.
+See:
+[GraphQL field deprecation](https://spec.graphql.org/June2018/#sec-Field-Deprecation).
+
+
+
+
+
+```python
+import strawberry
+import datetime
+from typing import Optional
+
+
+@strawberry.type
+class User:
+ name: str
+ dob: datetime.date
+ age: Optional[int] = strawberry.field(deprecation_reason="Age is deprecated")
+```
+
+```graphql
+type User {
+ name: String!
+ dob: Date!
+ age: Int @deprecated(reason: "Age is deprecated")
+}
+```
+
+
diff --git a/docs/types/union.md b/docs/types/union.md
index e6734da0e5..a63f316b9e 100644
--- a/docs/types/union.md
+++ b/docs/types/union.md
@@ -49,26 +49,34 @@ In Strawberry there are two ways to define a union:
You can use the `Union` type from the `typing` module which will autogenerate
the type name from the names of the union members:
-```python+schema
+
+
+```python
from typing import Union
import strawberry
+
@strawberry.type
class Audio:
duration: int
+
@strawberry.type
class Video:
thumbnail_url: str
+
@strawberry.type
class Image:
src: str
+
@strawberry.type
class Query:
latest_media: Union[Audio, Video, Image]
----
+```
+
+```graphql
union AudioVideoImage = Audio | Video | Image
type Query {
@@ -88,18 +96,25 @@ type Image {
}
```
+
+
Or if you need to specify a name or a description for a union you can use
Annotated with the `strawberry.union` function:
-```python+schema
+
+
+```python
import strawberry
from typing import Union, Annotated
+
@strawberry.type
class Query:
latest_media: Annotated[Union[Audio, Video, Image], strawberry.union("MediaItem")]
----
+```
+
+```graphql
union MediaItem = Audio | Video | Image
type Query {
@@ -119,6 +134,8 @@ type Image {
}
```
+
+
## Resolving a union
When a fieldโs return type is a union, GraphQL needs to know what specific
@@ -150,25 +167,30 @@ Python's `typing.Union` does not really support this use case, but using
Annotated and `strawberry.union` you can tell Strawberry that you want to define
a union with only one member:
-```python+schema
+
+
+```python
import strawberry
from typing import Annotated
+
@strawberry.type
class Audio:
duration: int
+
@strawberry.type
class Query:
latest_media: Annotated[Audio, strawberry.union("MediaItem")]
+```
-
-schema = strawberry.Schema(query=Query)
----
+```graphql
union MediaItem = Audio
type Query {
latestMedia: MediaItem!
}
```
+
+
diff --git a/federation-compatibility/schema.py b/federation-compatibility/schema.py
index 8c08e1d47d..b3d5c5bafa 100644
--- a/federation-compatibility/schema.py
+++ b/federation-compatibility/schema.py
@@ -115,8 +115,7 @@ def get_product_by_sku_and_variation(sku: str, variation: dict) -> Optional["Pro
compose=True,
import_url="https://myspecs.dev/myCustomDirective/v1.0",
)
-class Custom:
- ...
+class Custom: ...
@strawberry.federation.type(extend=True, keys=["email"])
diff --git a/noxfile.py b/noxfile.py
index 12f420751e..406ad3c6b9 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -1,3 +1,6 @@
+import itertools
+from typing import Any, Callable, List
+
import nox
from nox_poetry import Session, session
@@ -5,7 +8,10 @@
nox.options.error_on_external_run = True
PYTHON_VERSIONS = ["3.12", "3.11", "3.10", "3.9", "3.8"]
-
+GQL_CORE_VERSIONS = [
+ "3.2.3",
+ "3.3.0",
+]
COMMON_PYTEST_OPTIONS = [
"--cov=.",
@@ -15,8 +21,7 @@
"auto",
"--showlocals",
"-vv",
- "--ignore=tests/mypy",
- "--ignore=tests/pyright",
+ "--ignore=tests/typecheckers",
"--ignore=tests/cli",
"--ignore=tests/benchmarks",
"--ignore=tests/experimental/pydantic",
@@ -38,10 +43,34 @@
]
+def _install_gql_core(session: Session, version: str) -> None:
+ # hack for better workflow names # noqa: FIX004
+ if version == "3.2.3":
+ session._session.install(f"graphql-core=={version}") # type: ignore
+ session._session.install(
+ "https://github.com/graphql-python/graphql-core/archive/876aef67b6f1e1f21b3b5db94c7ff03726cb6bdf.zip"
+ ) # type: ignore
+
+
+gql_core_parametrize = nox.parametrize(
+ "gql_core",
+ GQL_CORE_VERSIONS,
+)
+
+
+def with_gql_core_parametrize(name: str, params: List[str]) -> Callable[[Any], Any]:
+ # github cache doesn't support comma in the name, this is a workaround.
+ arg_names = f"{name}, gql_core"
+ combinations = list(itertools.product(params, GQL_CORE_VERSIONS))
+ ids = [f"{name}-{comb[0]}__graphql-core-{comb[1]}" for comb in combinations]
+ return lambda fn: nox.parametrize(arg_names, combinations, ids=ids)(fn)
+
+
@session(python=PYTHON_VERSIONS, name="Tests", tags=["tests"])
-def tests(session: Session) -> None:
+@gql_core_parametrize
+def tests(session: Session, gql_core: str) -> None:
session.run_always("poetry", "install", external=True)
-
+ _install_gql_core(session, gql_core)
markers = (
["-m", f"not {integration}", f"--ignore=tests/{integration}"]
for integration in INTEGRATIONS
@@ -56,10 +85,10 @@ def tests(session: Session) -> None:
@session(python=["3.11", "3.12"], name="Django tests", tags=["tests"])
-@nox.parametrize("django", ["4.2.0", "4.1.0", "4.0.0", "3.2.0"])
-def tests_django(session: Session, django: str) -> None:
+@with_gql_core_parametrize("django", ["4.2.0", "4.1.0", "4.0.0", "3.2.0"])
+def tests_django(session: Session, django: str, gql_core: str) -> None:
session.run_always("poetry", "install", external=True)
-
+ _install_gql_core(session, gql_core)
session._session.install(f"django~={django}") # type: ignore
session._session.install("pytest-django") # type: ignore
@@ -67,17 +96,17 @@ def tests_django(session: Session, django: str) -> None:
@session(python=["3.11"], name="Starlette tests", tags=["tests"])
-@nox.parametrize("starlette", ["0.28.0", "0.27.0", "0.26.1"])
-def tests_starlette(session: Session, starlette: str) -> None:
+@with_gql_core_parametrize("starlette", ["0.28.0", "0.27.0", "0.26.1"])
+def tests_starlette(session: Session, starlette: str, gql_core: str) -> None:
session.run_always("poetry", "install", external=True)
session._session.install(f"starlette=={starlette}") # type: ignore
-
+ _install_gql_core(session, gql_core)
session.run("pytest", *COMMON_PYTEST_OPTIONS, "-m", "asgi")
@session(python=["3.11"], name="Test integrations", tags=["tests"])
-@nox.parametrize(
+@with_gql_core_parametrize(
"integration",
[
"aiohttp",
@@ -91,11 +120,11 @@ def tests_starlette(session: Session, starlette: str) -> None:
"litestar",
],
)
-def tests_integrations(session: Session, integration: str) -> None:
+def tests_integrations(session: Session, integration: str, gql_core: str) -> None:
session.run_always("poetry", "install", external=True)
session._session.install(integration) # type: ignore
-
+ _install_gql_core(session, gql_core)
if integration == "aiohttp":
session._session.install("pytest-aiohttp") # type: ignore
elif integration == "channels":
@@ -107,13 +136,13 @@ def tests_integrations(session: Session, integration: str) -> None:
session.run("pytest", *COMMON_PYTEST_OPTIONS, "-m", integration)
-@session(python=PYTHON_VERSIONS, name="Pydantic tests", tags=["tests"])
-@nox.parametrize("pydantic", ["1.10", "2.0.3"])
-def test_pydantic(session: Session, pydantic: str) -> None:
+@session(python=PYTHON_VERSIONS, name="Pydantic tests", tags=["tests", "pydantic"])
+@with_gql_core_parametrize("pydantic", ["1.10", "2.7.0", "2.8.0"])
+def test_pydantic(session: Session, pydantic: str, gql_core: str) -> None:
session.run_always("poetry", "install", external=True)
session._session.install(f"pydantic~={pydantic}") # type: ignore
-
+ _install_gql_core(session, gql_core)
session.run(
"pytest",
"--cov=.",
@@ -125,42 +154,24 @@ def test_pydantic(session: Session, pydantic: str) -> None:
)
-@session(python=PYTHON_VERSIONS, name="Mypy tests")
-def tests_mypy(session: Session) -> None:
- session.run_always("poetry", "install", "--with", "integrations", external=True)
-
- session.run(
- "pytest",
- "--cov=.",
- "--cov-append",
- "--cov-report=xml",
- "tests/mypy",
- "-vv",
- )
-
-
-@session(python=PYTHON_VERSIONS, name="Pyright tests", tags=["tests"])
-def tests_pyright(session: Session) -> None:
+@session(python=PYTHON_VERSIONS, name="Type checkers tests", tags=["tests"])
+def tests_typecheckers(session: Session) -> None:
session.run_always("poetry", "install", external=True)
+
session.install("pyright")
+ session.install("pydantic")
+ session.install("git+https://github.com/python/mypy.git#master")
session.run(
"pytest",
"--cov=.",
"--cov-append",
"--cov-report=xml",
- "tests/pyright",
+ "tests/typecheckers",
"-vv",
)
-@session(name="Mypy", tags=["lint"])
-def mypy(session: Session) -> None:
- session.run_always("poetry", "install", "--with", "integrations", external=True)
-
- session.run("mypy", "--config-file", "mypy.ini")
-
-
@session(python=PYTHON_VERSIONS, name="CLI tests", tags=["tests"])
def tests_cli(session: Session) -> None:
session.run_always("poetry", "install", external=True)
@@ -176,3 +187,11 @@ def tests_cli(session: Session) -> None:
"tests/cli",
"-vv",
)
+
+
+@session(name="Mypy", tags=["lint"])
+def mypy(session: Session) -> None:
+ session.run_always("poetry", "install", "--with", "integrations", external=True)
+ session.install("mypy")
+
+ session.run("mypy", "--config-file", "mypy.ini")
diff --git a/poetry.lock b/poetry.lock
index ce48bec520..01a194fb62 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,99 +1,99 @@
-# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "aiofiles"
-version = "23.2.1"
+version = "24.1.0"
description = "File support for asyncio."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"},
- {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
+ {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"},
+ {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
]
[[package]]
name = "aiohttp"
-version = "3.9.3"
+version = "3.9.5"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.8"
files = [
- {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"},
- {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"},
- {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"},
- {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"},
- {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"},
- {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"},
- {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"},
- {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"},
- {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"},
- {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"},
- {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"},
- {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"},
- {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"},
- {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"},
- {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"},
- {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"},
- {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"},
- {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"},
- {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"},
- {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"},
- {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"},
- {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"},
- {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"},
- {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"},
- {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"},
- {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"},
- {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"},
- {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"},
- {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"},
- {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"},
- {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"},
- {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"},
- {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"},
- {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"},
- {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"},
- {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"},
- {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"},
- {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"},
- {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"},
- {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"},
- {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"},
- {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"},
- {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"},
- {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"},
- {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"},
- {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"},
- {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"},
- {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"},
- {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"},
- {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"},
- {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"},
- {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"},
- {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"},
- {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"},
- {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"},
- {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"},
- {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"},
- {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"},
- {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"},
- {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"},
- {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"},
- {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"},
- {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"},
- {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"},
- {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"},
- {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"},
- {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"},
- {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"},
- {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"},
- {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"},
- {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"},
- {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"},
- {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"},
- {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"},
- {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"},
- {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"},
+ {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
+ {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"},
+ {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"},
+ {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"},
+ {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"},
+ {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"},
+ {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"},
+ {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"},
+ {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"},
+ {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"},
+ {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"},
+ {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"},
+ {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"},
+ {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"},
+ {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"},
+ {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"},
+ {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"},
+ {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"},
+ {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"},
]
[package.dependencies]
@@ -123,13 +123,13 @@ frozenlist = ">=1.1.0"
[[package]]
name = "annotated-types"
-version = "0.6.0"
+version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
- {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
- {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[package.dependencies]
@@ -148,13 +148,13 @@ files = [
[[package]]
name = "anyio"
-version = "4.2.0"
+version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
- {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"},
- {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"},
+ {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
+ {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[package.dependencies]
@@ -170,13 +170,13 @@ trio = ["trio (>=0.23)"]
[[package]]
name = "argcomplete"
-version = "3.2.2"
+version = "3.4.0"
description = "Bash tab completion for argparse"
optional = false
python-versions = ">=3.8"
files = [
- {file = "argcomplete-3.2.2-py3-none-any.whl", hash = "sha256:e44f4e7985883ab3e73a103ef0acd27299dbfe2dfed00142c35d4ddd3005901d"},
- {file = "argcomplete-3.2.2.tar.gz", hash = "sha256:f3e49e8ea59b4026ee29548e24488af46e30c9de57d48638e24f54a1ea1000a2"},
+ {file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"},
+ {file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"},
]
[package.extras]
@@ -184,13 +184,13 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
[[package]]
name = "asgiref"
-version = "3.7.2"
+version = "3.8.1"
description = "ASGI specs, helper code, and adapters"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
- {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
+ {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
+ {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
]
[package.dependencies]
@@ -199,6 +199,24 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""}
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+[[package]]
+name = "asttokens"
+version = "2.4.1"
+description = "Annotate AST trees with source code positions"
+optional = false
+python-versions = "*"
+files = [
+ {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
+ {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
+]
+
+[package.dependencies]
+six = ">=1.12.0"
+
+[package.extras]
+astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
+test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
+
[[package]]
name = "astunparse"
version = "1.6.3"
@@ -318,6 +336,52 @@ files = [
[package.extras]
tzdata = ["tzdata"]
+[[package]]
+name = "black"
+version = "24.4.2"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
+ {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
+ {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
+ {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
+ {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
+ {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
+ {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
+ {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
+ {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
+ {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
+ {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
+ {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
+ {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
+ {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
+ {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
+ {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
+ {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
+ {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
+ {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
+ {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
+ {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
+ {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
[[package]]
name = "blessed"
version = "1.20.0"
@@ -336,24 +400,24 @@ wcwidth = ">=0.1.4"
[[package]]
name = "blinker"
-version = "1.7.0"
+version = "1.8.2"
description = "Fast, simple object-to-object and broadcast signaling"
optional = false
python-versions = ">=3.8"
files = [
- {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
- {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
+ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
+ {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
]
[[package]]
name = "botocore"
-version = "1.34.35"
+version = "1.34.147"
description = "Low-level, data-driven core of boto 3."
optional = false
-python-versions = ">= 3.8"
+python-versions = ">=3.8"
files = [
- {file = "botocore-1.34.35-py3-none-any.whl", hash = "sha256:b67b8c865973202dc655a493317ae14b33d115e49ed6960874eb05d950167b37"},
- {file = "botocore-1.34.35.tar.gz", hash = "sha256:8a2b53ab772584a5f7e2fe1e4a59028b0602cfef8e39d622db7c6b670e4b1ee6"},
+ {file = "botocore-1.34.147-py3-none-any.whl", hash = "sha256:be94a2f4874b1d1705cae2bd512c475047497379651678593acb6c61c50d91de"},
+ {file = "botocore-1.34.147.tar.gz", hash = "sha256:2e8f000b77e4ca345146cb2edab6403769a517b564f627bb084ab335417f3dbe"},
]
[package.dependencies]
@@ -361,34 +425,35 @@ jmespath = ">=0.7.1,<2.0.0"
python-dateutil = ">=2.1,<3.0.0"
urllib3 = [
{version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""},
- {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""},
+ {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""},
]
[package.extras]
-crt = ["awscrt (==0.19.19)"]
+crt = ["awscrt (==0.20.11)"]
[[package]]
name = "build"
-version = "1.0.3"
+version = "1.2.1"
description = "A simple, correct Python build frontend"
optional = false
-python-versions = ">= 3.7"
+python-versions = ">=3.8"
files = [
- {file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"},
- {file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"},
+ {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"},
+ {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"},
]
[package.dependencies]
colorama = {version = "*", markers = "os_name == \"nt\""}
-importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
-packaging = ">=19.0"
+importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""}
+packaging = ">=19.1"
pyproject_hooks = "*"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
-test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
-typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
+test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
+typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
+uv = ["uv (>=0.1.18)"]
virtualenv = ["virtualenv (>=20.0.35)"]
[[package]]
@@ -407,22 +472,22 @@ typing-extensions = {version = "*", markers = "python_version < \"3.10\""}
[[package]]
name = "cachecontrol"
-version = "0.13.1"
+version = "0.14.0"
description = "httplib2 caching for requests"
optional = false
python-versions = ">=3.7"
files = [
- {file = "cachecontrol-0.13.1-py3-none-any.whl", hash = "sha256:95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4"},
- {file = "cachecontrol-0.13.1.tar.gz", hash = "sha256:f012366b79d2243a6118309ce73151bf52a38d4a5dac8ea57f09bd29087e506b"},
+ {file = "cachecontrol-0.14.0-py3-none-any.whl", hash = "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0"},
+ {file = "cachecontrol-0.14.0.tar.gz", hash = "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938"},
]
[package.dependencies]
filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""}
-msgpack = ">=0.5.2"
+msgpack = ">=0.5.2,<2.0.0"
requests = ">=2.16.0"
[package.extras]
-dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "mypy", "pytest", "pytest-cov", "sphinx", "tox", "types-redis", "types-requests"]
+dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "furo", "mypy", "pytest", "pytest-cov", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"]
filecache = ["filelock (>=3.8.0)"]
redis = ["redis (>=2.10.5)"]
@@ -453,86 +518,74 @@ ujson = ["ujson (>=5.7.0)"]
[[package]]
name = "certifi"
-version = "2024.2.2"
+version = "2024.7.4"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
- {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
- {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
+ {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
]
[[package]]
name = "cffi"
-version = "1.15.1"
+version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
- {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
- {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
- {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
- {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
- {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
- {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
- {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
- {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
- {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
- {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
- {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
- {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
- {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
- {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
- {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
- {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
- {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
- {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[package.dependencies]
@@ -540,13 +593,13 @@ pycparser = "*"
[[package]]
name = "chalice"
-version = "1.30.0"
+version = "1.31.2"
description = "Microframework"
optional = false
python-versions = "*"
files = [
- {file = "chalice-1.30.0-py3-none-any.whl", hash = "sha256:a7cb5b44b35cfcd86a6d9abfeb5e1c06360adb9cba218787085e0a877f13938f"},
- {file = "chalice-1.30.0.tar.gz", hash = "sha256:76b731850b71e01c6a2d0e4466e2d6780d56f19347fcb73b93994303d343c142"},
+ {file = "chalice-1.31.2-py3-none-any.whl", hash = "sha256:c076dc6bd73fa77b6d4d0a687e66f33eb2e26632b7208cd8164eb42df87c92f1"},
+ {file = "chalice-1.31.2.tar.gz", hash = "sha256:57f3632b3fcb642e6a5932d8ee1d6975b3858f589306706881941e31828fb557"},
]
[package.dependencies]
@@ -554,11 +607,10 @@ botocore = ">=1.14.0,<2.0.0"
click = ">=7,<9.0"
inquirer = ">=2.7.0,<3.0.0"
jmespath = ">=0.9.3,<2.0.0"
-pip = ">=9,<23.4"
+pip = ">=9,<24.1"
pyyaml = ">=5.3.1,<7.0.0"
setuptools = "*"
six = ">=1.10.0,<2.0.0"
-typing-extensions = ">=4.0.0,<5.0.0"
wheel = "*"
[package.extras]
@@ -568,18 +620,18 @@ event-file-poller = ["watchdog (==2.3.1)"]
[[package]]
name = "channels"
-version = "4.0.0"
+version = "4.1.0"
description = "Brings async, event-driven capabilities to Django 3.2 and up."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "channels-4.0.0-py3-none-any.whl", hash = "sha256:2253334ac76f67cba68c2072273f7e0e67dbdac77eeb7e318f511d2f9a53c5e4"},
- {file = "channels-4.0.0.tar.gz", hash = "sha256:0ce53507a7da7b148eaa454526e0e05f7da5e5d1c23440e4886cf146981d8420"},
+ {file = "channels-4.1.0-py3-none-any.whl", hash = "sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48"},
+ {file = "channels-4.1.0.tar.gz", hash = "sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d"},
]
[package.dependencies]
-asgiref = ">=3.5.0,<4"
-Django = ">=3.2"
+asgiref = ">=3.6.0,<4"
+Django = ">=4.2"
[package.extras]
daphne = ["daphne (>=4.0.0)"]
@@ -754,63 +806,63 @@ files = [
[[package]]
name = "coverage"
-version = "7.4.1"
+version = "7.6.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
- {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
- {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
- {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
- {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
- {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
- {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
- {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
- {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
- {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
- {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
- {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
- {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
- {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
- {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
- {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
- {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
- {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
- {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
- {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
- {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
- {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
- {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
- {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
- {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
- {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
- {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
- {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
- {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
- {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
- {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
- {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
- {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
- {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
- {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
- {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
- {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
- {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
- {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
- {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
- {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
- {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
- {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
- {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
- {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
- {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
- {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
- {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
- {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
- {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
- {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
- {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
+ {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"},
+ {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"},
+ {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"},
+ {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"},
+ {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"},
+ {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"},
+ {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"},
+ {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"},
+ {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"},
+ {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"},
+ {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"},
+ {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"},
+ {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"},
+ {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"},
+ {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"},
+ {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"},
+ {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"},
+ {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"},
+ {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"},
+ {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"},
+ {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"},
+ {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"},
+ {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"},
+ {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"},
+ {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"},
+ {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"},
+ {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"},
+ {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"},
+ {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"},
+ {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"},
+ {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"},
+ {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"},
]
[package.dependencies]
@@ -832,43 +884,38 @@ files = [
[[package]]
name = "cryptography"
-version = "42.0.2"
+version = "43.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
- {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"},
- {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"},
- {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"},
- {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"},
- {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"},
- {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"},
- {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"},
- {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"},
- {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"},
- {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"},
- {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"},
- {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"},
- {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"},
- {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"},
- {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"},
- {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"},
- {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"},
- {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"},
- {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"},
- {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"},
- {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"},
- {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"},
- {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"},
- {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"},
- {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"},
- {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"},
- {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"},
- {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"},
- {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"},
- {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"},
- {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"},
- {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"},
+ {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
+ {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
+ {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
+ {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
+ {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
+ {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
+ {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
]
[package.dependencies]
@@ -881,18 +928,18 @@ nox = ["nox"]
pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"]
-test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
[[package]]
name = "daphne"
-version = "4.0.0"
+version = "4.1.2"
description = "Django ASGI (HTTP/WebSocket) server"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "daphne-4.0.0-py3-none-any.whl", hash = "sha256:a288ece46012b6b719c37150be67c69ebfca0793a8521bf821533bad983179b2"},
- {file = "daphne-4.0.0.tar.gz", hash = "sha256:cce9afc8f49a4f15d4270b8cfb0e0fe811b770a5cc795474e97e4da287497666"},
+ {file = "daphne-4.1.2-py3-none-any.whl", hash = "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a"},
+ {file = "daphne-4.1.2.tar.gz", hash = "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761"},
]
[package.dependencies]
@@ -905,94 +952,100 @@ tests = ["django", "hypothesis", "pytest", "pytest-asyncio"]
[[package]]
name = "ddsketch"
-version = "2.0.4"
+version = "3.0.1"
description = "Distributed quantile sketches"
optional = false
-python-versions = ">=2.7"
+python-versions = ">=3.7"
files = [
- {file = "ddsketch-2.0.4-py3-none-any.whl", hash = "sha256:3227a270fd686a29d3a7128f9352ccf852314410380fc11384356f1ae2a75938"},
- {file = "ddsketch-2.0.4.tar.gz", hash = "sha256:32f7314077fec8747d4faebaec2c854b5ffc399c5f552f73fa94024f48d74d64"},
+ {file = "ddsketch-3.0.1-py3-none-any.whl", hash = "sha256:6d047b455fe2837c43d366ff1ae6ba0c3166e15499de8688437a75cea914224e"},
+ {file = "ddsketch-3.0.1.tar.gz", hash = "sha256:aa8f20b2965e61731ca4fee2ca9c209f397f5bbb23f9d192ec8bd7a2f5bd9824"},
]
[package.dependencies]
-protobuf = {version = ">=3.0.0", markers = "python_version >= \"3.7\""}
six = "*"
+[package.extras]
+serialization = ["protobuf (>=3.0.0)"]
+
[[package]]
name = "ddtrace"
-version = "2.5.2"
+version = "2.9.3"
description = "Datadog APM client library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "ddtrace-2.5.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:f918538a6adb33696be653d343ee318b16ea977376d9b7214d14fe97c42e9bd9"},
- {file = "ddtrace-2.5.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f56735eb636d3ab2f7224f261d3a6bd43f884e9901d68407d485ea65f3dc0f46"},
- {file = "ddtrace-2.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72d21fe6842a8d80c8765dd699153a2475ae2d49e82e10f9668eadb08b454040"},
- {file = "ddtrace-2.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6e48caf63506d7ac3df7caa955b6258de91c1a1f55149506ab8ac36143770b9"},
- {file = "ddtrace-2.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3f26e04ba7521f6885d871fd6266fedc0a7ccf2637b85579c058927404bad7"},
- {file = "ddtrace-2.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:15d78b0cd5d2090c063031d76e933b8b24e043d524a6091a751cf57b0fab025f"},
- {file = "ddtrace-2.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee76beaf87695f2204b0c2c2a3664b39f3483b7a8447b28e5e2bcc899861b3eb"},
- {file = "ddtrace-2.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8840f0e82d6dca3888bd06e7ab0ca6d39009f3cd3475028d8bc03c939127afc2"},
- {file = "ddtrace-2.5.2-cp310-cp310-win32.whl", hash = "sha256:a34ccab0c8991c5fc5252d5cd6e88852cd7f77c8bf838de84e70b4a3bfacaad4"},
- {file = "ddtrace-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:ffa4f5779c7000fe5960156bd15339184355b30a661b0955799cae50da5d03a7"},
- {file = "ddtrace-2.5.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ea2740a3d61876cb07b271af444e98cdc8b730497cfcddbc3794c7a7441b8d15"},
- {file = "ddtrace-2.5.2-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:62e775ba9d2a2b5f952a6609029e965057bdd852ccd6e53b55c0f82ae83aa542"},
- {file = "ddtrace-2.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30186112f156a564efda5e2018240b55baee7664897ca5fc35c452d032a77185"},
- {file = "ddtrace-2.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9dccdc69de364cffc2b892280724c78cb54db151452a0b6d1b4a89b6f060c44"},
- {file = "ddtrace-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa2543c2303ab325af7794f2a8a420133cd9222e70bfbce3869da146fc5e2ba"},
- {file = "ddtrace-2.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aa2e64f79ada9f2fd5307cd0eba726d8585e47b0282fb9463aaa4b267265e94a"},
- {file = "ddtrace-2.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:37b4d55a5be59530e6e5761a36d727aee812be69c81b00ee0182eb62be6f3b75"},
- {file = "ddtrace-2.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d97f990d2322a23e82203cc5a2aa694fb0d42541a44bb120390e6598a63e5f5"},
- {file = "ddtrace-2.5.2-cp311-cp311-win32.whl", hash = "sha256:5d3f1bc3ce87fbcf2256197178179ef681df720ebbc39b0559bda00247744533"},
- {file = "ddtrace-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:a50057085b0972e695bb1ef3042f6cd6a1a3b12111fac4985942f2dbbcf8ac2f"},
- {file = "ddtrace-2.5.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b923b099b9a1e50f01ce8bcd4d11e3255a48c71f3e6314dd9a482baed0a87ed6"},
- {file = "ddtrace-2.5.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:512d3975b1657c706ca9c84373e5fce323f6fc94bfac33c30876ad8d55e0ea71"},
- {file = "ddtrace-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c54bc474c70151d5a141061b6c20a1efabdf458e4239c790d45fa12a13b8e7d"},
- {file = "ddtrace-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5fb2bbd38dc46ba6a7ea1031c4751b1ca888be5fac8a42049ebc2517707c00d"},
- {file = "ddtrace-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa6fb6bcfb3810d8f0882e489e7d2ef4dd3a92b452cfdd8d1fd4703dc496b17"},
- {file = "ddtrace-2.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3f4eed40d978352c7371804ecb68bbe9e55967bb904bd03b0568554e0b6b92cf"},
- {file = "ddtrace-2.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:57606af5380888e2e7cc67b7c4fa5e1bc51d29c48f004b4be0cbe1b319fddc75"},
- {file = "ddtrace-2.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ee8d0259a004964a8eddb394aa84a5754435d4270cd2041e6559c9e68fa49141"},
- {file = "ddtrace-2.5.2-cp312-cp312-win32.whl", hash = "sha256:4df564e620ec7e657fcdb0d5bf1231aa1357bf49b736f0d9e9f6df17a23fc569"},
- {file = "ddtrace-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:637f16af1c84566bde044798312c67bc5676df949632ab02e740440558f2a598"},
- {file = "ddtrace-2.5.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:d24841a9390f3e169edcaf1ca5ac80599062e66dee43a510decb25e779b6f7b4"},
- {file = "ddtrace-2.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49aa4e0210862e829e09569de2e2f34ac17c5e246567c5b6662ec21e2a06d938"},
- {file = "ddtrace-2.5.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:985738fe875b11f05dfa2b1f21a619d499344eb740f63e01d6eae1fb29eb949b"},
- {file = "ddtrace-2.5.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8814321822e4afc95ac86fbc476dc20d78dd4b1d510c02606459df4580093d18"},
- {file = "ddtrace-2.5.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ad6c0ae7baff9d00c689834aec0627274d681ed1d2a8ae627348a6191e8d32ec"},
- {file = "ddtrace-2.5.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa596f2e80c525a2310e605bfa3fa6ba6790b2ae90c02e47ceee0e62ceae17a6"},
- {file = "ddtrace-2.5.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6bdfae9fa03af334820678196a4895450d0b6bd9f1b5119d42ddbd327a55fcce"},
- {file = "ddtrace-2.5.2-cp37-cp37m-win32.whl", hash = "sha256:227bb0391d310e0d5a54505c7ab59f9692a5db91dc492373489bc45726980e1d"},
- {file = "ddtrace-2.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6e55c4738b58b4452933204305243e19000f6f283af93bf51b63382100cb8f21"},
- {file = "ddtrace-2.5.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4d9e7a9e26c38ae1e368f5d820e78459ff2d39689f40d4a3db185ddb3686c383"},
- {file = "ddtrace-2.5.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:c361ea11b442b04d8e011528205ed65b926d71d18f38d372270204eabf49b068"},
- {file = "ddtrace-2.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aafd86eeea622cd0e8cf6b63632efc67a52a32317d2a376382ef6170d383c9f"},
- {file = "ddtrace-2.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ff039635470ba483ed448baaf6337d85a731b17af62fef06dfa811f761f374f"},
- {file = "ddtrace-2.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f1cb3bea1170410d603f9d557918c24d4d8783659c03817daea6352d9f37f9"},
- {file = "ddtrace-2.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7351500241eb24c7d789b371a6860ca2b0e2db1ff9d317089153b562a3a461e1"},
- {file = "ddtrace-2.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a2cfc6ee800890556e404b94d13680c83952efa5d3dafa72ef8cb08a8782f874"},
- {file = "ddtrace-2.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96a791f03b62ebdb9f3e635a0e93711149123a8fc1c1c152be0d1cdb5d8e6359"},
- {file = "ddtrace-2.5.2-cp38-cp38-win32.whl", hash = "sha256:6c61e72abec3f2f6b46e53712a32a971de1b6a9be657d5ebeff1334f6146babc"},
- {file = "ddtrace-2.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:b93d8b536f5fc45a72bb2785051dc729f4d581ef2d69ed10bccae6a7487477b2"},
- {file = "ddtrace-2.5.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:38cbcb7b4ff1371480b29228d2b8e570e7d7b386a7632b96f9600135ec3eb9db"},
- {file = "ddtrace-2.5.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a270d128c6067f52a76ecbb658fae3f4d3bd4888baa9e6159ff82b6de14c53be"},
- {file = "ddtrace-2.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e59f3958016fcec5eb16abd7979a9ec4d850733e2a03b878b096277fc092784"},
- {file = "ddtrace-2.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:066403f0e00a8de09c8187037befe7463d1fab5d8178b62a07c2542792710d14"},
- {file = "ddtrace-2.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbcbf24bca8497f1412ec438fbdc94847aef9e86092ffd4f8626bbe6d278d33"},
- {file = "ddtrace-2.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d34f8da809e2783770a6c88396b3653fb12a4196e9b5f16b8c10f37bbf2b7b31"},
- {file = "ddtrace-2.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9eaca41664dd0c2bd7257fe2e91c7e46718b20591bfaa0b5c01c39b599115f88"},
- {file = "ddtrace-2.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f4b67e02ba5c316711719dcfc15e94f47684e7af1785289d016a29a2c664827"},
- {file = "ddtrace-2.5.2-cp39-cp39-win32.whl", hash = "sha256:9bbd675d73aae6516e02a86cb830778771dafb0e182d5a122270ccd82ee77eed"},
- {file = "ddtrace-2.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:e93f3f5d3d57beb492b04286c758be65495908bd313df6f56865ad7af222e49e"},
- {file = "ddtrace-2.5.2.tar.gz", hash = "sha256:5addeb19eea5ebdc23c493e5635f4c8737795b48ba637117a1895f31b900985f"},
+ {file = "ddtrace-2.9.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:2153208fbd7f9092ec6561a3b8f53eaeedaa274902a6feff1044c00087801c56"},
+ {file = "ddtrace-2.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:4f895d1b786d8cdefd84418a6607cc33c4032a987a1291cc3c72c66febe0c251"},
+ {file = "ddtrace-2.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08634a6ff2e48db30dc57505d04119981ebd9fd33a8dfb0f4db05f659b0b9041"},
+ {file = "ddtrace-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbbbffec8db868c26c4d28590a739ec0edab7da5ef8c983efc25bb91dc4004c0"},
+ {file = "ddtrace-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1de0ec2ffdd11d73048f5e98b9004ece0bb76c035bc492f33adc64e43bedb181"},
+ {file = "ddtrace-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62e863b4902e733a78f8ec270a7d137fa2ec6d8788f02a06f358be6310a011f4"},
+ {file = "ddtrace-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0419cb6fb4aa61a61abdf9d5ec8fdb4298765f080b13da09db4dd8b18b85b255"},
+ {file = "ddtrace-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:638b0c1d98eb68639318015b153ca6e2abe711b62b0fa7599721f63049a1bf8f"},
+ {file = "ddtrace-2.9.3-cp310-cp310-win32.whl", hash = "sha256:df7c4c9fad7b0ac769757a0e78c731178b6f2566ba12dc52163f44a8b8467950"},
+ {file = "ddtrace-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:0c51834dca12ef06c88b2100fc94330d1272795b82310a6e50979736bbf7da1d"},
+ {file = "ddtrace-2.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:af615b92e85ab49b859a286d3ec67e73e7a386ae8020c5cb5f592e2f572dbf35"},
+ {file = "ddtrace-2.9.3-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:2da4ac974c4c11782f1048bace1b31de5e084c17fa3827e0cb8c66f9fb8a38b2"},
+ {file = "ddtrace-2.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:079d2abf8485ffba9c6db1a6c7f479c087b9a64fb6339182202169ddb0e9900c"},
+ {file = "ddtrace-2.9.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14d0d6023a0225b9b6de1f5587962ee6b0a603dbf29c4107b8f43c08a438b978"},
+ {file = "ddtrace-2.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6282b6134ef320f631fb17fd3b8ad3225ee44051d129bd5783b332adf0aae12"},
+ {file = "ddtrace-2.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:12addd77efd483c9224056c1e2452e6af9a5c6c1e64086439181121fb5ed2504"},
+ {file = "ddtrace-2.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:364c0428839dc59767596da1152b765808cc40d74b2ec94fba490fe8e2c90266"},
+ {file = "ddtrace-2.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c10ac6357a92e5e392ce9926df7d5f27f599aa005a47068eff7c1de81fe52857"},
+ {file = "ddtrace-2.9.3-cp311-cp311-win32.whl", hash = "sha256:961cb89e6cc0c577a4971ce0bdd3fee9602eed773f6ee323e0284bafdc282d7e"},
+ {file = "ddtrace-2.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf0bf61d9859d7d11d117f169d65e6c8fbcb53711fee2ce7a6a0707ff540563"},
+ {file = "ddtrace-2.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:c3f05d0a6a89caa137f12008220ad8adf421d17f8a410f19e6b7b5c9f83a042f"},
+ {file = "ddtrace-2.9.3-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:6324e985d4ab8f9c2e1b399f80a7fc326adfe54d029cd21564ba90152e5256bd"},
+ {file = "ddtrace-2.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8691cb1d22b962aebfe168fd5c495d6e639d9d608103eded501b88964a106416"},
+ {file = "ddtrace-2.9.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9e7b6a574dd49a5cbacd0d4b14a4080f389c11bd3c77fb2cde6a78e39f91fca"},
+ {file = "ddtrace-2.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0008297aa82902bfc3c3cfcb430c3857f31b9f8133d9ecee71e47c871f36fd"},
+ {file = "ddtrace-2.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6b7d0ebbfdc98e23b0aab64b943944d859512cdf71c76a2520f3c07b17627a88"},
+ {file = "ddtrace-2.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:00e04b7a3b738ab0fe989572e9eb0cb439bda671f3f60c27cbfb29dd3ae4144e"},
+ {file = "ddtrace-2.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5082480ac147e4d46a318e0496e2b9b4a2018011abd436ed745481e4843ddf51"},
+ {file = "ddtrace-2.9.3-cp312-cp312-win32.whl", hash = "sha256:81e3d3e1453c10750c33a100fb248649b1bf22a0d12a8cbc56bf99bc0a996d9f"},
+ {file = "ddtrace-2.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:55ec5c6429ff399d984f5654cf060a9bd4a9f0e78937245d07c935280e16effc"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:57f0f9adbfb98be6a8b28299e19ebbce0e53c31d959013473bedf9019d76eb9e"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a41254dae0ad7bb0e7bc8a9a04fc39f8bf1ff68013a6c2718b65f902ef14c7d8"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e09c1a74565bd1f855a24ad6ff1704fc55f794e4eb7223a442e9f9ed35c36fdb"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a07438a82c0a6969189fb623e79c51e2fa8cd1838a6ea89f4438cb3da47793c6"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:339768b009142054b7c33b9b1d1bbf814c4aafb7e9a8fe9fadaf803b1e1302d3"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f48e91fd5e43390db6e935e0ac9053c21922915f9362c3fd91227e68af324233"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da1c54a59643c2fe712b01c0ff9347b6c9265dc595eaac7676c75cc1ccdc4b44"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:5ac32f16599bb87fd5b6191ccb200605ad29fc26a42ad16bedd42db8330ed41e"},
+ {file = "ddtrace-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:73136af9cbdd5d21518c9e620c03414d93df929d434f2a7f854603c6bcb1c2d6"},
+ {file = "ddtrace-2.9.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:913bad3a954f1f7e6f36f8fccd4452d2fbabeb1ba1a606c4ec58319d6195128a"},
+ {file = "ddtrace-2.9.3-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:1f775a79b951e0a39179ec4bae0257d1441296da9173003f9ab7ee6d0f29a95f"},
+ {file = "ddtrace-2.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:761fd76cf155b21c7258da95f1f10a5d16d7bb898d3b3b7a134e0c05e85244e7"},
+ {file = "ddtrace-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35b5d65e267c15f2470b41000fe3f5aae856c54fb4ed93b0675919556e481ec3"},
+ {file = "ddtrace-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1f495e10ac0c2950b1367b92258f3f709efa3be50428239d347497e58d04a72"},
+ {file = "ddtrace-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bb16653a9fc83f876e2290330599544b932a3caed223334a1cde8b5da5171f4e"},
+ {file = "ddtrace-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:aedf80b0514c5184226041a2ea4d29b120da48cfa88ed26ab79bd3dc9004975c"},
+ {file = "ddtrace-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d142020f4eb28e7b308ab1afe2b94641cb7cd8c572edc42308607c2076af28b3"},
+ {file = "ddtrace-2.9.3-cp38-cp38-win32.whl", hash = "sha256:e9ce7d605b8ff7662f0c386964faec86c3fc80f4d6f86d98221d97db8b2b4b32"},
+ {file = "ddtrace-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0cdfc6a0d831cf571c8b5bc52412a59ad6dc4cbcd9ee081786671bc3cc1f6c61"},
+ {file = "ddtrace-2.9.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2cae027f42b588dccf4baa0051f7950a13d40e9c997952c834908d912dd93f52"},
+ {file = "ddtrace-2.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4f2442dcb2d8d85ccef2a63039b329868bb217638f22b41d5e8a1c6cf5d7c736"},
+ {file = "ddtrace-2.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:183d2d8c2d4b5f8e5c800a5d60840a8719f00f184cc165326268c2626c531b9c"},
+ {file = "ddtrace-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d4f4ba6cda7281ea63b5526154cac6a5982f7e8fcadd1c4057b22cc06df548"},
+ {file = "ddtrace-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a6e19c9bfc55e0989a06add969c486c9add8826aba617818e1b4e900d00efbe"},
+ {file = "ddtrace-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f05cfa4ef11a0ccfa0a82318878136e7b239d5bde8e82901d47eb46106a6f7b"},
+ {file = "ddtrace-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1682a9ed6176e9b336eaa3e3271fe37fb346135996eb87022af535a17a73ceb4"},
+ {file = "ddtrace-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f300a5cd24363ce958f9db4d0d7ae293813be97e6b845a01e84f2961311cb140"},
+ {file = "ddtrace-2.9.3-cp39-cp39-win32.whl", hash = "sha256:010be94f9a9bc9258c16744873141a6072c7491ba40dd06acc85afbd006c33dd"},
+ {file = "ddtrace-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:c8a0de82e67af8be3ece325d0e4168aae25e831b1e03b891a6724cb614bf8a7e"},
+ {file = "ddtrace-2.9.3.tar.gz", hash = "sha256:7c916208ce905134e9f09ffcc150d1ebd480ce8cf51c2dcb13eb4d9b3e44cb22"},
]
[package.dependencies]
attrs = ">=20"
-bytecode = {version = "*", markers = "python_version >= \"3.8\""}
+bytecode = [
+ {version = ">=0.13.0", markers = "python_version < \"3.11.0\""},
+ {version = ">=0.15.0", markers = "python_version >= \"3.12.0\""},
+ {version = ">=0.14.0", markers = "python_version ~= \"3.11.0\""},
+]
cattrs = "*"
-ddsketch = ">=2.0.1"
-envier = "*"
+ddsketch = ">=3.0.0"
+envier = ">=0.5,<1.0"
opentelemetry-api = ">=1"
protobuf = ">=3"
setuptools = {version = "*", markers = "python_version >= \"3.12\""}
@@ -1001,19 +1054,9 @@ typing-extensions = "*"
xmltodict = ">=0.12"
[package.extras]
+openai = ["tiktoken"]
opentracing = ["opentracing (>=2.0.0)"]
-[[package]]
-name = "decorator"
-version = "5.1.1"
-description = "Decorators for Humans"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
- {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
-]
-
[[package]]
name = "deprecated"
version = "1.2.14"
@@ -1044,13 +1087,13 @@ files = [
[[package]]
name = "django"
-version = "4.2.9"
+version = "4.2.14"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.8"
files = [
- {file = "Django-4.2.9-py3-none-any.whl", hash = "sha256:2cc2fc7d1708ada170ddd6c99f35cc25db664f165d3794bc7723f46b2f8c8984"},
- {file = "Django-4.2.9.tar.gz", hash = "sha256:12498cc3cb8bc8038539fef9e90e95f507502436c1f0c3a673411324fa675d14"},
+ {file = "Django-4.2.14-py3-none-any.whl", hash = "sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240"},
+ {file = "Django-4.2.14.tar.gz", hash = "sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96"},
]
[package.dependencies]
@@ -1065,22 +1108,22 @@ bcrypt = ["bcrypt"]
[[package]]
name = "dnspython"
-version = "2.5.0"
+version = "2.6.1"
description = "DNS toolkit"
optional = false
python-versions = ">=3.8"
files = [
- {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"},
- {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"},
+ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
+ {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
]
[package.extras]
-dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"]
+dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
dnssec = ["cryptography (>=41)"]
-doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"]
-doq = ["aioquic (>=0.9.20)"]
-idna = ["idna (>=2.1)"]
-trio = ["trio (>=0.14)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
+doq = ["aioquic (>=0.9.25)"]
+idna = ["idna (>=3.6)"]
+trio = ["trio (>=0.23)"]
wmi = ["wmi (>=1.5.1)"]
[[package]]
@@ -1172,13 +1215,13 @@ pgp = ["gpg"]
[[package]]
name = "email-validator"
-version = "2.1.0.post1"
+version = "2.2.0"
description = "A robust email address syntax and deliverability validation library."
optional = false
python-versions = ">=3.8"
files = [
- {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"},
- {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"},
+ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
+ {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
]
[package.dependencies]
@@ -1187,13 +1230,13 @@ idna = ">=2.0.0"
[[package]]
name = "envier"
-version = "0.5.1"
+version = "0.5.2"
description = "Python application configuration via the environment"
optional = false
python-versions = ">=3.7"
files = [
- {file = "envier-0.5.1-py3-none-any.whl", hash = "sha256:b45ef6051fea33d0c32a64e186bff2cfb446e2242d6781216c9bc9ce708c5909"},
- {file = "envier-0.5.1.tar.gz", hash = "sha256:bd5ccf707447973ea0f4125b7df202ba415ad888bcdcb8df80e0b002ee11ffdb"},
+ {file = "envier-0.5.2-py3-none-any.whl", hash = "sha256:65099cf3aa9b3b3b4b92db2f7d29e2910672e085b76f7e587d2167561a834add"},
+ {file = "envier-0.5.2.tar.gz", hash = "sha256:4e7e398cb09a8dd360508ef7e12511a152355426d2544b8487a34dad27cc20ad"},
]
[package.extras]
@@ -1201,13 +1244,13 @@ mypy = ["mypy"]
[[package]]
name = "exceptiongroup"
-version = "1.2.0"
+version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
- {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
- {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
@@ -1215,32 +1258,45 @@ test = ["pytest (>=6)"]
[[package]]
name = "execnet"
-version = "2.0.2"
+version = "2.1.1"
description = "execnet: rapid multi-Python deployment"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
- {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
+ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
+ {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
]
[package.extras]
testing = ["hatch", "pre-commit", "pytest", "tox"]
+[[package]]
+name = "executing"
+version = "2.0.1"
+description = "Get the currently executing AST node of a frame, and other information"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
+ {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
+]
+
+[package.extras]
+tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
+
[[package]]
name = "faker"
-version = "22.7.0"
+version = "26.0.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.8"
files = [
- {file = "Faker-22.7.0-py3-none-any.whl", hash = "sha256:d12edbac08a82a75ecd588f299f44f12e33f000c15fe414abc417f0836cb51ae"},
- {file = "Faker-22.7.0.tar.gz", hash = "sha256:f797529ebeb9bd9e1851106b99e156c9bebe67d2730c8393a1705ed1c864f1bf"},
+ {file = "Faker-26.0.0-py3-none-any.whl", hash = "sha256:886ee28219be96949cd21ecc96c4c742ee1680e77f687b095202c8def1a08f06"},
+ {file = "Faker-26.0.0.tar.gz", hash = "sha256:0f60978314973de02c00474c2ae899785a42b2cf4f41b7987e93c132a2b8a4a9"},
]
[package.dependencies]
python-dateutil = ">=2.4"
-typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\""}
[[package]]
name = "fast-query-parsers"
@@ -1269,32 +1325,55 @@ files = [
[[package]]
name = "fastapi"
-version = "0.109.2"
+version = "0.111.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
- {file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
- {file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
+ {file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"},
+ {file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"},
]
[package.dependencies]
+email_validator = ">=2.0.0"
+fastapi-cli = ">=0.0.2"
+httpx = ">=0.23.0"
+jinja2 = ">=2.11.2"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
-starlette = ">=0.36.3,<0.37.0"
+python-multipart = ">=0.0.7"
+starlette = ">=0.37.2,<0.38.0"
typing-extensions = ">=4.8.0"
+uvicorn = {version = ">=0.12.0", extras = ["standard"]}
+
+[package.extras]
+all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+
+[[package]]
+name = "fastapi-cli"
+version = "0.0.4"
+description = "Run and manage FastAPI apps from the command line with FastAPI CLI. ๐"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"},
+ {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"},
+]
+
+[package.dependencies]
+typer = ">=0.12.3"
[package.extras]
-all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"]
[[package]]
name = "fastjsonschema"
-version = "2.19.1"
+version = "2.20.0"
description = "Fastest Python implementation of JSON schema"
optional = false
python-versions = "*"
files = [
- {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"},
- {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"},
+ {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"},
+ {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"},
]
[package.extras]
@@ -1302,29 +1381,29 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc
[[package]]
name = "filelock"
-version = "3.13.1"
+version = "3.15.4"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
- {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
- {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
+ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
+ {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
]
[package.extras]
-docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "flask"
-version = "3.0.2"
+version = "3.0.3"
description = "A simple framework for building complex web applications."
optional = false
python-versions = ">=3.8"
files = [
- {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"},
- {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"},
+ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
+ {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
]
[package.dependencies]
@@ -1341,13 +1420,13 @@ dotenv = ["python-dotenv"]
[[package]]
name = "freezegun"
-version = "1.4.0"
+version = "1.5.1"
description = "Let your Python tests travel through time"
optional = false
python-versions = ">=3.7"
files = [
- {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"},
- {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"},
+ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
+ {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
]
[package.dependencies]
@@ -1439,6 +1518,17 @@ files = [
{file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"},
]
+[[package]]
+name = "graphlib-backport"
+version = "1.1.0"
+description = "Backport of the Python 3.9 graphlib module for Python 3.6+"
+optional = false
+python-versions = ">=3.6,<4.0"
+files = [
+ {file = "graphlib_backport-1.1.0-py3-none-any.whl", hash = "sha256:eccacf9f2126cdf89ce32a6018c88e1ecd3e4898a07568add6e1907a439055ba"},
+ {file = "graphlib_backport-1.1.0.tar.gz", hash = "sha256:00a7888b21e5393064a133209cb5d3b3ef0a2096cf023914c9d778dff5644125"},
+]
+
[[package]]
name = "graphql-core"
version = "3.2.3"
@@ -1500,13 +1590,13 @@ files = [
[[package]]
name = "httpcore"
-version = "1.0.2"
+version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
- {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"},
- {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"},
+ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
+ {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
@@ -1517,7 +1607,7 @@ h11 = ">=0.13,<0.15"
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
-trio = ["trio (>=0.22.0,<0.23.0)"]
+trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httptools"
@@ -1569,13 +1659,13 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
[[package]]
name = "httpx"
-version = "0.26.0"
+version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
- {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"},
- {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"},
+ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
+ {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
@@ -1593,28 +1683,30 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "hypercorn"
-version = "0.16.0"
+version = "0.17.3"
description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn"
optional = false
python-versions = ">=3.8"
files = [
- {file = "hypercorn-0.16.0-py3-none-any.whl", hash = "sha256:929e45c4acde3fbf7c58edf55336d30a009d2b4cb1f1eb96e6a515d61b663f58"},
- {file = "hypercorn-0.16.0.tar.gz", hash = "sha256:3b17d1dcf4992c1f262d9f9dd799c374125d0b9a8e40e1e2d11e2938b0adfe03"},
+ {file = "hypercorn-0.17.3-py3-none-any.whl", hash = "sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547"},
+ {file = "hypercorn-0.17.3.tar.gz", hash = "sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165"},
]
[package.dependencies]
+exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
h11 = "*"
h2 = ">=3.1.0"
priority = "*"
taskgroup = {version = "*", markers = "python_version < \"3.11\""}
tomli = {version = "*", markers = "python_version < \"3.11\""}
+typing_extensions = {version = "*", markers = "python_version < \"3.11\""}
wsproto = ">=0.14.0"
[package.extras]
docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"]
h3 = ["aioquic (>=0.9.0,<1.0)"]
-trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"]
-uvloop = ["uvloop"]
+trio = ["trio (>=0.22.0)"]
+uvloop = ["uvloop (>=0.18)"]
[[package]]
name = "hyperframe"
@@ -1643,43 +1735,43 @@ idna = ">=2.5"
[[package]]
name = "idna"
-version = "3.6"
+version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
- {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
- {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "importlib-metadata"
-version = "6.11.0"
+version = "7.1.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"},
- {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"},
+ {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
+ {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
+testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "importlib-resources"
-version = "6.1.1"
+version = "6.4.0"
description = "Read resources from Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"},
- {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"},
+ {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"},
+ {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"},
]
[package.dependencies]
@@ -1687,7 +1779,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"]
+testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
[[package]]
name = "incremental"
@@ -1715,6 +1807,26 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
+[[package]]
+name = "inline-snapshot"
+version = "0.10.2"
+description = "golden master/snapshot/approval testing library which puts the values right into your source code"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "inline_snapshot-0.10.2-py3-none-any.whl", hash = "sha256:f61d42f0d4bddd2a3efae041f5b168e94ac2df566cbf2c67a26d03d5f090835a"},
+ {file = "inline_snapshot-0.10.2.tar.gz", hash = "sha256:fb3c1410a08c9700ca838a269f70117760b024d99d6193661a8b47f8302b09cd"},
+]
+
+[package.dependencies]
+asttokens = ">=2.0.5"
+black = ">=23.3.0"
+click = ">=8.1.4"
+executing = ">=2.0.0"
+rich = ">=13.7.1"
+toml = ">=0.10.2"
+types-toml = ">=0.10.8.7"
+
[[package]]
name = "inquirer"
version = "2.10.1"
@@ -1744,24 +1856,24 @@ files = [
[[package]]
name = "itsdangerous"
-version = "2.1.2"
+version = "2.2.0"
description = "Safely pass data to untrusted environments and back."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
- {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
+ {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
]
[[package]]
name = "jaraco-classes"
-version = "3.3.0"
+version = "3.4.0"
description = "Utility functions for Python class constructs"
optional = false
python-versions = ">=3.8"
files = [
- {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"},
- {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"},
+ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"},
+ {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"},
]
[package.dependencies]
@@ -1769,7 +1881,7 @@ more-itertools = "*"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "jeepney"
@@ -1788,13 +1900,13 @@ trio = ["async_generator", "trio"]
[[package]]
name = "jinja2"
-version = "3.1.3"
+version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
- {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
- {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
+ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
+ {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
]
[package.dependencies]
@@ -1830,13 +1942,13 @@ files = [
[[package]]
name = "keyring"
-version = "24.3.0"
+version = "24.3.1"
description = "Store and access your passwords safely."
optional = false
python-versions = ">=3.8"
files = [
- {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"},
- {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"},
+ {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"},
+ {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"},
]
[package.dependencies]
@@ -1850,7 +1962,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
[package.extras]
completion = ["shtab (>=1.1.0)"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "libcst"
@@ -1902,13 +2014,13 @@ dev = ["Sphinx (>=5.1.1)", "black (==23.9.1)", "build (>=0.10.0)", "coverage (>=
[[package]]
name = "litestar"
-version = "2.5.5"
+version = "2.9.1"
description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework"
optional = false
python-versions = "<4.0,>=3.8"
files = [
- {file = "litestar-2.5.5-py3-none-any.whl", hash = "sha256:7797e74dd5bb465f6398cf35e5d5bfb99fee92f66b4afd4c066f7dc370510021"},
- {file = "litestar-2.5.5.tar.gz", hash = "sha256:0ae9e526ec89c869883e3e5818f71fb2ae3d53490e0f316ccd71d5cbe5ce0c49"},
+ {file = "litestar-2.9.1-py3-none-any.whl", hash = "sha256:fe3e4ec91a9c24af652775fed5fa4d789902f165cabbd7d2e62821fec1f69462"},
+ {file = "litestar-2.9.1.tar.gz", hash = "sha256:7c13bb4dd7b1c77f6c462262cfe401ca6429eab3e4d98f38586b68268bd5ac97"},
]
[package.dependencies]
@@ -1932,7 +2044,7 @@ attrs = ["attrs"]
brotli = ["brotli"]
cli = ["jsbeautifier", "uvicorn[standard]", "uvloop (>=0.18.0)"]
cryptography = ["cryptography"]
-full = ["litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog]"]
+full = ["advanced-alchemy (>=0.2.2)", "annotated-types", "attrs", "brotli", "cryptography", "email-validator", "fast-query-parsers (>=1.0.2)", "jinja2", "jinja2 (>=3.1.2)", "jsbeautifier", "mako (>=1.2.4)", "minijinja (>=1.0.0)", "opentelemetry-instrumentation-asgi", "piccolo", "picologging", "prometheus-client", "pydantic", "pydantic-extra-types", "python-jose", "redis[hiredis] (>=4.4.4)", "structlog", "uvicorn[standard]", "uvloop (>=0.18.0)"]
jinja = ["jinja2 (>=3.1.2)"]
jwt = ["cryptography", "python-jose"]
mako = ["mako (>=1.2.4)"]
@@ -1943,19 +2055,19 @@ picologging = ["picologging"]
prometheus = ["prometheus-client"]
pydantic = ["email-validator", "pydantic", "pydantic-extra-types"]
redis = ["redis[hiredis] (>=4.4.4)"]
-sqlalchemy = ["advanced-alchemy (>=0.2.2,<1.0.0)"]
+sqlalchemy = ["advanced-alchemy (>=0.2.2)"]
standard = ["fast-query-parsers (>=1.0.2)", "jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop (>=0.18.0)"]
structlog = ["structlog"]
[[package]]
name = "mako"
-version = "1.3.2"
+version = "1.3.5"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false
python-versions = ">=3.8"
files = [
- {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"},
- {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"},
+ {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
+ {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
]
[package.dependencies]
@@ -2072,78 +2184,78 @@ files = [
[[package]]
name = "more-itertools"
-version = "10.2.0"
+version = "10.3.0"
description = "More routines for operating on iterables, beyond itertools"
optional = false
python-versions = ">=3.8"
files = [
- {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"},
- {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"},
+ {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
+ {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
]
[[package]]
name = "msgpack"
-version = "1.0.7"
+version = "1.0.8"
description = "MessagePack serializer"
optional = false
python-versions = ">=3.8"
files = [
- {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"},
- {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"},
- {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"},
- {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"},
- {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"},
- {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"},
- {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"},
- {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"},
- {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"},
- {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"},
- {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"},
- {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"},
- {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"},
- {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"},
- {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"},
- {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"},
- {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"},
- {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"},
- {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"},
- {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"},
- {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"},
- {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"},
- {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"},
- {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"},
- {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"},
- {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"},
- {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"},
- {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"},
- {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"},
- {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"},
- {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"},
- {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"},
- {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"},
- {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"},
- {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"},
- {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"},
- {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"},
- {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"},
- {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"},
- {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"},
- {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"},
- {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"},
- {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"},
- {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"},
- {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"},
- {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"},
- {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"},
- {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"},
- {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"},
- {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"},
- {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"},
- {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"},
- {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"},
- {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"},
- {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"},
- {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"},
+ {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"},
+ {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"},
+ {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"},
+ {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"},
+ {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"},
+ {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"},
+ {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"},
+ {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"},
+ {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"},
+ {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"},
+ {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"},
+ {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"},
+ {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"},
+ {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"},
+ {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"},
+ {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"},
+ {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"},
+ {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"},
+ {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"},
+ {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"},
+ {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"},
+ {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"},
+ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
+ {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
+ {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
+ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
]
[[package]]
@@ -2297,53 +2409,6 @@ files = [
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
-[[package]]
-name = "mypy"
-version = "1.8.0"
-description = "Optional static typing for Python"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
- {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
- {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
- {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
- {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
- {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
- {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
- {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
- {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
- {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
- {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
- {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
- {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
- {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
- {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
- {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
- {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
- {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
- {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
- {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
- {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
- {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
- {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
- {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
- {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
- {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
- {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
-]
-
-[package.dependencies]
-mypy-extensions = ">=1.0.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = ">=4.1.0"
-
-[package.extras]
-dmypy = ["psutil (>=4.0)"]
-install-types = ["pip"]
-mypyc = ["setuptools (>=50)"]
-reports = ["lxml"]
-
[[package]]
name = "mypy-extensions"
version = "1.0.0"
@@ -2393,55 +2458,69 @@ tomlkit = ">=0.7"
[[package]]
name = "opentelemetry-api"
-version = "1.22.0"
+version = "1.25.0"
description = "OpenTelemetry Python API"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "opentelemetry_api-1.22.0-py3-none-any.whl", hash = "sha256:43621514301a7e9f5d06dd8013a1b450f30c2e9372b8e30aaeb4562abf2ce034"},
- {file = "opentelemetry_api-1.22.0.tar.gz", hash = "sha256:15ae4ca925ecf9cfdfb7a709250846fbb08072260fca08ade78056c502b86bed"},
+ {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"},
+ {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"},
]
[package.dependencies]
deprecated = ">=1.2.6"
-importlib-metadata = ">=6.0,<7.0"
+importlib-metadata = ">=6.0,<=7.1"
[[package]]
name = "opentelemetry-sdk"
-version = "1.22.0"
+version = "1.25.0"
description = "OpenTelemetry Python SDK"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "opentelemetry_sdk-1.22.0-py3-none-any.whl", hash = "sha256:a730555713d7c8931657612a88a141e3a4fe6eb5523d9e2d5a8b1e673d76efa6"},
- {file = "opentelemetry_sdk-1.22.0.tar.gz", hash = "sha256:45267ac1f38a431fc2eb5d6e0c0d83afc0b78de57ac345488aa58c28c17991d0"},
+ {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"},
+ {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"},
]
[package.dependencies]
-opentelemetry-api = "1.22.0"
-opentelemetry-semantic-conventions = "0.43b0"
+opentelemetry-api = "1.25.0"
+opentelemetry-semantic-conventions = "0.46b0"
typing-extensions = ">=3.7.4"
[[package]]
name = "opentelemetry-semantic-conventions"
-version = "0.43b0"
+version = "0.46b0"
description = "OpenTelemetry Semantic Conventions"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "opentelemetry_semantic_conventions-0.43b0-py3-none-any.whl", hash = "sha256:291284d7c1bf15fdaddf309b3bd6d3b7ce12a253cec6d27144439819a15d8445"},
- {file = "opentelemetry_semantic_conventions-0.43b0.tar.gz", hash = "sha256:b9576fb890df479626fa624e88dde42d3d60b8b6c8ae1152ad157a8b97358635"},
+ {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"},
+ {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"},
]
+[package.dependencies]
+opentelemetry-api = "1.25.0"
+
[[package]]
name = "packaging"
-version = "23.2"
+version = "24.1"
description = "Core utilities for Python packages"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
files = [
- {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
- {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
@@ -2460,53 +2539,54 @@ ptyprocess = ">=0.5"
[[package]]
name = "pip"
-version = "23.3.2"
+version = "24.0"
description = "The PyPA recommended tool for installing Python packages."
optional = false
python-versions = ">=3.7"
files = [
- {file = "pip-23.3.2-py3-none-any.whl", hash = "sha256:5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76"},
- {file = "pip-23.3.2.tar.gz", hash = "sha256:7fd9972f96db22c8077a1ee2691b172c8089b17a5652a44494a9ecb0d78f9149"},
+ {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"},
+ {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"},
]
[[package]]
name = "pkginfo"
-version = "1.9.6"
+version = "1.11.1"
description = "Query metadata from sdists / bdists / installed packages."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
files = [
- {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"},
- {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"},
+ {file = "pkginfo-1.11.1-py3-none-any.whl", hash = "sha256:bfa76a714fdfc18a045fcd684dbfc3816b603d9d075febef17cb6582bea29573"},
+ {file = "pkginfo-1.11.1.tar.gz", hash = "sha256:2e0dca1cf4c8e39644eed32408ea9966ee15e0d324c62ba899a393b3c6b467aa"},
]
[package.extras]
-testing = ["pytest", "pytest-cov"]
+testing = ["pytest", "pytest-cov", "wheel"]
[[package]]
name = "platformdirs"
-version = "3.11.0"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "4.2.2"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
- {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
+ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
+ {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[package.extras]
-docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
+docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
[[package]]
name = "pluggy"
-version = "1.4.0"
+version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
- {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
@@ -2515,18 +2595,18 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "poetry"
-version = "1.7.1"
+version = "1.8.3"
description = "Python dependency management and packaging made easy."
optional = false
-python-versions = ">=3.8,<4.0"
+python-versions = "<4.0,>=3.8"
files = [
- {file = "poetry-1.7.1-py3-none-any.whl", hash = "sha256:03d3807a0fb3bc1028cc3707dfd646aae629d58e476f7e7f062437680741c561"},
- {file = "poetry-1.7.1.tar.gz", hash = "sha256:b348a70e7d67ad9c0bd3d0ea255bc6df84c24cf4b16f8d104adb30b425d6ff32"},
+ {file = "poetry-1.8.3-py3-none-any.whl", hash = "sha256:88191c69b08d06f9db671b793d68f40048e8904c0718404b63dcc2b5aec62d13"},
+ {file = "poetry-1.8.3.tar.gz", hash = "sha256:67f4eb68288eab41e841cc71a00d26cf6bdda9533022d0189a145a34d0a35f48"},
]
[package.dependencies]
build = ">=1.0.3,<2.0.0"
-cachecontrol = {version = ">=0.13.0,<0.14.0", extras = ["filecache"]}
+cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]}
cleo = ">=2.1.0,<3.0.0"
crashtest = ">=0.4.1,<0.5.0"
dulwich = ">=0.21.2,<0.22.0"
@@ -2534,57 +2614,57 @@ fastjsonschema = ">=2.18.0,<3.0.0"
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
installer = ">=0.7.0,<0.8.0"
keyring = ">=24.0.0,<25.0.0"
-packaging = ">=20.5"
+packaging = ">=23.1"
pexpect = ">=4.7.0,<5.0.0"
-pkginfo = ">=1.9.4,<2.0.0"
-platformdirs = ">=3.0.0,<4.0.0"
-poetry-core = "1.8.1"
+pkginfo = ">=1.10,<2.0"
+platformdirs = ">=3.0.0,<5"
+poetry-core = "1.9.0"
poetry-plugin-export = ">=1.6.0,<2.0.0"
pyproject-hooks = ">=1.0.0,<2.0.0"
requests = ">=2.26,<3.0"
-requests-toolbelt = ">=0.9.1,<2"
+requests-toolbelt = ">=1.0.0,<2.0.0"
shellingham = ">=1.5,<2.0"
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
tomlkit = ">=0.11.4,<1.0.0"
trove-classifiers = ">=2022.5.19"
virtualenv = ">=20.23.0,<21.0.0"
-xattr = {version = ">=0.10.0,<0.11.0", markers = "sys_platform == \"darwin\""}
+xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""}
[[package]]
name = "poetry-core"
-version = "1.8.1"
+version = "1.9.0"
description = "Poetry PEP 517 Build Backend"
optional = false
python-versions = ">=3.8,<4.0"
files = [
- {file = "poetry_core-1.8.1-py3-none-any.whl", hash = "sha256:194832b24f3283e01c5402eae71a6aae850ecdfe53f50a979c76bf7aa5010ffa"},
- {file = "poetry_core-1.8.1.tar.gz", hash = "sha256:67a76c671da2a70e55047cddda83566035b701f7e463b32a2abfeac6e2a16376"},
+ {file = "poetry_core-1.9.0-py3-none-any.whl", hash = "sha256:4e0c9c6ad8cf89956f03b308736d84ea6ddb44089d16f2adc94050108ec1f5a1"},
+ {file = "poetry_core-1.9.0.tar.gz", hash = "sha256:fa7a4001eae8aa572ee84f35feb510b321bd652e5cf9293249d62853e1f935a2"},
]
[[package]]
name = "poetry-plugin-export"
-version = "1.6.0"
+version = "1.8.0"
description = "Poetry plugin to export the dependencies to various formats"
optional = false
-python-versions = ">=3.8,<4.0"
+python-versions = "<4.0,>=3.8"
files = [
- {file = "poetry_plugin_export-1.6.0-py3-none-any.whl", hash = "sha256:2dce6204c9318f1f6509a11a03921fb3f461b201840b59f1c237b6ab454dabcf"},
- {file = "poetry_plugin_export-1.6.0.tar.gz", hash = "sha256:091939434984267a91abf2f916a26b00cff4eee8da63ec2a24ba4b17cf969a59"},
+ {file = "poetry_plugin_export-1.8.0-py3-none-any.whl", hash = "sha256:adbe232cfa0cc04991ea3680c865cf748bff27593b9abcb1f35fb50ed7ba2c22"},
+ {file = "poetry_plugin_export-1.8.0.tar.gz", hash = "sha256:1fa6168a85d59395d835ca564bc19862a7c76061e60c3e7dfaec70d50937fc61"},
]
[package.dependencies]
-poetry = ">=1.6.0,<2.0.0"
-poetry-core = ">=1.7.0,<2.0.0"
+poetry = ">=1.8.0,<3.0.0"
+poetry-core = ">=1.7.0,<3.0.0"
[[package]]
name = "polyfactory"
-version = "2.14.1"
+version = "2.16.2"
description = "Mock data generation factories"
optional = false
python-versions = "<4.0,>=3.8"
files = [
- {file = "polyfactory-2.14.1-py3-none-any.whl", hash = "sha256:8aff3be75e046501ec5c411c78c23db284322c760fef50d560ee6ed683f217c8"},
- {file = "polyfactory-2.14.1.tar.gz", hash = "sha256:8c1d5f15dad1ebfd0845d65d4a55f9791cddfa6b3096ad9f9e2fd02a4804631b"},
+ {file = "polyfactory-2.16.2-py3-none-any.whl", hash = "sha256:e5eaf97358fee07d0d8de86a93e81dc56e3be1e1514d145fea6c5f486cda6ea1"},
+ {file = "polyfactory-2.16.2.tar.gz", hash = "sha256:6d0d90deb85e5bb1733ea8744c2d44eea2b31656e11b4fa73832d2e2ab5422da"},
]
[package.dependencies]
@@ -2613,47 +2693,48 @@ files = [
[[package]]
name = "protobuf"
-version = "4.25.2"
+version = "5.27.2"
description = ""
optional = false
python-versions = ">=3.8"
files = [
- {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"},
- {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"},
- {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"},
- {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"},
- {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"},
- {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"},
- {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"},
- {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"},
- {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"},
- {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"},
- {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"},
+ {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"},
+ {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"},
+ {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"},
+ {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"},
+ {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"},
+ {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"},
+ {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"},
+ {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"},
+ {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"},
+ {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"},
+ {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"},
]
[[package]]
name = "psutil"
-version = "5.9.8"
+version = "6.0.0"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-files = [
- {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
- {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
- {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
- {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
- {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
- {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
- {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
- {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
- {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
- {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+files = [
+ {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
+ {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
+ {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
+ {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
+ {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
+ {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
+ {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
+ {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
+ {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
+ {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
+ {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
+ {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
+ {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
+ {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
]
[package.extras]
@@ -2672,145 +2753,158 @@ files = [
[[package]]
name = "pyasn1"
-version = "0.5.1"
+version = "0.6.0"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+python-versions = ">=3.8"
files = [
- {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"},
- {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"},
+ {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
+ {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
]
[[package]]
name = "pyasn1-modules"
-version = "0.3.0"
+version = "0.4.0"
description = "A collection of ASN.1-based protocols modules"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+python-versions = ">=3.8"
files = [
- {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"},
- {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"},
+ {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"},
+ {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"},
]
[package.dependencies]
-pyasn1 = ">=0.4.6,<0.6.0"
+pyasn1 = ">=0.4.6,<0.7.0"
[[package]]
name = "pycparser"
-version = "2.21"
+version = "2.22"
description = "C parser in Python"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.8"
files = [
- {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
- {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
name = "pydantic"
-version = "2.6.1"
+version = "2.8.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"},
- {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"},
+ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
+ {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
-pydantic-core = "2.16.2"
-typing-extensions = ">=4.6.1"
+pydantic-core = "2.20.1"
+typing-extensions = [
+ {version = ">=4.6.1", markers = "python_version < \"3.13\""},
+ {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
+]
[package.extras]
email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
-version = "2.16.2"
-description = ""
+version = "2.20.1"
+description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"},
- {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"},
- {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"},
- {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"},
- {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"},
- {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"},
- {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"},
- {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"},
- {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"},
- {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"},
- {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"},
- {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"},
- {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"},
- {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"},
- {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"},
- {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"},
- {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"},
- {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"},
- {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"},
- {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"},
- {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"},
- {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"},
- {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"},
- {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"},
- {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"},
- {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"},
- {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"},
- {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"},
- {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"},
- {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"},
- {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"},
- {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"},
- {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"},
- {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"},
- {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"},
- {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"},
- {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"},
- {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"},
- {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"},
- {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
+ {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
+ {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
+ {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
+ {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
+ {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
+ {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
+ {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
+ {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
+ {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
+ {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
+ {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
+ {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
+ {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
+ {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
+ {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
+ {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
+ {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
+ {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
+ {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
+ {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
+ {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
]
[package.dependencies]
@@ -2849,17 +2943,16 @@ pydantic = ">=1.10.0"
[[package]]
name = "pygments"
-version = "2.17.2"
+version = "2.18.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
- {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
+ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
+ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
]
[package.extras]
-plugins = ["importlib-metadata"]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
@@ -2940,36 +3033,33 @@ types = ["typing-extensions"]
[[package]]
name = "pyopenssl"
-version = "24.0.0"
+version = "24.2.1"
description = "Python wrapper module around the OpenSSL library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"},
- {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"},
+ {file = "pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d"},
+ {file = "pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95"},
]
[package.dependencies]
-cryptography = ">=41.0.5,<43"
+cryptography = ">=41.0.5,<44"
[package.extras]
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"]
-test = ["flaky", "pretend", "pytest (>=3.0.1)"]
+test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"]
[[package]]
name = "pyproject-hooks"
-version = "1.0.0"
+version = "1.1.0"
description = "Wrappers to call pyproject.toml-based build backend hooks."
optional = false
python-versions = ">=3.7"
files = [
- {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"},
- {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"},
+ {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"},
+ {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"},
]
-[package.dependencies]
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-
[[package]]
name = "pytest"
version = "7.4.4"
@@ -3013,17 +3103,17 @@ testing = ["coverage (==6.2)", "mypy (==0.931)"]
[[package]]
name = "pytest-asyncio"
-version = "0.23.4"
+version = "0.23.8"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"},
- {file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"},
+ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
+ {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
]
[package.dependencies]
-pytest = ">=7.0.0,<8"
+pytest = ">=7.0.0,<9"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
@@ -3031,23 +3121,24 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "pytest-codspeed"
-version = "2.1.0"
+version = "2.2.1"
description = "Pytest plugin to create CodSpeed benchmarks"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest_codspeed-2.1.0-py3-none-any.whl", hash = "sha256:91831cd8a8cdf68d489d4f393ff80fcc2ba05153c3c5afc2385b75e9897dcbb6"},
- {file = "pytest_codspeed-2.1.0.tar.gz", hash = "sha256:af289d5a1c32166a221df5920141924942a59906c7af572aacf892a726b86b49"},
+ {file = "pytest_codspeed-2.2.1-py3-none-any.whl", hash = "sha256:aad08033015f3e6c8c14c8bf0eca475921a9b088e92c98b626bf8af8f516471e"},
+ {file = "pytest_codspeed-2.2.1.tar.gz", hash = "sha256:0adc24baf01c64a6ca0a0b83b3cd704351708997e09ec086b7776c32227d4e0a"},
]
[package.dependencies]
-cffi = ">=1.15.1,<1.16.0"
+cffi = ">=1.15.1"
+filelock = ">=3.12.2"
pytest = ">=3.8"
-setuptools = {version = ">=67.8.0,<67.9.0", markers = "python_full_version >= \"3.12.0b1\""}
+setuptools = {version = "*", markers = "python_full_version >= \"3.12.0\""}
[package.extras]
-compat = ["pytest-benchmark (>=4.0.0,<4.1.0)"]
-lint = ["black (>=23.3.0,<23.4.0)", "isort (>=5.12.0,<5.13.0)", "mypy (>=1.3.0,<1.4.0)", "ruff (>=0.0.275,<0.1.0)"]
+compat = ["pytest-benchmark (>=4.0.0,<4.1.0)", "pytest-xdist (>=2.0.0,<2.1.0)"]
+lint = ["mypy (>=1.3.0,<1.4.0)", "ruff (>=0.3.3,<0.4.0)"]
test = ["pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)"]
[[package]]
@@ -3102,42 +3193,21 @@ pytest = ">=4.2.1"
[[package]]
name = "pytest-mock"
-version = "3.12.0"
+version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"},
- {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"},
+ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
+ {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
]
[package.dependencies]
-pytest = ">=5.0"
+pytest = ">=6.2.5"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
-[[package]]
-name = "pytest-mypy-plugins"
-version = "3.0.0"
-description = "pytest plugin for writing tests for mypy plugins"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pytest-mypy-plugins-3.0.0.tar.gz", hash = "sha256:05a728c7cbc4f33610f97fe9266b2c3eb209e41c28935011b4fc9531662625f6"},
- {file = "pytest_mypy_plugins-3.0.0-py3-none-any.whl", hash = "sha256:a1e3f51b68898bc25713cc53718a28d9dc0cfd51d28a537ef18c7df3b123ed84"},
-]
-
-[package.dependencies]
-decorator = "*"
-Jinja2 = "*"
-mypy = ">=1.3"
-packaging = "*"
-pytest = ">=7.0.0"
-pyyaml = "*"
-regex = "*"
-tomlkit = ">=0.11"
-
[[package]]
name = "pytest-snapshot"
version = "0.9.0"
@@ -3154,19 +3224,19 @@ pytest = ">=3.0.0"
[[package]]
name = "pytest-xdist"
-version = "3.5.0"
+version = "3.6.1"
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
- {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
+ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
+ {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
]
[package.dependencies]
-execnet = ">=1.1"
+execnet = ">=2.1"
psutil = {version = ">=3.0", optional = true, markers = "extra == \"psutil\""}
-pytest = ">=6.2.0"
+pytest = ">=7.0.0"
[package.extras]
psutil = ["psutil (>=3.0)"]
@@ -3175,18 +3245,32 @@ testing = ["filelock"]
[[package]]
name = "python-dateutil"
-version = "2.8.2"
+version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
six = ">=1.5"
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
[[package]]
name = "python-editor"
version = "1.0.4"
@@ -3201,17 +3285,17 @@ files = [
[[package]]
name = "python-multipart"
-version = "0.0.7"
+version = "0.0.9"
description = "A streaming multipart parser for Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "python_multipart-0.0.7-py3-none-any.whl", hash = "sha256:b1fef9a53b74c795e2347daac8c54b252d9e0df9c619712691c1cc8021bd3c49"},
- {file = "python_multipart-0.0.7.tar.gz", hash = "sha256:288a6c39b06596c1b988bb6794c6fbc80e6c369e35e5062637df256bee0c9af9"},
+ {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
+ {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
]
[package.extras]
-dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==2.2.0)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"]
+dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
[[package]]
name = "pywin32-ctypes"
@@ -3236,6 +3320,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -3243,8 +3328,16 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -3261,6 +3354,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -3268,6 +3362,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -3275,13 +3370,13 @@ files = [
[[package]]
name = "quart"
-version = "0.19.4"
+version = "0.19.6"
description = "A Python ASGI web microframework with the same API as Flask"
optional = false
python-versions = ">=3.8"
files = [
- {file = "quart-0.19.4-py3-none-any.whl", hash = "sha256:959da9371b44b6f48d952661863f8f64e68a893481ef3f2ef45b177629dc0928"},
- {file = "quart-0.19.4.tar.gz", hash = "sha256:22ff186cf164955a7bf7483ff42a739a9fad3b119041846b15dc9597ec74c85c"},
+ {file = "quart-0.19.6-py3-none-any.whl", hash = "sha256:f9092310f4eb120903da692a5e4354f05d48c28ca7ec3054d3d94dd862412c58"},
+ {file = "quart-0.19.6.tar.gz", hash = "sha256:89ddda6da24300a5ea4f21e4582d5e89bc8ea678e724e0b747767143401e4558"},
]
[package.dependencies]
@@ -3303,101 +3398,104 @@ dotenv = ["python-dotenv"]
[[package]]
name = "rapidfuzz"
-version = "3.6.1"
+version = "3.9.4"
description = "rapid fuzzy string matching"
optional = false
python-versions = ">=3.8"
files = [
- {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f"},
- {file = "rapidfuzz-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-win32.whl", hash = "sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe"},
- {file = "rapidfuzz-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-win32.whl", hash = "sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1"},
- {file = "rapidfuzz-3.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-win32.whl", hash = "sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105"},
- {file = "rapidfuzz-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-win32.whl", hash = "sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198"},
- {file = "rapidfuzz-3.6.1-cp39-cp39-win_arm64.whl", hash = "sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e"},
- {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c"},
- {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6"},
- {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05"},
- {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c"},
- {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9"},
- {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87"},
- {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2"},
- {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b"},
- {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec"},
- {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b"},
- {file = "rapidfuzz-3.6.1.tar.gz", hash = "sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b9793c19bdf38656c8eaefbcf4549d798572dadd70581379e666035c9df781"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:015b5080b999404fe06ec2cb4f40b0be62f0710c926ab41e82dfbc28e80675b4"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc5ceca9c1e1663f3e6c23fb89a311f69b7615a40ddd7645e3435bf3082688a"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1424e238bc3f20e1759db1e0afb48a988a9ece183724bef91ea2a291c0b92a95"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed01378f605aa1f449bee82cd9c83772883120d6483e90aa6c5a4ce95dc5c3aa"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb26d412271e5a76cdee1c2d6bf9881310665d3fe43b882d0ed24edfcb891a84"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f37e9e1f17be193c41a31c864ad4cd3ebd2b40780db11cd5c04abf2bcf4201b"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d070ec5cf96b927c4dc5133c598c7ff6db3b833b363b2919b13417f1002560bc"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:10e61bb7bc807968cef09a0e32ce253711a2d450a4dce7841d21d45330ffdb24"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:31a2fc60bb2c7face4140010a7aeeafed18b4f9cdfa495cc644a68a8c60d1ff7"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fbebf1791a71a2e89f5c12b78abddc018354d5859e305ec3372fdae14f80a826"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aee9fc9e3bb488d040afc590c0a7904597bf4ccd50d1491c3f4a5e7e67e6cd2c"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-win32.whl", hash = "sha256:005a02688a51c7d2451a2d41c79d737aa326ff54167211b78a383fc2aace2c2c"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:3a2e75e41ee3274754d3b2163cc6c82cd95b892a85ab031f57112e09da36455f"},
+ {file = "rapidfuzz-3.9.4-cp310-cp310-win_arm64.whl", hash = "sha256:2c99d355f37f2b289e978e761f2f8efeedc2b14f4751d9ff7ee344a9a5ca98d9"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:07141aa6099e39d48637ce72a25b893fc1e433c50b3e837c75d8edf99e0c63e1"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db1664eaff5d7d0f2542dd9c25d272478deaf2c8412e4ad93770e2e2d828e175"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc01a223f6605737bec3202e94dcb1a449b6c76d46082cfc4aa980f2a60fd40e"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1869c42e73e2a8910b479be204fa736418741b63ea2325f9cc583c30f2ded41a"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62ea7007941fb2795fff305ac858f3521ec694c829d5126e8f52a3e92ae75526"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:698e992436bf7f0afc750690c301215a36ff952a6dcd62882ec13b9a1ebf7a39"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b76f611935f15a209d3730c360c56b6df8911a9e81e6a38022efbfb96e433bab"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129627d730db2e11f76169344a032f4e3883d34f20829419916df31d6d1338b1"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:90a82143c14e9a14b723a118c9ef8d1bbc0c5a16b1ac622a1e6c916caff44dd8"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ded58612fe3b0e0d06e935eaeaf5a9fd27da8ba9ed3e2596307f40351923bf72"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f16f5d1c4f02fab18366f2d703391fcdbd87c944ea10736ca1dc3d70d8bd2d8b"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26aa7eece23e0df55fb75fbc2a8fb678322e07c77d1fd0e9540496e6e2b5f03e"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-win32.whl", hash = "sha256:f187a9c3b940ce1ee324710626daf72c05599946bd6748abe9e289f1daa9a077"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8e9130fe5d7c9182990b366ad78fd632f744097e753e08ace573877d67c32f8"},
+ {file = "rapidfuzz-3.9.4-cp311-cp311-win_arm64.whl", hash = "sha256:40419e98b10cd6a00ce26e4837a67362f658fc3cd7a71bd8bd25c99f7ee8fea5"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b5d5072b548db1b313a07d62d88fe0b037bd2783c16607c647e01b070f6cf9e5"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf5bcf22e1f0fd273354462631d443ef78d677f7d2fc292de2aec72ae1473e66"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c8fc973adde8ed52810f590410e03fb6f0b541bbaeb04c38d77e63442b2df4c"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2464bb120f135293e9a712e342c43695d3d83168907df05f8c4ead1612310c7"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d9d58689aca22057cf1a5851677b8a3ccc9b535ca008c7ed06dc6e1899f7844"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167e745f98baa0f3034c13583e6302fb69249a01239f1483d68c27abb841e0a1"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0bf0663b4b6da1507869722420ea9356b6195aa907228d6201303e69837af9"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd6ac61b74fdb9e23f04d5f068e6cf554f47e77228ca28aa2347a6ca8903972f"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:60ff67c690acecf381759c16cb06c878328fe2361ddf77b25d0e434ea48a29da"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cb934363380c60f3a57d14af94325125cd8cded9822611a9f78220444034e36e"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fe833493fb5cc5682c823ea3e2f7066b07612ee8f61ecdf03e1268f262106cdd"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2797fb847d89e04040d281cb1902cbeffbc4b5131a5c53fc0db490fd76b2a547"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-win32.whl", hash = "sha256:52e3d89377744dae68ed7c84ad0ddd3f5e891c82d48d26423b9e066fc835cc7c"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:c76da20481c906e08400ee9be230f9e611d5931a33707d9df40337c2655c84b5"},
+ {file = "rapidfuzz-3.9.4-cp312-cp312-win_arm64.whl", hash = "sha256:f2d2846f3980445864c7e8b8818a29707fcaff2f0261159ef6b7bd27ba139296"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:355fc4a268ffa07bab88d9adee173783ec8d20136059e028d2a9135c623c44e6"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d81a78f90269190b568a8353d4ea86015289c36d7e525cd4d43176c88eff429"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e618625ffc4660b26dc8e56225f8b966d5842fa190e70c60db6cd393e25b86e"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b712336ad6f2bacdbc9f1452556e8942269ef71f60a9e6883ef1726b52d9228a"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc1ee19fdad05770c897e793836c002344524301501d71ef2e832847425707"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1950f8597890c0c707cb7e0416c62a1cf03dcdb0384bc0b2dbda7e05efe738ec"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a6c35f272ec9c430568dc8c1c30cb873f6bc96be2c79795e0bce6db4e0e101d"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1df0f9e9239132a231c86ae4f545ec2b55409fa44470692fcfb36b1bd00157ad"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d2c51955329bfccf99ae26f63d5928bf5be9fcfcd9f458f6847fd4b7e2b8986c"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:3c522f462d9fc504f2ea8d82e44aa580e60566acc754422c829ad75c752fbf8d"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:d8a52fc50ded60d81117d7647f262c529659fb21d23e14ebfd0b35efa4f1b83d"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:04dbdfb0f0bfd3f99cf1e9e24fadc6ded2736d7933f32f1151b0f2abb38f9a25"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-win32.whl", hash = "sha256:4968c8bd1df84b42f382549e6226710ad3476f976389839168db3e68fd373298"},
+ {file = "rapidfuzz-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:3fe4545f89f8d6c27b6bbbabfe40839624873c08bd6700f63ac36970a179f8f5"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f256c8fb8f3125574c8c0c919ab0a1f75d7cba4d053dda2e762dcc36357969d"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fdc09cf6e9d8eac3ce48a4615b3a3ee332ea84ac9657dbbefef913b13e632f"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d395d46b80063d3b5d13c0af43d2c2cedf3ab48c6a0c2aeec715aa5455b0c632"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fa714fb96ce9e70c37e64c83b62fe8307030081a0bfae74a76fac7ba0f91715"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc1a0f29f9119be7a8d3c720f1d2068317ae532e39e4f7f948607c3a6de8396"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6022674aa1747d6300f699cd7c54d7dae89bfe1f84556de699c4ac5df0838082"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb72e5f9762fd469701a7e12e94b924af9004954f8c739f925cb19c00862e38"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad04ae301129f0eb5b350a333accd375ce155a0c1cec85ab0ec01f770214e2e4"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f46a22506f17c0433e349f2d1dc11907c393d9b3601b91d4e334fa9a439a6a4d"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:01b42a8728c36011718da409aa86b84984396bf0ca3bfb6e62624f2014f6022c"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e590d5d5443cf56f83a51d3c4867bd1f6be8ef8cfcc44279522bcef3845b2a51"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4c72078b5fdce34ba5753f9299ae304e282420e6455e043ad08e4488ca13a2b0"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-win32.whl", hash = "sha256:f75639277304e9b75e6a7b3c07042d2264e16740a11e449645689ed28e9c2124"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:e81e27e8c32a1e1278a4bb1ce31401bfaa8c2cc697a053b985a6f8d013df83ec"},
+ {file = "rapidfuzz-3.9.4-cp39-cp39-win_arm64.whl", hash = "sha256:15bc397ee9a3ed1210b629b9f5f1da809244adc51ce620c504138c6e7095b7bd"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:20488ade4e1ddba3cfad04f400da7a9c1b91eff5b7bd3d1c50b385d78b587f4f"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e61b03509b1a6eb31bc5582694f6df837d340535da7eba7bedb8ae42a2fcd0b9"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098d231d4e51644d421a641f4a5f2f151f856f53c252b03516e01389b2bfef99"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17ab8b7d10fde8dd763ad428aa961c0f30a1b44426e675186af8903b5d134fb0"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e272df61bee0a056a3daf99f9b1bd82cf73ace7d668894788139c868fdf37d6f"},
+ {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d6481e099ff8c4edda85b8b9b5174c200540fd23c8f38120016c765a86fa01f5"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad61676e9bdae677d577fe80ec1c2cea1d150c86be647e652551dcfe505b1113"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:af65020c0dd48d0d8ae405e7e69b9d8ae306eb9b6249ca8bf511a13f465fad85"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d38b4e026fcd580e0bda6c0ae941e0e9a52c6bc66cdce0b8b0da61e1959f5f8"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f74ed072c2b9dc6743fb19994319d443a4330b0e64aeba0aa9105406c7c5b9c2"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee5f6b8321f90615c184bd8a4c676e9becda69b8e4e451a90923db719d6857c"},
+ {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3a555e3c841d6efa350f862204bb0a3fea0c006b8acc9b152b374fa36518a1c6"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0772150d37bf018110351c01d032bf9ab25127b966a29830faa8ad69b7e2f651"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:addcdd3c3deef1bd54075bd7aba0a6ea9f1d01764a08620074b7a7b1e5447cb9"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fe86b82b776554add8f900b6af202b74eb5efe8f25acdb8680a5c977608727f"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0fc91ac59f4414d8542454dfd6287a154b8e6f1256718c898f695bdbb993467"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a944e546a296a5fdcaabb537b01459f1b14d66f74e584cb2a91448bffadc3c1"},
+ {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fb96ba96d58c668a17a06b5b5e8340fedc26188e87b0d229d38104556f30cd8"},
+ {file = "rapidfuzz-3.9.4.tar.gz", hash = "sha256:366bf8947b84e37f2f4cf31aaf5f37c39f620d8c0eddb8b633e6ba0129ca4a0a"},
]
[package.extras]
@@ -3405,129 +3503,24 @@ full = ["numpy"]
[[package]]
name = "readchar"
-version = "4.0.5"
+version = "4.1.0"
description = "Library to easily read single chars and key strokes"
optional = false
-python-versions = ">=3.7"
-files = [
- {file = "readchar-4.0.5-py3-none-any.whl", hash = "sha256:76ec784a5dd2afac3b7da8003329834cdd9824294c260027f8c8d2e4d0a78f43"},
- {file = "readchar-4.0.5.tar.gz", hash = "sha256:08a456c2d7c1888cde3f4688b542621b676eb38cd6cfed7eb6cb2e2905ddc826"},
-]
-
-[package.dependencies]
-setuptools = ">=41.0"
-
-[[package]]
-name = "regex"
-version = "2023.12.25"
-description = "Alternative regular expression module, to replace re."
-optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
- {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
- {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
- {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
- {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
- {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
- {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
- {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
- {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
- {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
- {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
- {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
- {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
- {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
- {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
- {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
- {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
+ {file = "readchar-4.1.0-py3-none-any.whl", hash = "sha256:d163680656b34f263fb5074023db44b999c68ff31ab394445ebfd1a2a41fe9a2"},
+ {file = "readchar-4.1.0.tar.gz", hash = "sha256:6f44d1b5f0fd93bd93236eac7da39609f15df647ab9cea39f5bc7478b3344b99"},
]
[[package]]
name = "requests"
-version = "2.31.0"
+version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
- {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+ {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
@@ -3556,13 +3549,13 @@ requests = ">=2.0.1,<3.0.0"
[[package]]
name = "rich"
-version = "13.7.0"
+version = "13.7.1"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.7.0"
files = [
- {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
- {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
+ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
+ {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[package.dependencies]
@@ -3575,32 +3568,33 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "rich-click"
-version = "1.7.3"
+version = "1.8.3"
description = "Format click help output nicely with rich"
optional = false
python-versions = ">=3.7"
files = [
- {file = "rich-click-1.7.3.tar.gz", hash = "sha256:bced1594c497dc007ab49508ff198bb437c576d01291c13a61658999066481f4"},
- {file = "rich_click-1.7.3-py3-none-any.whl", hash = "sha256:bc4163d4e2a3361e21c4d72d300eca6eb8896dfc978667923cb1d4937b8769a3"},
+ {file = "rich_click-1.8.3-py3-none-any.whl", hash = "sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd"},
+ {file = "rich_click-1.8.3.tar.gz", hash = "sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3"},
]
[package.dependencies]
click = ">=7"
-rich = ">=10.7.0"
+rich = ">=10.7"
typing-extensions = "*"
[package.extras]
-dev = ["flake8", "flake8-docstrings", "mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "types-setuptools"]
+dev = ["mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "rich-codex", "ruff", "types-setuptools"]
+docs = ["markdown-include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"]
[[package]]
name = "sanic"
-version = "23.12.1"
+version = "24.6.0"
description = "A web server and web framework that's written to go fast. Build fast. Run fast."
optional = false
python-versions = ">=3.8"
files = [
- {file = "sanic-23.12.1-py3-none-any.whl", hash = "sha256:e292293b2663a7afeb380bdc48ab93978468b27deae46ad9561513941eb0311f"},
- {file = "sanic-23.12.1.tar.gz", hash = "sha256:2528ca81d2bdc58ea67d93c500df1a9c58404904b0bc3442425b464c72b4bb84"},
+ {file = "sanic-24.6.0-py3-none-any.whl", hash = "sha256:e2c6b392e213d85d9843cf27c64e3f2dacb3ec5c31c8c7ade4c404cd3030e994"},
+ {file = "sanic-24.6.0.tar.gz", hash = "sha256:2e0841e2c8c28e68a0e6fc570c42aafbbe3b385d7141b9f96997d9d6c17d7afb"},
]
[package.dependencies]
@@ -3609,6 +3603,7 @@ html5tagger = ">=1.2.1"
httptools = ">=0.0.10"
multidict = ">=5.0,<7.0"
sanic-routing = ">=23.12.0"
+setuptools = ">=70.1.0"
tracerite = ">=1.0.0"
typing-extensions = ">=4.4.0"
ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""}
@@ -3616,12 +3611,12 @@ uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and impleme
websockets = ">=10.0"
[package.extras]
-all = ["autodocsumm (>=0.2.11)", "bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
-dev = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
+all = ["autodocsumm (>=0.2.11)", "bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "mypy", "pygments", "pytest (>=8.2.2)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn"]
+dev = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "mypy", "pygments", "pytest (>=8.2.2)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn"]
docs = ["autodocsumm (>=0.2.11)", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"]
ext = ["sanic-ext"]
http3 = ["aioquic"]
-test = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"]
+test = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "docutils", "mypy", "pygments", "pytest (>=8.2.2)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn"]
[[package]]
name = "sanic-routing"
@@ -3668,13 +3663,13 @@ jeepney = ">=0.6"
[[package]]
name = "sentry-sdk"
-version = "1.40.0"
+version = "1.45.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = "*"
files = [
- {file = "sentry-sdk-1.40.0.tar.gz", hash = "sha256:34ad8cfc9b877aaa2a8eb86bfe5296a467fffe0619b931a05b181c45f6da59bf"},
- {file = "sentry_sdk-1.40.0-py2.py3-none-any.whl", hash = "sha256:78575620331186d32f34b7ece6edea97ce751f58df822547d3ab85517881a27a"},
+ {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"},
+ {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"},
]
[package.dependencies]
@@ -3688,6 +3683,7 @@ asyncpg = ["asyncpg (>=0.23)"]
beam = ["apache-beam (>=2.12)"]
bottle = ["bottle (>=0.12.13)"]
celery = ["celery (>=3)"]
+celery-redbeat = ["celery-redbeat (>=2)"]
chalice = ["chalice (>=1.16.0)"]
clickhouse-driver = ["clickhouse-driver (>=0.2.0)"]
django = ["django (>=1.8)"]
@@ -3698,6 +3694,7 @@ grpcio = ["grpcio (>=1.21.1)"]
httpx = ["httpx (>=0.16.0)"]
huey = ["huey (>=2)"]
loguru = ["loguru (>=0.5)"]
+openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
@@ -3737,19 +3734,19 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"]
[[package]]
name = "setuptools"
-version = "67.8.0"
+version = "71.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"},
- {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"},
+ {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"},
+ {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"},
]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "shellingham"
@@ -3775,40 +3772,39 @@ files = [
[[package]]
name = "sniffio"
-version = "1.3.0"
+version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
- {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
- {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "sqlparse"
-version = "0.4.4"
+version = "0.5.1"
description = "A non-validating SQL parser."
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
files = [
- {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
- {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
+ {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
+ {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
]
[package.extras]
-dev = ["build", "flake8"]
+dev = ["build", "hatch"]
doc = ["sphinx"]
-test = ["pytest", "pytest-cov"]
[[package]]
name = "starlette"
-version = "0.36.3"
+version = "0.37.2"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.8"
files = [
- {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"},
- {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"},
+ {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
+ {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
]
[package.dependencies]
@@ -3871,6 +3867,17 @@ files = [
[package.dependencies]
exceptiongroup = "*"
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+
[[package]]
name = "tomli"
version = "2.0.1"
@@ -3884,13 +3891,13 @@ files = [
[[package]]
name = "tomlkit"
-version = "0.12.3"
+version = "0.13.0"
description = "Style preserving TOML library"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"},
- {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"},
+ {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"},
+ {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"},
]
[[package]]
@@ -3909,24 +3916,24 @@ html5tagger = ">=1.2.1"
[[package]]
name = "trove-classifiers"
-version = "2024.1.31"
+version = "2024.7.2"
description = "Canonical source for classifiers on PyPI (pypi.org)."
optional = false
python-versions = "*"
files = [
- {file = "trove-classifiers-2024.1.31.tar.gz", hash = "sha256:bfdfe60bbf64985c524416afb637ecc79c558e0beb4b7f52b0039e01044b0229"},
- {file = "trove_classifiers-2024.1.31-py3-none-any.whl", hash = "sha256:854aba3358f3cf10e5c0916aa533f5a39e27aadd8ade26a54cdc2a93257e39c4"},
+ {file = "trove_classifiers-2024.7.2-py3-none-any.whl", hash = "sha256:ccc57a33717644df4daca018e7ec3ef57a835c48e96a1e71fc07eb7edac67af6"},
+ {file = "trove_classifiers-2024.7.2.tar.gz", hash = "sha256:8328f2ac2ce3fd773cbb37c765a0ed7a83f89dc564c7d452f039b69249d0ac35"},
]
[[package]]
name = "twisted"
-version = "23.10.0"
+version = "24.3.0"
description = "An asynchronous networking framework written in Python"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "twisted-23.10.0-py3-none-any.whl", hash = "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444"},
- {file = "twisted-23.10.0.tar.gz", hash = "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"},
+ {file = "twisted-24.3.0-py3-none-any.whl", hash = "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63"},
+ {file = "twisted-24.3.0.tar.gz", hash = "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"},
]
[package.dependencies]
@@ -3950,7 +3957,7 @@ dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.0)", "
gtk-platform = ["pygobject", "pygobject", "twisted[all-non-platform]", "twisted[all-non-platform]"]
http2 = ["h2 (>=3.0,<5.0)", "priority (>=1.1.0,<2.0)"]
macos-platform = ["pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "twisted[all-non-platform]", "twisted[all-non-platform]"]
-mypy = ["mypy (>=1.5.1,<1.6.0)", "mypy-zope (>=1.0.1,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"]
+mypy = ["mypy (>=1.8,<2.0)", "mypy-zope (>=1.0.3,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"]
osx-platform = ["twisted[macos-platform]", "twisted[macos-platform]"]
serial = ["pyserial (>=3.0)", "pywin32 (!=226)"]
test = ["cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.56)", "pyhamcrest (>=2)"]
@@ -4003,34 +4010,30 @@ twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
[[package]]
name = "typer"
-version = "0.9.0"
+version = "0.12.3"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"},
- {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"},
+ {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
+ {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
]
[package.dependencies]
-click = ">=7.1.1,<9.0.0"
+click = ">=8.0.0"
+rich = ">=10.11.0"
+shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
-[package.extras]
-all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
-dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
-doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"]
-test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
-
[[package]]
name = "types-aiofiles"
-version = "23.2.0.20240106"
+version = "24.1.0.20240626"
description = "Typing stubs for aiofiles"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-aiofiles-23.2.0.20240106.tar.gz", hash = "sha256:ef4fa3072441c58beaadbd0d07ba18e89beff49c71648dd223e2ca861f3dac53"},
- {file = "types_aiofiles-23.2.0.20240106-py3-none-any.whl", hash = "sha256:7324f9a9f7200c1f4986a9e40a42b548290f707b967709f30b280e99fdacbd99"},
+ {file = "types-aiofiles-24.1.0.20240626.tar.gz", hash = "sha256:48604663e24bc2d5038eac05ccc33e75799b0779e93e13d6a8f711ddc306ac08"},
+ {file = "types_aiofiles-24.1.0.20240626-py3-none-any.whl", hash = "sha256:7939eca4a8b4f9c6491b6e8ef160caee9a21d32e18534a57d5ed90aee47c66b4"},
]
[[package]]
@@ -4068,35 +4071,35 @@ files = [
[[package]]
name = "types-protobuf"
-version = "4.24.0.20240129"
+version = "4.25.0.20240417"
description = "Typing stubs for protobuf"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-protobuf-4.24.0.20240129.tar.gz", hash = "sha256:8a83dd3b9b76a33e08d8636c5daa212ace1396418ed91837635fcd564a624891"},
- {file = "types_protobuf-4.24.0.20240129-py3-none-any.whl", hash = "sha256:23be68cc29f3f5213b5c5878ac0151706182874040e220cfb11336f9ee642ead"},
+ {file = "types-protobuf-4.25.0.20240417.tar.gz", hash = "sha256:c34eff17b9b3a0adb6830622f0f302484e4c089f533a46e3f147568313544352"},
+ {file = "types_protobuf-4.25.0.20240417-py3-none-any.whl", hash = "sha256:e9b613227c2127e3d4881d75d93c93b4d6fd97b5f6a099a0b654a05351c8685d"},
]
[[package]]
name = "types-python-dateutil"
-version = "2.8.19.20240106"
+version = "2.9.0.20240316"
description = "Typing stubs for python-dateutil"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"},
- {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"},
+ {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"},
+ {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"},
]
[[package]]
name = "types-toml"
-version = "0.10.8.7"
+version = "0.10.8.20240310"
description = "Typing stubs for toml"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "types-toml-0.10.8.7.tar.gz", hash = "sha256:58b0781c681e671ff0b5c0319309910689f4ab40e8a2431e205d70c94bb6efb1"},
- {file = "types_toml-0.10.8.7-py3-none-any.whl", hash = "sha256:61951da6ad410794c97bec035d59376ce1cbf4453dc9b6f90477e81e4442d631"},
+ {file = "types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331"},
+ {file = "types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d"},
]
[[package]]
@@ -4112,24 +4115,24 @@ files = [
[[package]]
name = "types-ujson"
-version = "5.9.0.0"
+version = "5.10.0.20240515"
description = "Typing stubs for ujson"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "types-ujson-5.9.0.0.tar.gz", hash = "sha256:7e7042454dc7cd7f31b09c420d7caf36b93d30bdf4b8db93791bd0561713d017"},
- {file = "types_ujson-5.9.0.0-py3-none-any.whl", hash = "sha256:f274fa604ed6317effcd1c424ef4cf292c3b0689cb118fb3180689d40ed1f4ed"},
+ {file = "types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7"},
+ {file = "types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310"},
]
[[package]]
name = "typing-extensions"
-version = "4.9.0"
+version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
- {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
@@ -4149,98 +4152,111 @@ typing-extensions = ">=3.7.4"
[[package]]
name = "tzdata"
-version = "2023.4"
+version = "2024.1"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
- {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"},
- {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"},
+ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
+ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
]
[[package]]
name = "ujson"
-version = "5.9.0"
+version = "5.10.0"
description = "Ultra fast JSON encoder and decoder for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"},
- {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"},
- {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"},
- {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"},
- {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"},
- {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"},
- {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"},
- {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"},
- {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"},
- {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"},
- {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"},
- {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"},
- {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"},
- {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"},
- {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"},
- {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"},
- {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"},
- {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"},
- {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"},
- {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"},
- {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"},
- {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"},
- {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"},
- {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"},
- {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"},
- {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"},
- {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"},
- {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"},
- {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"},
- {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"},
- {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"},
- {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"},
- {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"},
- {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"},
- {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"},
- {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"},
- {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"},
- {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"},
- {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"},
- {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"},
- {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"},
- {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"},
- {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"},
- {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"},
- {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"},
- {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"},
- {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"},
- {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"},
- {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"},
- {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"},
- {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"},
- {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"},
- {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"},
- {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"},
- {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"},
- {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"},
- {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"},
- {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"},
- {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"},
- {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"},
- {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"},
- {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"},
- {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"},
- {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"},
- {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"},
+ {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
+ {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
+ {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"},
+ {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"},
+ {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"},
+ {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"},
+ {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"},
+ {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"},
+ {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"},
+ {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"},
+ {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"},
+ {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"},
+ {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"},
+ {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"},
+ {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"},
+ {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"},
+ {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"},
+ {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"},
+ {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"},
+ {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"},
+ {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
+ {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
+ {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
+ {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
+ {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
+ {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
+ {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
+ {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
+ {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
+ {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
+ {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"},
+ {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"},
+ {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"},
+ {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"},
+ {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"},
+ {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"},
+ {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"},
+ {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"},
+ {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"},
+ {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"},
+ {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"},
+ {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"},
+ {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"},
+ {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"},
+ {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"},
+ {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"},
+ {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"},
+ {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"},
+ {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"},
+ {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"},
+ {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"},
+ {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"},
+ {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"},
+ {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"},
+ {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"},
+ {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"},
+ {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"},
+ {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"},
+ {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"},
+ {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"},
+ {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"},
+ {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"},
+ {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"},
+ {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"},
+ {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"},
+ {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"},
+ {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"},
+ {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
]
[[package]]
name = "urllib3"
-version = "1.26.18"
+version = "1.26.19"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
- {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
- {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
+ {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"},
+ {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"},
]
[package.extras]
@@ -4250,19 +4266,26 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
-version = "0.27.0.post1"
+version = "0.30.3"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
files = [
- {file = "uvicorn-0.27.0.post1-py3-none-any.whl", hash = "sha256:4b85ba02b8a20429b9b205d015cbeb788a12da527f731811b643fd739ef90d5f"},
- {file = "uvicorn-0.27.0.post1.tar.gz", hash = "sha256:54898fcd80c13ff1cd28bf77b04ec9dbd8ff60c5259b499b4b12bb0917f22907"},
+ {file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"},
+ {file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"},
]
[package.dependencies]
click = ">=7.0"
+colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
h11 = ">=0.8"
+httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
+python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
@@ -4313,13 +4336,13 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)"
[[package]]
name = "virtualenv"
-version = "20.25.0"
+version = "20.26.3"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
- {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
+ {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
+ {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
]
[package.dependencies]
@@ -4328,9 +4351,96 @@ filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5"
[package.extras]
-docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+[[package]]
+name = "watchfiles"
+version = "0.22.0"
+description = "Simple, modern and high performance file watching and code reload in python."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"},
+ {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"},
+ {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"},
+ {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"},
+ {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"},
+ {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"},
+ {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"},
+ {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"},
+ {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"},
+ {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"},
+ {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"},
+ {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"},
+ {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"},
+ {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"},
+ {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"},
+ {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"},
+ {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"},
+ {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"},
+ {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"},
+ {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"},
+ {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"},
+ {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"},
+ {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"},
+ {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"},
+ {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"},
+ {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"},
+ {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"},
+ {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"},
+ {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"},
+ {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"},
+ {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"},
+ {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"},
+ {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"},
+ {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"},
+ {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"},
+ {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"},
+ {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"},
+ {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"},
+ {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"},
+ {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"},
+ {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"},
+ {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"},
+ {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"},
+ {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"},
+ {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"},
+ {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"},
+ {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"},
+ {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"},
+ {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"},
+ {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"},
+]
+
+[package.dependencies]
+anyio = ">=3.0.0"
+
[[package]]
name = "wcwidth"
version = "0.2.13"
@@ -4425,13 +4535,13 @@ files = [
[[package]]
name = "werkzeug"
-version = "3.0.1"
+version = "3.0.3"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.8"
files = [
- {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
- {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
+ {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
+ {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
]
[package.dependencies]
@@ -4442,13 +4552,13 @@ watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "wheel"
-version = "0.42.0"
+version = "0.43.0"
description = "A built-package format for Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"},
- {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"},
+ {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"},
+ {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"},
]
[package.extras]
@@ -4549,87 +4659,76 @@ h11 = ">=0.9.0,<1"
[[package]]
name = "xattr"
-version = "0.10.1"
+version = "1.1.0"
description = "Python wrapper for extended filesystem attributes"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"},
- {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"},
- {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"},
- {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"},
- {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"},
- {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"},
- {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"},
- {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"},
- {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"},
- {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"},
- {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"},
- {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"},
- {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"},
- {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"},
- {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"},
- {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"},
- {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"},
- {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"},
- {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"},
- {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"},
- {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"},
- {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"},
- {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"},
- {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"},
- {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"},
- {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"},
- {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"},
- {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"},
- {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"},
- {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"},
- {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"},
- {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"},
- {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"},
- {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"},
- {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"},
- {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"},
- {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"},
- {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"},
- {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"},
- {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"},
- {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"},
- {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"},
- {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"},
- {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"},
- {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"},
- {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"},
- {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"},
- {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"},
- {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"},
- {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"},
- {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"},
- {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"},
- {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"},
- {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"},
- {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"},
- {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"},
- {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"},
- {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"},
- {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"},
- {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"},
- {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"},
- {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"},
- {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"},
- {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"},
- {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"},
- {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"},
- {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"},
- {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"},
- {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"},
- {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"},
- {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"},
- {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"},
+ {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"},
+ {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"},
+ {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"},
+ {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"},
+ {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"},
+ {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"},
+ {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"},
+ {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"},
+ {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"},
+ {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"},
+ {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"},
+ {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"},
+ {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"},
+ {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"},
+ {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"},
+ {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"},
+ {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"},
+ {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"},
+ {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"},
+ {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"},
+ {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"},
+ {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"},
+ {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"},
+ {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"},
+ {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"},
+ {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"},
+ {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"},
+ {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"},
+ {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"},
+ {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"},
+ {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"},
+ {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"},
+ {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"},
+ {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"},
+ {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"},
+ {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"},
+ {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"},
+ {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"},
+ {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"},
+ {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"},
+ {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"},
+ {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"},
+ {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"},
+ {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"},
+ {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"},
+ {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"},
+ {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"},
+ {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"},
+ {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"},
+ {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"},
+ {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"},
+ {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"},
+ {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"},
+ {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"},
+ {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"},
+ {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"},
+ {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"},
+ {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"},
]
[package.dependencies]
-cffi = ">=1.0"
+cffi = ">=1.16.0"
+
+[package.extras]
+test = ["pytest"]
[[package]]
name = "xmltodict"
@@ -4747,62 +4846,62 @@ multidict = ">=4.0"
[[package]]
name = "zipp"
-version = "3.17.0"
+version = "3.19.2"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
- {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
- {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
+ {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
+ {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "zope-interface"
-version = "6.1"
+version = "6.4.post2"
description = "Interfaces for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb"},
- {file = "zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92"},
- {file = "zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3"},
- {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd"},
- {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41"},
- {file = "zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f"},
- {file = "zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1"},
- {file = "zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736"},
- {file = "zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605"},
- {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8"},
- {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"},
- {file = "zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1"},
- {file = "zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a"},
- {file = "zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7"},
- {file = "zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d"},
- {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff"},
- {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0"},
- {file = "zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b"},
- {file = "zope.interface-6.1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d"},
- {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c"},
- {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83"},
- {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379"},
- {file = "zope.interface-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9"},
- {file = "zope.interface-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f"},
- {file = "zope.interface-6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1"},
- {file = "zope.interface-6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56"},
- {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b"},
- {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43"},
- {file = "zope.interface-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d"},
- {file = "zope.interface-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179"},
- {file = "zope.interface-6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941"},
- {file = "zope.interface-6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3"},
- {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d"},
- {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac"},
- {file = "zope.interface-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40"},
- {file = "zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9"},
+ {file = "zope.interface-6.4.post2-cp310-cp310-win_amd64.whl", hash = "sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8"},
+ {file = "zope.interface-6.4.post2-cp311-cp311-win_amd64.whl", hash = "sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854"},
+ {file = "zope.interface-6.4.post2-cp312-cp312-win_amd64.whl", hash = "sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e"},
+ {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc"},
+ {file = "zope.interface-6.4.post2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1"},
+ {file = "zope.interface-6.4.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43"},
+ {file = "zope.interface-6.4.post2-cp38-cp38-win_amd64.whl", hash = "sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671"},
+ {file = "zope.interface-6.4.post2-cp39-cp39-win_amd64.whl", hash = "sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15"},
+ {file = "zope.interface-6.4.post2.tar.gz", hash = "sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e"},
]
[package.dependencies]
@@ -4818,7 +4917,7 @@ aiohttp = ["aiohttp"]
asgi = ["python-multipart", "starlette"]
chalice = ["chalice"]
channels = ["asgiref", "channels"]
-cli = ["libcst", "pygments", "rich", "typer"]
+cli = ["graphlib_backport", "libcst", "pygments", "rich", "typer"]
debug = ["libcst", "rich"]
debug-server = ["libcst", "pygments", "python-multipart", "rich", "starlette", "typer", "uvicorn"]
django = ["Django", "asgiref"]
@@ -4835,4 +4934,4 @@ starlite = ["starlite"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "c45e495e5856ed24bb1a5bf24f1c05dfc669c9064e4f8950f54b281882b17b41"
+content-hash = "9a0980f125fee845876c9ea685a0ca3741866b746d4cb06e30aed34b11ab51f9"
diff --git a/pyproject.toml b/pyproject.toml
index b5255b4932..bf9bbffc04 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[tool.poetry]
name = "strawberry-graphql"
packages = [ { include = "strawberry" } ]
-version = "0.219.2"
+version = "0.237.3"
description = "A library for creating GraphQL APIs"
authors = ["Patrick Arminio "]
license = "MIT"
@@ -35,7 +35,7 @@ build-backend = "poetry.masonry.api"
[tool.poetry.dependencies]
python = "^3.8"
-graphql-core = "~3.2.0"
+graphql-core = ">=3.2.0,<3.4.0"
typing-extensions = ">=4.5.0"
python-dateutil = "^2.7.0"
starlette = {version = ">=0.18.0", optional = true}
@@ -61,13 +61,14 @@ astunparse = {version = "^1.6.3", python = "<3.9"}
libcst = {version = ">=0.4.7", optional = true}
rich = {version = ">=12.0.0", optional = true}
pyinstrument = {version = ">=4.0.0", optional = true}
+graphlib_backport = {version = "*", python = "<3.9", optional = true}
[tool.poetry.group.dev.dependencies]
asgiref = "^3.2"
-ddtrace = ">=1.6.4,<3.0.0"
+ddtrace = ">=1.6.4"
email-validator = {version = ">=1.1.3,<3.0.0", optional = false}
freezegun = "^1.2.1"
-libcst = {version = ">=0.4.7,<1.2.0", optional = false}
+libcst = {version = ">=1.0.0", optional = false}
MarkupSafe = "2.1.3"
nox = "^2023.4.22"
nox-poetry = "^1.0.3"
@@ -76,7 +77,7 @@ opentelemetry-sdk = "<2"
pygments = "^2.3"
pyinstrument = {version = ">=4.0.0", optional = false}
pytest = "^7.2"
-pytest-asyncio = ">=0.20.3,<0.24.0"
+pytest-asyncio = ">=0.20.3"
pytest-codspeed = "^2.0.1"
pytest-cov = "^4.0.0"
pytest-emoji = "^0.2.0"
@@ -84,11 +85,11 @@ pytest-mock = "^3.10"
pytest-snapshot = "^0.9.0"
pytest-xdist = {extras = ["psutil"], version = "^3.1.0"}
python-multipart = ">=0.0.7"
-rich = {version = ">=12.5.1,<14.0.0", optional = false}
+rich = {version = ">=12.5.1", optional = false}
sanic-testing = ">=22.9,<24.0"
sentry-sdk = "^1.39.2"
typer = {version = ">=0.7.0", optional = false}
-types-aiofiles = ">=22.1,<24.0"
+types-aiofiles = ">=22.1"
types-certifi = "^2021.10.8"
types-chardet = "^5.0.4"
types-freezegun = "^1.1.9"
@@ -97,11 +98,11 @@ types-toml = "^0.10.8"
types-typed-ast = "^1.5.8"
types-ujson = "^5.6.0"
types-protobuf = "^4.23.0.1"
-mypy = "1.8.0"
-pytest-mypy-plugins = ">=1.10,<4.0"
poetry-plugin-export = "^1.6.0"
# another bug in poetry
urllib3 = "<2"
+graphlib_backport = {version = "*", python = "<3.9", optional = false}
+inline-snapshot = "^0.10.1"
[tool.poetry.group.integrations]
optional = true
@@ -138,7 +139,7 @@ pydantic = ["pydantic"]
sanic = ["sanic"]
fastapi = ["fastapi", "python-multipart"]
chalice = ["chalice"]
-cli = ["typer", "pygments", "rich", "libcst"]
+cli = ["typer", "pygments", "rich", "libcst", "graphlib_backport"]
starlite = ["starlite"]
litestar = ["litestar"]
pyinstrument = ["pyinstrument"]
@@ -147,7 +148,7 @@ pyinstrument = ["pyinstrument"]
strawberry = "strawberry.cli:run"
[tool.pytest.ini_options]
-addopts = "--emoji --mypy-ini-file=mypy.ini"
+addopts = "--emoji"
DJANGO_SETTINGS_MODULE = "tests.django.django_settings"
testpaths = ["tests/"]
markers = [
@@ -171,6 +172,8 @@ asyncio_mode = "auto"
filterwarnings = [
"ignore::DeprecationWarning:strawberry.*.resolver",
"ignore:LazyType is deprecated:DeprecationWarning",
+ "ignore::DeprecationWarning:ddtrace.internal",
+ "ignore::DeprecationWarning:django.utils.encoding",
# ignoring the text instead of the whole warning because we'd
# get an error when django is not installed
"ignore:The default value of USE_TZ",
@@ -238,15 +241,9 @@ ignore = [
"S102",
"S104",
"S324",
- # maybe we can enable this in future
- # we'd want to have consistent docstrings in future
- "D",
"ANN101", # missing annotation for self?
# definitely enable these, maybe not in tests
"ANN102",
- "ANN202",
- "ANN204",
- "ANN205",
"ANN401",
"PGH003",
"PGH004",
@@ -357,11 +354,38 @@ ignore = [
"FIX001",
"FIX002",
"FA100",
+
+ # Docstrings, maybe to enable later
+ "D100",
+ "D101",
+ "D102",
+ "D103",
+ "D104",
+ "D105",
+ "D106",
+ "D107",
+ "D412",
]
[tool.ruff.lint.per-file-ignores]
"strawberry/schema/types/concrete_type.py" = ["TCH002"]
-"tests/*" = ["RSE102", "SLF001", "TCH001", "TCH002", "TCH003", "ANN001", "ANN201", "PLW0603", "PLC1901", "S603", "S607", "B018"]
+"tests/*" = [
+ "RSE102",
+ "SLF001",
+ "TCH001",
+ "TCH002",
+ "TCH003",
+ "ANN001",
+ "ANN201",
+ "ANN202",
+ "ANN204",
+ "PLW0603",
+ "PLC1901",
+ "S603",
+ "S607",
+ "B018",
+ "D",
+]
"strawberry/extensions/tracing/__init__.py" = ["TCH004"]
"tests/http/clients/__init__.py" = ["F401"]
@@ -375,3 +399,6 @@ exclude =[
'tests/codegen/snapshots/',
'tests/cli/snapshots/'
]
+
+[tool.ruff.lint.pydocstyle]
+convention = "google"
diff --git a/strawberry/__init__.py b/strawberry/__init__.py
index 66aaff4757..2cbda32b2f 100644
--- a/strawberry/__init__.py
+++ b/strawberry/__init__.py
@@ -1,26 +1,34 @@
+"""Strawberry is a Python library for GraphQL.
+
+Strawberry is a Python library for GraphQL that aims to stay close to the GraphQL
+specification and allow for a more natural way of defining GraphQL schemas.
+"""
+
from . import experimental, federation, relay
-from .arguments import argument
-from .auto import auto
-from .custom_scalar import scalar
from .directive import directive, directive_field
-from .enum import enum, enum_value
-from .field import field
-from .lazy_type import LazyType, lazy
-from .mutation import mutation, subscription
-from .object_type import asdict, input, interface, type
from .parent import Parent
from .permission import BasePermission
-from .private import Private
from .scalars import ID
from .schema import Schema
from .schema_directive import schema_directive
-from .union import union
-from .unset import UNSET
+from .types.arguments import argument
+from .types.auto import auto
+from .types.enum import enum, enum_value
+from .types.field import field
+from .types.info import Info
+from .types.lazy_type import LazyType, lazy
+from .types.mutation import mutation, subscription
+from .types.object_type import asdict, input, interface, type
+from .types.private import Private
+from .types.scalar import scalar
+from .types.union import union
+from .types.unset import UNSET
__all__ = [
"BasePermission",
"experimental",
"ID",
+ "Info",
"UNSET",
"lazy",
"LazyType",
diff --git a/strawberry/aiohttp/handlers/graphql_transport_ws_handler.py b/strawberry/aiohttp/handlers/graphql_transport_ws_handler.py
index fc5b1316b0..52350199f7 100644
--- a/strawberry/aiohttp/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/aiohttp/handlers/graphql_transport_ws_handler.py
@@ -23,7 +23,7 @@ def __init__(
get_context: Callable[..., Dict[str, Any]],
get_root_value: Any,
request: web.Request,
- ):
+ ) -> None:
super().__init__(schema, debug, connection_init_wait_timeout)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -57,3 +57,6 @@ async def handle_request(self) -> web.StreamResponse:
await self.shutdown()
return self._ws
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/aiohttp/handlers/graphql_ws_handler.py b/strawberry/aiohttp/handlers/graphql_ws_handler.py
index 4615e5b44a..a8a80e481c 100644
--- a/strawberry/aiohttp/handlers/graphql_ws_handler.py
+++ b/strawberry/aiohttp/handlers/graphql_ws_handler.py
@@ -22,7 +22,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
request: web.Request,
- ):
+ ) -> None:
super().__init__(schema, debug, keep_alive, keep_alive_interval)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -60,3 +60,6 @@ async def handle_request(self) -> Any:
await self.cleanup_operation(operation_id)
return self._ws
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/aiohttp/test/client.py b/strawberry/aiohttp/test/client.py
index 4b7f860cfc..6f30b7e7aa 100644
--- a/strawberry/aiohttp/test/client.py
+++ b/strawberry/aiohttp/test/client.py
@@ -48,3 +48,6 @@ async def request(
)
return response
+
+
+__all__ = ["GraphQLTestClient"]
diff --git a/strawberry/aiohttp/views.py b/strawberry/aiohttp/views.py
index 033586dae0..1cec0ab274 100644
--- a/strawberry/aiohttp/views.py
+++ b/strawberry/aiohttp/views.py
@@ -38,7 +38,7 @@
class AioHTTPRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: web.Request):
+ def __init__(self, request: web.Request) -> None:
self.request = request
@property
@@ -109,7 +109,7 @@ def __init__(
GRAPHQL_WS_PROTOCOL,
),
connection_init_wait_timeout: timedelta = timedelta(minutes=1),
- ):
+ ) -> None:
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
self.keep_alive = keep_alive
@@ -209,3 +209,6 @@ async def create_multipart_response(
await response.write_eof()
return response
+
+
+__all__ = ["GraphQLView"]
diff --git a/strawberry/annotation.py b/strawberry/annotation.py
index 86c3b11e95..86d06d73e5 100644
--- a/strawberry/annotation.py
+++ b/strawberry/annotation.py
@@ -20,26 +20,26 @@
)
from typing_extensions import Annotated, Self, get_args, get_origin
-from strawberry.custom_scalar import ScalarDefinition
-from strawberry.enum import EnumDefinition
from strawberry.exceptions.not_a_strawberry_enum import NotAStrawberryEnumError
-from strawberry.lazy_type import LazyType
-from strawberry.private import is_private
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
+ StrawberryObjectDefinition,
StrawberryOptional,
StrawberryTypeVar,
get_object_definition,
has_object_definition,
)
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.unset import UNSET
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.private import is_private
+from strawberry.types.scalar import ScalarDefinition
+from strawberry.types.unset import UNSET
from strawberry.utils.typing import eval_type, is_generic, is_type_var
if TYPE_CHECKING:
- from strawberry.field import StrawberryField
- from strawberry.type import StrawberryType
- from strawberry.union import StrawberryUnion
+ from strawberry.types.base import StrawberryType
+ from strawberry.types.field import StrawberryField
+ from strawberry.types.union import StrawberryUnion
ASYNC_TYPES = (
@@ -54,18 +54,18 @@
class StrawberryAnnotation:
- __slots__ = "raw_annotation", "namespace", "__eval_cache__"
+ __slots__ = "raw_annotation", "namespace", "__resolve_cache__"
def __init__(
self,
annotation: Union[object, str],
*,
namespace: Optional[Dict[str, Any]] = None,
- ):
+ ) -> None:
self.raw_annotation = annotation
self.namespace = namespace
- self.__eval_cache__: Optional[Type[Any]] = None
+ self.__resolve_cache__: Optional[Union[StrawberryType, type]] = None
def __eq__(self, other: object) -> bool:
if not isinstance(other, StrawberryAnnotation):
@@ -101,19 +101,17 @@ def annotation(self) -> Union[object, str]:
def annotation(self, value: Union[object, str]) -> None:
self.raw_annotation = value
+ self.__resolve_cache__ = None
+
def evaluate(self) -> type:
"""Return evaluated annotation using `strawberry.util.typing.eval_type`."""
- evaled_type = self.__eval_cache__
- if evaled_type:
- return evaled_type
-
annotation = self.raw_annotation
+
if isinstance(annotation, str):
annotation = ForwardRef(annotation)
evaled_type = eval_type(annotation, self.namespace, None)
- self.__eval_cache__ = evaled_type
return evaled_type
def _get_type_with_args(
@@ -131,6 +129,12 @@ def _get_type_with_args(
def resolve(self) -> Union[StrawberryType, type]:
"""Return resolved (transformed) annotation."""
+ if self.__resolve_cache__ is None:
+ self.__resolve_cache__ = self._resolve()
+
+ return self.__resolve_cache__
+
+ def _resolve(self) -> Union[StrawberryType, type]:
evaled_type = cast(Any, self.evaluate())
if is_private(evaled_type):
@@ -172,7 +176,7 @@ def set_namespace_from_field(self, field: StrawberryField) -> None:
module = sys.modules[field.origin.__module__]
self.namespace = module.__dict__
- self.__eval_cache__ = None # Invalidate cache to allow re-evaluation
+ self.__resolve_cache__ = None # Invalidate cache to allow re-evaluation
def create_concrete_type(self, evaled_type: type) -> type:
if has_object_definition(evaled_type):
@@ -222,7 +226,7 @@ def create_type_var(self, evaled_type: TypeVar) -> StrawberryTypeVar:
def create_union(self, evaled_type: Type[Any], args: list[Any]) -> StrawberryUnion:
# Prevent import cycles
- from strawberry.union import StrawberryUnion
+ from strawberry.types.union import StrawberryUnion
# TODO: Deal with Forward References/origin
if isinstance(evaled_type, StrawberryUnion):
@@ -286,8 +290,7 @@ def _is_lazy_type(cls, annotation: Any) -> bool:
@classmethod
def _is_optional(cls, annotation: Any, args: List[Any]) -> bool:
- """Returns True if the annotation is Optional[SomeType]"""
-
+ """Returns True if the annotation is Optional[SomeType]."""
# Optionals are represented as unions
if not cls._is_union(annotation, args):
return False
@@ -299,8 +302,7 @@ def _is_optional(cls, annotation: Any, args: List[Any]) -> bool:
@classmethod
def _is_list(cls, annotation: Any) -> bool:
- """Returns True if annotation is a List"""
-
+ """Returns True if annotation is a List."""
annotation_origin = get_origin(annotation)
annotation_mro = getattr(annotation, "__mro__", [])
is_list = any(x is list for x in annotation_mro)
@@ -314,7 +316,7 @@ def _is_list(cls, annotation: Any) -> bool:
@classmethod
def _is_strawberry_type(cls, evaled_type: Any) -> bool:
# Prevent import cycles
- from strawberry.union import StrawberryUnion
+ from strawberry.types.union import StrawberryUnion
if isinstance(evaled_type, EnumDefinition):
return True
@@ -340,8 +342,7 @@ def _is_strawberry_type(cls, evaled_type: Any) -> bool:
@classmethod
def _is_union(cls, annotation: Any, args: List[Any]) -> bool:
- """Returns True if annotation is a Union"""
-
+ """Returns True if annotation is a Union."""
# this check is needed because unions declared with the new syntax `A | B`
# don't have a `__origin__` property on them, but they are instances of
# `UnionType`, which is only available in Python 3.10+
@@ -359,7 +360,7 @@ def _is_union(cls, annotation: Any, args: List[Any]) -> bool:
if annotation_origin is typing.Union:
return True
- from strawberry.union import StrawberryUnion
+ from strawberry.types.union import StrawberryUnion
return any(isinstance(arg, StrawberryUnion) for arg in args)
@@ -382,3 +383,6 @@ def _is_input_type(type_: Any) -> bool:
return False
return type_.__strawberry_definition__.is_input
+
+
+__all__ = ["StrawberryAnnotation"]
diff --git a/strawberry/asgi/__init__.py b/strawberry/asgi/__init__.py
index 271f3b71a3..d88628c57d 100644
--- a/strawberry/asgi/__init__.py
+++ b/strawberry/asgi/__init__.py
@@ -51,7 +51,7 @@ def __init__(self, request: Request) -> None:
@property
def query_params(self) -> QueryParams:
- return dict(self.request.query_params)
+ return self.request.query_params
@property
def method(self) -> HTTPMethod:
@@ -124,12 +124,12 @@ def __init__(
else:
self.graphql_ide = graphql_ide
- async def __call__(self, scope: Scope, receive: Receive, send: Send):
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "http":
return await self.handle_http(scope, receive, send)
elif scope["type"] == "websocket":
- ws = WebSocket(scope=scope, receive=receive, send=send)
+ ws = WebSocket(scope, receive=receive, send=send)
preferred_protocol = self.pick_preferred_protocol(ws)
if preferred_protocol == GRAPHQL_TRANSPORT_WS_PROTOCOL:
diff --git a/strawberry/asgi/handlers/graphql_transport_ws_handler.py b/strawberry/asgi/handlers/graphql_transport_ws_handler.py
index ceb8d7faa6..7cec132ffd 100644
--- a/strawberry/asgi/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/asgi/handlers/graphql_transport_ws_handler.py
@@ -26,7 +26,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, connection_init_wait_timeout)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -61,3 +61,6 @@ async def handle_request(self) -> None:
pass
finally:
await self.shutdown()
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/asgi/handlers/graphql_ws_handler.py b/strawberry/asgi/handlers/graphql_ws_handler.py
index 2da60a408f..5ef966f63b 100644
--- a/strawberry/asgi/handlers/graphql_ws_handler.py
+++ b/strawberry/asgi/handlers/graphql_ws_handler.py
@@ -25,7 +25,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, keep_alive, keep_alive_interval)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -65,3 +65,6 @@ async def handle_request(self) -> Any:
for operation_id in list(self.subscriptions.keys()):
await self.cleanup_operation(operation_id)
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/asgi/test/client.py b/strawberry/asgi/test/client.py
index fc2ccd4a41..74afd78229 100644
--- a/strawberry/asgi/test/client.py
+++ b/strawberry/asgi/test/client.py
@@ -46,5 +46,8 @@ def request(
headers=headers,
)
- def _decode(self, response: Any, type: Literal["multipart", "json"]):
+ def _decode(self, response: Any, type: Literal["multipart", "json"]) -> Any:
return response.json()
+
+
+__all__ = ["GraphQLTestClient"]
diff --git a/strawberry/chalice/views.py b/strawberry/chalice/views.py
index 2340e42129..3da2838eaa 100644
--- a/strawberry/chalice/views.py
+++ b/strawberry/chalice/views.py
@@ -17,7 +17,7 @@
class ChaliceHTTPRequestAdapter(SyncHTTPRequestAdapter):
- def __init__(self, request: Request):
+ def __init__(self, request: Request) -> None:
self.request = request
@property
@@ -62,7 +62,7 @@ def __init__(
graphiql: Optional[bool] = None,
graphql_ide: Optional[GraphQL_IDE] = "graphiql",
allow_queries_via_get: bool = True,
- ):
+ ) -> None:
self.allow_queries_via_get = allow_queries_via_get
self.schema = schema
if graphiql is not None:
@@ -94,10 +94,16 @@ def error_response(
http_status_code: int,
headers: Optional[Dict[str, str | List[str]]] = None,
) -> Response:
- """
- A wrapper for error responses
+ """A wrapper for error responses.
+
+ Args:
+ message: The error message.
+ error_code: The error code.
+ http_status_code: The HTTP status code.
+ headers: The headers to include in the response.
+
Returns:
- An errors response
+ An errors response.
"""
body = {"Code": error_code, "Message": message}
@@ -139,3 +145,6 @@ def execute_request(self, request: Request) -> Response:
message=e.reason,
http_status_code=e.status_code,
)
+
+
+__all__ = ["GraphQLView"]
diff --git a/tests/pyright/__init__.py b/strawberry/channels/handlers/__init__.py
similarity index 100%
rename from tests/pyright/__init__.py
rename to strawberry/channels/handlers/__init__.py
diff --git a/strawberry/channels/handlers/base.py b/strawberry/channels/handlers/base.py
index 5d837d16da..ec2ffe6b2c 100644
--- a/strawberry/channels/handlers/base.py
+++ b/strawberry/channels/handlers/base.py
@@ -33,32 +33,25 @@ class ChannelsLayer(Protocol): # pragma: no cover
extensions: List[Literal["groups", "flush"]]
- async def send(self, channel: str, message: dict) -> None:
- ...
+ async def send(self, channel: str, message: dict) -> None: ...
- async def receive(self, channel: str) -> dict:
- ...
+ async def receive(self, channel: str) -> dict: ...
- async def new_channel(self, prefix: str = ...) -> str:
- ...
+ async def new_channel(self, prefix: str = ...) -> str: ...
# If groups extension is supported
group_expiry: int
- async def group_add(self, group: str, channel: str) -> None:
- ...
+ async def group_add(self, group: str, channel: str) -> None: ...
- async def group_discard(self, group: str, channel: str) -> None:
- ...
+ async def group_discard(self, group: str, channel: str) -> None: ...
- async def group_send(self, group: str, message: dict) -> None:
- ...
+ async def group_send(self, group: str, message: dict) -> None: ...
# If flush extension is supported
- async def flush(self) -> None:
- ...
+ async def flush(self) -> None: ...
class ChannelsConsumer(AsyncConsumer):
@@ -68,7 +61,7 @@ class ChannelsConsumer(AsyncConsumer):
channel_layer: Optional[ChannelsLayer]
channel_receive: Callable[[], Awaitable[dict]]
- def __init__(self, *args: str, **kwargs: Any):
+ def __init__(self, *args: str, **kwargs: Any) -> None:
self.listen_queues: DefaultDict[str, WeakSet[asyncio.Queue]] = defaultdict(
WeakSet
)
@@ -99,7 +92,7 @@ async def channel_listen(
Utility to listen for channels messages for this consumer inside
a resolver (usually inside a subscription).
- Parameters:
+ Args:
type:
The type of the message to wait for.
timeout:
@@ -111,7 +104,6 @@ async def channel_listen(
execution and then discarded using `self.channel_layer.group_discard`
at the end of the execution.
"""
-
warnings.warn("Use listen_to_channel instead", DeprecationWarning, stacklevel=2)
if self.channel_layer is None:
raise RuntimeError(
@@ -159,7 +151,7 @@ async def listen_to_channel(
Utility to listen for channels messages for this consumer inside
a resolver (usually inside a subscription).
- Parameters:
+ Args:
type:
The type of the message to wait for.
timeout:
@@ -171,7 +163,6 @@ async def listen_to_channel(
execution and then discarded using `self.channel_layer.group_discard`
at the end of the execution.
"""
-
# Code to acquire resource (Channels subscriptions)
if self.channel_layer is None:
raise RuntimeError(
@@ -208,7 +199,6 @@ async def _listen_to_channel_generator(
Seperated to allow user code to be run after subscribing to channels
and before blocking to wait for incoming channel messages.
"""
-
while True:
awaitable = queue.get()
if timeout is not None:
@@ -222,3 +212,6 @@ async def _listen_to_channel_generator(
class ChannelsWSConsumer(ChannelsConsumer, AsyncJsonWebsocketConsumer):
"""Base channels websocket async consumer."""
+
+
+__all__ = ["ChannelsConsumer", "ChannelsWSConsumer"]
diff --git a/strawberry/channels/handlers/graphql_transport_ws_handler.py b/strawberry/channels/handlers/graphql_transport_ws_handler.py
index cfc15463f5..db290f4ef8 100644
--- a/strawberry/channels/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/channels/handlers/graphql_transport_ws_handler.py
@@ -23,7 +23,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: ChannelsWSConsumer,
- ):
+ ) -> None:
super().__init__(schema, debug, connection_init_wait_timeout)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -57,3 +57,6 @@ async def handle_request(self) -> Any:
async def handle_disconnect(self, code: int) -> None:
await self.shutdown()
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/channels/handlers/graphql_ws_handler.py b/strawberry/channels/handlers/graphql_ws_handler.py
index 8382add31a..41ed4dc6ea 100644
--- a/strawberry/channels/handlers/graphql_ws_handler.py
+++ b/strawberry/channels/handlers/graphql_ws_handler.py
@@ -22,7 +22,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: ChannelsWSConsumer,
- ):
+ ) -> None:
super().__init__(schema, debug, keep_alive, keep_alive_interval)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -67,3 +67,6 @@ async def handle_invalid_message(self, error_message: str) -> None:
# channels integration is a high level wrapper that forwards this to
# both us and the BaseGraphQLTransportWSHandler.
pass
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/channels/handlers/http_handler.py b/strawberry/channels/handlers/http_handler.py
index 87309b9659..f36b1be13f 100644
--- a/strawberry/channels/handlers/http_handler.py
+++ b/strawberry/channels/handlers/http_handler.py
@@ -1,7 +1,8 @@
-"""GraphQLHTTPHandler
+"""GraphQLHTTPHandler.
A consumer to provide a graphql endpoint, and optionally graphiql.
"""
+
from __future__ import annotations
import dataclasses
@@ -34,7 +35,7 @@
from strawberry.http.temporal_response import TemporalResponse
from strawberry.http.types import FormData
from strawberry.http.typevars import Context, RootValue
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
from .base import ChannelsConsumer
@@ -119,7 +120,7 @@ def form_data(self) -> FormData:
class BaseChannelsRequestAdapter:
- def __init__(self, request: ChannelsRequest):
+ def __init__(self, request: ChannelsRequest) -> None:
self.request = request
@property
@@ -173,7 +174,7 @@ def __init__(
allow_queries_via_get: bool = True,
subscriptions_enabled: bool = True,
**kwargs: Any,
- ):
+ ) -> None:
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
self.subscriptions_enabled = subscriptions_enabled
@@ -341,3 +342,6 @@ def run( # type: ignore[override]
root_value: Optional[RootValue] = UNSET,
) -> ChannelsResponse | MultipartChannelsResponse:
return super().run(request, context, root_value)
+
+
+__all__ = ["GraphQLHTTPConsumer", "SyncGraphQLHTTPConsumer"]
diff --git a/strawberry/channels/handlers/ws_handler.py b/strawberry/channels/handlers/ws_handler.py
index 1adf21e5ea..7f7929b3f3 100644
--- a/strawberry/channels/handlers/ws_handler.py
+++ b/strawberry/channels/handlers/ws_handler.py
@@ -15,7 +15,7 @@
class GraphQLWSConsumer(ChannelsWSConsumer):
- """A channels websocket consumer for GraphQL
+ """A channels websocket consumer for GraphQL.
This handles the connections, then hands off to the appropriate
handler based on the subprotocol.
@@ -54,7 +54,7 @@ def __init__(
GRAPHQL_WS_PROTOCOL,
),
connection_init_wait_timeout: Optional[datetime.timedelta] = None,
- ):
+ ) -> None:
if connection_init_wait_timeout is None:
connection_init_wait_timeout = datetime.timedelta(minutes=1)
self.connection_init_wait_timeout = connection_init_wait_timeout
@@ -126,3 +126,6 @@ async def get_context(
"connection_params": connection_params,
"ws": request,
} # type: ignore
+
+
+__all__ = ["GraphQLWSConsumer"]
diff --git a/strawberry/channels/router.py b/strawberry/channels/router.py
index d04e3cd697..414902cd40 100644
--- a/strawberry/channels/router.py
+++ b/strawberry/channels/router.py
@@ -1,4 +1,4 @@
-"""GraphQLWebSocketRouter
+"""GraphQLWebSocketRouter.
This is a simple router class that might be better placed as part of Channels itself.
It's a simple "SubProtocolRouter" that selects the websocket subprotocol based
@@ -21,11 +21,12 @@
class GraphQLProtocolTypeRouter(ProtocolTypeRouter):
- """
- Convenience class to set up GraphQL on both HTTP and Websocket, optionally with a
- Django application for all other HTTP routes:
+ """HTTP and Websocket GraphQL type router.
- ```
+ Convenience class to set up GraphQL on both HTTP and Websocket,
+ optionally with a Django application for all other HTTP routes.
+
+ ```python
from strawberry.channels import GraphQLProtocolTypeRouter
from django.core.asgi import get_asgi_application
@@ -48,7 +49,7 @@ def __init__(
schema: BaseSchema,
django_application: Optional[str] = None,
url_pattern: str = "^graphql",
- ):
+ ) -> None:
http_urls = [re_path(url_pattern, GraphQLHTTPConsumer.as_asgi(schema=schema))]
if django_application is not None:
http_urls.append(re_path("^", django_application))
@@ -63,3 +64,6 @@ def __init__(
),
}
)
+
+
+__all__ = ["GraphQLProtocolTypeRouter"]
diff --git a/strawberry/channels/testing.py b/strawberry/channels/testing.py
index 570842a965..db3e78e097 100644
--- a/strawberry/channels/testing.py
+++ b/strawberry/channels/testing.py
@@ -41,8 +41,8 @@
class GraphQLWebsocketCommunicator(WebsocketCommunicator):
- """
- Usage:
+ """A test communicator for GraphQL over Websockets.
+
```python
import pytest
from strawberry.channels.testing import GraphQLWebsocketCommunicator
@@ -69,18 +69,24 @@ def __init__(
path: str,
headers: Optional[List[Tuple[bytes, bytes]]] = None,
protocol: str = GRAPHQL_TRANSPORT_WS_PROTOCOL,
+ connection_params: dict = {},
**kwargs: Any,
- ):
- """
+ ) -> None:
+ """Create a new communicator.
Args:
application: Your asgi application that encapsulates the strawberry schema.
path: the url endpoint for the schema.
protocol: currently this supports `graphql-transport-ws` only.
+ connection_params: a dictionary of connection parameters to send to the server.
+ headers: a list of tuples to be sent as headers to the server.
+ subprotocols: an ordered list of preferred subprotocols to be sent to the server.
+ **kwargs: additional arguments to be passed to the `WebsocketCommunicator` constructor.
"""
self.protocol = protocol
subprotocols = kwargs.get("subprotocols", [])
subprotocols.append(protocol)
+ self.connection_params = connection_params
super().__init__(application, path, headers, subprotocols=subprotocols)
async def __aenter__(self) -> Self:
@@ -99,7 +105,9 @@ async def gql_init(self) -> None:
res = await self.connect()
if self.protocol == GRAPHQL_TRANSPORT_WS_PROTOCOL:
assert res == (True, GRAPHQL_TRANSPORT_WS_PROTOCOL)
- await self.send_json_to(ConnectionInitMessage().as_dict())
+ await self.send_json_to(
+ ConnectionInitMessage(payload=self.connection_params).as_dict()
+ )
response = await self.receive_json_from()
assert response == ConnectionAckMessage().as_dict()
else:
@@ -151,7 +159,7 @@ async def subscribe(
return
def process_errors(self, errors: List[GraphQLFormattedError]) -> List[GraphQLError]:
- """Reconst a GraphQLError from a FormattedGraphQLError"""
+ """Reconstructs a GraphQLError from a FormattedGraphQLError."""
result = []
for f_error in errors:
error = GraphQLError(
@@ -161,3 +169,6 @@ def process_errors(self, errors: List[GraphQLFormattedError]) -> List[GraphQLErr
error.path = f_error.get("path", None)
result.append(error)
return result
+
+
+__all__ = ["GraphQLWebsocketCommunicator"]
diff --git a/strawberry/cli/__init__.py b/strawberry/cli/__init__.py
index f0d77cfadd..6dbaaca5f5 100644
--- a/strawberry/cli/__init__.py
+++ b/strawberry/cli/__init__.py
@@ -1,11 +1,15 @@
-from .commands.codegen import codegen as codegen # noqa
-from .commands.export_schema import export_schema as export_schema # noqa
-from .commands.server import server as server # noqa
-from .commands.upgrade import upgrade as upgrade # noqa
-from .commands.schema_codegen import schema_codegen as schema_codegen # noqa
+try:
+ from .app import app
+ from .commands.codegen import codegen as codegen # noqa
+ from .commands.export_schema import export_schema as export_schema # noqa
+ from .commands.schema_codegen import schema_codegen as schema_codegen # noqa
+ from .commands.server import server as server # noqa
+ from .commands.upgrade import upgrade as upgrade # noqa
-from .app import app
+ def run() -> None:
+ app()
+except ModuleNotFoundError as exc:
+ from strawberry.exceptions import MissingOptionalDependenciesError
-def run() -> None:
- app()
+ raise MissingOptionalDependenciesError(extras=["cli"]) from exc
diff --git a/strawberry/cli/commands/upgrade/__init__.py b/strawberry/cli/commands/upgrade/__init__.py
index 5ea4761e23..2b8f387ccb 100644
--- a/strawberry/cli/commands/upgrade/__init__.py
+++ b/strawberry/cli/commands/upgrade/__init__.py
@@ -11,11 +11,13 @@
from strawberry.cli.app import app
from strawberry.codemods.annotated_unions import ConvertUnionToAnnotatedUnion
+from strawberry.codemods.update_imports import UpdateImportsCodemod
from ._run_codemod import run_codemod
codemods = {
"annotated-union": ConvertUnionToAnnotatedUnion,
+ "update-imports": UpdateImportsCodemod,
}
@@ -46,11 +48,17 @@ def upgrade(
python_target_version = tuple(int(x) for x in python_target.split("."))
- transformer = ConvertUnionToAnnotatedUnion(
- CodemodContext(),
- use_pipe_syntax=python_target_version >= (3, 10),
- use_typing_extensions=use_typing_extensions,
- )
+ transformer: ConvertUnionToAnnotatedUnion | UpdateImportsCodemod
+
+ if codemod == "update-imports":
+ transformer = UpdateImportsCodemod(context=CodemodContext())
+
+ else:
+ transformer = ConvertUnionToAnnotatedUnion(
+ CodemodContext(),
+ use_pipe_syntax=python_target_version >= (3, 10),
+ use_typing_extensions=use_typing_extensions,
+ )
files: list[str] = []
diff --git a/strawberry/cli/commands/upgrade/_fake_progress.py b/strawberry/cli/commands/upgrade/_fake_progress.py
index 6aacc36179..05ba840544 100644
--- a/strawberry/cli/commands/upgrade/_fake_progress.py
+++ b/strawberry/cli/commands/upgrade/_fake_progress.py
@@ -6,7 +6,8 @@
class FakeProgress:
"""A fake progress bar that does nothing.
- This is used when the user has only one file to process."""
+ This is used when the user has only one file to process.
+ """
def advance(self, task_id: TaskID) -> None:
pass
diff --git a/strawberry/cli/commands/upgrade/_run_codemod.py b/strawberry/cli/commands/upgrade/_run_codemod.py
index d08cbc05e9..04e168e240 100644
--- a/strawberry/cli/commands/upgrade/_run_codemod.py
+++ b/strawberry/cli/commands/upgrade/_run_codemod.py
@@ -2,6 +2,7 @@
import contextlib
import os
+from importlib.metadata import version
from multiprocessing import Pool, cpu_count
from typing import TYPE_CHECKING, Any, Dict, Generator, Sequence, Type, Union
@@ -18,13 +19,29 @@
PoolType = Union[Type[Pool], Type[DummyPool]] # type: ignore
+def _get_libcst_version() -> tuple[int, int, int]:
+ package_version_str = version("libcst")
+
+ try:
+ major, minor, patch = map(int, package_version_str.split("."))
+ except ValueError:
+ major, minor, patch = (0, 0, 0)
+
+ return major, minor, patch
+
+
def _execute_transform_wrap(
job: Dict[str, Any],
) -> ExecutionResult:
+ additional_kwargs: Dict[str, Any] = {}
+
+ if _get_libcst_version() >= (1, 4, 0):
+ additional_kwargs["scratch"] = {}
+
# TODO: maybe capture warnings?
with open(os.devnull, "w") as null: # noqa: PTH123
with contextlib.redirect_stderr(null):
- return _execute_transform(**job)
+ return _execute_transform(**job, **additional_kwargs)
def _get_progress_and_pool(
diff --git a/strawberry/codegen/exceptions.py b/strawberry/codegen/exceptions.py
index c78454a965..ca7bd6ad10 100644
--- a/strawberry/codegen/exceptions.py
+++ b/strawberry/codegen/exceptions.py
@@ -12,3 +12,11 @@ class NoOperationNameProvidedError(CodegenError):
class MultipleOperationsProvidedError(CodegenError):
pass
+
+
+__all__ = [
+ "CodegenError",
+ "NoOperationProvidedError",
+ "NoOperationNameProvidedError",
+ "MultipleOperationsProvidedError",
+]
diff --git a/strawberry/codegen/query_codegen.py b/strawberry/codegen/query_codegen.py
index 728d4fc330..92d93d98db 100644
--- a/strawberry/codegen/query_codegen.py
+++ b/strawberry/codegen/query_codegen.py
@@ -42,19 +42,19 @@
parse,
)
-from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
-from strawberry.enum import EnumDefinition
-from strawberry.lazy_type import LazyType
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
+ StrawberryObjectDefinition,
StrawberryOptional,
StrawberryType,
get_object_definition,
has_object_definition,
)
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
-from strawberry.unset import UNSET
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
+from strawberry.types.union import StrawberryUnion
+from strawberry.types.unset import UNSET
from strawberry.utils.str_converters import capitalize_first, to_camel_case
from .exceptions import (
@@ -139,11 +139,9 @@ def __init__(self, query: Path) -> None:
"""
self.query = query
- def on_start(self) -> None:
- ...
+ def on_start(self) -> None: ...
- def on_end(self, result: CodegenResult) -> None:
- ...
+ def on_end(self, result: CodegenResult) -> None: ...
def generate_code(
self, types: List[GraphQLType], operation: GraphQLOperation
@@ -152,7 +150,7 @@ def generate_code(
class ConsolePlugin:
- def __init__(self, output_dir: Path):
+ def __init__(self, output_dir: Path) -> None:
self.output_dir = output_dir
self.files_generated: List[Path] = []
@@ -305,7 +303,7 @@ def __init__(
schema: Schema,
plugins: List[QueryCodegenPlugin],
console_plugin: Optional[ConsolePlugin] = None,
- ):
+ ) -> None:
self.schema = schema
self.plugin_manager = QueryCodegenPluginManager(plugins, console_plugin)
self.types: List[GraphQLType] = []
@@ -919,3 +917,12 @@ def _collect_enum(self, enum: EnumDefinition) -> GraphQLEnum:
)
self._collect_type(graphql_enum)
return graphql_enum
+
+
+__all__ = [
+ "QueryCodegen",
+ "QueryCodegenPlugin",
+ "ConsolePlugin",
+ "CodegenFile",
+ "CodegenResult",
+]
diff --git a/strawberry/codegen/types.py b/strawberry/codegen/types.py
index 4d39fc4707..1518cb5f1f 100644
--- a/strawberry/codegen/types.py
+++ b/strawberry/codegen/types.py
@@ -7,7 +7,7 @@
from enum import EnumMeta
from typing_extensions import Literal
- from strawberry.unset import UnsetType
+ from strawberry.types.unset import UnsetType
@dataclass
@@ -194,3 +194,34 @@ class GraphQLOperation:
variables: List[GraphQLVariable]
type: GraphQLObjectType
variables_type: Optional[GraphQLObjectType]
+
+
+__all__ = [
+ "GraphQLOptional",
+ "GraphQLList",
+ "GraphQLUnion",
+ "GraphQLField",
+ "GraphQLFragmentSpread",
+ "GraphQLObjectType",
+ "GraphQLFragmentType",
+ "GraphQLEnum",
+ "GraphQLScalar",
+ "GraphQLType",
+ "GraphQLFieldSelection",
+ "GraphQLInlineFragment",
+ "GraphQLSelection",
+ "GraphQLStringValue",
+ "GraphQLIntValue",
+ "GraphQLFloatValue",
+ "GraphQLEnumValue",
+ "GraphQLBoolValue",
+ "GraphQLNullValue",
+ "GraphQLListValue",
+ "GraphQLObjectValue",
+ "GraphQLVariableReference",
+ "GraphQLArgumentValue",
+ "GraphQLArgument",
+ "GraphQLDirective",
+ "GraphQLVariable",
+ "GraphQLOperation",
+]
diff --git a/strawberry/codemods/update_imports.py b/strawberry/codemods/update_imports.py
new file mode 100644
index 0000000000..24e34164f2
--- /dev/null
+++ b/strawberry/codemods/update_imports.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+import libcst as cst
+import libcst.matchers as m
+from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
+from libcst.codemod.visitors import AddImportsVisitor, RemoveImportsVisitor
+
+
+class UpdateImportsCodemod(VisitorBasedCodemodCommand):
+ def __init__(self, context: CodemodContext) -> None:
+ super().__init__(context)
+ self.add_imports_visitor = AddImportsVisitor(context)
+ self.remove_imports_visitor = RemoveImportsVisitor(context)
+
+ def _update_imports(
+ self, node: cst.ImportFrom, updated_node: cst.ImportFrom
+ ) -> cst.ImportFrom:
+ imports = [
+ "field",
+ "union",
+ "auto",
+ "unset",
+ "arguments",
+ "lazy_type",
+ "object_type",
+ "private",
+ "enum",
+ ]
+
+ for import_name in imports:
+ if m.matches(
+ node,
+ m.ImportFrom(
+ module=m.Attribute(
+ value=m.Name("strawberry"), attr=m.Name(import_name)
+ )
+ ),
+ ):
+ updated_node = updated_node.with_changes(
+ module=cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name("strawberry"), attr=cst.Name("types")
+ ),
+ attr=cst.Name(import_name),
+ ),
+ )
+
+ return updated_node
+
+ def _update_types_types_imports(
+ self, node: cst.ImportFrom, updated_node: cst.ImportFrom
+ ) -> cst.ImportFrom:
+ if m.matches(
+ node,
+ m.ImportFrom(
+ module=m.Attribute(
+ value=m.Attribute(value=m.Name("strawberry"), attr=m.Name("types")),
+ attr=m.Name("types"),
+ )
+ ),
+ ):
+ updated_node = updated_node.with_changes(
+ module=cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name("strawberry"), attr=cst.Name("types")
+ ),
+ attr=cst.Name("base"),
+ ),
+ )
+
+ return updated_node
+
+ def _update_strawberry_type_imports(
+ self, node: cst.ImportFrom, updated_node: cst.ImportFrom
+ ) -> cst.ImportFrom:
+ if m.matches(
+ node,
+ m.ImportFrom(
+ module=m.Attribute(value=m.Name("strawberry"), attr=m.Name("type"))
+ ),
+ ):
+ has_get_object_definition = (
+ any(
+ m.matches(name, m.ImportAlias(name=m.Name("get_object_definition")))
+ for name in node.names
+ )
+ if not isinstance(node.names, cst.ImportStar)
+ else False
+ )
+
+ has_has_object_definition = (
+ any(
+ m.matches(name, m.ImportAlias(name=m.Name("has_object_definition")))
+ for name in node.names
+ )
+ if not isinstance(node.names, cst.ImportStar)
+ else False
+ )
+
+ updated_node = updated_node.with_changes(
+ module=cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name("strawberry"), attr=cst.Name("types")
+ ),
+ attr=cst.Name("base"),
+ ),
+ )
+
+ self.remove_imports_visitor.remove_unused_import(
+ self.context, "strawberry.types.base", "get_object_definition"
+ )
+
+ self.remove_imports_visitor.remove_unused_import(
+ self.context, "strawberry.types.base", "has_object_definition"
+ )
+
+ if has_get_object_definition:
+ self.add_imports_visitor.add_needed_import(
+ self.context, "strawberry.types", "get_object_definition"
+ )
+
+ if has_has_object_definition:
+ self.add_imports_visitor.add_needed_import(
+ self.context, "strawberry.types", "has_object_definition"
+ )
+
+ return updated_node
+
+ def leave_ImportFrom(
+ self, node: cst.ImportFrom, updated_node: cst.ImportFrom
+ ) -> cst.ImportFrom:
+ updated_node = self._update_imports(updated_node, updated_node)
+ updated_node = self._update_types_types_imports(updated_node, updated_node)
+ updated_node = self._update_strawberry_type_imports(updated_node, updated_node)
+
+ return updated_node
diff --git a/strawberry/dataloader.py b/strawberry/dataloader.py
index f334d8bed4..8a04628930 100644
--- a/strawberry/dataloader.py
+++ b/strawberry/dataloader.py
@@ -105,8 +105,7 @@ def __init__(
loop: Optional[AbstractEventLoop] = None,
cache_map: Optional[AbstractCache[K, T]] = None,
cache_key_fn: Optional[Callable[[K], Hashable]] = None,
- ) -> None:
- ...
+ ) -> None: ...
# fallback if load_fn is untyped and there's no other info for inference
@overload
@@ -118,8 +117,7 @@ def __init__(
loop: Optional[AbstractEventLoop] = None,
cache_map: Optional[AbstractCache[K, T]] = None,
cache_key_fn: Optional[Callable[[K], Hashable]] = None,
- ) -> None:
- ...
+ ) -> None: ...
def __init__(
self,
@@ -269,3 +267,16 @@ async def dispatch_batch(loader: DataLoader, batch: Batch) -> None:
except Exception as e:
for task in batch.tasks:
task.future.set_exception(e)
+
+
+__all__ = [
+ "DataLoader",
+ "Batch",
+ "LoaderTask",
+ "AbstractCache",
+ "DefaultCache",
+ "should_create_new_batch",
+ "get_current_batch",
+ "dispatch",
+ "dispatch_batch",
+]
diff --git a/strawberry/directive.py b/strawberry/directive.py
index 5fb2c734ff..17eaafd0c8 100644
--- a/strawberry/directive.py
+++ b/strawberry/directive.py
@@ -7,21 +7,45 @@
from graphql import DirectiveLocation
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
from strawberry.types.fields.resolver import (
INFO_PARAMSPEC,
ReservedType,
StrawberryResolver,
)
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
if TYPE_CHECKING:
import inspect
- from strawberry.arguments import StrawberryArgument
+ from strawberry.types.arguments import StrawberryArgument
-def directive_field(name: str, default: object = UNSET) -> Any:
+# TODO: should this be directive argument?
+def directive_field(
+ name: str,
+ default: object = UNSET,
+) -> Any:
+ """Function to add metadata to a directive argument, like the GraphQL name.
+
+ Args:
+ name: The GraphQL name of the directive argument
+ default: The default value of the argument
+
+ Returns:
+ A StrawberryField object that can be used to customise a directive argument
+
+ Example:
+ ```python
+ import strawberry
+ from strawberry.schema_directive import Location
+
+
+ @strawberry.schema_directive(locations=[Location.FIELD_DEFINITION])
+ class Sensitive:
+ reason: str = strawberry.directive_field(name="as")
+ ```
+ """
return StrawberryField(
python_name=None,
graphql_name=name,
@@ -32,8 +56,7 @@ def directive_field(name: str, default: object = UNSET) -> Any:
T = TypeVar("T")
-class StrawberryDirectiveValue:
- ...
+class StrawberryDirectiveValue: ...
DirectiveValue = Annotated[T, StrawberryDirectiveValue()]
@@ -75,6 +98,30 @@ def directive(
description: Optional[str] = None,
name: Optional[str] = None,
) -> Callable[[Callable[..., T]], StrawberryDirective[T]]:
+ """Decorator to create a GraphQL operation directive.
+
+ Args:
+ locations: The locations where the directive can be used
+ description: The GraphQL description of the directive
+ name: The GraphQL name of the directive
+
+ Returns:
+ A StrawberryDirective object that can be used to customise a directive
+
+ Example:
+ ```python
+ import strawberry
+ from strawberry.directive import DirectiveLocation
+
+
+ @strawberry.directive(
+ locations=[DirectiveLocation.FIELD], description="Make string uppercase"
+ )
+ def turn_uppercase(value: str):
+ return value.upper()
+ ```
+ """
+
def _wrap(f: Callable[..., T]) -> StrawberryDirective[T]:
return StrawberryDirective(
python_name=f.__name__,
diff --git a/strawberry/django/__init__.py b/strawberry/django/__init__.py
index b9cc014224..c28422e346 100644
--- a/strawberry/django/__init__.py
+++ b/strawberry/django/__init__.py
@@ -1,3 +1,5 @@
+from typing import Any
+
try:
# import modules and objects from external strawberry-graphql-django
# package so that it can be used through strawberry.django namespace
@@ -5,7 +7,7 @@
except ModuleNotFoundError:
import importlib
- def __getattr__(name: str):
+ def __getattr__(name: str) -> Any:
# try to import submodule and raise exception only if it does not exist
import_symbol = f"{__name__}.{name}"
try:
diff --git a/strawberry/django/context.py b/strawberry/django/context.py
index 2f39658dba..b1705cdae1 100644
--- a/strawberry/django/context.py
+++ b/strawberry/django/context.py
@@ -12,11 +12,14 @@ class StrawberryDjangoContext:
request: HttpRequest
response: HttpResponse
- def __getitem__(self, key: str):
+ def __getitem__(self, key: str) -> Any:
# __getitem__ override needed to avoid issues for who's
# using info.context["request"]
return super().__getattribute__(key)
def get(self, key: str) -> Any:
- """Enable .get notation for accessing the request"""
+ """Enable .get notation for accessing the request."""
return super().__getattribute__(key)
+
+
+__all__ = ["StrawberryDjangoContext"]
diff --git a/strawberry/django/test/client.py b/strawberry/django/test/client.py
index c3d15c9de4..1ce5ab4df4 100644
--- a/strawberry/django/test/client.py
+++ b/strawberry/django/test/client.py
@@ -18,3 +18,6 @@ def request(
return self._client.post(
self.url, data=body, content_type="application/json", headers=headers
)
+
+
+__all__ = ["GraphQLTestClient"]
diff --git a/strawberry/django/views.py b/strawberry/django/views.py
index 22912db83b..932beac5dd 100644
--- a/strawberry/django/views.py
+++ b/strawberry/django/views.py
@@ -61,7 +61,7 @@ def __repr__(self) -> str:
if self.status_code is not None:
return super().__repr__()
- return "<{cls} status_code={status_code}{content_type}>".format(
+ return "<{cls} status_code={status_code}{content_type}>".format( # noqa: UP032
cls=self.__class__.__name__,
status_code=self.status_code,
content_type=self._content_type_for_repr, # pyright: ignore
@@ -69,7 +69,7 @@ def __repr__(self) -> str:
class DjangoHTTPRequestAdapter(SyncHTTPRequestAdapter):
- def __init__(self, request: HttpRequest):
+ def __init__(self, request: HttpRequest) -> None:
self.request = request
@property
@@ -104,7 +104,7 @@ def content_type(self) -> Optional[str]:
class AsyncDjangoHTTPRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: HttpRequest):
+ def __init__(self, request: HttpRequest) -> None:
self.request = request
@property
@@ -147,7 +147,7 @@ def __init__(
allow_queries_via_get: bool = True,
subscriptions_enabled: bool = False,
**kwargs: Any,
- ):
+ ) -> None:
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
self.subscriptions_enabled = subscriptions_enabled
@@ -308,3 +308,6 @@ async def render_graphql_ide(self, request: HttpRequest) -> HttpResponse:
response.content = template.render(RequestContext(request, context))
return response
+
+
+__all__ = ["GraphQLView", "AsyncGraphQLView"]
diff --git a/strawberry/exceptions/__init__.py b/strawberry/exceptions/__init__.py
index 443cbf400a..7d3b3ccb1b 100644
--- a/strawberry/exceptions/__init__.py
+++ b/strawberry/exceptions/__init__.py
@@ -12,6 +12,7 @@
from .invalid_argument_type import InvalidArgumentTypeError
from .invalid_union_type import InvalidTypeForUnionMergeError, InvalidUnionTypeError
from .missing_arguments_annotations import MissingArgumentsAnnotationsError
+from .missing_dependencies import MissingOptionalDependenciesError
from .missing_field_annotation import MissingFieldAnnotationError
from .missing_return_annotation import MissingReturnAnnotationError
from .not_a_strawberry_enum import NotAStrawberryEnumError
@@ -24,7 +25,7 @@
if TYPE_CHECKING:
from graphql import GraphQLInputObjectType, GraphQLObjectType
- from strawberry.type import StrawberryType
+ from strawberry.types.base import StrawberryType
from .exception_source import ExceptionSource
@@ -33,9 +34,9 @@
# TODO: this doesn't seem to be tested
class WrongReturnTypeForUnion(Exception):
- """The Union type cannot be resolved because it's not a field"""
+ """The Union type cannot be resolved because it's not a field."""
- def __init__(self, field_name: str, result_type: str):
+ def __init__(self, field_name: str, result_type: str) -> None:
message = (
f'The type "{result_type}" cannot be resolved for the field "{field_name}" '
", are you using a strawberry.field?"
@@ -44,13 +45,12 @@ def __init__(self, field_name: str, result_type: str):
super().__init__(message)
-# TODO: this doesn't seem to be tested
class UnallowedReturnTypeForUnion(Exception):
- """The return type is not in the list of Union types"""
+ """The return type is not in the list of Union types."""
def __init__(
self, field_name: str, result_type: str, allowed_types: Set[GraphQLObjectType]
- ):
+ ) -> None:
formatted_allowed_types = list(sorted(type_.name for type_ in allowed_types))
message = (
@@ -63,7 +63,7 @@ def __init__(
# TODO: this doesn't seem to be tested
class InvalidTypeInputForUnion(Exception):
- def __init__(self, annotation: GraphQLInputObjectType):
+ def __init__(self, annotation: GraphQLInputObjectType) -> None:
message = f"Union for {annotation} is not supported because it is an Input type"
super().__init__(message)
@@ -72,14 +72,14 @@ def __init__(self, annotation: GraphQLInputObjectType):
class MissingTypesForGenericError(Exception):
"""Raised when a generic types was used without passing any type."""
- def __init__(self, annotation: Union[StrawberryType, type]):
+ def __init__(self, annotation: Union[StrawberryType, type]) -> None:
message = f'The type "{annotation!r}" is generic, but no type has been passed'
super().__init__(message)
class UnsupportedTypeError(StrawberryException):
- def __init__(self, annotation: Union[StrawberryType, type]):
+ def __init__(self, annotation: Union[StrawberryType, type]) -> None:
message = f"{annotation} conversion is not supported"
super().__init__(message)
@@ -90,7 +90,7 @@ def exception_source(self) -> Optional[ExceptionSource]:
class MultipleStrawberryArgumentsError(Exception):
- def __init__(self, argument_name: str):
+ def __init__(self, argument_name: str) -> None:
message = (
f"Annotation for argument `{argument_name}` cannot have multiple "
f"`strawberry.argument`s"
@@ -100,7 +100,7 @@ def __init__(self, argument_name: str):
class WrongNumberOfResultsReturned(Exception):
- def __init__(self, expected: int, received: int):
+ def __init__(self, expected: int, received: int) -> None:
message = (
"Received wrong number of results in dataloader, "
f"expected: {expected}, received: {received}"
@@ -110,7 +110,7 @@ def __init__(self, expected: int, received: int):
class FieldWithResolverAndDefaultValueError(Exception):
- def __init__(self, field_name: str, type_name: str):
+ def __init__(self, field_name: str, type_name: str) -> None:
message = (
f'Field "{field_name}" on type "{type_name}" cannot define a default '
"value and a resolver."
@@ -120,7 +120,7 @@ def __init__(self, field_name: str, type_name: str):
class FieldWithResolverAndDefaultFactoryError(Exception):
- def __init__(self, field_name: str, type_name: str):
+ def __init__(self, field_name: str, type_name: str) -> None:
message = (
f'Field "{field_name}" on type "{type_name}" cannot define a '
"default_factory and a resolver."
@@ -130,23 +130,23 @@ def __init__(self, field_name: str, type_name: str):
class MissingQueryError(Exception):
- def __init__(self):
+ def __init__(self) -> None:
message = 'Request data is missing a "query" value'
super().__init__(message)
class InvalidDefaultFactoryError(Exception):
- def __init__(self):
+ def __init__(self) -> None:
message = "`default_factory` must be a callable that requires no arguments"
super().__init__(message)
class InvalidCustomContext(Exception):
- """Raised when a custom context object is of the wrong python type"""
+ """Raised when a custom context object is of the wrong python type."""
- def __init__(self):
+ def __init__(self) -> None:
message = (
"The custom context must be either a class "
"that inherits from BaseContext or a dictionary"
@@ -155,7 +155,7 @@ def __init__(self):
class StrawberryGraphQLError(GraphQLError):
- """Use it when you want to override the graphql.GraphQLError in custom extensions"""
+ """Use it when you want to override the graphql.GraphQLError in custom extensions."""
__all__ = [
@@ -187,4 +187,5 @@ class StrawberryGraphQLError(GraphQLError):
"MissingFieldAnnotationError",
"DuplicatedTypeName",
"StrawberryGraphQLError",
+ "MissingOptionalDependenciesError",
]
diff --git a/strawberry/exceptions/conflicting_arguments.py b/strawberry/exceptions/conflicting_arguments.py
index 4104a4c682..d83fb32572 100644
--- a/strawberry/exceptions/conflicting_arguments.py
+++ b/strawberry/exceptions/conflicting_arguments.py
@@ -17,7 +17,7 @@ def __init__(
self,
resolver: StrawberryResolver,
arguments: List[str],
- ):
+ ) -> None:
self.function = resolver.wrapped_func
self.argument_names = arguments
diff --git a/strawberry/exceptions/duplicated_type_name.py b/strawberry/exceptions/duplicated_type_name.py
index 49e3a335df..9f3eb691f3 100644
--- a/strawberry/exceptions/duplicated_type_name.py
+++ b/strawberry/exceptions/duplicated_type_name.py
@@ -13,14 +13,14 @@
class DuplicatedTypeName(StrawberryException):
- """Raised when the same type with different definition is reused inside a schema"""
+ """Raised when the same type with different definition is reused inside a schema."""
def __init__(
self,
first_cls: Optional[Type],
second_cls: Optional[Type],
duplicated_type_name: str,
- ):
+ ) -> None:
self.first_cls = first_cls
self.second_cls = second_cls
diff --git a/strawberry/exceptions/handler.py b/strawberry/exceptions/handler.py
index 86f104be73..2a49d497b6 100644
--- a/strawberry/exceptions/handler.py
+++ b/strawberry/exceptions/handler.py
@@ -32,7 +32,7 @@ def _handler(
exception_type: Type[BaseException],
exception: BaseException,
traceback: Optional[TracebackType],
- ):
+ ) -> None:
try:
rich.print(exception)
diff --git a/strawberry/exceptions/invalid_argument_type.py b/strawberry/exceptions/invalid_argument_type.py
index 25dcdbe630..092b8ad6d0 100644
--- a/strawberry/exceptions/invalid_argument_type.py
+++ b/strawberry/exceptions/invalid_argument_type.py
@@ -3,13 +3,13 @@
from functools import cached_property
from typing import TYPE_CHECKING, Optional
-from strawberry.type import get_object_definition
+from strawberry.types.base import get_object_definition
from .exception import StrawberryException
from .utils.source_finder import SourceFinder
if TYPE_CHECKING:
- from strawberry.arguments import StrawberryArgument
+ from strawberry.types.arguments import StrawberryArgument
from strawberry.types.fields.resolver import StrawberryResolver
from .exception_source import ExceptionSource
@@ -20,8 +20,8 @@ def __init__(
self,
resolver: StrawberryResolver,
argument: StrawberryArgument,
- ):
- from strawberry.union import StrawberryUnion
+ ) -> None:
+ from strawberry.types.union import StrawberryUnion
self.function = resolver.wrapped_func
self.argument_name = argument.python_name
diff --git a/strawberry/exceptions/invalid_union_type.py b/strawberry/exceptions/invalid_union_type.py
index cd54b8dfae..28e65de6c5 100644
--- a/strawberry/exceptions/invalid_union_type.py
+++ b/strawberry/exceptions/invalid_union_type.py
@@ -10,13 +10,13 @@
from .exception import StrawberryException
if TYPE_CHECKING:
- from strawberry.union import StrawberryUnion
+ from strawberry.types.union import StrawberryUnion
from .exception_source import ExceptionSource
class InvalidUnionTypeError(StrawberryException):
- """The union is constructed with an invalid type"""
+ """The union is constructed with an invalid type."""
invalid_type: object
@@ -26,8 +26,8 @@ def __init__(
invalid_type: object,
union_definition: Optional[StrawberryUnion] = None,
) -> None:
- from strawberry.custom_scalar import ScalarWrapper
- from strawberry.type import StrawberryList
+ from strawberry.types.base import StrawberryList
+ from strawberry.types.scalar import ScalarWrapper
self.union_name = union_name
self.invalid_type = invalid_type
@@ -78,8 +78,7 @@ def exception_source(self) -> Optional[ExceptionSource]:
class InvalidTypeForUnionMergeError(StrawberryException):
- """A specialized version of InvalidUnionTypeError for when trying
- to merge unions using the pipe operator."""
+ """A specialized version of InvalidUnionTypeError for when trying to merge unions using the pipe operator."""
invalid_type: Type
diff --git a/strawberry/exceptions/missing_arguments_annotations.py b/strawberry/exceptions/missing_arguments_annotations.py
index f8cbd048bf..97610584fa 100644
--- a/strawberry/exceptions/missing_arguments_annotations.py
+++ b/strawberry/exceptions/missing_arguments_annotations.py
@@ -13,9 +13,13 @@
class MissingArgumentsAnnotationsError(StrawberryException):
- """The field is missing the annotation for one or more arguments"""
+ """The field is missing the annotation for one or more arguments."""
- def __init__(self, resolver: StrawberryResolver, arguments: List[str]):
+ def __init__(
+ self,
+ resolver: StrawberryResolver,
+ arguments: List[str],
+ ) -> None:
self.missing_arguments = arguments
self.function = resolver.wrapped_func
self.argument_name = arguments[0]
diff --git a/strawberry/exceptions/missing_dependencies.py b/strawberry/exceptions/missing_dependencies.py
new file mode 100644
index 0000000000..fa72f17ea3
--- /dev/null
+++ b/strawberry/exceptions/missing_dependencies.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from typing import Optional
+
+
+class MissingOptionalDependenciesError(Exception):
+ """Some optional dependencies that are required for a particular task are missing."""
+
+ def __init__(
+ self,
+ *,
+ packages: Optional[list[str]] = None,
+ extras: Optional[list[str]] = None,
+ ) -> None:
+ """Initialize the error.
+
+ Args:
+ packages: List of packages that are required but missing.
+ extras: List of extras that are required but missing.
+ """
+ packages = packages or []
+
+ if extras:
+ packages.append(f"'strawberry-graphql[{','.join(extras)}]'")
+
+ hint = f" (hint: pip install {' '.join(packages)})" if packages else ""
+
+ self.message = f"Some optional dependencies are missing{hint}"
diff --git a/strawberry/exceptions/missing_field_annotation.py b/strawberry/exceptions/missing_field_annotation.py
index ad279cf325..a417c15dd8 100644
--- a/strawberry/exceptions/missing_field_annotation.py
+++ b/strawberry/exceptions/missing_field_annotation.py
@@ -11,7 +11,7 @@
class MissingFieldAnnotationError(StrawberryException):
- def __init__(self, field_name: str, cls: Type):
+ def __init__(self, field_name: str, cls: Type) -> None:
self.cls = cls
self.field_name = field_name
diff --git a/strawberry/exceptions/missing_return_annotation.py b/strawberry/exceptions/missing_return_annotation.py
index 4969fe8cdf..60d64bffa0 100644
--- a/strawberry/exceptions/missing_return_annotation.py
+++ b/strawberry/exceptions/missing_return_annotation.py
@@ -13,9 +13,13 @@
class MissingReturnAnnotationError(StrawberryException):
- """The field is missing the return annotation"""
+ """The field is missing the return annotation."""
- def __init__(self, field_name: str, resolver: StrawberryResolver):
+ def __init__(
+ self,
+ field_name: str,
+ resolver: StrawberryResolver,
+ ) -> None:
self.function = resolver.wrapped_func
self.message = (
diff --git a/strawberry/exceptions/not_a_strawberry_enum.py b/strawberry/exceptions/not_a_strawberry_enum.py
index 5696fc9920..dd0c94d48f 100644
--- a/strawberry/exceptions/not_a_strawberry_enum.py
+++ b/strawberry/exceptions/not_a_strawberry_enum.py
@@ -13,7 +13,7 @@
class NotAStrawberryEnumError(StrawberryException):
- def __init__(self, enum: EnumMeta):
+ def __init__(self, enum: EnumMeta) -> None:
self.enum = enum
self.message = f'Enum "{enum.__name__}" is not a Strawberry enum.'
diff --git a/strawberry/exceptions/object_is_not_a_class.py b/strawberry/exceptions/object_is_not_a_class.py
index 4419f15a4d..0537e9a39a 100644
--- a/strawberry/exceptions/object_is_not_a_class.py
+++ b/strawberry/exceptions/object_is_not_a_class.py
@@ -17,7 +17,7 @@ class MethodType(Enum):
INTERFACE = "interface"
TYPE = "type"
- def __init__(self, obj: object, method_type: MethodType):
+ def __init__(self, obj: object, method_type: MethodType) -> None:
self.obj = obj
self.function = obj
diff --git a/strawberry/exceptions/object_is_not_an_enum.py b/strawberry/exceptions/object_is_not_an_enum.py
index 1d209cbc64..fe79fe2550 100644
--- a/strawberry/exceptions/object_is_not_an_enum.py
+++ b/strawberry/exceptions/object_is_not_an_enum.py
@@ -13,7 +13,7 @@
class ObjectIsNotAnEnumError(StrawberryException):
- def __init__(self, cls: Type[Enum]):
+ def __init__(self, cls: Type[Enum]) -> None:
self.cls = cls
self.message = (
"strawberry.enum can only be used with subclasses of Enum. "
diff --git a/strawberry/exceptions/permission_fail_silently_requires_optional.py b/strawberry/exceptions/permission_fail_silently_requires_optional.py
index 09b39b0883..9a922e2f8e 100644
--- a/strawberry/exceptions/permission_fail_silently_requires_optional.py
+++ b/strawberry/exceptions/permission_fail_silently_requires_optional.py
@@ -12,7 +12,7 @@
class PermissionFailSilentlyRequiresOptionalError(StrawberryException):
- def __init__(self, field: StrawberryField):
+ def __init__(self, field: StrawberryField) -> None:
self.field = field
self.message = (
"Cannot use fail_silently=True with a non-optional " "or non-list field"
@@ -20,7 +20,7 @@ def __init__(self, field: StrawberryField):
self.rich_message = (
"fail_silently permissions can only be used with fields of type "
f"optional or list. Provided field `[underline]{field.name}[/]` "
- f"is of type `[underline]{field.type.__name__}[/]`" # type: ignore
+ f"is of type `[underline]{field.type.__name__}[/]`"
)
self.annotation_message = "field defined here"
self.suggestion = (
@@ -38,14 +38,14 @@ def exception_source(self) -> Optional[ExceptionSource]:
source = None
if origin is not None:
source = source_finder.find_class_attribute_from_object(
- origin, # type: ignore
+ origin,
self.field.python_name,
)
# in case it is a function
if source is None and self.field.base_resolver is not None:
source = source_finder.find_function_from_object(
- self.field.base_resolver.wrapped_func # type: ignore
+ self.field.base_resolver.wrapped_func
)
return source
diff --git a/strawberry/exceptions/private_strawberry_field.py b/strawberry/exceptions/private_strawberry_field.py
index 0c09b9c18d..c36a1f8993 100644
--- a/strawberry/exceptions/private_strawberry_field.py
+++ b/strawberry/exceptions/private_strawberry_field.py
@@ -11,7 +11,7 @@
class PrivateStrawberryFieldError(StrawberryException):
- def __init__(self, field_name: str, cls: Type):
+ def __init__(self, field_name: str, cls: Type) -> None:
self.cls = cls
self.field_name = field_name
diff --git a/strawberry/exceptions/scalar_already_registered.py b/strawberry/exceptions/scalar_already_registered.py
index ae24c9b0c0..c950b5d369 100644
--- a/strawberry/exceptions/scalar_already_registered.py
+++ b/strawberry/exceptions/scalar_already_registered.py
@@ -9,7 +9,7 @@
from .exception import StrawberryException
if TYPE_CHECKING:
- from strawberry.custom_scalar import ScalarDefinition
+ from strawberry.types.scalar import ScalarDefinition
from .exception_source import ExceptionSource
@@ -19,7 +19,7 @@ def __init__(
self,
scalar_definition: ScalarDefinition,
other_scalar_definition: ScalarDefinition,
- ):
+ ) -> None:
self.scalar_definition = scalar_definition
scalar_name = scalar_definition.name
diff --git a/strawberry/exceptions/unresolved_field_type.py b/strawberry/exceptions/unresolved_field_type.py
index 9c0406ac2d..70eff59ed4 100644
--- a/strawberry/exceptions/unresolved_field_type.py
+++ b/strawberry/exceptions/unresolved_field_type.py
@@ -8,8 +8,8 @@
from .exception import StrawberryException
if TYPE_CHECKING:
- from strawberry.field import StrawberryField
- from strawberry.object_type import StrawberryObjectDefinition
+ from strawberry.types.field import StrawberryField
+ from strawberry.types.object_type import StrawberryObjectDefinition
from .exception_source import ExceptionSource
@@ -19,7 +19,7 @@ def __init__(
self,
type_definition: StrawberryObjectDefinition,
field: StrawberryField,
- ):
+ ) -> None:
self.type_definition = type_definition
self.field = field
diff --git a/strawberry/exceptions/utils/source_finder.py b/strawberry/exceptions/utils/source_finder.py
index 2aec969cf8..9b2c8010d0 100644
--- a/strawberry/exceptions/utils/source_finder.py
+++ b/strawberry/exceptions/utils/source_finder.py
@@ -15,8 +15,8 @@
from libcst import BinaryOperation, Call, CSTNode, FunctionDef
- from strawberry.custom_scalar import ScalarDefinition
- from strawberry.union import StrawberryUnion
+ from strawberry.types.scalar import ScalarDefinition
+ from strawberry.types.union import StrawberryUnion
@dataclass
@@ -605,3 +605,6 @@ def find_annotated_union(
if self.cst
else None
)
+
+
+__all__ = ["SourceFinder"]
diff --git a/strawberry/experimental/__init__.py b/strawberry/experimental/__init__.py
index 6386ad81d7..32363dedfa 100644
--- a/strawberry/experimental/__init__.py
+++ b/strawberry/experimental/__init__.py
@@ -1,6 +1,6 @@
try:
from . import pydantic
-except ImportError:
+except ModuleNotFoundError:
pass
else:
__all__ = ["pydantic"]
diff --git a/strawberry/experimental/pydantic/_compat.py b/strawberry/experimental/pydantic/_compat.py
index 79d919af23..9971c962b5 100644
--- a/strawberry/experimental/pydantic/_compat.py
+++ b/strawberry/experimental/pydantic/_compat.py
@@ -1,10 +1,16 @@
import dataclasses
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type
+from decimal import Decimal
+from functools import cached_property
+from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type
+from uuid import UUID
+import pydantic
from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
+from strawberry.experimental.pydantic.exceptions import UnsupportedTypeError
+
if TYPE_CHECKING:
from pydantic.fields import FieldInfo
@@ -24,21 +30,101 @@ class CompatModelField:
allow_none: bool
has_alias: bool
description: Optional[str]
+ _missing_type: Any
+ is_v1: bool
+ @property
+ def has_default_factory(self) -> bool:
+ return self.default_factory is not self._missing_type
-if IS_PYDANTIC_V2:
- from typing_extensions import get_args, get_origin
+ @property
+ def has_default(self) -> bool:
+ return self.default is not self._missing_type
- from pydantic._internal._typing_extra import is_new_type
- from pydantic._internal._utils import lenient_issubclass, smart_deepcopy
- from pydantic_core import PydanticUndefined
- PYDANTIC_MISSING_TYPE = PydanticUndefined
+ATTR_TO_TYPE_MAP = {
+ "NoneStr": Optional[str],
+ "NoneBytes": Optional[bytes],
+ "StrBytes": None,
+ "NoneStrBytes": None,
+ "StrictStr": str,
+ "ConstrainedBytes": bytes,
+ "conbytes": bytes,
+ "ConstrainedStr": str,
+ "constr": str,
+ "EmailStr": str,
+ "PyObject": None,
+ "ConstrainedInt": int,
+ "conint": int,
+ "PositiveInt": int,
+ "NegativeInt": int,
+ "ConstrainedFloat": float,
+ "confloat": float,
+ "PositiveFloat": float,
+ "NegativeFloat": float,
+ "ConstrainedDecimal": Decimal,
+ "condecimal": Decimal,
+ "UUID1": UUID,
+ "UUID3": UUID,
+ "UUID4": UUID,
+ "UUID5": UUID,
+ "FilePath": None,
+ "DirectoryPath": None,
+ "Json": None,
+ "JsonWrapper": None,
+ "SecretStr": str,
+ "SecretBytes": bytes,
+ "StrictBool": bool,
+ "StrictInt": int,
+ "StrictFloat": float,
+ "PaymentCardNumber": None,
+ "ByteSize": None,
+ "AnyUrl": str,
+ "AnyHttpUrl": str,
+ "HttpUrl": str,
+ "PostgresDsn": str,
+ "RedisDsn": str,
+}
- def new_type_supertype(type_: Any) -> Any:
- return type_.__supertype__
+ATTR_TO_TYPE_MAP_Pydantic_V2 = {
+ "EmailStr": str,
+ "SecretStr": str,
+ "SecretBytes": bytes,
+ "AnyUrl": str,
+}
+
+ATTR_TO_TYPE_MAP_Pydantic_Core_V2 = {
+ "MultiHostUrl": str,
+}
+
+
+def get_fields_map_for_v2() -> Dict[Any, Any]:
+ import pydantic_core
- def get_model_fields(model: Type[BaseModel]) -> Dict[str, CompatModelField]:
+ fields_map = {
+ getattr(pydantic, field_name): type
+ for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_V2.items()
+ if hasattr(pydantic, field_name)
+ }
+ fields_map.update(
+ {
+ getattr(pydantic_core, field_name): type
+ for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_Core_V2.items()
+ if hasattr(pydantic_core, field_name)
+ }
+ )
+
+ return fields_map
+
+
+class PydanticV2Compat:
+ @property
+ def PYDANTIC_MISSING_TYPE(self) -> Any:
+ from pydantic_core import PydanticUndefined
+
+ return PydanticUndefined
+
+ def get_model_fields(self, model: Type[BaseModel]) -> Dict[str, CompatModelField]:
field_info: dict[str, FieldInfo] = model.model_fields
new_fields = {}
# Convert it into CompatModelField
@@ -55,24 +141,37 @@ def get_model_fields(model: Type[BaseModel]) -> Dict[str, CompatModelField]:
allow_none=False,
has_alias=field is not None,
description=field.description,
+ _missing_type=self.PYDANTIC_MISSING_TYPE,
+ is_v1=False,
)
return new_fields
-else:
- from pydantic.typing import ( # type: ignore[no-redef]
- get_args,
- get_origin,
- is_new_type,
- new_type_supertype,
- )
- from pydantic.utils import ( # type: ignore[no-redef]
- lenient_issubclass,
- smart_deepcopy,
- )
+ @cached_property
+ def fields_map(self) -> Dict[Any, Any]:
+ return get_fields_map_for_v2()
+
+ def get_basic_type(self, type_: Any) -> Type[Any]:
+ if type_ in self.fields_map:
+ type_ = self.fields_map[type_]
- PYDANTIC_MISSING_TYPE = dataclasses.MISSING # type: ignore[assignment]
+ if type_ is None:
+ raise UnsupportedTypeError()
- def get_model_fields(model: Type[BaseModel]) -> Dict[str, CompatModelField]:
+ if is_new_type(type_):
+ return new_type_supertype(type_)
+
+ return type_
+
+ def model_dump(self, model_instance: BaseModel) -> Dict[Any, Any]:
+ return model_instance.model_dump()
+
+
+class PydanticV1Compat:
+ @property
+ def PYDANTIC_MISSING_TYPE(self) -> Any:
+ return dataclasses.MISSING
+
+ def get_model_fields(self, model: Type[BaseModel]) -> Dict[str, CompatModelField]:
new_fields = {}
# Convert it into CompatModelField
for name, field in model.__fields__.items(): # type: ignore[attr-defined]
@@ -87,17 +186,107 @@ def get_model_fields(model: Type[BaseModel]) -> Dict[str, CompatModelField]:
allow_none=field.allow_none,
has_alias=field.has_alias,
description=field.field_info.description,
+ _missing_type=self.PYDANTIC_MISSING_TYPE,
+ is_v1=True,
)
return new_fields
+ @cached_property
+ def fields_map(self) -> Dict[Any, Any]:
+ if IS_PYDANTIC_V2:
+ return {
+ getattr(pydantic.v1, field_name): type
+ for field_name, type in ATTR_TO_TYPE_MAP.items()
+ if hasattr(pydantic.v1, field_name)
+ }
+
+ return {
+ getattr(pydantic, field_name): type
+ for field_name, type in ATTR_TO_TYPE_MAP.items()
+ if hasattr(pydantic, field_name)
+ }
+
+ def get_basic_type(self, type_: Any) -> Type[Any]:
+ if IS_PYDANTIC_V1:
+ ConstrainedInt = pydantic.ConstrainedInt
+ ConstrainedFloat = pydantic.ConstrainedFloat
+ ConstrainedStr = pydantic.ConstrainedStr
+ ConstrainedList = pydantic.ConstrainedList
+ else:
+ ConstrainedInt = pydantic.v1.ConstrainedInt
+ ConstrainedFloat = pydantic.v1.ConstrainedFloat
+ ConstrainedStr = pydantic.v1.ConstrainedStr
+ ConstrainedList = pydantic.v1.ConstrainedList
+
+ if lenient_issubclass(type_, ConstrainedInt):
+ return int
+ if lenient_issubclass(type_, ConstrainedFloat):
+ return float
+ if lenient_issubclass(type_, ConstrainedStr):
+ return str
+ if lenient_issubclass(type_, ConstrainedList):
+ return List[self.get_basic_type(type_.item_type)] # type: ignore
+
+ if type_ in self.fields_map:
+ type_ = self.fields_map[type_]
+
+ if type_ is None:
+ raise UnsupportedTypeError()
+
+ if is_new_type(type_):
+ return new_type_supertype(type_)
+
+ return type_
+
+ def model_dump(self, model_instance: BaseModel) -> Dict[Any, Any]:
+ return model_instance.dict()
+
+
+class PydanticCompat:
+ def __init__(self, is_v2: bool) -> None:
+ if is_v2:
+ self._compat = PydanticV2Compat()
+ else:
+ self._compat = PydanticV1Compat() # type: ignore[assignment]
+
+ @classmethod
+ def from_model(cls, model: Type[BaseModel]) -> "PydanticCompat":
+ if hasattr(model, "model_fields"):
+ return cls(is_v2=True)
+
+ return cls(is_v2=False)
+
+ def __getattr__(self, name: str) -> Any:
+ return getattr(self._compat, name)
+
+
+if IS_PYDANTIC_V2:
+ from typing_extensions import get_args, get_origin
+
+ from pydantic._internal._typing_extra import is_new_type
+ from pydantic._internal._utils import lenient_issubclass, smart_deepcopy
+
+ def new_type_supertype(type_: Any) -> Any:
+ return type_.__supertype__
+else:
+ from pydantic.typing import ( # type: ignore[no-redef]
+ get_args,
+ get_origin,
+ is_new_type,
+ new_type_supertype,
+ )
+ from pydantic.utils import ( # type: ignore[no-redef]
+ lenient_issubclass,
+ smart_deepcopy,
+ )
+
__all__ = [
- "smart_deepcopy",
+ "PydanticCompat",
+ "is_new_type",
"lenient_issubclass",
- "get_args",
"get_origin",
- "is_new_type",
+ "get_args",
"new_type_supertype",
- "get_model_fields",
- "PYDANTIC_MISSING_TYPE",
+ "smart_deepcopy",
]
diff --git a/strawberry/experimental/pydantic/conversion.py b/strawberry/experimental/pydantic/conversion.py
index 0cc43c3bd3..ae3d2aaddb 100644
--- a/strawberry/experimental/pydantic/conversion.py
+++ b/strawberry/experimental/pydantic/conversion.py
@@ -4,24 +4,24 @@
import dataclasses
from typing import TYPE_CHECKING, Any, Type, Union, cast
-from strawberry.enum import EnumDefinition
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
StrawberryOptional,
has_object_definition,
)
-from strawberry.union import StrawberryUnion
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.union import StrawberryUnion
if TYPE_CHECKING:
- from strawberry.field import StrawberryField
- from strawberry.type import StrawberryType
+ from strawberry.types.base import StrawberryType
+ from strawberry.types.field import StrawberryField
def _convert_from_pydantic_to_strawberry_type(
type_: Union[StrawberryType, type],
data_from_model=None, # noqa: ANN001
extra=None, # noqa: ANN001
-):
+) -> Any:
data = data_from_model if data_from_model is not None else extra
if isinstance(type_, StrawberryOptional):
diff --git a/strawberry/experimental/pydantic/conversion_types.py b/strawberry/experimental/pydantic/conversion_types.py
index ca5dde2048..0e24ba2f00 100644
--- a/strawberry/experimental/pydantic/conversion_types.py
+++ b/strawberry/experimental/pydantic/conversion_types.py
@@ -6,7 +6,7 @@
from pydantic import BaseModel
if TYPE_CHECKING:
- from strawberry.types.types import StrawberryObjectDefinition
+ from strawberry.types.base import StrawberryObjectDefinition
PydanticModel = TypeVar("PydanticModel", bound=BaseModel)
@@ -14,24 +14,21 @@
class StrawberryTypeFromPydantic(Protocol[PydanticModel]):
"""This class does not exist in runtime.
- It only makes the methods below visible for IDEs"""
- def __init__(self, **kwargs: Any):
- ...
+ It only makes the methods below visible for IDEs.
+ """
+
+ def __init__(self, **kwargs: Any) -> None: ...
@staticmethod
def from_pydantic(
instance: PydanticModel, extra: Optional[Dict[str, Any]] = None
- ) -> StrawberryTypeFromPydantic[PydanticModel]:
- ...
+ ) -> StrawberryTypeFromPydantic[PydanticModel]: ...
- def to_pydantic(self, **kwargs: Any) -> PydanticModel:
- ...
+ def to_pydantic(self, **kwargs: Any) -> PydanticModel: ...
@property
- def __strawberry_definition__(self) -> StrawberryObjectDefinition:
- ...
+ def __strawberry_definition__(self) -> StrawberryObjectDefinition: ...
@property
- def _pydantic_type(self) -> Type[PydanticModel]:
- ...
+ def _pydantic_type(self) -> Type[PydanticModel]: ...
diff --git a/strawberry/experimental/pydantic/error_type.py b/strawberry/experimental/pydantic/error_type.py
index adcebdc2f7..bbedfe610b 100644
--- a/strawberry/experimental/pydantic/error_type.py
+++ b/strawberry/experimental/pydantic/error_type.py
@@ -16,10 +16,9 @@
from pydantic import BaseModel
-from strawberry.auto import StrawberryAuto
from strawberry.experimental.pydantic._compat import (
CompatModelField,
- get_model_fields,
+ PydanticCompat,
lenient_issubclass,
)
from strawberry.experimental.pydantic.utils import (
@@ -27,7 +26,8 @@
get_strawberry_type_from_model,
normalize_type,
)
-from strawberry.object_type import _process_type, _wrap_dataclass
+from strawberry.types.auto import StrawberryAuto
+from strawberry.types.object_type import _process_type, _wrap_dataclass
from strawberry.types.type_resolver import _get_fields
from strawberry.utils.typing import get_list_annotation, is_list
@@ -72,7 +72,8 @@ def error_type(
all_fields: bool = False,
) -> Callable[..., Type]:
def wrap(cls: Type) -> Type:
- model_fields = get_model_fields(model)
+ compat = PydanticCompat.from_model(model)
+ model_fields = compat.get_model_fields(model)
fields_set = set(fields) if fields else set()
if fields:
@@ -113,7 +114,7 @@ def wrap(cls: Type) -> Type:
]
wrapped = _wrap_dataclass(cls)
- extra_fields = cast(List[dataclasses.Field], _get_fields(wrapped))
+ extra_fields = cast(List[dataclasses.Field], _get_fields(wrapped, {}))
private_fields = get_private_fields(wrapped)
all_model_fields.extend(
diff --git a/strawberry/experimental/pydantic/exceptions.py b/strawberry/experimental/pydantic/exceptions.py
index cdb16baaad..10b7999fa9 100644
--- a/strawberry/experimental/pydantic/exceptions.py
+++ b/strawberry/experimental/pydantic/exceptions.py
@@ -8,7 +8,7 @@
class MissingFieldsListError(Exception):
- def __init__(self, type: Type[BaseModel]):
+ def __init__(self, type: Type[BaseModel]) -> None:
message = (
f"List of fields to copy from {type} is empty. Add fields with the "
f"`auto` type annotation"
@@ -22,7 +22,7 @@ class UnsupportedTypeError(Exception):
class UnregisteredTypeException(Exception):
- def __init__(self, type: Type[BaseModel]):
+ def __init__(self, type: Type[BaseModel]) -> None:
message = (
f"Cannot find a Strawberry Type for {type} did you forget to register it?"
)
@@ -31,7 +31,7 @@ def __init__(self, type: Type[BaseModel]):
class BothDefaultAndDefaultFactoryDefinedError(Exception):
- def __init__(self, default: Any, default_factory: NoArgAnyCallable):
+ def __init__(self, default: Any, default_factory: NoArgAnyCallable) -> None:
message = (
f"Not allowed to specify both default and default_factory. "
f"default:{default} default_factory:{default_factory}"
@@ -41,7 +41,12 @@ def __init__(self, default: Any, default_factory: NoArgAnyCallable):
class AutoFieldsNotInBaseModelError(Exception):
- def __init__(self, fields: List[str], cls_name: str, model: Type[BaseModel]):
+ def __init__(
+ self,
+ fields: List[str],
+ cls_name: str,
+ model: Type[BaseModel],
+ ) -> None:
message = (
f"{cls_name} defines {fields} with strawberry.auto. "
f"Field(s) not present in {model.__name__} BaseModel."
diff --git a/strawberry/experimental/pydantic/fields.py b/strawberry/experimental/pydantic/fields.py
index 6ffa6dc3d7..9cac486290 100644
--- a/strawberry/experimental/pydantic/fields.py
+++ b/strawberry/experimental/pydantic/fields.py
@@ -1,25 +1,19 @@
import builtins
-from decimal import Decimal
-from typing import Any, Dict, List, Optional, Type, Union
+from typing import Any, Union
from typing_extensions import Annotated
-from uuid import UUID
-import pydantic
from pydantic import BaseModel
from strawberry.experimental.pydantic._compat import (
- IS_PYDANTIC_V1,
+ PydanticCompat,
get_args,
get_origin,
- is_new_type,
lenient_issubclass,
- new_type_supertype,
)
from strawberry.experimental.pydantic.exceptions import (
UnregisteredTypeException,
- UnsupportedTypeError,
)
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import StrawberryObjectDefinition
try:
from types import UnionType as TypingUnionType
@@ -44,115 +38,6 @@
raise
-ATTR_TO_TYPE_MAP = {
- "NoneStr": Optional[str],
- "NoneBytes": Optional[bytes],
- "StrBytes": None,
- "NoneStrBytes": None,
- "StrictStr": str,
- "ConstrainedBytes": bytes,
- "conbytes": bytes,
- "ConstrainedStr": str,
- "constr": str,
- "EmailStr": str,
- "PyObject": None,
- "ConstrainedInt": int,
- "conint": int,
- "PositiveInt": int,
- "NegativeInt": int,
- "ConstrainedFloat": float,
- "confloat": float,
- "PositiveFloat": float,
- "NegativeFloat": float,
- "ConstrainedDecimal": Decimal,
- "condecimal": Decimal,
- "UUID1": UUID,
- "UUID3": UUID,
- "UUID4": UUID,
- "UUID5": UUID,
- "FilePath": None,
- "DirectoryPath": None,
- "Json": None,
- "JsonWrapper": None,
- "SecretStr": str,
- "SecretBytes": bytes,
- "StrictBool": bool,
- "StrictInt": int,
- "StrictFloat": float,
- "PaymentCardNumber": None,
- "ByteSize": None,
- "AnyUrl": str,
- "AnyHttpUrl": str,
- "HttpUrl": str,
- "PostgresDsn": str,
- "RedisDsn": str,
-}
-
-ATTR_TO_TYPE_MAP_Pydantic_V2 = {
- "EmailStr": str,
- "SecretStr": str,
- "SecretBytes": bytes,
- "AnyUrl": str,
-}
-
-ATTR_TO_TYPE_MAP_Pydantic_Core_V2 = {
- "MultiHostUrl": str,
-}
-
-
-def get_fields_map_for_v2() -> Dict[Any, Any]:
- import pydantic_core
-
- fields_map = {
- getattr(pydantic, field_name): type
- for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_V2.items()
- if hasattr(pydantic, field_name)
- }
- fields_map.update(
- {
- getattr(pydantic_core, field_name): type
- for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_Core_V2.items()
- if hasattr(pydantic_core, field_name)
- }
- )
-
- return fields_map
-
-
-FIELDS_MAP = (
- {
- getattr(pydantic, field_name): type
- for field_name, type in ATTR_TO_TYPE_MAP.items()
- if hasattr(pydantic, field_name)
- }
- if IS_PYDANTIC_V1
- else get_fields_map_for_v2()
-)
-
-
-def get_basic_type(type_: Any) -> Type[Any]:
- if IS_PYDANTIC_V1:
- # only pydantic v1 has these
- if lenient_issubclass(type_, pydantic.ConstrainedInt):
- return int
- if lenient_issubclass(type_, pydantic.ConstrainedFloat):
- return float
- if lenient_issubclass(type_, pydantic.ConstrainedStr):
- return str
- if lenient_issubclass(type_, pydantic.ConstrainedList):
- return List[get_basic_type(type_.item_type)] # type: ignore
-
- if type_ in FIELDS_MAP:
- type_ = FIELDS_MAP.get(type_)
- if type_ is None:
- raise UnsupportedTypeError()
-
- if is_new_type(type_):
- return new_type_supertype(type_)
-
- return type_
-
-
def replace_pydantic_types(type_: Any, is_input: bool) -> Any:
if lenient_issubclass(type_, BaseModel):
attr = "_strawberry_input_type" if is_input else "_strawberry_type"
@@ -163,17 +48,21 @@ def replace_pydantic_types(type_: Any, is_input: bool) -> Any:
return type_
-def replace_types_recursively(type_: Any, is_input: bool) -> Any:
- """Runs the conversions recursively into the arguments of generic types if any"""
- basic_type = get_basic_type(type_)
+def replace_types_recursively(
+ type_: Any, is_input: bool, compat: PydanticCompat
+) -> Any:
+ """Runs the conversions recursively into the arguments of generic types if any."""
+ basic_type = compat.get_basic_type(type_)
replaced_type = replace_pydantic_types(basic_type, is_input)
origin = get_origin(type_)
+
if not origin or not hasattr(type_, "__args__"):
return replaced_type
converted = tuple(
- replace_types_recursively(t, is_input=is_input) for t in get_args(replaced_type)
+ replace_types_recursively(t, is_input=is_input, compat=compat)
+ for t in get_args(replaced_type)
)
if isinstance(replaced_type, TypingGenericAlias):
diff --git a/strawberry/experimental/pydantic/object_type.py b/strawberry/experimental/pydantic/object_type.py
index f7e87ed6ef..caed153c9b 100644
--- a/strawberry/experimental/pydantic/object_type.py
+++ b/strawberry/experimental/pydantic/object_type.py
@@ -17,11 +17,9 @@
)
from strawberry.annotation import StrawberryAnnotation
-from strawberry.auto import StrawberryAuto
from strawberry.experimental.pydantic._compat import (
- IS_PYDANTIC_V1,
CompatModelField,
- get_model_fields,
+ PydanticCompat,
)
from strawberry.experimental.pydantic.conversion import (
convert_pydantic_model_to_strawberry_class,
@@ -35,8 +33,9 @@
get_default_factory_for_field,
get_private_fields,
)
-from strawberry.field import StrawberryField
-from strawberry.object_type import _process_type, _wrap_dataclass
+from strawberry.types.auto import StrawberryAuto
+from strawberry.types.field import StrawberryField
+from strawberry.types.object_type import _process_type, _wrap_dataclass
from strawberry.types.type_resolver import _get_fields
from strawberry.utils.dataclasses import add_custom_init_fn
@@ -44,10 +43,12 @@
from graphql import GraphQLResolveInfo
-def get_type_for_field(field: CompatModelField, is_input: bool): # noqa: ANN201
+def get_type_for_field(field: CompatModelField, is_input: bool, compat: PydanticCompat): # noqa: ANN201
outer_type = field.outer_type_
- replaced_type = replace_types_recursively(outer_type, is_input)
- if IS_PYDANTIC_V1:
+
+ replaced_type = replace_types_recursively(outer_type, is_input, compat=compat)
+
+ if field.is_v1:
# only pydantic v1 has this Optional logic
should_add_optional: bool = field.allow_none
if should_add_optional:
@@ -62,9 +63,10 @@ def _build_dataclass_creation_fields(
existing_fields: Dict[str, StrawberryField],
auto_fields_set: Set[str],
use_pydantic_alias: bool,
+ compat: PydanticCompat,
) -> DataclassCreationFields:
field_type = (
- get_type_for_field(field, is_input)
+ get_type_for_field(field, is_input, compat=compat)
if field.name in auto_fields_set
else existing_fields[field.name].type
)
@@ -89,7 +91,7 @@ def _build_dataclass_creation_fields(
graphql_name=graphql_name,
# always unset because we use default_factory instead
default=dataclasses.MISSING,
- default_factory=get_default_factory_for_field(field),
+ default_factory=get_default_factory_for_field(field, compat=compat),
type_annotation=StrawberryAnnotation.from_annotation(field_type),
description=field.description,
deprecation_reason=(
@@ -129,7 +131,8 @@ def type(
use_pydantic_alias: bool = True,
) -> Callable[..., Type[StrawberryTypeFromPydantic[PydanticModel]]]:
def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
- model_fields = get_model_fields(model)
+ compat = PydanticCompat.from_model(model)
+ model_fields = compat.get_model_fields(model)
original_fields_set = set(fields) if fields else set()
if fields:
@@ -174,7 +177,7 @@ def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
)
wrapped = _wrap_dataclass(cls)
- extra_strawberry_fields = _get_fields(wrapped)
+ extra_strawberry_fields = _get_fields(wrapped, {})
extra_fields = cast(List[dataclasses.Field], extra_strawberry_fields)
private_fields = get_private_fields(wrapped)
@@ -182,7 +185,12 @@ def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
all_model_fields: List[DataclassCreationFields] = [
_build_dataclass_creation_fields(
- field, is_input, extra_fields_dict, auto_fields_set, use_pydantic_alias
+ field,
+ is_input,
+ extra_fields_dict,
+ auto_fields_set,
+ use_pydantic_alias,
+ compat=compat,
)
for field_name, field in model_fields.items()
if field_name in fields_set
@@ -304,8 +312,10 @@ def input(
use_pydantic_alias: bool = True,
) -> Callable[..., Type[StrawberryTypeFromPydantic[PydanticModel]]]:
"""Convenience decorator for creating an input type from a Pydantic model.
- Equal to partial(type, is_input=True)
- See https://github.com/strawberry-graphql/strawberry/issues/1830
+
+ Equal to `partial(type, is_input=True)`
+
+ See https://github.com/strawberry-graphql/strawberry/issues/1830.
"""
return type(
model=model,
@@ -332,8 +342,10 @@ def interface(
use_pydantic_alias: bool = True,
) -> Callable[..., Type[StrawberryTypeFromPydantic[PydanticModel]]]:
"""Convenience decorator for creating an interface type from a Pydantic model.
- Equal to partial(type, is_interface=True)
- See https://github.com/strawberry-graphql/strawberry/issues/1830
+
+ Equal to `partial(type, is_interface=True)`
+
+ See https://github.com/strawberry-graphql/strawberry/issues/1830.
"""
return type(
model=model,
diff --git a/strawberry/experimental/pydantic/utils.py b/strawberry/experimental/pydantic/utils.py
index 4f8629a0fc..912553fb98 100644
--- a/strawberry/experimental/pydantic/utils.py
+++ b/strawberry/experimental/pydantic/utils.py
@@ -13,10 +13,11 @@
cast,
)
+from pydantic import BaseModel
+
from strawberry.experimental.pydantic._compat import (
- PYDANTIC_MISSING_TYPE,
CompatModelField,
- get_model_fields,
+ PydanticCompat,
smart_deepcopy,
)
from strawberry.experimental.pydantic.exceptions import (
@@ -24,8 +25,8 @@
BothDefaultAndDefaultFactoryDefinedError,
UnregisteredTypeException,
)
-from strawberry.private import is_private
-from strawberry.unset import UNSET
+from strawberry.types.private import is_private
+from strawberry.types.unset import UNSET
from strawberry.utils.typing import (
get_list_annotation,
get_optional_annotation,
@@ -34,7 +35,6 @@
)
if TYPE_CHECKING:
- from pydantic import BaseModel
from pydantic.typing import NoArgAnyCallable
@@ -60,7 +60,7 @@ def get_private_fields(cls: Type) -> List[dataclasses.Field]:
class DataclassCreationFields(NamedTuple):
- """Fields required for the fields parameter of make_dataclass"""
+ """Fields required for the fields parameter of make_dataclass."""
name: str
field_type: Type
@@ -73,9 +73,9 @@ def to_tuple(self) -> Tuple[str, Type, dataclasses.Field]:
def get_default_factory_for_field(
field: CompatModelField,
+ compat: PydanticCompat,
) -> Union[NoArgAnyCallable, dataclasses._MISSING_TYPE]:
- """
- Gets the default factory for a pydantic field.
+ """Gets the default factory for a pydantic field.
Handles mutable defaults when making the dataclass by
using pydantic's smart_deepcopy
@@ -83,12 +83,8 @@ def get_default_factory_for_field(
Returns optionally a NoArgAnyCallable representing a default_factory parameter
"""
# replace dataclasses.MISSING with our own UNSET to make comparisons easier
- default_factory = (
- field.default_factory
- if field.default_factory is not PYDANTIC_MISSING_TYPE
- else UNSET
- )
- default = field.default if field.default is not PYDANTIC_MISSING_TYPE else UNSET
+ default_factory = field.default_factory if field.has_default_factory else UNSET
+ default = field.default if field.has_default else UNSET
has_factory = default_factory is not None and default_factory is not UNSET
has_default = default is not None and default is not UNSET
@@ -110,9 +106,14 @@ def get_default_factory_for_field(
return default_factory
# if we have a default, we should return it
-
if has_default:
- return lambda: smart_deepcopy(default)
+ # if the default value is a pydantic base model
+ # we should return the serialized version of that default for
+ # printing the value.
+ if isinstance(default, BaseModel):
+ return lambda: compat.model_dump(default)
+ else:
+ return lambda: smart_deepcopy(default)
# if we don't have default or default_factory, but the field is not required,
# we should return a factory that returns None
@@ -126,8 +127,9 @@ def get_default_factory_for_field(
def ensure_all_auto_fields_in_pydantic(
model: Type[BaseModel], auto_fields: Set[str], cls_name: str
) -> None:
+ compat = PydanticCompat.from_model(model)
# Raise error if user defined a strawberry.auto field not present in the model
- non_existing_fields = list(auto_fields - get_model_fields(model).keys())
+ non_existing_fields = list(auto_fields - compat.get_model_fields(model).keys())
if non_existing_fields:
raise AutoFieldsNotInBaseModelError(
diff --git a/strawberry/ext/dataclasses/dataclasses.py b/strawberry/ext/dataclasses/dataclasses.py
index 5667c5c4c3..820649c18d 100644
--- a/strawberry/ext/dataclasses/dataclasses.py
+++ b/strawberry/ext/dataclasses/dataclasses.py
@@ -21,7 +21,8 @@ def dataclass_init_fn(
self_name: str,
globals_: Dict[str, Any],
) -> Any:
- """
+ """Create an __init__ function for a dataclass.
+
We create a custom __init__ function for the dataclasses that back
Strawberry object types to only accept keyword arguments. This allows us to
avoid the problem where a type cannot define a field with a default value
diff --git a/strawberry/ext/mypy_plugin.py b/strawberry/ext/mypy_plugin.py
index 3867ffebef..5d5c3d08ef 100644
--- a/strawberry/ext/mypy_plugin.py
+++ b/strawberry/ext/mypy_plugin.py
@@ -11,6 +11,7 @@
List,
Optional,
Set,
+ Tuple,
Union,
cast,
)
@@ -59,11 +60,16 @@
except ImportError:
TypeVarDef = TypeVarType
+PYDANTIC_VERSION: Optional[Tuple[int, ...]] = None
+
# To be compatible with user who don't use pydantic
try:
+ import pydantic
from pydantic.mypy import METADATA_KEY as PYDANTIC_METADATA_KEY
from pydantic.mypy import PydanticModelField
+ PYDANTIC_VERSION = tuple(map(int, pydantic.__version__.split(".")))
+
from strawberry.experimental.pydantic._compat import IS_PYDANTIC_V1
except ImportError:
PYDANTIC_METADATA_KEY = ""
@@ -86,7 +92,7 @@
class MypyVersion:
- """Stores the mypy version to be used by the plugin"""
+ """Stores the mypy version to be used by the plugin."""
VERSION: Decimal
@@ -113,7 +119,7 @@ def lazy_type_analyze_callback(ctx: AnalyzeTypeContext) -> Type:
return type_
-def _get_named_type(name: str, api: SemanticAnalyzerPluginInterface):
+def _get_named_type(name: str, api: SemanticAnalyzerPluginInterface) -> Any:
if "." in name:
return api.named_type_or_none(name)
@@ -324,9 +330,11 @@ def add_static_method_to_class(
return_type: Type,
tvar_def: Optional[TypeVarType] = None,
) -> None:
- """Adds a static method
- Edited add_method_to_class to incorporate static method logic
- https://github.com/python/mypy/blob/9c05d3d19/mypy/plugins/common.py
+ """Adds a static method.
+
+ Edited `add_method_to_class` to incorporate static method logic
+
+ https://github.com/python/mypy/blob/9c05d3d19/mypy/plugins/common.py.
"""
info = cls.info
@@ -464,6 +472,16 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
return_type=model_type,
)
else:
+ extra = {}
+
+ if PYDANTIC_VERSION:
+ if PYDANTIC_VERSION >= (2, 7, 0):
+ extra["api"] = ctx.api
+ if PYDANTIC_VERSION >= (2, 8, 0):
+ # Based on pydantic's default value
+ # https://github.com/pydantic/pydantic/pull/9606/files#diff-469037bbe55bbf9aa359480a16040d368c676adad736e133fb07e5e20d6ac523R1066
+ extra["force_typevars_invariant"] = False
+
add_method(
ctx,
"to_pydantic",
@@ -474,6 +492,7 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
typed=True,
force_optional=False,
use_alias=True,
+ **extra,
)
for f in missing_pydantic_fields
],
@@ -487,12 +506,23 @@ def strawberry_pydantic_class_callback(ctx: ClassDefContext) -> None:
initializer=None,
kind=ARG_OPT,
)
+ extra_type = ctx.api.named_type(
+ "builtins.dict",
+ [ctx.api.named_type("builtins.str"), AnyType(TypeOfAny.explicit)],
+ )
+
+ extra_argument = Argument(
+ variable=Var(name="extra", type=UnionType([NoneType(), extra_type])),
+ type_annotation=UnionType([NoneType(), extra_type]),
+ initializer=None,
+ kind=ARG_OPT,
+ )
add_static_method_to_class(
ctx.api,
ctx.cls,
name="from_pydantic",
- args=[model_argument],
+ args=[model_argument, extra_argument],
return_type=fill_typevars(ctx.cls.info),
)
@@ -532,22 +562,22 @@ def get_class_decorator_hook(
return None
def _is_strawberry_union(self, fullname: str) -> bool:
- return fullname == "strawberry.union.union" or fullname.endswith(
+ return fullname == "strawberry.types.union.union" or fullname.endswith(
"strawberry.union"
)
def _is_strawberry_enum(self, fullname: str) -> bool:
- return fullname == "strawberry.enum.enum" or fullname.endswith(
+ return fullname == "strawberry.types.enum.enum" or fullname.endswith(
"strawberry.enum"
)
def _is_strawberry_scalar(self, fullname: str) -> bool:
- return fullname == "strawberry.custom_scalar.scalar" or fullname.endswith(
+ return fullname == "strawberry.types.scalar.scalar" or fullname.endswith(
"strawberry.scalar"
)
def _is_strawberry_lazy_type(self, fullname: str) -> bool:
- return fullname == "strawberry.lazy_type.LazyType"
+ return fullname == "strawberry.types.lazy_type.LazyType"
def _is_strawberry_create_type(self, fullname: str) -> bool:
# using endswith(.create_type) is not ideal as there might be
diff --git a/strawberry/extensions/__init__.py b/strawberry/extensions/__init__.py
index 04024eabf9..a94f69651b 100644
--- a/strawberry/extensions/__init__.py
+++ b/strawberry/extensions/__init__.py
@@ -1,4 +1,5 @@
import warnings
+from typing import Type
from .add_validation_rules import AddValidationRules
from .base_extension import LifecycleStep, SchemaExtension
@@ -12,7 +13,7 @@
from .validation_cache import ValidationCache
-def __getattr__(name: str):
+def __getattr__(name: str) -> Type[SchemaExtension]:
if name == "Extension":
warnings.warn(
(
diff --git a/strawberry/extensions/add_validation_rules.py b/strawberry/extensions/add_validation_rules.py
index 188ef037d6..763ef70b05 100644
--- a/strawberry/extensions/add_validation_rules.py
+++ b/strawberry/extensions/add_validation_rules.py
@@ -9,36 +9,37 @@
class AddValidationRules(SchemaExtension):
- """
- Add graphql-core validation rules
+ """Add graphql-core validation rules.
Example:
-
- >>> import strawberry
- >>> from strawberry.extensions import AddValidationRules
- >>> from graphql import ValidationRule, GraphQLError
- >>>
- >>> class MyCustomRule(ValidationRule):
- ... def enter_field(self, node, *args) -> None:
- ... if node.name.value == "secret_field":
- ... self.report_error(
- ... GraphQLError("Can't query field 'secret_field'")
- ... )
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... AddValidationRules([
- ... MyCustomRule,
- ... ]),
- ... ]
- ... )
-
+ ```python
+ import strawberry
+ from strawberry.extensions import AddValidationRules
+ from graphql import ValidationRule, GraphQLError
+
+
+ class MyCustomRule(ValidationRule):
+ def enter_field(self, node, *args) -> None:
+ if node.name.value == "secret_field":
+ self.report_error(GraphQLError("Can't query field 'secret_field'"))
+
+
+ schema = strawberry.Schema(
+ Query,
+ extensions=[
+ AddValidationRules(
+ [
+ MyCustomRule,
+ ]
+ ),
+ ],
+ )
+ ```
"""
validation_rules: List[Type[ASTValidationRule]]
- def __init__(self, validation_rules: List[Type[ASTValidationRule]]):
+ def __init__(self, validation_rules: List[Type[ASTValidationRule]]) -> None:
self.validation_rules = validation_rules
def on_operation(self) -> Iterator[None]:
@@ -46,3 +47,6 @@ def on_operation(self) -> Iterator[None]:
self.execution_context.validation_rules + tuple(self.validation_rules)
)
yield
+
+
+__all__ = ["AddValidationRules"]
diff --git a/strawberry/extensions/base_extension.py b/strawberry/extensions/base_extension.py
index f10b7db797..92160315f0 100644
--- a/strawberry/extensions/base_extension.py
+++ b/strawberry/extensions/base_extension.py
@@ -21,31 +21,31 @@ class LifecycleStep(Enum):
class SchemaExtension:
execution_context: ExecutionContext
- def __init__(self, *, execution_context: ExecutionContext):
+ def __init__(self, *, execution_context: ExecutionContext) -> None:
self.execution_context = execution_context
- def on_operation(
+ def on_operation( # type: ignore
self,
- ) -> AsyncIteratorOrIterator[None]: # pragma: no cover # pyright: ignore
- """Called before and after a GraphQL operation (query / mutation) starts"""
+ ) -> AsyncIteratorOrIterator[None]: # pragma: no cover
+ """Called before and after a GraphQL operation (query / mutation) starts."""
yield None
- def on_validate(
+ def on_validate( # type: ignore
self,
- ) -> AsyncIteratorOrIterator[None]: # pragma: no cover # pyright: ignore
- """Called before and after the validation step"""
+ ) -> AsyncIteratorOrIterator[None]: # pragma: no cover
+ """Called before and after the validation step."""
yield None
- def on_parse(
+ def on_parse( # type: ignore
self,
- ) -> AsyncIteratorOrIterator[None]: # pragma: no cover # pyright: ignore
- """Called before and after the parsing step"""
+ ) -> AsyncIteratorOrIterator[None]: # pragma: no cover
+ """Called before and after the parsing step."""
yield None
- def on_execute(
+ def on_execute( # type: ignore
self,
- ) -> AsyncIteratorOrIterator[None]: # pragma: no cover # pyright: ignore
- """Called before and after the execution step"""
+ ) -> AsyncIteratorOrIterator[None]: # pragma: no cover
+ """Called before and after the execution step."""
yield None
def resolve(
@@ -70,3 +70,5 @@ def get_results(self) -> AwaitableOrValue[Dict[str, Any]]:
SchemaExtension.on_parse.__name__,
SchemaExtension.on_execute.__name__,
}
+
+__all__ = ["SchemaExtension", "Hook", "HOOK_METHODS", "LifecycleStep"]
diff --git a/strawberry/extensions/context.py b/strawberry/extensions/context.py
index f2ef681562..f9a231fed4 100644
--- a/strawberry/extensions/context.py
+++ b/strawberry/extensions/context.py
@@ -2,13 +2,16 @@
import contextlib
import inspect
+import types
import warnings
from asyncio import iscoroutinefunction
from typing import (
TYPE_CHECKING,
Any,
+ AsyncContextManager,
AsyncIterator,
Callable,
+ ContextManager,
Iterator,
List,
NamedTuple,
@@ -28,14 +31,20 @@
class WrappedHook(NamedTuple):
extension: SchemaExtension
- initialized_hook: Union[AsyncIterator[None], Iterator[None]]
+ hook: Callable[..., Union[AsyncContextManager[None], ContextManager[None]]]
is_async: bool
class ExtensionContextManagerBase:
- __slots__ = ("hooks", "deprecation_message", "default_hook")
-
- def __init_subclass__(cls):
+ __slots__ = (
+ "hooks",
+ "deprecation_message",
+ "default_hook",
+ "async_exit_stack",
+ "exit_stack",
+ )
+
+ def __init_subclass__(cls) -> None:
cls.DEPRECATION_MESSAGE = (
f"Event driven styled extensions for "
f"{cls.LEGACY_ENTER} or {cls.LEGACY_EXIT}"
@@ -47,7 +56,7 @@ def __init_subclass__(cls):
LEGACY_ENTER: str
LEGACY_EXIT: str
- def __init__(self, extensions: List[SchemaExtension]):
+ def __init__(self, extensions: List[SchemaExtension]) -> None:
self.hooks: List[WrappedHook] = []
self.default_hook: Hook = getattr(SchemaExtension, self.HOOK_NAME)
for extension in extensions:
@@ -73,10 +82,20 @@ def get_hook(self, extension: SchemaExtension) -> Optional[WrappedHook]:
if hook_fn:
if inspect.isgeneratorfunction(hook_fn):
- return WrappedHook(extension, hook_fn(extension), False)
+ context_manager = contextlib.contextmanager(
+ types.MethodType(hook_fn, extension)
+ )
+ return WrappedHook(
+ extension=extension, hook=context_manager, is_async=False
+ )
if inspect.isasyncgenfunction(hook_fn):
- return WrappedHook(extension, hook_fn(extension), True)
+ context_manager_async = contextlib.asynccontextmanager(
+ types.MethodType(hook_fn, extension)
+ )
+ return WrappedHook(
+ extension=extension, hook=context_manager_async, is_async=True
+ )
if callable(hook_fn):
return self.from_callable(extension, hook_fn)
@@ -96,27 +115,31 @@ def from_legacy(
) -> WrappedHook:
if iscoroutinefunction(on_start) or iscoroutinefunction(on_end):
- async def iterator():
+ @contextlib.asynccontextmanager
+ async def iterator() -> AsyncIterator:
if on_start:
await await_maybe(on_start())
+
yield
+
if on_end:
await await_maybe(on_end())
- hook = iterator()
- return WrappedHook(extension, hook, True)
+ return WrappedHook(extension=extension, hook=iterator, is_async=True)
else:
- def iterator():
+ @contextlib.contextmanager
+ def iterator_async() -> Iterator[None]:
if on_start:
on_start()
+
yield
+
if on_end:
on_end()
- hook = iterator()
- return WrappedHook(extension, hook, False)
+ return WrappedHook(extension=extension, hook=iterator_async, is_async=False)
@staticmethod
def from_callable(
@@ -125,78 +148,61 @@ def from_callable(
) -> WrappedHook:
if iscoroutinefunction(func):
- async def async_iterator():
+ @contextlib.asynccontextmanager
+ async def iterator() -> AsyncIterator[None]:
await func(extension)
yield
- hook = async_iterator()
- return WrappedHook(extension, hook, True)
+ return WrappedHook(extension=extension, hook=iterator, is_async=True)
else:
- def iterator():
+ @contextlib.contextmanager
+ def iterator() -> Iterator[None]:
func(extension)
yield
- hook = iterator()
- return WrappedHook(extension, hook, False)
+ return WrappedHook(extension=extension, hook=iterator, is_async=False)
- def run_hooks_sync(self, is_exit: bool = False) -> None:
- """Run extensions synchronously."""
- ctx = (
- contextlib.suppress(StopIteration, StopAsyncIteration)
- if is_exit
- else contextlib.nullcontext()
- )
- for hook in self.hooks:
- with ctx:
- if hook.is_async:
- raise RuntimeError(
- f"SchemaExtension hook {hook.extension}.{self.HOOK_NAME} "
- "failed to complete synchronously."
- )
- else:
- hook.initialized_hook.__next__() # type: ignore[union-attr]
-
- async def run_hooks_async(self, is_exit: bool = False) -> None:
- """Run extensions asynchronously with support for sync lifecycle hooks.
-
- The ``is_exit`` flag is required as a `StopIteration` cannot be raised from
- within a coroutine.
- """
- ctx = (
- contextlib.suppress(StopIteration, StopAsyncIteration)
- if is_exit
- else contextlib.nullcontext()
- )
+ def __enter__(self) -> None:
+ self.exit_stack = contextlib.ExitStack()
- for hook in self.hooks:
- with ctx:
- if hook.is_async:
- await hook.initialized_hook.__anext__() # type: ignore[union-attr]
- else:
- hook.initialized_hook.__next__() # type: ignore[union-attr]
+ self.exit_stack.__enter__()
- def __enter__(self):
- self.run_hooks_sync()
+ for hook in self.hooks:
+ if hook.is_async:
+ raise RuntimeError(
+ f"SchemaExtension hook {hook.extension}.{self.HOOK_NAME} "
+ "failed to complete synchronously."
+ )
+ else:
+ self.exit_stack.enter_context(hook.hook()) # type: ignore
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
- ):
- self.run_hooks_sync(is_exit=True)
+ ) -> None:
+ self.exit_stack.__exit__(exc_type, exc_val, exc_tb)
- async def __aenter__(self):
- await self.run_hooks_async()
+ async def __aenter__(self) -> None:
+ self.async_exit_stack = contextlib.AsyncExitStack()
+
+ await self.async_exit_stack.__aenter__()
+
+ for hook in self.hooks:
+ if hook.is_async:
+ await self.async_exit_stack.enter_async_context(hook.hook()) # type: ignore
+ else:
+ self.async_exit_stack.enter_context(hook.hook()) # type: ignore
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
- ):
- await self.run_hooks_async(is_exit=True)
+ ) -> None:
+ await self.async_exit_stack.__aexit__(exc_type, exc_val, exc_tb)
class OperationContextManager(ExtensionContextManagerBase):
diff --git a/strawberry/extensions/directives.py b/strawberry/extensions/directives.py
index 87a4ce5d5a..b72923adda 100644
--- a/strawberry/extensions/directives.py
+++ b/strawberry/extensions/directives.py
@@ -11,8 +11,8 @@
from graphql import DirectiveNode, GraphQLResolveInfo
from strawberry.directive import StrawberryDirective
- from strawberry.field import StrawberryField
from strawberry.schema.schema import Schema
+ from strawberry.types.field import StrawberryField
from strawberry.utils.await_maybe import AwaitableOrValue
@@ -85,3 +85,6 @@ def process_directive(
if value_parameter:
arguments[value_parameter.name] = value
return strawberry_directive, arguments
+
+
+__all__ = ["DirectivesExtension", "DirectivesExtensionSync"]
diff --git a/strawberry/extensions/disable_validation.py b/strawberry/extensions/disable_validation.py
index 4c344ae981..cd9aeafaed 100644
--- a/strawberry/extensions/disable_validation.py
+++ b/strawberry/extensions/disable_validation.py
@@ -4,24 +4,24 @@
class DisableValidation(SchemaExtension):
- """
- Disable query validation
+ """Disable query validation.
Example:
- >>> import strawberry
- >>> from strawberry.extensions import DisableValidation
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... DisableValidation,
- ... ]
- ... )
-
+ ```python
+ import strawberry
+ from strawberry.extensions import DisableValidation
+
+ schema = strawberry.Schema(
+ Query,
+ extensions=[
+ DisableValidation,
+ ],
+ )
+ ```
"""
- def __init__(self):
+ def __init__(self) -> None:
# There aren't any arguments to this extension yet but we might add
# some in the future
pass
@@ -29,3 +29,6 @@ def __init__(self):
def on_operation(self) -> Iterator[None]:
self.execution_context.validation_rules = () # remove all validation_rules
yield
+
+
+__all__ = ["DisableValidation"]
diff --git a/strawberry/extensions/field_extension.py b/strawberry/extensions/field_extension.py
index 15786f4e8d..afb70e53ab 100644
--- a/strawberry/extensions/field_extension.py
+++ b/strawberry/extensions/field_extension.py
@@ -7,8 +7,8 @@
if TYPE_CHECKING:
from typing_extensions import TypeAlias
- from strawberry.field import StrawberryField
from strawberry.types import Info
+ from strawberry.types.field import StrawberryField
SyncExtensionResolver: TypeAlias = Callable[..., Any]
@@ -44,7 +44,9 @@ def supports_async(self) -> bool:
class SyncToAsyncExtension(FieldExtension):
"""Helper class for mixing async extensions with sync resolvers.
- Applied automatically"""
+
+ Applied automatically.
+ """
async def resolve_async(
self, next_: AsyncExtensionResolver, source: Any, info: Info, **kwargs: Any
@@ -67,11 +69,12 @@ def _get_async_resolvers(
def build_field_extension_resolvers(
field: StrawberryField,
) -> list[Union[SyncExtensionResolver, AsyncExtensionResolver]]:
- """
+ """Builds a list of resolvers for a field with extensions.
+
Verifies that all of the field extensions for a given field support
sync or async depending on the field resolver.
- Inserts a SyncToAsyncExtension to be able to
- use Async extensions on sync resolvers
+
+ Inserts a SyncToAsyncExtension to be able to use Async extensions on sync resolvers
Throws a TypeError otherwise.
Returns True if resolving should be async, False on sync resolving
@@ -151,3 +154,6 @@ def build_field_extension_resolvers(
f"If possible try to change the execution order so that all sync-only "
f"extensions are executed first."
)
+
+
+__all__ = ["FieldExtension"]
diff --git a/strawberry/extensions/mask_errors.py b/strawberry/extensions/mask_errors.py
index a862187808..5cb0ffbaa7 100644
--- a/strawberry/extensions/mask_errors.py
+++ b/strawberry/extensions/mask_errors.py
@@ -18,7 +18,7 @@ def __init__(
self,
should_mask_error: Callable[[GraphQLError], bool] = default_should_mask_error,
error_message: str = "Unexpected error.",
- ):
+ ) -> None:
self.should_mask_error = should_mask_error
self.error_message = error_message
@@ -44,3 +44,6 @@ def on_operation(self) -> Iterator[None]:
processed_errors.append(error)
result.errors = processed_errors
+
+
+__all__ = ["MaskErrors"]
diff --git a/strawberry/extensions/max_aliases.py b/strawberry/extensions/max_aliases.py
index 77e55dc1f9..b1d0aba268 100644
--- a/strawberry/extensions/max_aliases.py
+++ b/strawberry/extensions/max_aliases.py
@@ -13,38 +13,37 @@
class MaxAliasesLimiter(AddValidationRules):
- """
- Add a validator to limit the number of aliases used.
+ """Add a validator to limit the number of aliases used.
Example:
- >>> import strawberry
- >>> from strawberry.extensions import MaxAliasesLimiter
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... MaxAliasesLimiter(max_alias_count=15)
- ... ]
- ... )
-
- Arguments:
+ ```python
+ import strawberry
+ from strawberry.extensions import MaxAliasesLimiter
- `max_alias_count: int`
- The maximum number of aliases allowed in a GraphQL document.
+ schema = strawberry.Schema(Query, extensions=[MaxAliasesLimiter(max_alias_count=15)])
+ ```
"""
- def __init__(
- self,
- max_alias_count: int,
- ):
+ def __init__(self, max_alias_count: int) -> None:
+ """Initialize the MaxAliasesLimiter.
+
+ Args:
+ max_alias_count: The maximum number of aliases allowed in a GraphQL document.
+ """
validator = create_validator(max_alias_count)
super().__init__([validator])
def create_validator(max_alias_count: int) -> Type[ValidationRule]:
+ """Create a validator that checks the number of aliases in a document.
+
+ Args:
+ max_alias_count: The maximum number of aliases allowed in a GraphQL document.
+ """
+
class MaxAliasesValidator(ValidationRule):
- def __init__(self, validation_context: ValidationContext):
+ def __init__(self, validation_context: ValidationContext) -> None:
document = validation_context.document
def_that_can_contain_alias = (
def_
@@ -82,3 +81,6 @@ def count_fields_with_alias(
result += count_fields_with_alias(selection)
return result
+
+
+__all__ = ["MaxAliasesLimiter"]
diff --git a/strawberry/extensions/max_tokens.py b/strawberry/extensions/max_tokens.py
index cfb902f42a..60accd8a8a 100644
--- a/strawberry/extensions/max_tokens.py
+++ b/strawberry/extensions/max_tokens.py
@@ -4,25 +4,15 @@
class MaxTokensLimiter(SchemaExtension):
- """
- Add a validator to limit the number of tokens in a GraphQL document.
+ """Add a validator to limit the number of tokens in a GraphQL document.
Example:
+ ```python
+ import strawberry
+ from strawberry.extensions import MaxTokensLimiter
- >>> import strawberry
- >>> from strawberry.extensions import MaxTokensLimiter
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... MaxTokensLimiter(max_token_count=1000)
- ... ]
- ... )
-
- Arguments:
-
- `max_token_count: int`
- The maximum number of tokens allowed in a GraphQL document.
+ schema = strawberry.Schema(Query, extensions=[MaxTokensLimiter(max_token_count=1000)])
+ ```
The following things are counted as tokens:
* various brackets: "{", "}", "(", ")"
@@ -36,9 +26,17 @@ class MaxTokensLimiter(SchemaExtension):
def __init__(
self,
max_token_count: int,
- ):
+ ) -> None:
+ """Initialize the MaxTokensLimiter.
+
+ Args:
+ max_token_count: The maximum number of tokens allowed in a GraphQL document.
+ """
self.max_token_count = max_token_count
def on_operation(self) -> Iterator[None]:
self.execution_context.parse_options["max_tokens"] = self.max_token_count
yield
+
+
+__all__ = ["MaxTokensLimiter"]
diff --git a/strawberry/extensions/parser_cache.py b/strawberry/extensions/parser_cache.py
index 648b0364e0..39b28c039b 100644
--- a/strawberry/extensions/parser_cache.py
+++ b/strawberry/extensions/parser_cache.py
@@ -6,31 +6,31 @@
class ParserCache(SchemaExtension):
- """
- Add LRU caching the parsing step during execution to improve performance.
+ """Add LRU caching the parsing step during execution to improve performance.
Example:
- >>> import strawberry
- >>> from strawberry.extensions import ParserCache
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... ParserCache(maxsize=100),
- ... ]
- ... )
-
- Arguments:
-
- `maxsize: Optional[int]`
- Set the maxsize of the cache. If `maxsize` is set to `None` then the
- cache will grow without bound.
- More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
-
+ ```python
+ import strawberry
+ from strawberry.extensions import ParserCache
+
+ schema = strawberry.Schema(
+ Query,
+ extensions=[
+ ParserCache(maxsize=100),
+ ],
+ )
+ ```
"""
- def __init__(self, maxsize: Optional[int] = None):
+ def __init__(self, maxsize: Optional[int] = None) -> None:
+ """Initialize the ParserCache.
+
+ Args:
+ maxsize: Set the maxsize of the cache. If `maxsize` is set to `None` then the
+ cache will grow without bound.
+ More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
+ """
self.cached_parse_document = lru_cache(maxsize=maxsize)(parse_document)
def on_parse(self) -> Iterator[None]:
@@ -40,3 +40,6 @@ def on_parse(self) -> Iterator[None]:
execution_context.query, **execution_context.parse_options
)
yield
+
+
+__all__ = ["ParserCache"]
diff --git a/strawberry/extensions/pyinstrument.py b/strawberry/extensions/pyinstrument.py
index 44735c91ce..53dd9fe66a 100644
--- a/strawberry/extensions/pyinstrument.py
+++ b/strawberry/extensions/pyinstrument.py
@@ -9,9 +9,7 @@
class PyInstrument(SchemaExtension):
- """
- Extension to profile the execution time of resolvers using PyInstrument.
- """
+ """Extension to profile the execution time of resolvers using PyInstrument."""
def __init__(
self,
@@ -20,11 +18,6 @@ def __init__(
self._report_path = report_path
def on_operation(self) -> Iterator[None]:
- """
- Called when an operation is started,
- in this case we start the profiler and yield
- then we stop the profiler when the operation is done
- """
profiler = Profiler()
profiler.start()
@@ -33,3 +26,6 @@ def on_operation(self) -> Iterator[None]:
profiler.stop()
Path(self._report_path, encoding="utf-8").write_text(profiler.output_html())
+
+
+__all__ = ["PyInstrument"]
diff --git a/strawberry/extensions/query_depth_limiter.py b/strawberry/extensions/query_depth_limiter.py
index 7ffda20d56..f23831a00b 100644
--- a/strawberry/extensions/query_depth_limiter.py
+++ b/strawberry/extensions/query_depth_limiter.py
@@ -81,32 +81,19 @@ class IgnoreContext:
class QueryDepthLimiter(AddValidationRules):
- """
- Add a validator to limit the query depth of GraphQL operations
+ """Add a validator to limit the query depth of GraphQL operations.
Example:
- >>> import strawberry
- >>> from strawberry.extensions import QueryDepthLimiter
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... QueryDepthLimiter(max_depth=4)
- ... ]
- ... )
-
- Arguments:
-
- `max_depth: int`
- The maximum allowed depth for any operation in a GraphQL document.
- `callback: Optional[Callable[[Dict[str, int]], None]`
- Called each time validation runs. Receives an Object which is a
- map of the depths for each operation.
- `should_ignore: Optional[ShouldIgnoreType]`
- Stops recursive depth checking based on a field name and arguments.
- A function that returns a boolean and conforms to the ShouldIgnoreType
- function signature.
+ ```python
+ import strawberry
+ from strawberry.extensions import QueryDepthLimiter
+
+ schema = strawberry.Schema(
+ Query,
+ extensions=[QueryDepthLimiter(max_depth=4)],
+ )
+ ```
"""
def __init__(
@@ -114,7 +101,16 @@ def __init__(
max_depth: int,
callback: Optional[Callable[[Dict[str, int]], None]] = None,
should_ignore: Optional[ShouldIgnoreType] = None,
- ):
+ ) -> None:
+ """Initialize the QueryDepthLimiter.
+
+ Args:
+ max_depth: The maximum allowed depth for any operation in a GraphQL document.
+ callback: Called each time validation runs.
+ Receives an Object which is a map of the depths for each operation.
+ should_ignore: Stops recursive depth checking based on a field name and arguments.
+ A function that returns a boolean and conforms to the ShouldIgnoreType function signature.
+ """
if should_ignore is not None and not callable(should_ignore):
raise TypeError(
"The `should_ignore` argument to "
@@ -130,7 +126,7 @@ def create_validator(
callback: Optional[Callable[[Dict[str, int]], None]] = None,
) -> Type[ValidationRule]:
class DepthLimitValidator(ValidationRule):
- def __init__(self, validation_context: ValidationContext):
+ def __init__(self, validation_context: ValidationContext) -> None:
document = validation_context.document
definitions = document.definitions
@@ -317,3 +313,6 @@ def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bo
raise TypeError(f"Invalid ignore option: {rule}")
return False
+
+
+__all__ = ["QueryDepthLimiter"]
diff --git a/strawberry/extensions/runner.py b/strawberry/extensions/runner.py
index 902ef7aa4e..44d12ac009 100644
--- a/strawberry/extensions/runner.py
+++ b/strawberry/extensions/runner.py
@@ -28,7 +28,7 @@ def __init__(
extensions: Optional[
List[Union[Type[SchemaExtension], SchemaExtension]]
] = None,
- ):
+ ) -> None:
self.execution_context = execution_context
if not extensions:
@@ -84,3 +84,6 @@ def as_middleware_manager(self, *additional_middlewares: Any) -> MiddlewareManag
middlewares = tuple(self.extensions) + additional_middlewares
return MiddlewareManager(*middlewares)
+
+
+__all__ = ["SchemaExtensionsRunner"]
diff --git a/strawberry/extensions/tracing/__init__.py b/strawberry/extensions/tracing/__init__.py
index 40d735f130..772b18dbf0 100644
--- a/strawberry/extensions/tracing/__init__.py
+++ b/strawberry/extensions/tracing/__init__.py
@@ -1,5 +1,5 @@
import importlib
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from .apollo import ApolloTracingExtension, ApolloTracingExtensionSync
@@ -22,7 +22,7 @@
]
-def __getattr__(name: str):
+def __getattr__(name: str) -> Any:
if name in {"DatadogTracingExtension", "DatadogTracingExtensionSync"}:
return getattr(importlib.import_module(".datadog", __name__), name)
diff --git a/strawberry/extensions/tracing/apollo.py b/strawberry/extensions/tracing/apollo.py
index 9da0f8fccb..2245f54643 100644
--- a/strawberry/extensions/tracing/apollo.py
+++ b/strawberry/extensions/tracing/apollo.py
@@ -2,7 +2,7 @@
import dataclasses
import time
-from datetime import datetime
+from datetime import datetime, timezone
from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional
@@ -80,16 +80,16 @@ def to_json(self) -> Dict[str, Any]:
class ApolloTracingExtension(SchemaExtension):
- def __init__(self, execution_context: ExecutionContext):
+ def __init__(self, execution_context: ExecutionContext) -> None:
self._resolver_stats: List[ApolloResolverStats] = []
self.execution_context = execution_context
def on_operation(self) -> Generator[None, None, None]:
self.start_timestamp = self.now()
- self.start_time = datetime.utcnow()
+ self.start_time = datetime.now(timezone.utc)
yield
self.end_timestamp = self.now()
- self.end_time = datetime.utcnow()
+ self.end_time = datetime.now(timezone.utc)
def on_parse(self) -> Generator[None, None, None]:
self._start_parsing = self.now()
@@ -191,3 +191,6 @@ def resolve(
end_timestamp = self.now()
resolver_stats.duration = end_timestamp - start_timestamp
self._resolver_stats.append(resolver_stats)
+
+
+__all__ = ["ApolloTracingExtension", "ApolloTracingExtensionSync"]
diff --git a/strawberry/extensions/tracing/datadog.py b/strawberry/extensions/tracing/datadog.py
index b026a007fd..2b8c676ca2 100644
--- a/strawberry/extensions/tracing/datadog.py
+++ b/strawberry/extensions/tracing/datadog.py
@@ -21,13 +21,14 @@ def __init__(
self,
*,
execution_context: Optional[ExecutionContext] = None,
- ):
+ ) -> None:
if execution_context:
self.execution_context = execution_context
@cached_property
- def _resource_name(self):
- assert self.execution_context.query
+ def _resource_name(self) -> str:
+ if self.execution_context.query is None:
+ return "query_missing"
query_hash = self.hash_query(self.execution_context.query)
@@ -42,18 +43,20 @@ def create_span(
name: str,
**kwargs: Any,
) -> Span:
- """
- Create a span with the given name and kwargs.
+ """Create a span with the given name and kwargs.
+
You can override this if you want to add more tags to the span.
Example:
+ ```python
class CustomExtension(DatadogTracingExtension):
def create_span(self, lifecycle_step, name, **kwargs):
span = super().create_span(lifecycle_step, name, **kwargs)
if lifecycle_step == LifeCycleStep.OPERATION:
span.set_tag("graphql.query", self.execution_context.query)
return span
+ ```
"""
return tracer.trace(
name,
@@ -78,15 +81,23 @@ def on_operation(self) -> Iterator[None]:
)
self.request_span.set_tag("graphql.operation_name", self._operation_name)
- assert self.execution_context.query
+ query = self.execution_context.query
+
+ if query is not None:
+ query = query.strip()
+ operation_type = "query"
+
+ if query.startswith("mutation"):
+ operation_type = "mutation"
+ elif query.startswith("subscription"): # pragma: no cover
+ operation_type = "subscription"
+ else:
+ operation_type = "query_missing"
- operation_type = "query"
- if self.execution_context.query.strip().startswith("mutation"):
- operation_type = "mutation"
- elif self.execution_context.query.strip().startswith("subscription"):
- operation_type = "subscription"
self.request_span.set_tag("graphql.operation_type", operation_type)
+
yield
+
self.request_span.finish()
def on_validate(self) -> Generator[None, None, None]:
@@ -164,3 +175,6 @@ def resolve(
span.set_tag("graphql.path", ".".join(map(str, info.path.as_list())))
return _next(root, info, *args, **kwargs)
+
+
+__all__ = ["DatadogTracingExtension", "DatadogTracingExtensionSync"]
diff --git a/strawberry/extensions/tracing/opentelemetry.py b/strawberry/extensions/tracing/opentelemetry.py
index 8051d23bf9..285c2e5f92 100644
--- a/strawberry/extensions/tracing/opentelemetry.py
+++ b/strawberry/extensions/tracing/opentelemetry.py
@@ -45,7 +45,7 @@ def __init__(
*,
execution_context: Optional[ExecutionContext] = None,
arg_filter: Optional[ArgFilter] = None,
- ):
+ ) -> None:
self._arg_filter = arg_filter
self._tracer = trace.get_tracer("strawberry")
if execution_context:
@@ -208,3 +208,6 @@ def resolve(
result = _next(root, info, *args, **kwargs)
return result
+
+
+__all__ = ["OpenTelemetryExtension", "OpenTelemetryExtensionSync"]
diff --git a/strawberry/extensions/tracing/sentry.py b/strawberry/extensions/tracing/sentry.py
index db2f04c6d0..7a0c6188b4 100644
--- a/strawberry/extensions/tracing/sentry.py
+++ b/strawberry/extensions/tracing/sentry.py
@@ -22,9 +22,9 @@ def __init__(
self,
*,
execution_context: Optional[ExecutionContext] = None,
- ):
+ ) -> None:
warnings.warn(
- "The Sentry tracing extension is deprecated, please update to sentry>=1.32.0",
+ "The Sentry tracing extension is deprecated, please update to sentry-sdk>=1.32.0",
DeprecationWarning,
stacklevel=2,
)
@@ -33,7 +33,7 @@ def __init__(
self.execution_context = execution_context
@cached_property
- def _resource_name(self):
+ def _resource_name(self) -> str:
assert self.execution_context.query
query_hash = self.hash_query(self.execution_context.query)
@@ -156,3 +156,6 @@ def resolve(
span.set_tag("graphql.path", ".".join(map(str, info.path.as_list())))
return _next(root, info, *args, **kwargs)
+
+
+__all__ = ["SentryTracingExtension", "SentryTracingExtensionSync"]
diff --git a/strawberry/extensions/tracing/utils.py b/strawberry/extensions/tracing/utils.py
index 04029e996a..c9bd3bcfd5 100644
--- a/strawberry/extensions/tracing/utils.py
+++ b/strawberry/extensions/tracing/utils.py
@@ -18,3 +18,6 @@ def should_skip_tracing(resolver: Callable[..., Any], info: GraphQLResolveInfo)
or is_default_resolver(resolver)
or resolver is None
)
+
+
+__all__ = ["should_skip_tracing"]
diff --git a/strawberry/extensions/utils.py b/strawberry/extensions/utils.py
index d606db4ee8..f857db5eaf 100644
--- a/strawberry/extensions/utils.py
+++ b/strawberry/extensions/utils.py
@@ -35,3 +35,6 @@ def get_path_from_info(info: GraphQLResolveInfo) -> List[str]:
path = path.prev
return elements[::-1]
+
+
+__all__ = ["is_introspection_key", "is_introspection_field", "get_path_from_info"]
diff --git a/strawberry/extensions/validation_cache.py b/strawberry/extensions/validation_cache.py
index 005603be1e..6c9cc153c4 100644
--- a/strawberry/extensions/validation_cache.py
+++ b/strawberry/extensions/validation_cache.py
@@ -6,31 +6,31 @@
class ValidationCache(SchemaExtension):
- """
- Add LRU caching the validation step during execution to improve performance.
+ """Add LRU caching the validation step during execution to improve performance.
Example:
+ ```python
+ import strawberry
+ from strawberry.extensions import ValidationCache
+
+ schema = strawberry.Schema(
+ Query,
+ extensions=[
+ ValidationCache(maxsize=100),
+ ],
+ )
+ ```
+ """
- >>> import strawberry
- >>> from strawberry.extensions import ValidationCache
- >>>
- >>> schema = strawberry.Schema(
- ... Query,
- ... extensions=[
- ... ValidationCache(maxsize=100),
- ... ]
- ... )
-
- Arguments:
-
- `maxsize: Optional[int]`
- Set the maxsize of the cache. If `maxsize` is set to `None` then the
- cache will grow without bound.
- More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
+ def __init__(self, maxsize: Optional[int] = None) -> None:
+ """Initialize the ValidationCache.
- """
+ Args:
+ maxsize: Set the maxsize of the cache. If `maxsize` is set to `None` then the
+ cache will grow without bound.
- def __init__(self, maxsize: Optional[int] = None):
+ More info: https://docs.python.org/3/library/functools.html#functools.lru_cache
+ """
self.cached_validate_document = lru_cache(maxsize=maxsize)(validate_document)
def on_validate(self) -> Iterator[None]:
@@ -43,3 +43,6 @@ def on_validate(self) -> Iterator[None]:
)
execution_context.errors = errors
yield
+
+
+__all__ = ["ValidationCache"]
diff --git a/strawberry/fastapi/context.py b/strawberry/fastapi/context.py
index 6a23a0e648..d6af1642cd 100644
--- a/strawberry/fastapi/context.py
+++ b/strawberry/fastapi/context.py
@@ -18,3 +18,6 @@ def __init__(self) -> None:
self.request: Optional[Union[Request, WebSocket]] = None
self.background_tasks: Optional[BackgroundTasks] = None
self.response: Optional[Response] = None
+
+
+__all__ = ["BaseContext"]
diff --git a/strawberry/fastapi/handlers/graphql_transport_ws_handler.py b/strawberry/fastapi/handlers/graphql_transport_ws_handler.py
index 62ae43fd6e..817f6996ac 100644
--- a/strawberry/fastapi/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/fastapi/handlers/graphql_transport_ws_handler.py
@@ -15,3 +15,6 @@ async def get_context(self) -> Any:
async def get_root_value(self) -> Any:
return await self._get_root_value()
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/fastapi/handlers/graphql_ws_handler.py b/strawberry/fastapi/handlers/graphql_ws_handler.py
index 33bdb4d444..0c43bbbd6e 100644
--- a/strawberry/fastapi/handlers/graphql_ws_handler.py
+++ b/strawberry/fastapi/handlers/graphql_ws_handler.py
@@ -13,3 +13,6 @@ async def get_context(self) -> Any:
async def get_root_value(self) -> Any:
return await self._get_root_value()
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/fastapi/router.py b/strawberry/fastapi/router.py
index 85f6558585..b503d49af7 100644
--- a/strawberry/fastapi/router.py
+++ b/strawberry/fastapi/router.py
@@ -9,9 +9,11 @@
AsyncIterator,
Awaitable,
Callable,
- Mapping,
+ Dict,
+ List,
Optional,
Sequence,
+ Type,
Union,
cast,
)
@@ -21,30 +23,32 @@
from starlette.requests import HTTPConnection, Request
from starlette.responses import (
HTMLResponse,
+ JSONResponse,
PlainTextResponse,
Response,
StreamingResponse,
)
from starlette.websockets import WebSocket
-from fastapi import APIRouter, Depends
+from fastapi import APIRouter, Depends, params
+from fastapi.datastructures import Default
+from fastapi.routing import APIRoute
+from fastapi.utils import generate_unique_id
+from strawberry.asgi import ASGIRequestAdapter
from strawberry.exceptions import InvalidCustomContext
from strawberry.fastapi.context import BaseContext, CustomContext
from strawberry.fastapi.handlers import GraphQLTransportWSHandler, GraphQLWSHandler
-from strawberry.http import (
- process_result,
-)
-from strawberry.http.async_base_view import AsyncBaseHTTPView, AsyncHTTPRequestAdapter
+from strawberry.http import process_result
+from strawberry.http.async_base_view import AsyncBaseHTTPView
from strawberry.http.exceptions import HTTPException
-from strawberry.http.types import FormData, HTTPMethod, QueryParams
-from strawberry.http.typevars import (
- Context,
- RootValue,
-)
+from strawberry.http.typevars import Context, RootValue
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
if TYPE_CHECKING:
- from starlette.types import ASGIApp
+ from enum import Enum
+
+ from starlette.routing import BaseRoute
+ from starlette.types import ASGIApp, Lifespan
from strawberry.fastapi.context import MergedContext
from strawberry.http import GraphQLHTTPResponse
@@ -53,45 +57,16 @@
from strawberry.types import ExecutionResult
-class FastAPIRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: Request):
- self.request = request
-
- @property
- def query_params(self) -> QueryParams:
- return dict(self.request.query_params)
-
- @property
- def method(self) -> HTTPMethod:
- return cast(HTTPMethod, self.request.method.upper())
-
- @property
- def headers(self) -> Mapping[str, str]:
- return self.request.headers
-
- @property
- def content_type(self) -> Optional[str]:
- return self.request.headers.get("Content-Type", None)
-
- async def get_body(self) -> bytes:
- return await self.request.body()
-
- async def get_form_data(self) -> FormData:
- multipart_data = await self.request.form()
-
- return FormData(files=multipart_data, form=multipart_data)
-
-
class GraphQLRouter(
AsyncBaseHTTPView[Request, Response, Response, Context, RootValue], APIRouter
):
graphql_ws_handler_class = GraphQLWSHandler
graphql_transport_ws_handler_class = GraphQLTransportWSHandler
allow_queries_via_get = True
- request_adapter_class = FastAPIRequestAdapter
+ request_adapter_class = ASGIRequestAdapter
@staticmethod
- async def __get_root_value():
+ async def __get_root_value() -> None:
return None
@staticmethod
@@ -162,14 +137,46 @@ def __init__(
GRAPHQL_WS_PROTOCOL,
),
connection_init_wait_timeout: timedelta = timedelta(minutes=1),
+ prefix: str = "",
+ tags: Optional[List[Union[str, Enum]]] = None,
+ dependencies: Optional[Sequence[params.Depends]] = None,
+ default_response_class: Type[Response] = Default(JSONResponse),
+ responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
+ callbacks: Optional[List[BaseRoute]] = None,
+ routes: Optional[List[BaseRoute]] = None,
+ redirect_slashes: bool = True,
default: Optional[ASGIApp] = None,
+ dependency_overrides_provider: Optional[Any] = None,
+ route_class: Type[APIRoute] = APIRoute,
on_startup: Optional[Sequence[Callable[[], Any]]] = None,
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
- ):
+ lifespan: Optional[Lifespan[Any]] = None,
+ deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
+ **kwargs: Any,
+ ) -> None:
super().__init__(
+ prefix=prefix,
+ tags=tags,
+ dependencies=dependencies,
+ default_response_class=default_response_class,
+ responses=responses,
+ callbacks=callbacks,
+ routes=routes,
+ redirect_slashes=redirect_slashes,
default=default,
+ dependency_overrides_provider=dependency_overrides_provider,
+ route_class=route_class,
on_startup=on_startup,
on_shutdown=on_shutdown,
+ lifespan=lifespan,
+ deprecated=deprecated,
+ include_in_schema=include_in_schema,
+ generate_unique_id_function=generate_unique_id_function,
+ **kwargs,
)
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
@@ -251,11 +258,11 @@ async def websocket_endpoint( # pyright: ignore
websocket: WebSocket,
context: Context = Depends(self.context_getter),
root_value: RootValue = Depends(self.root_value_getter),
- ):
- async def _get_context():
+ ) -> None:
+ async def _get_context() -> Context:
return context
- async def _get_root_value():
+ async def _get_root_value() -> RootValue:
return root_value
preferred_protocol = self.pick_preferred_protocol(websocket)
@@ -337,3 +344,6 @@ async def create_multipart_response(
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
},
)
+
+
+__all__ = ["GraphQLRouter"]
diff --git a/strawberry/federation/argument.py b/strawberry/federation/argument.py
index ff2f4fc3b4..2268211a76 100644
--- a/strawberry/federation/argument.py
+++ b/strawberry/federation/argument.py
@@ -1,6 +1,6 @@
from typing import Iterable, Optional
-from strawberry.arguments import StrawberryArgumentAnnotation
+from strawberry.types.arguments import StrawberryArgumentAnnotation
def argument(
@@ -27,3 +27,6 @@ def argument(
deprecation_reason=deprecation_reason,
directives=directives,
)
+
+
+__all__ = ["argument"]
diff --git a/strawberry/federation/enum.py b/strawberry/federation/enum.py
index e703ecdd61..0fdebcfedb 100644
--- a/strawberry/federation/enum.py
+++ b/strawberry/federation/enum.py
@@ -1,9 +1,18 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Union, overload
-
-from strawberry.enum import _process_enum
-from strawberry.enum import enum_value as base_enum_value
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Iterable,
+ List,
+ Optional,
+ Union,
+ overload,
+)
+
+from strawberry.types.enum import _process_enum
+from strawberry.types.enum import enum_value as base_enum_value
if TYPE_CHECKING:
from strawberry.enum import EnumType, EnumValueDefinition
@@ -36,10 +45,12 @@ def enum(
name: Optional[str] = None,
description: Optional[str] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
-) -> EnumType:
- ...
+) -> EnumType: ...
@overload
@@ -49,10 +60,12 @@ def enum(
name: Optional[str] = None,
description: Optional[str] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
-) -> Callable[[EnumType], EnumType]:
- ...
+) -> Callable[[EnumType], EnumType]: ...
def enum(
@@ -61,22 +74,39 @@ def enum(
name=None,
description=None,
directives=(),
- inaccessible=False,
- tags=(),
+ authenticated: bool = False,
+ inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
+ tags: Optional[Iterable[str]] = (),
) -> Union[EnumType, Callable[[EnumType], EnumType]]:
"""Registers the enum in the GraphQL type system.
If name is passed, the name of the GraphQL type will be
the value passed of name instead of the Enum class name.
"""
-
- from strawberry.federation.schema_directives import Inaccessible, Tag
+ from strawberry.federation.schema_directives import (
+ Authenticated,
+ Inaccessible,
+ Policy,
+ RequiresScopes,
+ Tag,
+ )
directives = list(directives)
+ if authenticated:
+ directives.append(Authenticated())
+
if inaccessible:
directives.append(Inaccessible())
+ if policy:
+ directives.append(Policy(policies=policy))
+
+ if requires_scopes:
+ directives.append(RequiresScopes(scopes=requires_scopes))
+
if tags:
directives.extend(Tag(name=tag) for tag in tags)
@@ -87,3 +117,6 @@ def wrap(cls: EnumType) -> EnumType:
return wrap
return wrap(_cls) # pragma: no cover
+
+
+__all__ = ["enum", "enum_value"]
diff --git a/strawberry/federation/field.py b/strawberry/federation/field.py
index 806e6ae8bb..35d4f34b47 100644
--- a/strawberry/federation/field.py
+++ b/strawberry/federation/field.py
@@ -15,16 +15,17 @@
overload,
)
-from strawberry.field import field as base_field
-from strawberry.unset import UNSET
+from strawberry.types.field import field as base_field
+from strawberry.types.unset import UNSET
if TYPE_CHECKING:
from typing_extensions import Literal
from strawberry.extensions.field_extension import FieldExtension
- from strawberry.field import _RESOLVER_TYPE, StrawberryField
from strawberry.permission import BasePermission
+ from strawberry.types.field import _RESOLVER_TYPE, StrawberryField
+ from .schema_directives import Override
T = TypeVar("T")
@@ -36,13 +37,16 @@ def field(
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
+ authenticated: bool = False,
+ external: bool = False,
+ inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
provides: Optional[List[str]] = None,
+ override: Optional[Union[Override, str]] = None,
requires: Optional[List[str]] = None,
- external: bool = False,
- shareable: bool = False,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
- override: Optional[str] = None,
- inaccessible: bool = False,
+ shareable: bool = False,
init: Literal[False] = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
@@ -51,8 +55,7 @@ def field(
directives: Sequence[object] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> T:
- ...
+) -> T: ...
@overload
@@ -61,13 +64,16 @@ def field(
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
+ authenticated: bool = False,
+ external: bool = False,
+ inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
provides: Optional[List[str]] = None,
+ override: Optional[Union[Override, str]] = None,
requires: Optional[List[str]] = None,
- external: bool = False,
- shareable: bool = False,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
- override: Optional[str] = None,
- inaccessible: bool = False,
+ shareable: bool = False,
init: Literal[True] = True,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
@@ -76,8 +82,7 @@ def field(
directives: Sequence[object] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> Any:
- ...
+) -> Any: ...
@overload
@@ -87,13 +92,16 @@ def field(
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
+ authenticated: bool = False,
+ external: bool = False,
+ inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
provides: Optional[List[str]] = None,
+ override: Optional[Union[Override, str]] = None,
requires: Optional[List[str]] = None,
- external: bool = False,
- shareable: bool = False,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
- override: Optional[str] = None,
- inaccessible: bool = False,
+ shareable: bool = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
@@ -101,8 +109,7 @@ def field(
directives: Sequence[object] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> StrawberryField:
- ...
+) -> StrawberryField: ...
def field(
@@ -111,13 +118,16 @@ def field(
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
+ authenticated: bool = False,
+ external: bool = False,
+ inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
provides: Optional[List[str]] = None,
+ override: Optional[Union[Override, str]] = None,
requires: Optional[List[str]] = None,
- external: bool = False,
- shareable: bool = False,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
- override: Optional[str] = None,
- inaccessible: bool = False,
+ shareable: bool = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = dataclasses.MISSING,
@@ -131,25 +141,47 @@ def field(
init: Literal[True, False, None] = None,
) -> Any:
from .schema_directives import (
+ Authenticated,
External,
Inaccessible,
Override,
+ Policy,
Provides,
Requires,
+ RequiresScopes,
Shareable,
Tag,
)
directives = list(directives)
+ if authenticated:
+ directives.append(Authenticated())
+
+ if external:
+ directives.append(External())
+
+ if inaccessible:
+ directives.append(Inaccessible())
+
+ if override:
+ directives.append(
+ Override(override_from=override, label=UNSET)
+ if isinstance(override, str)
+ else override
+ )
+
+ if policy:
+ directives.append(Policy(policies=policy))
+
if provides:
directives.append(Provides(fields=" ".join(provides)))
if requires:
directives.append(Requires(fields=" ".join(requires)))
- if external:
- directives.append(External())
+ if requires_scopes:
+ directives.append(RequiresScopes(scopes=requires_scopes))
if shareable:
directives.append(Shareable())
@@ -157,12 +189,6 @@ def field(
if tags:
directives.extend(Tag(name=tag) for tag in tags)
- if override:
- directives.append(Override(override_from=override))
-
- if inaccessible:
- directives.append(Inaccessible())
-
return base_field( # type: ignore
resolver=resolver, # type: ignore
name=name,
@@ -177,3 +203,6 @@ def field(
extensions=extensions,
graphql_type=graphql_type,
)
+
+
+__all__ = ["field"]
diff --git a/strawberry/federation/mutation.py b/strawberry/federation/mutation.py
index d033d61c97..18df817b73 100644
--- a/strawberry/federation/mutation.py
+++ b/strawberry/federation/mutation.py
@@ -1,3 +1,5 @@
from .field import field
mutation = field
+
+__all__ = ["mutation"]
diff --git a/strawberry/federation/object_type.py b/strawberry/federation/object_type.py
index c36b18d1e4..7005302370 100644
--- a/strawberry/federation/object_type.py
+++ b/strawberry/federation/object_type.py
@@ -2,6 +2,7 @@
TYPE_CHECKING,
Callable,
Iterable,
+ List,
Optional,
Sequence,
Type,
@@ -11,10 +12,10 @@
)
from typing_extensions import dataclass_transform
-from strawberry.field import StrawberryField
-from strawberry.field import field as base_field
-from strawberry.object_type import type as base_type
-from strawberry.unset import UNSET
+from strawberry.types.field import StrawberryField
+from strawberry.types.field import field as base_field
+from strawberry.types.object_type import type as base_type
+from strawberry.types.unset import UNSET
from .field import field
@@ -30,23 +31,31 @@ def _impl_type(
*,
name: Optional[str] = None,
description: Optional[str] = None,
+ one_of: Optional[bool] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
keys: Iterable[Union["Key", str]] = (),
extend: bool = False,
shareable: bool = False,
inaccessible: bool = UNSET,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
is_input: bool = False,
is_interface: bool = False,
is_interface_object: bool = False,
) -> T:
from strawberry.federation.schema_directives import (
+ Authenticated,
Inaccessible,
InterfaceObject,
Key,
+ Policy,
+ RequiresScopes,
Shareable,
Tag,
)
+ from strawberry.schema_directives import OneOf
directives = list(directives)
@@ -55,18 +64,30 @@ def _impl_type(
for key in keys
)
- if shareable:
- directives.append(Shareable())
+ if authenticated:
+ directives.append(Authenticated())
if inaccessible is not UNSET:
directives.append(Inaccessible())
+ if policy:
+ directives.append(Policy(policies=policy))
+
+ if requires_scopes:
+ directives.append(RequiresScopes(scopes=requires_scopes))
+
+ if shareable:
+ directives.append(Shareable())
+
if tags:
directives.extend(Tag(name=tag) for tag in tags)
if is_interface_object:
directives.append(InterfaceObject())
+ if one_of:
+ directives.append(OneOf())
+
return base_type( # type: ignore
cls,
name=name,
@@ -89,12 +110,16 @@ def type(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
+ extend: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
+ shareable: bool = False,
tags: Iterable[str] = (),
- extend: bool = False,
-) -> T:
- ...
+) -> T: ...
@overload
@@ -107,14 +132,16 @@ def type(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
- inaccessible: bool = UNSET,
- tags: Iterable[str] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
extend: bool = False,
+ inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
shareable: bool = False,
- directives: Iterable[object] = (),
-) -> Callable[[T], T]:
- ...
+ tags: Iterable[str] = (),
+) -> Callable[[T], T]: ...
def type(
@@ -122,22 +149,28 @@ def type(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
- inaccessible: bool = UNSET,
- tags: Iterable[str] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
extend: bool = False,
+ inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
shareable: bool = False,
- directives: Iterable[object] = (),
+ tags: Iterable[str] = (),
):
return _impl_type(
cls,
name=name,
description=description,
directives=directives,
+ authenticated=authenticated,
keys=keys,
extend=extend,
- shareable=shareable,
inaccessible=inaccessible,
+ policy=policy,
+ requires_scopes=requires_scopes,
+ shareable=shareable,
tags=tags,
)
@@ -152,12 +185,12 @@ def input(
cls: T,
*,
name: Optional[str] = None,
+ one_of: Optional[bool] = None,
description: Optional[str] = None,
directives: Sequence[object] = (),
inaccessible: bool = UNSET,
tags: Iterable[str] = (),
-) -> T:
- ...
+) -> T: ...
@overload
@@ -170,17 +203,18 @@ def input(
*,
name: Optional[str] = None,
description: Optional[str] = None,
+ one_of: Optional[bool] = None,
directives: Sequence[object] = (),
inaccessible: bool = UNSET,
tags: Iterable[str] = (),
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
def input(
cls: Optional[T] = None,
*,
name: Optional[str] = None,
+ one_of: Optional[bool] = None,
description: Optional[str] = None,
directives: Sequence[object] = (),
inaccessible: bool = UNSET,
@@ -193,6 +227,7 @@ def input(
directives=directives,
inaccessible=inaccessible,
is_input=True,
+ one_of=one_of,
tags=tags,
)
@@ -208,12 +243,14 @@ def interface(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
-) -> T:
- ...
+) -> T: ...
@overload
@@ -226,12 +263,14 @@ def interface(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
def interface(
@@ -239,20 +278,26 @@ def interface(
*,
name: Optional[str] = None,
description: Optional[str] = None,
- keys: Iterable[Union["Key", str]] = (),
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
):
return _impl_type(
cls,
name=name,
description=description,
directives=directives,
+ authenticated=authenticated,
keys=keys,
inaccessible=inaccessible,
- is_interface=True,
+ policy=policy,
+ requires_scopes=requires_scopes,
tags=tags,
+ is_interface=True,
)
@@ -265,14 +310,16 @@ def interface(
def interface_object(
cls: T,
*,
- keys: Iterable[Union["Key", str]],
name: Optional[str] = None,
description: Optional[str] = None,
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
-) -> T:
- ...
+) -> T: ...
@overload
@@ -283,34 +330,45 @@ def interface_object(
)
def interface_object(
*,
- keys: Iterable[Union["Key", str]],
name: Optional[str] = None,
description: Optional[str] = None,
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
def interface_object(
cls: Optional[T] = None,
*,
- keys: Iterable[Union["Key", str]],
name: Optional[str] = None,
description: Optional[str] = None,
+ directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = UNSET,
+ keys: Iterable[Union["Key", str]] = (),
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Iterable[str] = (),
- directives: Iterable[object] = (),
):
return _impl_type(
cls,
name=name,
description=description,
directives=directives,
+ authenticated=authenticated,
keys=keys,
inaccessible=inaccessible,
+ policy=policy,
+ requires_scopes=requires_scopes,
+ tags=tags,
is_interface=False,
is_interface_object=True,
- tags=tags,
)
+
+
+__all__ = ["type", "input", "interface", "interface_object"]
diff --git a/strawberry/federation/scalar.py b/strawberry/federation/scalar.py
index 551569c97e..9cb1183318 100644
--- a/strawberry/federation/scalar.py
+++ b/strawberry/federation/scalar.py
@@ -3,15 +3,15 @@
Any,
Callable,
Iterable,
+ List,
NewType,
Optional,
- Type,
TypeVar,
Union,
overload,
)
-from strawberry.custom_scalar import _process_scalar
+from strawberry.types.scalar import ScalarWrapper, _process_scalar
# in python 3.10+ NewType is a class
if sys.version_info >= (3, 10):
@@ -34,10 +34,12 @@ def scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
-) -> Callable[[_T], _T]:
- ...
+) -> Callable[[_T], _T]: ...
@overload
@@ -51,14 +53,16 @@ def scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
-) -> _T:
- ...
+) -> _T: ...
def scalar(
- cls=None,
+ cls: Optional[_T] = None,
*,
name: Optional[str] = None,
description: Optional[str] = None,
@@ -67,48 +71,86 @@ def scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
+ authenticated: bool = False,
inaccessible: bool = False,
+ policy: Optional[List[List[str]]] = None,
+ requires_scopes: Optional[List[List[str]]] = None,
tags: Optional[Iterable[str]] = (),
) -> Any:
"""Annotates a class or type as a GraphQL custom scalar.
- Example usages:
+ Args:
+ cls: The class or type to annotate
+ name: The GraphQL name of the scalar
+ description: The description of the scalar
+ specified_by_url: The URL of the specification
+ serialize: The function to serialize the scalar
+ parse_value: The function to parse the value
+ parse_literal: The function to parse the literal
+ directives: The directives to apply to the scalar
+ authenticated: Whether to add the @authenticated directive
+ inaccessible: Whether to add the @inaccessible directive
+ policy: The list of policy names to add to the @policy directive
+ requires_scopes: The list of scopes to add to the @requires directive
+ tags: The list of tags to add to the @tag directive
+
+ Returns:
+ The decorated class or type
- >>> strawberry.federation.scalar(
- >>> datetime.date,
- >>> serialize=lambda value: value.isoformat(),
- >>> parse_value=datetime.parse_date
- >>> )
-
- >>> Base64Encoded = strawberry.federation.scalar(
- >>> NewType("Base64Encoded", bytes),
- >>> serialize=base64.b64encode,
- >>> parse_value=base64.b64decode
- >>> )
-
- >>> @strawberry.federation.scalar(
- >>> serialize=lambda value: ",".join(value.items),
- >>> parse_value=lambda value: CustomList(value.split(","))
- >>> )
- >>> class CustomList:
- >>> def __init__(self, items):
- >>> self.items = items
+ Example usages:
+ ```python
+ strawberry.federation.scalar(
+ datetime.date,
+ serialize=lambda value: value.isoformat(),
+ parse_value=datetime.parse_date,
+ )
+
+ Base64Encoded = strawberry.federation.scalar(
+ NewType("Base64Encoded", bytes),
+ serialize=base64.b64encode,
+ parse_value=base64.b64decode,
+ )
+
+
+ @strawberry.federation.scalar(
+ serialize=lambda value: ",".join(value.items),
+ parse_value=lambda value: CustomList(value.split(",")),
+ )
+ class CustomList:
+ def __init__(self, items):
+ self.items = items
+ ```
"""
- from strawberry.federation.schema_directives import Inaccessible, Tag
+ from strawberry.federation.schema_directives import (
+ Authenticated,
+ Inaccessible,
+ Policy,
+ RequiresScopes,
+ Tag,
+ )
if parse_value is None:
parse_value = cls
directives = list(directives)
+ if authenticated:
+ directives.append(Authenticated())
+
if inaccessible:
directives.append(Inaccessible())
+ if policy:
+ directives.append(Policy(policies=policy))
+
+ if requires_scopes:
+ directives.append(RequiresScopes(scopes=requires_scopes))
+
if tags:
directives.extend(Tag(name=tag) for tag in tags)
- def wrap(cls: Type):
+ def wrap(cls: _T) -> ScalarWrapper:
return _process_scalar(
cls,
name=name,
@@ -124,3 +166,6 @@ def wrap(cls: Type):
return wrap
return wrap(cls)
+
+
+__all__ = ["scalar"]
diff --git a/strawberry/federation/schema.py b/strawberry/federation/schema.py
index ccdc0fe8f9..e9cbf1625e 100644
--- a/strawberry/federation/schema.py
+++ b/strawberry/federation/schema.py
@@ -1,6 +1,5 @@
from collections import defaultdict
-from copy import copy
-from functools import cached_property, partial
+from functools import cached_property
from itertools import chain
from typing import (
TYPE_CHECKING,
@@ -10,6 +9,7 @@
Iterable,
List,
Mapping,
+ NewType,
Optional,
Set,
Type,
@@ -17,36 +17,34 @@
cast,
)
-from graphql import (
- GraphQLError,
- GraphQLField,
- GraphQLInterfaceType,
- GraphQLList,
- GraphQLNonNull,
- GraphQLScalarType,
- GraphQLUnionType,
-)
-from graphql.type.definition import GraphQLArgument
-
+from strawberry.annotation import StrawberryAnnotation
from strawberry.printer import print_schema
from strawberry.schema import Schema as BaseSchema
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import (
+ StrawberryContainer,
+ StrawberryObjectDefinition,
+ WithStrawberryObjectDefinition,
+ get_object_definition,
+)
+from strawberry.types.info import Info
+from strawberry.types.scalar import scalar
+from strawberry.types.union import StrawberryUnion
from strawberry.utils.inspect import get_func_args
from .schema_directive import StrawberryFederationSchemaDirective
if TYPE_CHECKING:
from graphql import ExecutionContext as GraphQLExecutionContext
- from graphql import GraphQLObjectType
- from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
- from strawberry.enum import EnumDefinition
from strawberry.extensions import SchemaExtension
from strawberry.federation.schema_directives import ComposeDirective
from strawberry.schema.config import StrawberryConfig
- from strawberry.schema.types.concrete_type import TypeMap
from strawberry.schema_directive import StrawberrySchemaDirective
- from strawberry.union import StrawberryUnion
+ from strawberry.types.enum import EnumDefinition
+ from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
+
+
+FederationAny = scalar(NewType("_Any", object), name="_Any") # type: ignore
class Schema(BaseSchema):
@@ -66,8 +64,9 @@ def __init__(
] = None,
schema_directives: Iterable[object] = (),
enable_federation_2: bool = False,
- ):
- query = self._get_federation_query_type(query)
+ ) -> None:
+ query = self._get_federation_query_type(query, mutation, subscription, types)
+ types = [*types, FederationAny]
super().__init__(
query=query,
@@ -84,16 +83,19 @@ def __init__(
self.schema_directives = list(schema_directives)
- self._add_scalars()
- self._add_entities_to_query()
-
if enable_federation_2:
composed_directives = self._add_compose_directives()
self._add_link_directives(composed_directives) # type: ignore
else:
self._remove_resolvable_field()
- def _get_federation_query_type(self, query: Optional[Type]) -> Type:
+ def _get_federation_query_type(
+ self,
+ query: Optional[Type[WithStrawberryObjectDefinition]],
+ mutation: Optional[Type[WithStrawberryObjectDefinition]],
+ subscription: Optional[Type[WithStrawberryObjectDefinition]],
+ additional_types: Iterable[Type[WithStrawberryObjectDefinition]],
+ ) -> Type:
"""Returns a new query type that includes the _service field.
If the query type is provided, it will be used as the base for the new
@@ -108,14 +110,6 @@ def _get_federation_query_type(self, query: Optional[Type]) -> Type:
The _service field is added by default, but the _entities field is only
added if the schema contains an entity type.
"""
-
- # note we don't add the _entities field here, as we need to know if the
- # schema contains an entity type first and we do that by leveraging
- # the schema converter type map, so we don't have to do that twice
- # TODO: ideally we should be able to do this without using the schema
- # converter, but for now this is the easiest way to do it
- # see `_add_entities_to_query`
-
import strawberry
from strawberry.tools.create_type import create_type
from strawberry.tools.merge_types import merge_types
@@ -132,6 +126,19 @@ def service() -> Service:
fields = [service]
+ entity_type = _get_entity_type(query, mutation, subscription, additional_types)
+
+ if entity_type:
+ self.entities_resolver.__annotations__["return"] = List[
+ Optional[entity_type] # type: ignore
+ ]
+
+ entities_field = strawberry.field(
+ name="_entities", resolver=self.entities_resolver
+ )
+
+ fields.insert(0, entities_field)
+
FederationQuery = create_type(name="Query", fields=fields)
if query is None:
@@ -139,10 +146,7 @@ def service() -> Service:
query_type = merge_types(
"Query",
- (
- FederationQuery,
- query,
- ),
+ (FederationQuery, query),
)
# TODO: this should be probably done in merge_types
@@ -151,31 +155,9 @@ def service() -> Service:
return query_type
- def _add_entities_to_query(self):
- entity_type = _get_entity_type(self.schema_converter.type_map)
-
- if not entity_type:
- return
-
- self._schema.type_map[entity_type.name] = entity_type
- fields = {"_entities": self._get_entities_field(entity_type)}
-
- # Copy the query type, update it to use the modified fields
- query_type = cast("GraphQLObjectType", self._schema.query_type)
- fields.update(query_type.fields)
-
- query_type = copy(query_type)
- query_type.fields = fields
-
- self._schema.query_type = query_type
- self._schema.type_map[query_type.name] = query_type
-
def entities_resolver(
- self,
- root, # noqa: ANN001
- info, # noqa: ANN001
- representations, # noqa: ANN001
- ) -> List[object]:
+ self, info: Info, representations: List[FederationAny]
+ ) -> List[FederationAny]:
results = []
for representation in representations:
@@ -194,43 +176,35 @@ def entities_resolver(
if "info" in func_args:
kwargs["info"] = info
- get_result = partial(resolve_reference, **kwargs)
+ try:
+ result = resolve_reference(**kwargs)
+ except Exception as e:
+ result = e
else:
- from strawberry.arguments import convert_argument
-
- strawberry_schema = info.schema.extensions["strawberry-definition"]
- config = strawberry_schema.config
- scalar_registry = strawberry_schema.schema_converter.scalar_registry
-
- get_result = partial(
- convert_argument,
- representation,
- type_=definition.origin,
- scalar_registry=scalar_registry,
- config=config,
- )
+ from strawberry.types.arguments import convert_argument
- try:
- result = get_result()
- except Exception as e:
- result = GraphQLError(
- f"Unable to resolve reference for {definition.origin}",
- original_error=e,
- )
+ config = info.schema.config
+ scalar_registry = info.schema.schema_converter.scalar_registry
+
+ try:
+ result = convert_argument(
+ representation,
+ type_=definition.origin,
+ scalar_registry=scalar_registry,
+ config=config,
+ )
+ except Exception:
+ result = TypeError(f"Unable to resolve reference for {type_name}")
results.append(result)
return results
- def _add_scalars(self):
- self.Any = GraphQLScalarType("_Any")
-
- self._schema.type_map["_Any"] = self.Any
-
def _remove_resolvable_field(self) -> None:
# this might be removed when we remove support for federation 1
# or when we improve how we print the directives
- from ..unset import UNSET
+ from strawberry.types.unset import UNSET
+
from .schema_directives import Key
for directive in self.schema_directives_in_use:
@@ -283,7 +257,7 @@ def _add_link_for_composed_directive(
def _add_link_directives(
self, additional_directives: Optional[List[object]] = None
- ):
+ ) -> None:
from .schema_directives import FederationDirective, Link
directive_by_url: DefaultDict[str, Set[str]] = defaultdict(set)
@@ -335,17 +309,6 @@ def _add_compose_directives(self) -> List["ComposeDirective"]:
return compose_directives
- def _get_entities_field(self, entity_type: GraphQLUnionType) -> GraphQLField:
- return GraphQLField(
- GraphQLNonNull(GraphQLList(entity_type)),
- args={
- "representations": GraphQLArgument(
- GraphQLNonNull(GraphQLList(GraphQLNonNull(self.Any)))
- )
- },
- resolve=self.entities_resolver,
- )
-
def _warn_for_federation_directives(self) -> None:
# this is used in the main schema to raise if there's a directive
# that's for federation, but in this class we don't want to warn,
@@ -354,33 +317,56 @@ def _warn_for_federation_directives(self) -> None:
pass
-def _get_entity_type(type_map: "TypeMap"):
- # https://www.apollographql.com/docs/apollo-server/federation/federation-spec/#resolve-requests-for-entities
+def _get_entity_type(
+ query: Optional[Type[WithStrawberryObjectDefinition]],
+ mutation: Optional[Type[WithStrawberryObjectDefinition]],
+ subscription: Optional[Type[WithStrawberryObjectDefinition]],
+ additional_types: Iterable[Type[WithStrawberryObjectDefinition]],
+) -> Optional[StrawberryUnion]:
+ # recursively iterate over the schema to find all types annotated with @key
+ # if no types are annotated with @key, then the _Entity union and Query._entities
+ # field should not be added to the schema
- # To implement the _Entity union, each type annotated with @key
- # should be added to the _Entity union.
+ entity_types = set()
- federation_key_types = [
- type.implementation
- for type in type_map.values()
- if _has_federation_keys(type.definition)
- # TODO: check this
- and not isinstance(type.implementation, GraphQLInterfaceType)
- ]
+ # need a stack to keep track of the types we need to visit
+ stack: List[Any] = [query, mutation, subscription, *additional_types]
- # If no types are annotated with the key directive, then the _Entity
- # union and Query._entities field should be removed from the schema.
- if not federation_key_types:
- return None
+ seen = set()
- entity_type = GraphQLUnionType("_Entity", federation_key_types) # type: ignore
+ while stack:
+ type_ = stack.pop()
- def _resolve_type(self, value, _type): # noqa: ANN001
- return self.__strawberry_definition__.name
+ if type_ is None:
+ continue
- entity_type.resolve_type = _resolve_type
+ while isinstance(type_, StrawberryContainer):
+ type_ = type_.of_type
- return entity_type
+ type_definition = get_object_definition(type_, strict=False)
+
+ if type_definition is None:
+ continue
+
+ if type_definition.is_object_type and _has_federation_keys(type_definition):
+ entity_types.add(type_)
+
+ for field in type_definition.fields:
+ if field.type and field.type in seen:
+ continue
+
+ seen.add(field.type)
+ stack.append(field.type)
+
+ if not entity_types:
+ return None
+
+ sorted_types = sorted(entity_types, key=lambda t: t.__strawberry_definition__.name)
+
+ return StrawberryUnion(
+ "_Entity",
+ type_annotations=tuple(StrawberryAnnotation(type_) for type_ in sorted_types),
+ )
def _is_key(directive: Any) -> bool:
@@ -401,3 +387,6 @@ def _has_federation_keys(
return any(_is_key(directive) for directive in definition.directives or [])
return False
+
+
+__all__ = ["Schema"]
diff --git a/strawberry/federation/schema_directive.py b/strawberry/federation/schema_directive.py
index 8031d2fef9..06ffe76336 100644
--- a/strawberry/federation/schema_directive.py
+++ b/strawberry/federation/schema_directive.py
@@ -3,9 +3,9 @@
from typing_extensions import dataclass_transform
from strawberry.directive import directive_field
-from strawberry.field import StrawberryField, field
-from strawberry.object_type import _wrap_dataclass
from strawberry.schema_directive import Location, StrawberrySchemaDirective
+from strawberry.types.field import StrawberryField, field
+from strawberry.types.object_type import _wrap_dataclass
from strawberry.types.type_resolver import _get_fields
@@ -38,8 +38,8 @@ def schema_directive(
import_url: Optional[str] = None,
) -> Callable[..., T]:
def _wrap(cls: T) -> T:
- cls = _wrap_dataclass(cls)
- fields = _get_fields(cls)
+ cls = _wrap_dataclass(cls) # type: ignore
+ fields = _get_fields(cls, {})
cls.__strawberry_directive__ = StrawberryFederationSchemaDirective(
python_name=cls.__name__,
diff --git a/strawberry/federation/schema_directives.py b/strawberry/federation/schema_directives.py
index ced7b3791b..0f9232d7f3 100644
--- a/strawberry/federation/schema_directives.py
+++ b/strawberry/federation/schema_directives.py
@@ -3,15 +3,19 @@
from strawberry import directive_field
from strawberry.schema_directive import Location, schema_directive
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
-from .types import FieldSet, LinkImport, LinkPurpose
+from .types import (
+ FieldSet,
+ LinkImport,
+ LinkPurpose,
+)
@dataclass
class ImportedFrom:
name: str
- url: str = "https://specs.apollo.dev/federation/v2.3"
+ url: str = "https://specs.apollo.dev/federation/v2.7"
class FederationDirective:
@@ -23,7 +27,7 @@ class FederationDirective:
)
class External(FederationDirective):
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="external", url="https://specs.apollo.dev/federation/v2.3"
+ name="external", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -33,7 +37,7 @@ class External(FederationDirective):
class Requires(FederationDirective):
fields: FieldSet
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="requires", url="https://specs.apollo.dev/federation/v2.3"
+ name="requires", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -43,7 +47,7 @@ class Requires(FederationDirective):
class Provides(FederationDirective):
fields: FieldSet
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="provides", url="https://specs.apollo.dev/federation/v2.3"
+ name="provides", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -57,7 +61,7 @@ class Key(FederationDirective):
fields: FieldSet
resolvable: Optional[bool] = True
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="key", url="https://specs.apollo.dev/federation/v2.3"
+ name="key", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -69,7 +73,7 @@ class Key(FederationDirective):
)
class Shareable(FederationDirective):
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="shareable", url="https://specs.apollo.dev/federation/v2.3"
+ name="shareable", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -88,7 +92,7 @@ def __init__(
as_: Optional[str] = UNSET,
for_: Optional[LinkPurpose] = UNSET,
import_: Optional[List[Optional[LinkImport]]] = UNSET,
- ):
+ ) -> None:
self.url = url
self.as_ = as_
self.for_ = for_
@@ -115,7 +119,7 @@ def __init__(
class Tag(FederationDirective):
name: str
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="tag", url="https://specs.apollo.dev/federation/v2.3"
+ name="tag", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -124,8 +128,9 @@ class Tag(FederationDirective):
)
class Override(FederationDirective):
override_from: str = directive_field(name="from")
+ label: Optional[str] = UNSET
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="override", url="https://specs.apollo.dev/federation/v2.3"
+ name="override", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -147,7 +152,7 @@ class Override(FederationDirective):
)
class Inaccessible(FederationDirective):
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="inaccessible", url="https://specs.apollo.dev/federation/v2.3"
+ name="inaccessible", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -157,7 +162,7 @@ class Inaccessible(FederationDirective):
class ComposeDirective(FederationDirective):
name: str
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="composeDirective", url="https://specs.apollo.dev/federation/v2.3"
+ name="composeDirective", url="https://specs.apollo.dev/federation/v2.7"
)
@@ -166,5 +171,76 @@ class ComposeDirective(FederationDirective):
)
class InterfaceObject(FederationDirective):
imported_from: ClassVar[ImportedFrom] = ImportedFrom(
- name="interfaceObject", url="https://specs.apollo.dev/federation/v2.3"
+ name="interfaceObject", url="https://specs.apollo.dev/federation/v2.7"
)
+
+
+@schema_directive(
+ locations=[
+ Location.FIELD_DEFINITION,
+ Location.OBJECT,
+ Location.INTERFACE,
+ Location.SCALAR,
+ Location.ENUM,
+ ],
+ name="authenticated",
+ print_definition=False,
+)
+class Authenticated(FederationDirective):
+ imported_from: ClassVar[ImportedFrom] = ImportedFrom(
+ name="authenticated", url="https://specs.apollo.dev/federation/v2.7"
+ )
+
+
+@schema_directive(
+ locations=[
+ Location.FIELD_DEFINITION,
+ Location.OBJECT,
+ Location.INTERFACE,
+ Location.SCALAR,
+ Location.ENUM,
+ ],
+ name="requiresScopes",
+ print_definition=False,
+)
+class RequiresScopes(FederationDirective):
+ scopes: "List[List[str]]"
+ imported_from: ClassVar[ImportedFrom] = ImportedFrom(
+ name="requiresScopes", url="https://specs.apollo.dev/federation/v2.7"
+ )
+
+
+@schema_directive(
+ locations=[
+ Location.FIELD_DEFINITION,
+ Location.OBJECT,
+ Location.INTERFACE,
+ Location.SCALAR,
+ Location.ENUM,
+ ],
+ name="policy",
+ print_definition=False,
+)
+class Policy(FederationDirective):
+ policies: "List[List[str]]"
+ imported_from: ClassVar[ImportedFrom] = ImportedFrom(
+ name="policy", url="https://specs.apollo.dev/federation/v2.7"
+ )
+
+
+__all__ = [
+ "External",
+ "Requires",
+ "Provides",
+ "Key",
+ "Shareable",
+ "Link",
+ "Tag",
+ "Override",
+ "Inaccessible",
+ "ComposeDirective",
+ "InterfaceObject",
+ "Authenticated",
+ "RequiresScopes",
+ "Policy",
+]
diff --git a/strawberry/federation/types.py b/strawberry/federation/types.py
index 4a6cb19a9b..6584bc1765 100644
--- a/strawberry/federation/types.py
+++ b/strawberry/federation/types.py
@@ -1,7 +1,7 @@
from enum import Enum
-from strawberry.custom_scalar import scalar
-from strawberry.enum import enum
+from strawberry.types.enum import enum
+from strawberry.types.scalar import scalar
FieldSet = scalar(str, name="_FieldSet")
@@ -12,3 +12,6 @@
class LinkPurpose(Enum):
SECURITY = "SECURITY"
EXECUTION = "EXECUTION"
+
+
+__all__ = ["FieldSet", "LinkImport", "LinkPurpose"]
diff --git a/strawberry/federation/union.py b/strawberry/federation/union.py
index d65b9c6f60..c61e4f5578 100644
--- a/strawberry/federation/union.py
+++ b/strawberry/federation/union.py
@@ -1,7 +1,7 @@
from typing import Any, Collection, Iterable, Optional, Type
-from strawberry.union import StrawberryUnion
-from strawberry.union import union as base_union
+from strawberry.types.union import StrawberryUnion
+from strawberry.types.union import union as base_union
def union(
@@ -15,15 +15,31 @@ def union(
) -> StrawberryUnion:
"""Creates a new named Union type.
+ Args:
+ name: The GraphQL name of the Union type.
+ types: The types that the Union can be.
+ (Deprecated, use `Annotated[U, strawberry.federation.union("Name")]` instead)
+ description: The GraphQL description of the Union type.
+ directives: The directives to attach to the Union type.
+ inaccessible: Whether the Union type is inaccessible.
+ tags: The federation tags to attach to the Union type.
+
Example usages:
- >>> @strawberry.type
- ... class A: ...
- >>> @strawberry.type
- ... class B: ...
- >>> strawberry.federation.union("Name", (A, Optional[B]))
- """
+ ```python
+ import strawberry
+ from typing import Annotated
+
+ @strawberry.federation.type(keys=["id"])
+ class A:
+ id: strawberry.ID
+
+ @strawberry.federation.type(keys=["id"])
+ class B:
+ id: strawberry.ID
+ MyUnion = Annotated[A | B, strawberry.federation.union("Name", tags=["tag"])]
+ """
from strawberry.federation.schema_directives import Inaccessible, Tag
directives = list(directives)
@@ -40,3 +56,6 @@ def union(
description=description,
directives=directives,
)
+
+
+__all__ = ["union"]
diff --git a/strawberry/field_extensions/input_mutation.py b/strawberry/field_extensions/input_mutation.py
index 568515cfe7..f251d016da 100644
--- a/strawberry/field_extensions/input_mutation.py
+++ b/strawberry/field_extensions/input_mutation.py
@@ -8,13 +8,13 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument
from strawberry.extensions.field_extension import (
AsyncExtensionResolver,
FieldExtension,
SyncExtensionResolver,
)
-from strawberry.field import StrawberryField
+from strawberry.types.arguments import StrawberryArgument
+from strawberry.types.field import StrawberryField
from strawberry.utils.str_converters import capitalize_first, to_camel_case
if TYPE_CHECKING:
@@ -65,7 +65,7 @@ def resolve(
self,
next_: SyncExtensionResolver,
source: Any,
- info: Info[Any, Any],
+ info: Info,
**kwargs: Any,
) -> Any:
input_args = kwargs.pop("input")
@@ -90,3 +90,6 @@ async def resolve_async(
**kwargs,
**vars(input_args),
)
+
+
+__all__ = ["InputMutationExtension"]
diff --git a/strawberry/file_uploads/scalars.py b/strawberry/file_uploads/scalars.py
index 4dcaccf19a..680f9bd681 100644
--- a/strawberry/file_uploads/scalars.py
+++ b/strawberry/file_uploads/scalars.py
@@ -1,5 +1,7 @@
from typing import NewType
-from ..custom_scalar import scalar
+from strawberry.types.scalar import scalar
Upload = scalar(NewType("Upload", bytes), parse_value=lambda x: x)
+
+__all__ = ["Upload"]
diff --git a/strawberry/file_uploads/utils.py b/strawberry/file_uploads/utils.py
index 8a1203ca5b..0fec3a2c82 100644
--- a/strawberry/file_uploads/utils.py
+++ b/strawberry/file_uploads/utils.py
@@ -30,3 +30,6 @@ def replace_placeholders_with_files(
target_object[value_key] = file_object
return operations
+
+
+__all__ = ["replace_placeholders_with_files"]
diff --git a/strawberry/flask/views.py b/strawberry/flask/views.py
index 2664cd40cc..b855c602ea 100644
--- a/strawberry/flask/views.py
+++ b/strawberry/flask/views.py
@@ -4,7 +4,6 @@
from typing import (
TYPE_CHECKING,
Any,
- List,
Mapping,
Optional,
Union,
@@ -30,11 +29,11 @@
class FlaskHTTPRequestAdapter(SyncHTTPRequestAdapter):
- def __init__(self, request: Request):
+ def __init__(self, request: Request) -> None:
self.request = request
@property
- def query_params(self) -> Mapping[str, Union[str, Optional[List[str]]]]:
+ def query_params(self) -> QueryParams:
return self.request.args.to_dict()
@property
@@ -72,7 +71,7 @@ def __init__(
graphiql: Optional[bool] = None,
graphql_ide: Optional[GraphQL_IDE] = "graphiql",
allow_queries_via_get: bool = True,
- ):
+ ) -> None:
self.schema = schema
self.graphiql = graphiql
self.allow_queries_via_get = allow_queries_via_get
@@ -127,7 +126,7 @@ def render_graphql_ide(self, request: Request) -> Response:
class AsyncFlaskHTTPRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: Request):
+ def __init__(self, request: Request) -> None:
self.request = request
@property
@@ -185,3 +184,9 @@ async def dispatch_request(self) -> ResponseReturnValue: # type: ignore
async def render_graphql_ide(self, request: Request) -> Response:
return render_template_string(self.graphql_ide_html) # type: ignore
+
+
+__all__ = [
+ "GraphQLView",
+ "AsyncGraphQLView",
+]
diff --git a/strawberry/http/__init__.py b/strawberry/http/__init__.py
index dc86e7c9f8..b5fb6f5066 100644
--- a/strawberry/http/__init__.py
+++ b/strawberry/http/__init__.py
@@ -48,3 +48,12 @@ def parse_request_data(data: Mapping[str, Any]) -> GraphQLRequestData:
variables=data.get("variables"),
operation_name=data.get("operationName"),
)
+
+
+__all__ = [
+ "GraphQLHTTPResponse",
+ "process_result",
+ "GraphQLRequestData",
+ "parse_query_params",
+ "parse_request_data",
+]
diff --git a/strawberry/http/async_base_view.py b/strawberry/http/async_base_view.py
index de9e8578c0..dabd98958c 100644
--- a/strawberry/http/async_base_view.py
+++ b/strawberry/http/async_base_view.py
@@ -41,31 +41,25 @@
class AsyncHTTPRequestAdapter(abc.ABC):
@property
@abc.abstractmethod
- def query_params(self) -> QueryParams:
- ...
+ def query_params(self) -> QueryParams: ...
@property
@abc.abstractmethod
- def method(self) -> HTTPMethod:
- ...
+ def method(self) -> HTTPMethod: ...
@property
@abc.abstractmethod
- def headers(self) -> Mapping[str, str]:
- ...
+ def headers(self) -> Mapping[str, str]: ...
@property
@abc.abstractmethod
- def content_type(self) -> Optional[str]:
- ...
+ def content_type(self) -> Optional[str]: ...
@abc.abstractmethod
- async def get_body(self) -> Union[str, bytes]:
- ...
+ async def get_body(self) -> Union[str, bytes]: ...
@abc.abstractmethod
- async def get_form_data(self) -> FormData:
- ...
+ async def get_form_data(self) -> FormData: ...
class AsyncBaseHTTPView(
@@ -79,30 +73,24 @@ class AsyncBaseHTTPView(
@property
@abc.abstractmethod
- def allow_queries_via_get(self) -> bool:
- ...
+ def allow_queries_via_get(self) -> bool: ...
@abc.abstractmethod
- async def get_sub_response(self, request: Request) -> SubResponse:
- ...
+ async def get_sub_response(self, request: Request) -> SubResponse: ...
@abc.abstractmethod
- async def get_context(self, request: Request, response: SubResponse) -> Context:
- ...
+ async def get_context(self, request: Request, response: SubResponse) -> Context: ...
@abc.abstractmethod
- async def get_root_value(self, request: Request) -> Optional[RootValue]:
- ...
+ async def get_root_value(self, request: Request) -> Optional[RootValue]: ...
@abc.abstractmethod
def create_response(
self, response_data: GraphQLHTTPResponse, sub_response: SubResponse
- ) -> Response:
- ...
+ ) -> Response: ...
@abc.abstractmethod
- async def render_graphql_ide(self, request: Request) -> Response:
- ...
+ async def render_graphql_ide(self, request: Request) -> Response: ...
async def create_multipart_response(
self,
@@ -165,9 +153,7 @@ async def parse_multipart(self, request: AsyncHTTPRequestAdapter) -> Dict[str, s
def _handle_errors(
self, errors: List[GraphQLError], response_data: GraphQLHTTPResponse
) -> None:
- """
- Hook to allow custom handling of errors, used by the Sentry Integration
- """
+ """Hook to allow custom handling of errors, used by the Sentry Integration."""
async def run(
self,
@@ -237,7 +223,8 @@ def _stream_with_heartbeat(
self, stream: Callable[[], AsyncGenerator[str, None]]
) -> Callable[[], AsyncGenerator[str, None]]:
"""Adds a heartbeat to the stream, to prevent the connection from closing
- when there are no messages being sent."""
+ when there are no messages being sent.
+ """
queue = asyncio.Queue[Tuple[bool, Any]](1)
cancelling = False
@@ -317,7 +304,7 @@ async def parse_http_body(
) -> GraphQLRequestData:
content_type, params = parse_content_type(request.content_type or "")
- if content_type == "application/json":
+ if "application/json" in content_type:
data = self.parse_json(await request.get_body())
elif content_type == "multipart/form-data":
data = await self.parse_multipart(request)
@@ -338,3 +325,6 @@ async def process_result(
self, request: Request, result: ExecutionResult
) -> GraphQLHTTPResponse:
return process_result(result)
+
+
+__all__ = ["AsyncBaseHTTPView"]
diff --git a/strawberry/http/base.py b/strawberry/http/base.py
index b69766ec58..7f8e1802bc 100644
--- a/strawberry/http/base.py
+++ b/strawberry/http/base.py
@@ -4,7 +4,7 @@
from strawberry.http import GraphQLHTTPResponse
from strawberry.http.ides import GraphQL_IDE, get_graphql_ide_html
-from strawberry.http.types import HTTPMethod
+from strawberry.http.types import HTTPMethod, QueryParams
from .exceptions import HTTPException
from .typevars import Request
@@ -12,16 +12,13 @@
class BaseRequestProtocol(Protocol):
@property
- def query_params(self) -> Mapping[str, Optional[Union[str, List[str]]]]:
- ...
+ def query_params(self) -> Mapping[str, Optional[Union[str, List[str]]]]: ...
@property
- def method(self) -> HTTPMethod:
- ...
+ def method(self) -> HTTPMethod: ...
@property
- def headers(self) -> Mapping[str, str]:
- ...
+ def headers(self) -> Mapping[str, str]: ...
class BaseView(Generic[Request]):
@@ -53,17 +50,12 @@ def parse_json(self, data: Union[str, bytes]) -> Any:
def encode_json(self, response_data: GraphQLHTTPResponse) -> str:
return json.dumps(response_data)
- def parse_query_params(
- self, params: Mapping[str, Optional[Union[str, List[str]]]]
- ) -> Dict[str, Any]:
+ def parse_query_params(self, params: QueryParams) -> Dict[str, Any]:
params = dict(params)
if "variables" in params:
variables = params["variables"]
- if isinstance(variables, list):
- variables = variables[0]
-
if variables:
params["variables"] = self.parse_json(variables)
@@ -87,3 +79,6 @@ def _is_multipart_subscriptions(
return False
return params.get("subscriptionspec", "").startswith("1.0")
+
+
+__all__ = ["BaseView"]
diff --git a/strawberry/http/exceptions.py b/strawberry/http/exceptions.py
index 0ce1afadae..d934696806 100644
--- a/strawberry/http/exceptions.py
+++ b/strawberry/http/exceptions.py
@@ -1,4 +1,7 @@
class HTTPException(Exception):
- def __init__(self, status_code: int, reason: str):
+ def __init__(self, status_code: int, reason: str) -> None:
self.status_code = status_code
self.reason = reason
+
+
+__all__ = ["HTTPException"]
diff --git a/strawberry/http/ides.py b/strawberry/http/ides.py
index a65ab54ade..d9c52fb716 100644
--- a/strawberry/http/ides.py
+++ b/strawberry/http/ides.py
@@ -28,3 +28,6 @@ def get_graphql_ide_html(
)
return template
+
+
+__all__ = ["get_graphql_ide_html", "GraphQL_IDE"]
diff --git a/strawberry/http/sync_base_view.py b/strawberry/http/sync_base_view.py
index 8401e85ac8..5c36b94d91 100644
--- a/strawberry/http/sync_base_view.py
+++ b/strawberry/http/sync_base_view.py
@@ -37,38 +37,31 @@
class SyncHTTPRequestAdapter(abc.ABC):
@property
@abc.abstractmethod
- def query_params(self) -> QueryParams:
- ...
+ def query_params(self) -> QueryParams: ...
@property
@abc.abstractmethod
- def body(self) -> Union[str, bytes]:
- ...
+ def body(self) -> Union[str, bytes]: ...
@property
@abc.abstractmethod
- def method(self) -> HTTPMethod:
- ...
+ def method(self) -> HTTPMethod: ...
@property
@abc.abstractmethod
- def headers(self) -> Mapping[str, str]:
- ...
+ def headers(self) -> Mapping[str, str]: ...
@property
@abc.abstractmethod
- def content_type(self) -> Optional[str]:
- ...
+ def content_type(self) -> Optional[str]: ...
@property
@abc.abstractmethod
- def post_data(self) -> Mapping[str, Union[str, bytes]]:
- ...
+ def post_data(self) -> Mapping[str, Union[str, bytes]]: ...
@property
@abc.abstractmethod
- def files(self) -> Mapping[str, Any]:
- ...
+ def files(self) -> Mapping[str, Any]: ...
class SyncBaseHTTPView(
@@ -85,30 +78,24 @@ class SyncBaseHTTPView(
@property
@abc.abstractmethod
- def allow_queries_via_get(self) -> bool:
- ...
+ def allow_queries_via_get(self) -> bool: ...
@abc.abstractmethod
- def get_sub_response(self, request: Request) -> SubResponse:
- ...
+ def get_sub_response(self, request: Request) -> SubResponse: ...
@abc.abstractmethod
- def get_context(self, request: Request, response: SubResponse) -> Context:
- ...
+ def get_context(self, request: Request, response: SubResponse) -> Context: ...
@abc.abstractmethod
- def get_root_value(self, request: Request) -> Optional[RootValue]:
- ...
+ def get_root_value(self, request: Request) -> Optional[RootValue]: ...
@abc.abstractmethod
def create_response(
self, response_data: GraphQLHTTPResponse, sub_response: SubResponse
- ) -> Response:
- ...
+ ) -> Response: ...
@abc.abstractmethod
- def render_graphql_ide(self, request: Request) -> Response:
- ...
+ def render_graphql_ide(self, request: Request) -> Response: ...
def execute_operation(
self, request: Request, context: Context, root_value: Optional[RootValue]
@@ -151,7 +138,7 @@ def parse_multipart(self, request: SyncHTTPRequestAdapter) -> Dict[str, str]:
def parse_http_body(self, request: SyncHTTPRequestAdapter) -> GraphQLRequestData:
content_type, params = parse_content_type(request.content_type or "")
- if content_type == "application/json":
+ if "application/json" in content_type:
data = self.parse_json(request.body)
elif content_type == "multipart/form-data":
data = self.parse_multipart(request)
@@ -173,9 +160,7 @@ def parse_http_body(self, request: SyncHTTPRequestAdapter) -> GraphQLRequestData
def _handle_errors(
self, errors: List[GraphQLError], response_data: GraphQLHTTPResponse
) -> None:
- """
- Hook to allow custom handling of errors, used by the Sentry Integration
- """
+ """Hook to allow custom handling of errors, used by the Sentry Integration."""
def run(
self,
@@ -230,3 +215,6 @@ def process_result(
self, request: Request, result: ExecutionResult
) -> GraphQLHTTPResponse:
return process_result(result)
+
+
+__all__ = ["SyncBaseHTTPView"]
diff --git a/strawberry/http/temporal_response.py b/strawberry/http/temporal_response.py
index 854763b553..47bf52fa9d 100644
--- a/strawberry/http/temporal_response.py
+++ b/strawberry/http/temporal_response.py
@@ -6,3 +6,6 @@
class TemporalResponse:
status_code: int = 200
headers: Dict[str, str] = field(default_factory=dict)
+
+
+__all__ = ["TemporalResponse"]
diff --git a/strawberry/http/types.py b/strawberry/http/types.py
index 401231ae8b..28794c9afa 100644
--- a/strawberry/http/types.py
+++ b/strawberry/http/types.py
@@ -1,13 +1,16 @@
-from typing import Any, List, Mapping, Optional, Union
+from typing import Any, Mapping, Optional
from typing_extensions import Literal, TypedDict
HTTPMethod = Literal[
"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"
]
-QueryParams = Mapping[str, Optional[Union[str, List[str]]]]
+QueryParams = Mapping[str, Optional[str]]
class FormData(TypedDict):
files: Mapping[str, Any]
form: Mapping[str, Any]
+
+
+__all__ = ["HTTPMethod", "QueryParams", "FormData"]
diff --git a/strawberry/http/typevars.py b/strawberry/http/typevars.py
index f9bc77287f..a48cba848e 100644
--- a/strawberry/http/typevars.py
+++ b/strawberry/http/typevars.py
@@ -5,3 +5,6 @@
SubResponse = TypeVar("SubResponse")
Context = TypeVar("Context")
RootValue = TypeVar("RootValue")
+
+
+__all__ = ["Request", "Response", "SubResponse", "Context", "RootValue"]
diff --git a/strawberry/litestar/controller.py b/strawberry/litestar/controller.py
index ea7937f25c..09a5a5db67 100644
--- a/strawberry/litestar/controller.py
+++ b/strawberry/litestar/controller.py
@@ -220,9 +220,9 @@ class GraphQLController(
request_adapter_class = LitestarRequestAdapter
graphql_ws_handler_class: Type[GraphQLWSHandler] = GraphQLWSHandler
- graphql_transport_ws_handler_class: Type[
+ graphql_transport_ws_handler_class: Type[GraphQLTransportWSHandler] = (
GraphQLTransportWSHandler
- ] = GraphQLTransportWSHandler
+ )
allow_queries_via_get: bool = True
graphiql_allowed_accept: FrozenSet[str] = frozenset({"text/html", "*/*"})
@@ -347,10 +347,10 @@ async def websocket_endpoint(
context_ws: Any,
root_value: Any,
) -> None:
- async def _get_context():
+ async def _get_context() -> Any:
return context_ws
- async def _get_root_value():
+ async def _get_root_value() -> Any:
return root_value
preferred_protocol = self.pick_preferred_protocol(socket)
@@ -455,3 +455,9 @@ class _GraphQLController(GraphQLController):
_GraphQLController.graphql_ide = graphql_ide_
return _GraphQLController
+
+
+__all__ = [
+ "make_graphql_controller",
+ "GraphQLController",
+]
diff --git a/strawberry/litestar/handlers/__init__.py b/strawberry/litestar/handlers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/strawberry/litestar/handlers/graphql_transport_ws_handler.py b/strawberry/litestar/handlers/graphql_transport_ws_handler.py
index f2a6da20a7..b5aa915d08 100644
--- a/strawberry/litestar/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/litestar/handlers/graphql_transport_ws_handler.py
@@ -20,7 +20,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, connection_init_wait_timeout)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -55,3 +55,6 @@ async def handle_request(self) -> None:
pass
finally:
await self.shutdown()
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/litestar/handlers/graphql_ws_handler.py b/strawberry/litestar/handlers/graphql_ws_handler.py
index 58bc6c5dc3..d91ec4f8c3 100644
--- a/strawberry/litestar/handlers/graphql_ws_handler.py
+++ b/strawberry/litestar/handlers/graphql_ws_handler.py
@@ -20,7 +20,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, keep_alive, keep_alive_interval)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -60,3 +60,6 @@ async def handle_request(self) -> Any:
for operation_id in list(self.subscriptions.keys()):
await self.cleanup_operation(operation_id)
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/mutation.py b/strawberry/mutation.py
deleted file mode 100644
index 29466bc82e..0000000000
--- a/strawberry/mutation.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from functools import partial
-
-from .field import field
-
-# Mutations and subscriptions are field, we might want to separate
-# things in the long run for example to provide better errors
-mutation = field
-subscription = partial(field, is_subscription=True)
diff --git a/strawberry/parent.py b/strawberry/parent.py
index aa75c55012..f95f90b8e7 100644
--- a/strawberry/parent.py
+++ b/strawberry/parent.py
@@ -2,38 +2,43 @@
from typing_extensions import Annotated
-class StrawberryParent:
- ...
+class StrawberryParent: ...
T = TypeVar("T")
Parent = Annotated[T, StrawberryParent()]
-Parent.__doc__ = """Represents a parameter holding the parent resolver's value.
+"""Represents a parameter holding the parent resolver's value.
This can be used when defining a resolver on a type when the parent isn't expected
to return the type itself.
Example:
->>> import strawberry
->>> from dataclasses import dataclass
->>>
->>> @dataclass
->>> class UserRow:
-... id_: str
-...
->>> @strawberry.type
-... class User:
-... @strawberry.field
-... @staticmethod
-... async def name(parent: strawberry.Parent[UserRow]) -> str:
-... return f"User Number {parent.id}"
-...
->>> @strawberry.type
->>> class Query:
-... @strawberry.field
-... def user(self) -> User:
-... return UserRow(id_="1234")
-...
+```python
+import strawberry
+from dataclasses import dataclass
+
+
+@dataclass
+class UserRow:
+ id_: str
+
+
+@strawberry.type
+class User:
+ @strawberry.field
+ @staticmethod
+ async def name(parent: strawberry.Parent[UserRow]) -> str:
+ return f"User Number {parent.id_}"
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def user(self) -> User:
+ return UserRow(id_="1234")
+```
"""
+
+__all__ = ["Parent"]
diff --git a/strawberry/permission.py b/strawberry/permission.py
index 50fde5b55a..b52fdce102 100644
--- a/strawberry/permission.py
+++ b/strawberry/permission.py
@@ -21,7 +21,7 @@
)
from strawberry.extensions import FieldExtension
from strawberry.schema_directive import Location, StrawberrySchemaDirective
-from strawberry.type import StrawberryList, StrawberryOptional
+from strawberry.types.base import StrawberryList, StrawberryOptional
from strawberry.utils.await_maybe import await_maybe
if TYPE_CHECKING:
@@ -31,13 +31,25 @@
AsyncExtensionResolver,
SyncExtensionResolver,
)
- from strawberry.field import StrawberryField
from strawberry.types import Info
+ from strawberry.types.field import StrawberryField
class BasePermission(abc.ABC):
- """
- Base class for creating permissions
+ """Base class for permissions. All permissions should inherit from this class.
+
+ Example:
+
+ ```python
+ from strawberry.permission import BasePermission
+
+
+ class IsAuthenticated(BasePermission):
+ message = "User is not authenticated"
+
+ def has_permission(self, source, info, **kwargs):
+ return info.context["user"].is_authenticated
+ ```
"""
message: Optional[str] = None
@@ -52,16 +64,39 @@ class BasePermission(abc.ABC):
def has_permission(
self, source: Any, info: Info, **kwargs: Any
) -> Union[bool, Awaitable[bool]]:
+ """Check if the permission should be accepted.
+
+ This method should be overridden by the subclasses.
+ """
raise NotImplementedError(
"Permission classes should override has_permission method"
)
def on_unauthorized(self) -> None:
- """
- Default error raising for permissions.
- This can be overridden to customize the behavior.
- """
+ """Default error raising for permissions.
+
+ This method can be overridden to customize the error raised when the permission is not granted.
+
+ Example:
+
+ ```python
+ from strawberry.permission import BasePermission
+
+
+ class CustomPermissionError(PermissionError):
+ pass
+
+ class IsAuthenticated(BasePermission):
+ message = "User is not authenticated"
+
+ def has_permission(self, source, info, **kwargs):
+ return info.context["user"].is_authenticated
+
+ def on_unauthorized(self) -> None:
+ raise CustomPermissionError(self.message)
+ ```
+ """
# Instantiate error class
error = self.error_class(self.message or "")
@@ -91,17 +126,14 @@ class AutoDirective:
class PermissionExtension(FieldExtension):
- """
- Handles permissions for a field
- Instantiate this as a field extension with all of the permissions you want to apply
+ """Handles permissions for a field.
+
+ Instantiate this as a field extension with all of the permissions you want to apply.
- fail_silently: bool = False will return None or [] if the permission fails
- instead of raising an exception. This is only valid for optional or list fields.
+ Note:
+ Currently, this is automatically added to the field, when using field.permission_classes
- NOTE:
- Currently, this is automatically added to the field, when using
- field.permission_classes
- This is deprecated behavior, please manually add the extension to field.extensions
+ This is deprecated behaviour, please manually add the extension to field.extensions
"""
def __init__(
@@ -109,17 +141,22 @@ def __init__(
permissions: List[BasePermission],
use_directives: bool = True,
fail_silently: bool = False,
- ):
+ ) -> None:
+ """Initialize the permission extension.
+
+ Args:
+ permissions: List of permissions to apply.
+ fail_silently: If True, return None or [] instead of raising an exception.
+ This is only valid for optional or list fields.
+ use_directives: If True, add schema directives to the field.
+ """
self.permissions = permissions
self.fail_silently = fail_silently
self.return_empty_list = False
self.use_directives = use_directives
def apply(self, field: StrawberryField) -> None:
- """
- Applies all of the permission directives to the schema
- and sets up silent permissions
- """
+ """Applies all of the permission directives to the schema and sets up silent permissions."""
if self.use_directives:
field.directives.extend(
p.schema_directive for p in self.permissions if p.schema_directive
@@ -147,10 +184,7 @@ def resolve(
info: Info,
**kwargs: Dict[str, Any],
) -> Any:
- """
- Checks if the permission should be accepted and
- raises an exception if not
- """
+ """Checks if the permission should be accepted and raises an exception if not."""
for permission in self.permissions:
if not permission.has_permission(source, info, **kwargs):
return self._on_unauthorized(permission)
@@ -177,11 +211,20 @@ async def resolve_async(
@cached_property
def supports_sync(self) -> bool:
- """The Permission extension always supports async checking using await_maybe,
- but only supports sync checking if there are no async permissions"""
+ """Whether this extension can be resolved synchronously or not.
+
+ The Permission extension always supports async checking using await_maybe,
+ but only supports sync checking if there are no async permissions.
+ """
async_permissions = [
True
for permission in self.permissions
if iscoroutinefunction(permission.has_permission)
]
return len(async_permissions) == 0
+
+
+__all__ = [
+ "BasePermission",
+ "PermissionExtension",
+]
diff --git a/strawberry/printer/ast_from_value.py b/strawberry/printer/ast_from_value.py
index 0adeba83d1..7242d7b9df 100644
--- a/strawberry/printer/ast_from_value.py
+++ b/strawberry/printer/ast_from_value.py
@@ -27,7 +27,7 @@
)
import strawberry
-from strawberry.type import has_object_definition
+from strawberry.types.base import has_object_definition
if TYPE_CHECKING:
from graphql.language import ValueNode
@@ -149,3 +149,6 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
# Not reachable. All possible input types have been considered.
raise TypeError(f"Unexpected input type: {inspect(type_)}.") # pragma: no cover
+
+
+__all__ = ["ast_from_value"]
diff --git a/strawberry/printer/printer.py b/strawberry/printer/printer.py
index 15ad814356..d5ac653251 100644
--- a/strawberry/printer/printer.py
+++ b/strawberry/printer/printer.py
@@ -37,11 +37,11 @@
)
from graphql.utilities.print_schema import print_type as original_print_type
-from strawberry.custom_scalar import ScalarWrapper
-from strawberry.enum import EnumDefinition
from strawberry.schema_directive import Location, StrawberrySchemaDirective
-from strawberry.type import StrawberryContainer, has_object_definition
-from strawberry.unset import UNSET
+from strawberry.types.base import StrawberryContainer, has_object_definition
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.scalar import ScalarWrapper
+from strawberry.types.unset import UNSET
from .ast_from_value import ast_from_value
@@ -55,8 +55,8 @@
)
from graphql.type.directives import GraphQLDirective
- from strawberry.field import StrawberryField
from strawberry.schema import BaseSchema
+ from strawberry.types.field import StrawberryField
_T = TypeVar("_T")
@@ -69,23 +69,22 @@ class PrintExtras:
@overload
-def _serialize_dataclasses(value: Dict[_T, object]) -> Dict[_T, object]:
- ...
+def _serialize_dataclasses(value: Dict[_T, object]) -> Dict[_T, object]: ...
@overload
-def _serialize_dataclasses(value: Union[List[object], Tuple[object]]) -> List[object]:
- ...
+def _serialize_dataclasses(
+ value: Union[List[object], Tuple[object]],
+) -> List[object]: ...
@overload
-def _serialize_dataclasses(value: object) -> object:
- ...
+def _serialize_dataclasses(value: object) -> object: ...
def _serialize_dataclasses(value):
if dataclasses.is_dataclass(value):
- return dataclasses.asdict(value)
+ return dataclasses.asdict(value) # type: ignore
if isinstance(value, (list, tuple)):
return [_serialize_dataclasses(v) for v in value]
if isinstance(value, dict):
@@ -567,7 +566,7 @@ def print_schema(schema: BaseSchema) -> str:
None, [print_directive(directive, schema=schema) for directive in directives]
)
- def _name_getter(type_: Any):
+ def _name_getter(type_: Any) -> str:
if hasattr(type_, "name"):
return type_.name
if isinstance(type_, ScalarWrapper):
@@ -589,3 +588,6 @@ def _name_getter(type_: Any):
),
)
)
+
+
+__all__ = ["print_schema"]
diff --git a/strawberry/quart/views.py b/strawberry/quart/views.py
index c1601a6683..f841501744 100644
--- a/strawberry/quart/views.py
+++ b/strawberry/quart/views.py
@@ -17,7 +17,7 @@
class QuartHTTPRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: Request):
+ def __init__(self, request: Request) -> None:
self.request = request
@property
@@ -61,7 +61,7 @@ def __init__(
graphiql: Optional[bool] = None,
graphql_ide: Optional[GraphQL_IDE] = "graphiql",
allow_queries_via_get: bool = True,
- ):
+ ) -> None:
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
@@ -116,3 +116,6 @@ async def create_multipart_response(
"Content-type": "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json",
},
)
+
+
+__all__ = ["GraphQLView"]
diff --git a/strawberry/relay/exceptions.py b/strawberry/relay/exceptions.py
index a35f74c461..4150633d8c 100644
--- a/strawberry/relay/exceptions.py
+++ b/strawberry/relay/exceptions.py
@@ -13,7 +13,7 @@
class NodeIDAnnotationError(StrawberryException):
- def __init__(self, message: str, cls: Type):
+ def __init__(self, message: str, cls: Type) -> None:
self.cls = cls
self.message = message
@@ -41,7 +41,7 @@ def exception_source(self) -> Optional[ExceptionSource]:
class RelayWrongAnnotationError(StrawberryException):
- def __init__(self, field_name: str, cls: Type):
+ def __init__(self, field_name: str, cls: Type) -> None:
self.cls = cls
self.field_name = field_name
@@ -71,7 +71,7 @@ def exception_source(self) -> Optional[ExceptionSource]:
class RelayWrongResolverAnnotationError(StrawberryException):
- def __init__(self, field_name: str, resolver: StrawberryResolver):
+ def __init__(self, field_name: str, resolver: StrawberryResolver) -> None:
self.function = resolver.wrapped_func
self.field_name = field_name
@@ -102,3 +102,10 @@ def exception_source(self) -> Optional[ExceptionSource]:
source_finder = SourceFinder()
return source_finder.find_function_from_object(cast(Callable, self.function))
+
+
+__all__ = [
+ "NodeIDAnnotationError",
+ "RelayWrongAnnotationError",
+ "RelayWrongResolverAnnotationError",
+]
diff --git a/strawberry/relay/fields.py b/strawberry/relay/fields.py
index c5bb1d21d5..5af00700f8 100644
--- a/strawberry/relay/fields.py
+++ b/strawberry/relay/fields.py
@@ -9,6 +9,7 @@
TYPE_CHECKING,
Any,
AsyncIterator,
+ Awaitable,
Callable,
DefaultDict,
Dict,
@@ -28,21 +29,22 @@
from typing_extensions import Annotated, get_origin
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument, argument
from strawberry.extensions.field_extension import (
AsyncExtensionResolver,
FieldExtension,
SyncExtensionResolver,
)
-from strawberry.field import _RESOLVER_TYPE, StrawberryField, field
from strawberry.relay.exceptions import (
RelayWrongAnnotationError,
RelayWrongResolverAnnotationError,
)
-from strawberry.type import StrawberryList, StrawberryOptional
+from strawberry.types.arguments import StrawberryArgument, argument
+from strawberry.types.base import StrawberryList, StrawberryOptional
+from strawberry.types.field import _RESOLVER_TYPE, StrawberryField, field
from strawberry.types.fields.resolver import StrawberryResolver
+from strawberry.types.lazy_type import LazyType
from strawberry.utils.aio import asyncgen_to_list
-from strawberry.utils.typing import eval_type
+from strawberry.utils.typing import eval_type, is_generic_alias
from .types import Connection, GlobalID, Node, NodeIterableType, NodeType
@@ -60,7 +62,7 @@ def apply(self, field: StrawberryField) -> None:
if isinstance(field.type, StrawberryList):
resolver = self.get_node_list_resolver(field)
else:
- resolver = self.get_node_resolver(field)
+ resolver = self.get_node_resolver(field) # type: ignore
field.base_resolver = StrawberryResolver(resolver, type_override=field.type)
@@ -79,14 +81,16 @@ async def resolve_async(
# async extensions.
return await retval if inspect.isawaitable(retval) else retval
- def get_node_resolver(self, field: StrawberryField): # noqa: ANN201
+ def get_node_resolver(
+ self, field: StrawberryField
+ ) -> Callable[[Info, GlobalID], Union[Node, None, Awaitable[Union[Node, None]]]]:
type_ = field.type
is_optional = isinstance(type_, StrawberryOptional)
def resolver(
info: Info,
id: Annotated[GlobalID, argument(description="The ID of the object.")],
- ):
+ ) -> Union[Node, None, Awaitable[Union[Node, None]]]:
return id.resolve_type(info).resolve_node(
id.node_id,
info=info,
@@ -95,7 +99,9 @@ def resolver(
return resolver
- def get_node_list_resolver(self, field: StrawberryField): # noqa: ANN201
+ def get_node_list_resolver(
+ self, field: StrawberryField
+ ) -> Callable[[Info, List[GlobalID]], Union[List[Node], Awaitable[List[Node]]]]:
type_ = field.type
assert isinstance(type_, StrawberryList)
is_optional = isinstance(type_.of_type, StrawberryOptional)
@@ -105,7 +111,7 @@ def resolver(
ids: Annotated[
List[GlobalID], argument(description="The IDs of the objects.")
],
- ):
+ ) -> Union[List[Node], Awaitable[List[Node]]]:
nodes_map: DefaultDict[Type[Node], List[str]] = defaultdict(list)
# Store the index of the node in the list of nodes of the same type
# so that we can return them in the same order while also supporting
@@ -138,7 +144,7 @@ def resolver(
if awaitable_nodes or asyncgen_nodes:
- async def resolve(resolved=resolved_nodes): # noqa: ANN001
+ async def resolve(resolved: Any = resolved_nodes) -> List[Node]:
resolved.update(
zip(
[
@@ -149,7 +155,7 @@ async def resolve(resolved=resolved_nodes): # noqa: ANN001
await asyncio.gather(
*awaitable_nodes.values(),
*(
- asyncgen_to_list(nodes)
+ asyncgen_to_list(nodes) # type: ignore
for nodes in asyncgen_nodes.values()
),
),
@@ -222,7 +228,13 @@ def apply(self, field: StrawberryField) -> None:
]
f_type = field.type
- if not isinstance(f_type, type) or not issubclass(f_type, Connection):
+
+ if isinstance(f_type, LazyType):
+ f_type = f_type.resolve_type()
+ field.type = f_type
+
+ type_origin = get_origin(f_type) if is_generic_alias(f_type) else f_type
+ if not isinstance(type_origin, type) or not issubclass(type_origin, Connection):
raise RelayWrongAnnotationError(field.name, cast(type, field.origin))
assert field.base_resolver
@@ -331,8 +343,7 @@ def connection(
metadata: Optional[Mapping[Any, Any]] = None,
directives: Optional[Sequence[object]] = (),
extensions: List[FieldExtension] = (), # type: ignore
-) -> Any:
- ...
+) -> Any: ...
@overload
@@ -349,8 +360,7 @@ def connection(
metadata: Optional[Mapping[Any, Any]] = None,
directives: Optional[Sequence[object]] = (),
extensions: List[FieldExtension] = (), # type: ignore
-) -> StrawberryField:
- ...
+) -> StrawberryField: ...
def connection(
@@ -384,48 +394,66 @@ def connection(
case for this is to provide a filtered iterable of nodes by using some custom
filter arguments.
+ Args:
+ graphql_type: The type of the nodes in the connection. This is used to
+ determine the type of the edges and the node field in the connection.
+ resolver: The resolver for the connection. This is expected to return an
+ iterable of the expected node type.
+ name: The GraphQL name of the field.
+ is_subscription: Whether the field is a subscription.
+ description: The GraphQL description of the field.
+ permission_classes: The permission classes to apply to the field.
+ deprecation_reason: The deprecation reason of the field.
+ default: The default value of the field.
+ default_factory: The default factory of the field.
+ metadata: The metadata of the field.
+ directives: The directives to apply to the field.
+ extensions: The extensions to apply to the field.
+ init: Used only for type checking purposes.
+
Examples:
- Annotating something like this:
-
- >>> @strawberry.type
- >>> class X:
- ... some_node: relay.Connection[SomeType] = relay.connection(
- resolver=get_some_nodes,
- ... description="ABC",
- ... )
- ...
- ... @relay.connection(relay.Connection[SomeType], description="ABC")
- ... def get_some_nodes(self, age: int) -> Iterable[SomeType]:
- ... ...
-
- Will produce a query like this:
-
- ```
- query {
- someNode (
- before: String
- after: String
- first: String
- after: String
- age: Int
- ) {
- totalCount
- pageInfo {
- hasNextPage
- hasPreviousPage
- startCursor
- endCursor
- }
- edges {
- cursor
- node {
- id
- ...
- }
+ Annotating something like this:
+
+ ```python
+ @strawberry.type
+ class X:
+ some_node: relay.Connection[SomeType] = relay.connection(
+ resolver=get_some_nodes,
+ description="ABC",
+ )
+
+ @relay.connection(relay.Connection[SomeType], description="ABC")
+ def get_some_nodes(self, age: int) -> Iterable[SomeType]: ...
+ ```
+
+ Will produce a query like this:
+
+ ```graphql
+ query {
+ someNode (
+ before: String
+ after: String
+ first: String
+ after: String
+ age: Int
+ ) {
+ totalCount
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ edges {
+ cursor
+ node {
+ id
+ ...
}
}
}
- ```
+ }
+ ```
.. _Relay connections:
https://relay.dev/graphql/connections.htm
@@ -448,3 +476,6 @@ def connection(
if resolver is not None:
f = f(resolver)
return f
+
+
+__all__ = ["node", "connection"]
diff --git a/strawberry/relay/types.py b/strawberry/relay/types.py
index 7510dc642c..869e017be9 100644
--- a/strawberry/relay/types.py
+++ b/strawberry/relay/types.py
@@ -26,19 +26,27 @@
)
from typing_extensions import Annotated, Literal, Self, TypeAlias, get_args, get_origin
-from strawberry.field import field
-from strawberry.lazy_type import LazyType
-from strawberry.object_type import interface, type
-from strawberry.private import StrawberryPrivate
from strawberry.relay.exceptions import NodeIDAnnotationError
-from strawberry.type import StrawberryContainer, get_object_definition
+from strawberry.types.base import (
+ StrawberryContainer,
+ StrawberryObjectDefinition,
+ get_object_definition,
+)
+from strawberry.types.field import field
from strawberry.types.info import Info # noqa: TCH001
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.object_type import interface, type
+from strawberry.types.private import StrawberryPrivate
from strawberry.utils.aio import aenumerate, aislice, resolve_awaitable
from strawberry.utils.inspect import in_async_context
from strawberry.utils.typing import eval_type, is_classvar
-from .utils import from_base64, to_base64
+from .utils import (
+ SliceMetadata,
+ from_base64,
+ should_resolve_list_connection_edges,
+ to_base64,
+)
if TYPE_CHECKING:
from strawberry.scalars import ID
@@ -86,7 +94,7 @@ class GlobalID:
type_name: str
node_id: str
- def __post_init__(self):
+ def __post_init__(self) -> None:
if not isinstance(self.type_name, str):
raise GlobalIDValueError(
f"type_name is expected to be a string, found {self.type_name!r}"
@@ -96,7 +104,7 @@ def __post_init__(self):
f"node_id is expected to be a string, found {self.node_id!r}"
)
- def __str__(self):
+ def __str__(self) -> str:
return to_base64(self.type_name, self.node_id)
@classmethod
@@ -130,8 +138,7 @@ async def resolve_node(
*,
required: Literal[True] = ...,
ensure_type: Type[_T],
- ) -> _T:
- ...
+ ) -> _T: ...
@overload
async def resolve_node(
@@ -140,8 +147,7 @@ async def resolve_node(
*,
required: Literal[True],
ensure_type: None = ...,
- ) -> Node:
- ...
+ ) -> Node: ...
@overload
async def resolve_node(
@@ -150,8 +156,7 @@ async def resolve_node(
*,
required: bool = ...,
ensure_type: None = ...,
- ) -> Optional[Node]:
- ...
+ ) -> Optional[Node]: ...
async def resolve_node(self, info, *, required=False, ensure_type=None) -> Any:
"""Resolve the type name and node id info to the node itself.
@@ -180,7 +185,7 @@ async def resolve_node(self, info, *, required=False, ensure_type=None) -> Any:
"""
n_type = self.resolve_type(info)
- node = cast(
+ node: Node | Awaitable[Node] = cast(
Awaitable[Node],
n_type.resolve_node(
self.node_id,
@@ -218,14 +223,22 @@ def resolve_type(self, info: Info) -> Type[Node]:
"""
type_def = info.schema.get_type_by_name(self.type_name)
- assert isinstance(type_def, StrawberryObjectDefinition)
+ if not isinstance(type_def, StrawberryObjectDefinition):
+ raise GlobalIDValueError(
+ f"Cannot resolve. GlobalID requires a GraphQL type, "
+ f"received `{self.type_name}`."
+ )
origin = (
type_def.origin.resolve_type
if isinstance(type_def.origin, LazyType)
else type_def.origin
)
- assert issubclass(origin, Node)
+ if not issubclass(origin, Node):
+ raise GlobalIDValueError(
+ f"Cannot resolve. GlobalID requires a GraphQL Node type, "
+ f"received `{self.type_name}`."
+ )
return origin
@overload
@@ -235,8 +248,7 @@ def resolve_node_sync(
*,
required: Literal[True] = ...,
ensure_type: Type[_T],
- ) -> _T:
- ...
+ ) -> _T: ...
@overload
def resolve_node_sync(
@@ -245,8 +257,7 @@ def resolve_node_sync(
*,
required: Literal[True],
ensure_type: None = ...,
- ) -> Node:
- ...
+ ) -> Node: ...
@overload
def resolve_node_sync(
@@ -255,8 +266,7 @@ def resolve_node_sync(
*,
required: bool = ...,
ensure_type: None = ...,
- ) -> Optional[Node]:
- ...
+ ) -> Optional[Node]: ...
def resolve_node_sync(self, info, *, required=False, ensure_type=None) -> Any:
"""Resolve the type name and node id info to the node itself.
@@ -312,9 +322,14 @@ class NodeIDPrivate(StrawberryPrivate):
The `Node` interface will automatically create and resolve GlobalIDs
based on the field annotated with `NodeID`. e.g:
- >>> @strawberry.type
- ... class Fruit(Node):
- ... code: NodeID[str]
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Fruit(Node):
+ code: NodeID[str]
+ ```
In this case, `code` will be used to generate a global ID in the
format `Fruit:` and will be exposed as `id: GlobalID!` in the
@@ -345,17 +360,20 @@ class Node:
single node id.
Example:
+ ```python
+ import strawberry
+
- >>> @strawberry.type
- ... class Fruit(Node):
- ... id: NodeID[int]
- ... name: str
- ...
- ... @classmethod
- ... def resolve_nodes(cls, *, info, node_ids, required=False):
- ... # Return an iterable of fruits in here
- ... ...
+ @strawberry.type
+ class Fruit(strawberry.relay.Node):
+ id: strawberry.relay.NodeID[int]
+ name: str
+ @classmethod
+ def resolve_nodes(cls, *, info, node_ids, required=False):
+ # Return an iterable of fruits in here
+ ...
+ ```
"""
_id_attr: ClassVar[Optional[str]] = None
@@ -460,10 +478,8 @@ def resolve_id(
You can override this method to provide a custom implementation.
Args:
- info:
- The strawberry execution info resolve the type name from
- root:
- The node to resolve
+ info: The strawberry execution info resolve the type name from.
+ root: The node to resolve.
Returns:
The resolved id (which is expected to be str)
@@ -485,8 +501,7 @@ def resolve_nodes(
info: Info,
node_ids: Iterable[str],
required: Literal[True],
- ) -> AwaitableOrValue[Iterable[Self]]:
- ...
+ ) -> AwaitableOrValue[Iterable[Self]]: ...
@overload
@classmethod
@@ -496,8 +511,7 @@ def resolve_nodes(
info: Info,
node_ids: Iterable[str],
required: Literal[False] = ...,
- ) -> AwaitableOrValue[Iterable[Optional[Self]]]:
- ...
+ ) -> AwaitableOrValue[Iterable[Optional[Self]]]: ...
@overload
@classmethod
@@ -510,8 +524,7 @@ def resolve_nodes(
) -> Union[
AwaitableOrValue[Iterable[Self]],
AwaitableOrValue[Iterable[Optional[Self]]],
- ]:
- ...
+ ]: ...
@classmethod
def resolve_nodes(
@@ -531,12 +544,9 @@ def resolve_nodes(
returned as `None`.
Args:
- info:
- The strawberry execution info resolve the type name from
- node_ids:
- List of node ids that should be returned
- required:
- If `True`, all `node_ids` requested must exist. If they don't,
+ info: The strawberry execution info resolve the type name from.
+ node_ids: List of node ids that should be returned.
+ required: If `True`, all `node_ids` requested must exist. If they don't,
an error must be raised. If `False`, missing nodes should be
returned as `None`. It only makes sense when passing a list of
`node_ids`, otherwise it will should ignored.
@@ -555,8 +565,7 @@ def resolve_node(
*,
info: Info,
required: Literal[True],
- ) -> AwaitableOrValue[Self]:
- ...
+ ) -> AwaitableOrValue[Self]: ...
@overload
@classmethod
@@ -566,8 +575,7 @@ def resolve_node(
*,
info: Info,
required: Literal[False] = ...,
- ) -> AwaitableOrValue[Optional[Self]]:
- ...
+ ) -> AwaitableOrValue[Optional[Self]]: ...
@overload
@classmethod
@@ -577,8 +585,7 @@ def resolve_node(
*,
info: Info,
required: bool,
- ) -> AwaitableOrValue[Optional[Self]]:
- ...
+ ) -> AwaitableOrValue[Optional[Self]]: ...
@classmethod
def resolve_node(
@@ -594,18 +601,14 @@ def resolve_node(
a single node id.
Args:
- info:
- The strawberry execution info resolve the type name from
- node_id:
- The id of the node to be retrieved
- required:
- if the node is required or not to exist. If not, then None
+ info: The strawberry execution info resolve the type name from.
+ node_id: The id of the node to be retrieved.
+ required: if the node is required or not to exist. If not, then None
should be returned if it doesn't exist. Otherwise an exception
should be raised.
Returns:
The resolved node or None if it was not found
-
"""
retval = cls.resolve_nodes(info=info, node_ids=[node_id], required=required)
@@ -628,7 +631,6 @@ class PageInfo:
When paginating backwards, the cursor to continue
end_cursor:
When paginating forwards, the cursor to continue
-
"""
has_next_page: bool = field(
@@ -654,15 +656,10 @@ class Edge(Generic[NodeType]):
A cursor for use in pagination
node:
The item at the end of the edge
-
"""
- cursor: str = field(
- description="A cursor for use in pagination",
- )
- node: NodeType = field(
- description="The item at the end of the edge",
- )
+ cursor: str = field(description="A cursor for use in pagination")
+ node: NodeType = field(description="The item at the end of the edge")
@classmethod
def resolve_edge(cls, node: NodeType, *, cursor: Any = None) -> Self:
@@ -681,11 +678,9 @@ class Connection(Generic[NodeType]):
"""
- page_info: PageInfo = field(
- description="Pagination data for this connection",
- )
+ page_info: PageInfo = field(description="Pagination data for this connection")
edges: List[Edge[NodeType]] = field(
- description="Contains the nodes in this connection",
+ description="Contains the nodes in this connection"
)
@classmethod
@@ -701,9 +696,11 @@ def resolve_node(cls, node: Any, *, info: Info, **kwargs: Any) -> NodeType:
Args:
node:
The resolved node which should return an instance of this
- connection's `NodeType`
+ connection's `NodeType`.
info:
- The strawberry execution info resolve the type name from
+ The strawberry execution info resolve the type name from.
+ **kwargs:
+ Additional arguments passed to the resolver.
"""
return node
@@ -726,18 +723,13 @@ def resolve_connection(
on `first`/`last`/`before`/`after` arguments.
Args:
- info:
- The strawberry execution info resolve the type name from
- nodes:
- An iterable/iteretor of nodes to paginate
- before:
- Returns the items in the list that come before the specified cursor
- after:
- Returns the items in the list that come after the specified cursor
- first:
- Returns the first n items from the list
- last:
- Returns the items in the list that come after the specified cursor
+ info: The strawberry execution info resolve the type name from.
+ nodes: An iterable/iteretor of nodes to paginate.
+ before: Returns the items in the list that come before the specified cursor.
+ after: Returns the items in the list that come after the specified cursor.
+ first: Returns the first n items from the list.
+ last: Returns the items in the list that come after the specified cursor.
+ kwargs: Additional arguments passed to the resolver.
Returns:
The resolved `Connection`
@@ -758,11 +750,9 @@ class ListConnection(Connection[NodeType]):
"""
- page_info: PageInfo = field(
- description="Pagination data for this connection",
- )
+ page_info: PageInfo = field(description="Pagination data for this connection")
edges: List[Edge[NodeType]] = field(
- description="Contains the nodes in this connection",
+ description="Contains the nodes in this connection"
)
@classmethod
@@ -782,81 +772,27 @@ def resolve_connection(
This uses the described Relay Pagination algorithm_
Args:
- info:
- The strawberry execution info resolve the type name from
- nodes:
- An iterable/iteretor of nodes to paginate
- before:
- Returns the items in the list that come before the specified cursor
- after:
- Returns the items in the list that come after the specified cursor
- first:
- Returns the first n items from the list
- last:
- Returns the items in the list that come after the specified cursor
+ info: The strawberry execution info resolve the type name from.
+ nodes: An iterable/iteretor of nodes to paginate.
+ before: Returns the items in the list that come before the specified cursor.
+ after: Returns the items in the list that come after the specified cursor.
+ first: Returns the first n items from the list.
+ last: Returns the items in the list that come after the specified cursor.
+ kwargs: Additional arguments passed to the resolver.
Returns:
The resolved `Connection`
.. _Relay Pagination algorithm:
https://relay.dev/graphql/connections.htm#sec-Pagination-algorithm
-
"""
- max_results = info.schema.config.relay_max_results
- start = 0
- end: Optional[int] = None
-
- if after:
- after_type, after_parsed = from_base64(after)
- if after_type != PREFIX:
- # When the base64 hash doesnt exist, the after_type seems to return
- # arrayconnEction instead of PREFIX. Let's raise a predictable
- # instead of "An unknown error occurred."
- raise TypeError("Argument 'after' contains a non-existing value.")
-
- start = int(after_parsed) + 1
- if before:
- before_type, before_parsed = from_base64(before)
- if before_type != PREFIX:
- # When the base64 hash doesnt exist, the after_type seems to return
- # arrayconnEction instead of PREFIX. Let's raise a predictable
- # instead of "An unknown error occurred.
- raise TypeError("Argument 'before' contains a non-existing value.")
- end = int(before_parsed)
-
- if isinstance(first, int):
- if first < 0:
- raise ValueError("Argument 'first' must be a non-negative integer.")
-
- if first > max_results:
- raise ValueError(
- f"Argument 'first' cannot be higher than {max_results}."
- )
-
- if end is not None:
- start = max(0, end - 1)
-
- end = start + first
- if isinstance(last, int):
- if last < 0:
- raise ValueError("Argument 'last' must be a non-negative integer.")
-
- if last > max_results:
- raise ValueError(
- f"Argument 'last' cannot be higher than {max_results}."
- )
-
- if end is not None:
- start = max(start, end - last)
- else:
- end = sys.maxsize
-
- if end is None:
- end = start + max_results
-
- expected = end - start if end != sys.maxsize else None
- # Overfetch by 1 to check if we have a next result
- overfetch = end + 1 if end != sys.maxsize else end
+ slice_metadata = SliceMetadata.from_arguments(
+ info,
+ before=before,
+ after=after,
+ first=first,
+ last=last,
+ )
type_def = get_object_definition(cls)
assert type_def
@@ -871,19 +807,21 @@ def resolve_connection(
if isinstance(nodes, (AsyncIterator, AsyncIterable)) and in_async_context():
- async def resolver():
+ async def resolver() -> Self:
try:
iterator = cast(
Union[AsyncIterator[NodeType], AsyncIterable[NodeType]],
- cast(Sequence, nodes)[start:overfetch],
+ cast(Sequence, nodes)[
+ slice_metadata.start : slice_metadata.overfetch
+ ],
)
except TypeError:
# TODO: Why mypy isn't narrowing this based on the if above?
assert isinstance(nodes, (AsyncIterator, AsyncIterable))
iterator = aislice(
nodes,
- start,
- overfetch,
+ slice_metadata.start,
+ slice_metadata.overfetch,
)
# The slice above might return an object that now is not async
@@ -892,7 +830,7 @@ async def resolver():
edges: List[Edge] = [
edge_class.resolve_edge(
cls.resolve_node(v, info=info, **kwargs),
- cursor=start + i,
+ cursor=slice_metadata.start + i,
)
async for i, v in aenumerate(iterator)
]
@@ -900,17 +838,20 @@ async def resolver():
edges: List[Edge] = [ # type: ignore[no-redef]
edge_class.resolve_edge(
cls.resolve_node(v, info=info, **kwargs),
- cursor=start + i,
+ cursor=slice_metadata.start + i,
)
for i, v in enumerate(iterator)
]
- has_previous_page = start > 0
- if expected is not None and len(edges) == expected + 1:
+ has_previous_page = slice_metadata.start > 0
+ if (
+ slice_metadata.expected is not None
+ and len(edges) == slice_metadata.expected + 1
+ ):
# Remove the overfetched result
edges = edges[:-1]
has_next_page = True
- elif end == sys.maxsize:
+ elif slice_metadata.end == sys.maxsize:
# Last was asked without any after/before
assert last is not None
original_len = len(edges)
@@ -935,30 +876,44 @@ async def resolver():
try:
iterator = cast(
Union[Iterator[NodeType], Iterable[NodeType]],
- cast(Sequence, nodes)[start:overfetch],
+ cast(Sequence, nodes)[slice_metadata.start : slice_metadata.overfetch],
)
except TypeError:
assert isinstance(nodes, (Iterable, Iterator))
iterator = itertools.islice(
nodes,
- start,
- overfetch,
+ slice_metadata.start,
+ slice_metadata.overfetch,
+ )
+
+ if not should_resolve_list_connection_edges(info):
+ return cls(
+ edges=[],
+ page_info=PageInfo(
+ start_cursor=None,
+ end_cursor=None,
+ has_previous_page=False,
+ has_next_page=False,
+ ),
)
edges = [
edge_class.resolve_edge(
cls.resolve_node(v, info=info, **kwargs),
- cursor=start + i,
+ cursor=slice_metadata.start + i,
)
for i, v in enumerate(iterator)
]
- has_previous_page = start > 0
- if expected is not None and len(edges) == expected + 1:
+ has_previous_page = slice_metadata.start > 0
+ if (
+ slice_metadata.expected is not None
+ and len(edges) == slice_metadata.expected + 1
+ ):
# Remove the overfetched result
edges = edges[:-1]
has_next_page = True
- elif end == sys.maxsize:
+ elif slice_metadata.end == sys.maxsize:
# Last was asked without any after/before
assert last is not None
original_len = len(edges)
@@ -977,3 +932,20 @@ async def resolver():
has_next_page=has_next_page,
),
)
+
+
+__all__ = [
+ "GlobalID",
+ "GlobalIDValueError",
+ "Node",
+ "NodeID",
+ "NodeIDAnnotationError",
+ "NodeIDPrivate",
+ "NodeIterableType",
+ "NodeType",
+ "PREFIX",
+ "Connection",
+ "Edge",
+ "PageInfo",
+ "ListConnection",
+]
diff --git a/strawberry/relay/utils.py b/strawberry/relay/utils.py
index 2232429b5c..c25bd537b0 100644
--- a/strawberry/relay/utils.py
+++ b/strawberry/relay/utils.py
@@ -1,8 +1,16 @@
+from __future__ import annotations
+
import base64
-from typing import Any, Tuple, Union
-from typing_extensions import assert_never
+import dataclasses
+import sys
+from typing import TYPE_CHECKING, Any, Tuple, Union
+from typing_extensions import Self, assert_never
+
+from strawberry.types.base import StrawberryObjectDefinition
+from strawberry.types.nodes import InlineFragment, Selection
-from strawberry.types.types import StrawberryObjectDefinition
+if TYPE_CHECKING:
+ from strawberry.types.info import Info
def from_base64(value: str) -> Tuple[str, str]:
@@ -41,7 +49,7 @@ def to_base64(type_: Union[str, type, StrawberryObjectDefinition], node_id: Any)
The node id itself
Returns:
- A tuple of (TypeName, NodeID).
+ A GlobalID, which is a string resulting from base64 encoding :.
Raises:
ValueError:
@@ -61,3 +69,130 @@ def to_base64(type_: Union[str, type, StrawberryObjectDefinition], node_id: Any)
raise ValueError(f"{type_} is not a valid GraphQL type or name") from e
return base64.b64encode(f"{type_name}:{node_id}".encode()).decode()
+
+
+def should_resolve_list_connection_edges(info: Info) -> bool:
+ """Check if the user requested to resolve the `edges` field of a connection.
+
+ Args:
+ info:
+ The strawberry execution info resolve the type name from
+
+ Returns:
+ True if the user requested to resolve the `edges` field of a connection, False otherwise.
+
+ """
+ resolve_for_field_names = {"edges", "pageInfo"}
+
+ def _check_selection(selection: Selection) -> bool:
+ """Recursively inspect the selection to check if the user requested to resolve the `edges` field.
+
+ Args:
+ selection (Selection): The selection to check.
+
+ Returns:
+ bool: True if the user requested to resolve the `edges` field of a connection, False otherwise.
+ """
+ if (
+ not isinstance(selection, InlineFragment)
+ and selection.name in resolve_for_field_names
+ ):
+ return True
+ if selection.selections:
+ return any(
+ _check_selection(selection) for selection in selection.selections
+ )
+ return False
+
+ for selection_field in info.selected_fields:
+ for selection in selection_field.selections:
+ if _check_selection(selection):
+ return True
+ return False
+
+
+@dataclasses.dataclass
+class SliceMetadata:
+ start: int
+ end: int
+ expected: int | None
+
+ @property
+ def overfetch(self) -> int:
+ # Overfetch by 1 to check if we have a next result
+ return self.end + 1 if self.end != sys.maxsize else self.end
+
+ @classmethod
+ def from_arguments(
+ cls,
+ info: Info,
+ *,
+ before: str | None = None,
+ after: str | None = None,
+ first: int | None = None,
+ last: int | None = None,
+ ) -> Self:
+ """Get the slice metadata to use on ListConnection."""
+ from strawberry.relay.types import PREFIX
+
+ max_results = info.schema.config.relay_max_results
+ start = 0
+ end: int | None = None
+
+ if after:
+ after_type, after_parsed = from_base64(after)
+ if after_type != PREFIX:
+ raise TypeError("Argument 'after' contains a non-existing value.")
+
+ start = int(after_parsed) + 1
+ if before:
+ before_type, before_parsed = from_base64(before)
+ if before_type != PREFIX:
+ raise TypeError("Argument 'before' contains a non-existing value.")
+ end = int(before_parsed)
+
+ if isinstance(first, int):
+ if first < 0:
+ raise ValueError("Argument 'first' must be a non-negative integer.")
+
+ if first > max_results:
+ raise ValueError(
+ f"Argument 'first' cannot be higher than {max_results}."
+ )
+
+ if end is not None:
+ start = max(0, end - 1)
+
+ end = start + first
+ if isinstance(last, int):
+ if last < 0:
+ raise ValueError("Argument 'last' must be a non-negative integer.")
+
+ if last > max_results:
+ raise ValueError(
+ f"Argument 'last' cannot be higher than {max_results}."
+ )
+
+ if end is not None:
+ start = max(start, end - last)
+ else:
+ end = sys.maxsize
+
+ if end is None:
+ end = start + max_results
+
+ expected = end - start if end != sys.maxsize else None
+
+ return cls(
+ start=start,
+ end=end,
+ expected=expected,
+ )
+
+
+__all__ = [
+ "from_base64",
+ "to_base64",
+ "should_resolve_list_connection_edges",
+ "SliceMetadata",
+]
diff --git a/strawberry/resolvers.py b/strawberry/resolvers.py
index 9e034bccf5..5eac1803d2 100644
--- a/strawberry/resolvers.py
+++ b/strawberry/resolvers.py
@@ -4,3 +4,6 @@
def is_default_resolver(func: Callable[..., Any]) -> bool:
"""Check whether the function is a default resolver or a user provided one."""
return getattr(func, "_is_default", False)
+
+
+__all__ = ["is_default_resolver"]
diff --git a/strawberry/sanic/context.py b/strawberry/sanic/context.py
index 193fa52762..d828282fe8 100644
--- a/strawberry/sanic/context.py
+++ b/strawberry/sanic/context.py
@@ -22,3 +22,6 @@ def __getattr__(self, key: str) -> object: # type: ignore
)
return super().__getitem__(key)
+
+
+__all__ = ["StrawberrySanicContext"]
diff --git a/strawberry/sanic/utils.py b/strawberry/sanic/utils.py
index 5b3becc901..7c57b02664 100644
--- a/strawberry/sanic/utils.py
+++ b/strawberry/sanic/utils.py
@@ -10,18 +10,17 @@
def convert_request_to_files_dict(request: Request) -> Dict[str, Any]:
- """
- request.files has the following format, even if only a single file is uploaded:
+ """Converts the request.files dictionary to a dictionary of BytesIO objects.
+
+ `request.files` has the following format, even if only a single file is uploaded:
+ ```python
{
- 'textFile': [
- sanic.request.File(
- type='text/plain',
- body=b'strawberry',
- name='textFile.txt'
- )
+ "textFile": [
+ sanic.request.File(type="text/plain", body=b"strawberry", name="textFile.txt")
]
}
+ ```
Note that the dictionary entries are lists.
"""
@@ -38,3 +37,6 @@ def convert_request_to_files_dict(request: Request) -> Dict[str, Any]:
files_dict[field_name] = BytesIO(file_list[0].body)
return files_dict
+
+
+__all__ = ["convert_request_to_files_dict"]
diff --git a/strawberry/sanic/views.py b/strawberry/sanic/views.py
index f7f44cd7ce..a45d703056 100644
--- a/strawberry/sanic/views.py
+++ b/strawberry/sanic/views.py
@@ -8,7 +8,6 @@
AsyncGenerator,
Callable,
Dict,
- List,
Mapping,
Optional,
Type,
@@ -35,7 +34,7 @@
class SanicHTTPRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: Request):
+ def __init__(self, request: Request) -> None:
self.request = request
@property
@@ -45,12 +44,7 @@ def query_params(self) -> QueryParams:
# the keys are the unique variable names and the values are lists
# of values for each variable name. To ensure consistency, we're
# enforcing the use of the first value in each list.
-
- args = cast(
- Dict[str, Optional[List[str]]],
- self.request.get_args(keep_blank_values=True),
- )
-
+ args = self.request.get_args(keep_blank_values=True)
return {k: args.get(k, None) for k in args}
@property
@@ -80,8 +74,7 @@ class GraphQLView(
AsyncBaseHTTPView[Request, HTTPResponse, TemporalResponse, Context, RootValue],
HTTPMethodView,
):
- """
- Class based view to handle GraphQL HTTP Requests
+ """Class based view to handle GraphQL HTTP Requests.
Args:
schema: strawberry.Schema
@@ -109,7 +102,7 @@ def __init__(
allow_queries_via_get: bool = True,
json_encoder: Optional[Type[json.JSONEncoder]] = None,
json_dumps_params: Optional[Dict[str, Any]] = None,
- ):
+ ) -> None:
self.schema = schema
self.allow_queries_via_get = allow_queries_via_get
self.json_encoder = json_encoder
@@ -203,3 +196,6 @@ async def create_multipart_response(
await response.eof()
return response
+
+
+__all__ = ["GraphQLView"]
diff --git a/strawberry/scalars.py b/strawberry/scalars.py
index 8dc04f6a37..f87f81889a 100644
--- a/strawberry/scalars.py
+++ b/strawberry/scalars.py
@@ -3,23 +3,24 @@
import base64
from typing import TYPE_CHECKING, Any, Dict, NewType, Union
-from .custom_scalar import scalar
+from strawberry.types.scalar import scalar
if TYPE_CHECKING:
- from .custom_scalar import ScalarDefinition, ScalarWrapper
+ from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
ID = NewType("ID", str)
+"""Represent the GraphQL `ID` scalar type."""
JSON = scalar(
NewType("JSON", object), # mypy doesn't like `NewType("name", Any)`
description=(
"The `JSON` scalar type represents JSON values as specified by "
"[ECMA-404]"
- "(http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)."
+ "(https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf)."
),
specified_by_url=(
- "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf"
+ "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
),
serialize=lambda v: v,
parse_value=lambda v: v,
@@ -62,3 +63,6 @@ def is_scalar(
return True
return hasattr(annotation, "_scalar_definition")
+
+
+__all__ = ["ID", "JSON", "Base16", "Base32", "Base64", "is_scalar"]
diff --git a/strawberry/schema/base.py b/strawberry/schema/base.py
index c1b2a28ecc..9e040d3856 100644
--- a/strawberry/schema/base.py
+++ b/strawberry/schema/base.py
@@ -10,18 +10,18 @@
if TYPE_CHECKING:
from graphql import GraphQLError
- from strawberry.custom_scalar import ScalarDefinition
from strawberry.directive import StrawberryDirective
- from strawberry.enum import EnumDefinition
from strawberry.schema.schema_converter import GraphQLCoreConverter
from strawberry.types import (
ExecutionContext,
ExecutionResult,
SubscriptionExecutionResult,
)
+ from strawberry.types.base import StrawberryObjectDefinition
+ from strawberry.types.enum import EnumDefinition
from strawberry.types.graphql import OperationType
- from strawberry.types.types import StrawberryObjectDefinition
- from strawberry.union import StrawberryUnion
+ from strawberry.types.scalar import ScalarDefinition
+ from strawberry.types.union import StrawberryUnion
from .config import StrawberryConfig
@@ -91,6 +91,25 @@ def get_directive_by_name(self, graphql_name: str) -> Optional[StrawberryDirecti
def as_str(self) -> str:
raise NotImplementedError
+ @staticmethod
+ def remove_field_suggestion(error: GraphQLError) -> None:
+ if (
+ error.message.startswith("Cannot query field")
+ and "Did you mean" in error.message
+ ):
+ error.message = error.message.split("Did you mean")[0].strip()
+
+ def _process_errors(
+ self,
+ errors: List[GraphQLError],
+ execution_context: Optional[ExecutionContext] = None,
+ ) -> None:
+ if self.config.disable_field_suggestions:
+ for error in errors:
+ self.remove_field_suggestion(error)
+
+ self.process_errors(errors, execution_context)
+
def process_errors(
self,
errors: List[GraphQLError],
@@ -98,3 +117,6 @@ def process_errors(
) -> None:
for error in errors:
StrawberryLogger.error(error, execution_context)
+
+
+__all__ = ["BaseSchema"]
diff --git a/strawberry/schema/compat.py b/strawberry/schema/compat.py
index e71d2379d2..2831eab78a 100644
--- a/strawberry/schema/compat.py
+++ b/strawberry/schema/compat.py
@@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Dict, Union
from strawberry.scalars import is_scalar as is_strawberry_scalar
-from strawberry.type import StrawberryType, has_object_definition
+from strawberry.types.base import StrawberryType, has_object_definition
# TypeGuard is only available in typing_extensions => 3.10, we don't want
# to force updates to the typing_extensions package so we only use it when
@@ -12,7 +12,7 @@
if TYPE_CHECKING:
from typing_extensions import TypeGuard
- from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
+ from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
def is_input_type(type_: Union[StrawberryType, type]) -> TypeGuard[type]:
@@ -51,3 +51,13 @@ def is_graphql_generic(type_: Union[StrawberryType, type]) -> bool:
return type_.is_graphql_generic
return False
+
+
+__all__ = [
+ "is_input_type",
+ "is_interface_type",
+ "is_scalar",
+ "is_enum",
+ "is_schema_directive",
+ "is_graphql_generic",
+]
diff --git a/strawberry/schema/config.py b/strawberry/schema/config.py
index 18afbb7940..2c5a37eb53 100644
--- a/strawberry/schema/config.py
+++ b/strawberry/schema/config.py
@@ -12,10 +12,14 @@ class StrawberryConfig:
name_converter: NameConverter = field(default_factory=NameConverter)
default_resolver: Callable[[Any, str], object] = getattr
relay_max_results: int = 100
+ disable_field_suggestions: bool = False
def __post_init__(
self,
auto_camel_case: bool,
- ):
+ ) -> None:
if auto_camel_case is not None:
self.name_converter.auto_camel_case = auto_camel_case
+
+
+__all__ = ["StrawberryConfig"]
diff --git a/strawberry/schema/exceptions.py b/strawberry/schema/exceptions.py
index 85f21df459..21560da738 100644
--- a/strawberry/schema/exceptions.py
+++ b/strawberry/schema/exceptions.py
@@ -2,7 +2,7 @@
class InvalidOperationTypeError(Exception):
- def __init__(self, operation_type: OperationType):
+ def __init__(self, operation_type: OperationType) -> None:
self.operation_type = operation_type
def as_http_error_reason(self, method: str) -> str:
@@ -13,3 +13,6 @@ def as_http_error_reason(self, method: str) -> str:
}[self.operation_type]
return f"{operation_type} are not allowed when using {method}"
+
+
+__all__ = ["InvalidOperationTypeError"]
diff --git a/strawberry/schema/execute.py b/strawberry/schema/execute.py
index 44a7c665c2..8ccff111c5 100644
--- a/strawberry/schema/execute.py
+++ b/strawberry/schema/execute.py
@@ -4,7 +4,6 @@
from inspect import isawaitable
from typing import (
TYPE_CHECKING,
- Awaitable,
Callable,
Iterable,
List,
@@ -14,17 +13,18 @@
Type,
TypedDict,
Union,
- cast,
)
-from graphql import GraphQLError, parse, subscribe
+from graphql import GraphQLError, parse
from graphql import execute as original_execute
+from graphql.execution.subscribe import subscribe
from graphql.validation import validate
from strawberry.exceptions import MissingQueryError
from strawberry.extensions.runner import SchemaExtensionsRunner
+from strawberry.schema.validation_rules.one_of import OneOfInputValidationRule
from strawberry.types import ExecutionResult
-from strawberry.types.graphql import OperationType
+from strawberry.types.execution import SubscriptionExecutionResult
from .exceptions import InvalidOperationTypeError
@@ -32,13 +32,13 @@
from typing_extensions import NotRequired, Unpack
from graphql import ExecutionContext as GraphQLExecutionContext
- from graphql import ExecutionResult as GraphQLExecutionResult
from graphql import GraphQLSchema
from graphql.language import DocumentNode
from graphql.validation import ASTValidationRule
from strawberry.extensions import SchemaExtension
- from strawberry.types import ExecutionContext, SubscriptionExecutionResult
+ from strawberry.types import ExecutionContext
+ from strawberry.types.graphql import OperationType
# duplicated because of https://github.com/mkdocstrings/griffe-typingdoc/issues/7
@@ -55,6 +55,10 @@ def validate_document(
document: DocumentNode,
validation_rules: Tuple[Type[ASTValidationRule], ...],
) -> List[GraphQLError]:
+ validation_rules = (
+ *validation_rules,
+ OneOfInputValidationRule,
+ )
return validate(
schema,
document,
@@ -88,89 +92,93 @@ async def execute(
extensions=list(extensions),
)
- async with extensions_runner.operation():
- # Note: In graphql-core the schema would be validated here but in
- # Strawberry we are validating it at initialisation time instead
- if not execution_context.query:
- raise MissingQueryError()
-
- async with extensions_runner.parsing():
- try:
- if not execution_context.graphql_document:
- execution_context.graphql_document = parse_document(
- execution_context.query, **execution_context.parse_options
- )
-
- except GraphQLError as error:
- execution_context.errors = [error]
- process_errors([error], execution_context)
- return ExecutionResult(
- data=None,
- errors=[error],
- extensions=await extensions_runner.get_extensions_results(),
- )
-
- except Exception as error: # pragma: no cover
- error = GraphQLError(str(error), original_error=error)
-
- execution_context.errors = [error]
- process_errors([error], execution_context)
-
- return ExecutionResult(
- data=None,
- errors=[error],
- extensions=await extensions_runner.get_extensions_results(),
- )
-
- if execution_context.operation_type not in allowed_operation_types:
- raise InvalidOperationTypeError(execution_context.operation_type)
-
- async with extensions_runner.validation():
- _run_validation(execution_context)
- if execution_context.errors:
- process_errors(execution_context.errors, execution_context)
- return ExecutionResult(data=None, errors=execution_context.errors)
-
- async with extensions_runner.executing():
- if not execution_context.result:
- if execution_context.operation_type == OperationType.SUBSCRIPTION:
- # TODO: should we process errors here?
- # TODO: make our own wrapper?
- return await subscribe( # type: ignore
- schema,
- execution_context.graphql_document,
- root_value=execution_context.root_value,
- context_value=execution_context.context,
- variable_values=execution_context.variables,
- operation_name=execution_context.operation_name,
- )
- else:
- result = original_execute(
- schema,
- execution_context.graphql_document,
- root_value=execution_context.root_value,
- middleware=extensions_runner.as_middleware_manager(),
- variable_values=execution_context.variables,
- operation_name=execution_context.operation_name,
- context_value=execution_context.context,
- execution_context_class=execution_context_class,
+ try:
+ async with extensions_runner.operation():
+ # Note: In graphql-core the schema would be validated here but in
+ # Strawberry we are validating it at initialisation time instead
+ if not execution_context.query:
+ raise MissingQueryError()
+
+ async with extensions_runner.parsing():
+ try:
+ if not execution_context.graphql_document:
+ execution_context.graphql_document = parse_document(
+ execution_context.query, **execution_context.parse_options
+ )
+
+ except GraphQLError as exc:
+ execution_context.errors = [exc]
+ process_errors([exc], execution_context)
+ return ExecutionResult(
+ data=None,
+ errors=[exc],
+ extensions=await extensions_runner.get_extensions_results(),
)
- if isawaitable(result):
- result = await cast(Awaitable["GraphQLExecutionResult"], result)
-
- result = cast("GraphQLExecutionResult", result)
- execution_context.result = result
- # Also set errors on the execution_context so that it's easier
- # to access in extensions
- if result.errors:
- execution_context.errors = result.errors
-
- # Run the `Schema.process_errors` function here before
- # extensions have a chance to modify them (see the MaskErrors
- # extension). That way we can log the original errors but
- # only return a sanitised version to the client.
- process_errors(result.errors, execution_context)
+ if execution_context.operation_type not in allowed_operation_types:
+ raise InvalidOperationTypeError(execution_context.operation_type)
+
+ async with extensions_runner.validation():
+ _run_validation(execution_context)
+ if execution_context.errors:
+ process_errors(execution_context.errors, execution_context)
+ return ExecutionResult(data=None, errors=execution_context.errors)
+
+ async with extensions_runner.executing():
+ if not execution_context.result:
+ if execution_context.operation_type == OperationType.SUBSCRIPTION:
+ # TODO: should we process errors here?
+ # TODO: make our own wrapper?
+ return await subscribe( # type: ignore
+ schema,
+ execution_context.graphql_document,
+ root_value=execution_context.root_value,
+ context_value=execution_context.context,
+ variable_values=execution_context.variables,
+ operation_name=execution_context.operation_name,
+ )
+ else:
+ result = original_execute(
+ schema,
+ execution_context.graphql_document,
+ root_value=execution_context.root_value,
+ middleware=extensions_runner.as_middleware_manager(),
+ variable_values=execution_context.variables,
+ operation_name=execution_context.operation_name,
+ context_value=execution_context.context,
+ execution_context_class=execution_context_class,
+ )
+
+ if isawaitable(result):
+ result = await result
+
+ execution_context.result = result
+ # Also set errors on the execution_context so that it's easier
+ # to access in extensions
+ if result.errors:
+ execution_context.errors = result.errors
+
+ # Run the `Schema.process_errors` function here before
+ # extensions have a chance to modify them (see the MaskErrors
+ # extension). That way we can log the original errors but
+ # only return a sanitised version to the client.
+ process_errors(result.errors, execution_context)
+
+ except (MissingQueryError, InvalidOperationTypeError) as e:
+ raise e
+ except Exception as exc:
+ error = (
+ exc
+ if isinstance(exc, GraphQLError)
+ else GraphQLError(str(exc), original_error=exc)
+ )
+ execution_context.errors = [error]
+ process_errors([error], execution_context)
+ return ExecutionResult(
+ data=None,
+ errors=[error],
+ extensions=await extensions_runner.get_extensions_results(),
+ )
return ExecutionResult(
data=execution_context.result.data,
@@ -193,83 +201,90 @@ def execute_sync(
extensions=list(extensions),
)
- with extensions_runner.operation():
- # Note: In graphql-core the schema would be validated here but in
- # Strawberry we are validating it at initialisation time instead
- if not execution_context.query:
- raise MissingQueryError()
-
- with extensions_runner.parsing():
- try:
- if not execution_context.graphql_document:
- execution_context.graphql_document = parse_document(
- execution_context.query, **execution_context.parse_options
+ try:
+ with extensions_runner.operation():
+ # Note: In graphql-core the schema would be validated here but in
+ # Strawberry we are validating it at initialisation time instead
+ if not execution_context.query:
+ raise MissingQueryError()
+
+ with extensions_runner.parsing():
+ try:
+ if not execution_context.graphql_document:
+ execution_context.graphql_document = parse_document(
+ execution_context.query, **execution_context.parse_options
+ )
+
+ except GraphQLError as exc:
+ execution_context.errors = [exc]
+ process_errors([exc], execution_context)
+ return ExecutionResult(
+ data=None,
+ errors=[exc],
+ extensions=extensions_runner.get_extensions_results_sync(),
)
- except GraphQLError as error:
- execution_context.errors = [error]
- process_errors([error], execution_context)
- return ExecutionResult(
- data=None,
- errors=[error],
- extensions=extensions_runner.get_extensions_results_sync(),
- )
-
- except Exception as error: # pragma: no cover
- error = GraphQLError(str(error), original_error=error)
-
- execution_context.errors = [error]
- process_errors([error], execution_context)
- return ExecutionResult(
- data=None,
- errors=[error],
- extensions=extensions_runner.get_extensions_results_sync(),
- )
-
- if execution_context.operation_type not in allowed_operation_types:
- raise InvalidOperationTypeError(execution_context.operation_type)
-
- with extensions_runner.validation():
- _run_validation(execution_context)
- if execution_context.errors:
- process_errors(execution_context.errors, execution_context)
- return ExecutionResult(data=None, errors=execution_context.errors)
-
- with extensions_runner.executing():
- if not execution_context.result:
- result = original_execute(
- schema,
- execution_context.graphql_document,
- root_value=execution_context.root_value,
- middleware=extensions_runner.as_middleware_manager(),
- variable_values=execution_context.variables,
- operation_name=execution_context.operation_name,
- context_value=execution_context.context,
- execution_context_class=execution_context_class,
- )
-
- if isawaitable(result):
- result = cast(Awaitable["GraphQLExecutionResult"], result)
- ensure_future(result).cancel()
- raise RuntimeError(
- "GraphQL execution failed to complete synchronously."
- )
+ if execution_context.operation_type not in allowed_operation_types:
+ raise InvalidOperationTypeError(execution_context.operation_type)
- result = cast("GraphQLExecutionResult", result)
- execution_context.result = result
- # Also set errors on the execution_context so that it's easier
- # to access in extensions
- if result.errors:
- execution_context.errors = result.errors
+ with extensions_runner.validation():
+ _run_validation(execution_context)
+ if execution_context.errors:
+ process_errors(execution_context.errors, execution_context)
+ return ExecutionResult(data=None, errors=execution_context.errors)
- # Run the `Schema.process_errors` function here before
- # extensions have a chance to modify them (see the MaskErrors
- # extension). That way we can log the original errors but
- # only return a sanitised version to the client.
- process_errors(result.errors, execution_context)
+ with extensions_runner.executing():
+ if not execution_context.result:
+ result = original_execute(
+ schema,
+ execution_context.graphql_document,
+ root_value=execution_context.root_value,
+ middleware=extensions_runner.as_middleware_manager(),
+ variable_values=execution_context.variables,
+ operation_name=execution_context.operation_name,
+ context_value=execution_context.context,
+ execution_context_class=execution_context_class,
+ )
+
+ if isawaitable(result):
+ ensure_future(result).cancel()
+ raise RuntimeError(
+ "GraphQL execution failed to complete synchronously."
+ )
+
+ execution_context.result = result
+ # Also set errors on the execution_context so that it's easier
+ # to access in extensions
+ if result.errors:
+ execution_context.errors = result.errors
+
+ # Run the `Schema.process_errors` function here before
+ # extensions have a chance to modify them (see the MaskErrors
+ # extension). That way we can log the original errors but
+ # only return a sanitised version to the client.
+ process_errors(result.errors, execution_context)
+
+ except (MissingQueryError, InvalidOperationTypeError) as e:
+ raise e
+ except Exception as exc:
+ error = (
+ exc
+ if isinstance(exc, GraphQLError)
+ else GraphQLError(str(exc), original_error=exc)
+ )
+ execution_context.errors = [error]
+ process_errors([error], execution_context)
+ return ExecutionResult(
+ data=None,
+ errors=[error],
+ extensions=extensions_runner.get_extensions_results_sync(),
+ )
return ExecutionResult(
data=execution_context.result.data,
errors=execution_context.result.errors,
extensions=extensions_runner.get_extensions_results_sync(),
)
+
+
+__all__ = ["execute", "execute_sync"]
diff --git a/strawberry/schema/name_converter.py b/strawberry/schema/name_converter.py
index ae6b2ba47d..ec6d0edf2a 100644
--- a/strawberry/schema/name_converter.py
+++ b/strawberry/schema/name_converter.py
@@ -3,25 +3,25 @@
from typing import TYPE_CHECKING, List, Optional, Union, cast
from typing_extensions import Protocol
-from strawberry.custom_scalar import ScalarDefinition
from strawberry.directive import StrawberryDirective
-from strawberry.enum import EnumDefinition, EnumValue
-from strawberry.lazy_type import LazyType
from strawberry.schema_directive import StrawberrySchemaDirective
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
+ StrawberryObjectDefinition,
StrawberryOptional,
has_object_definition,
)
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
+from strawberry.types.enum import EnumDefinition, EnumValue
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.scalar import ScalarDefinition
+from strawberry.types.union import StrawberryUnion
from strawberry.utils.str_converters import capitalize_first, to_camel_case
from strawberry.utils.typing import eval_type
if TYPE_CHECKING:
- from strawberry.arguments import StrawberryArgument
- from strawberry.field import StrawberryField
- from strawberry.type import StrawberryType
+ from strawberry.types.arguments import StrawberryArgument
+ from strawberry.types.base import StrawberryType
+ from strawberry.types.field import StrawberryField
class HasGraphQLName(Protocol):
@@ -184,3 +184,6 @@ def get_graphql_name(self, obj: HasGraphQLName) -> str:
assert obj.python_name
return self.apply_naming_config(obj.python_name)
+
+
+__all__ = ["NameConverter"]
diff --git a/strawberry/schema/schema.py b/strawberry/schema/schema.py
index 8caed4c4bd..f209b021a4 100644
--- a/strawberry/schema/schema.py
+++ b/strawberry/schema/schema.py
@@ -16,6 +16,8 @@
)
from graphql import (
+ GraphQLBoolean,
+ GraphQLField,
GraphQLNamedType,
GraphQLNonNull,
GraphQLSchema,
@@ -34,12 +36,12 @@
)
from strawberry.schema.schema_converter import GraphQLCoreConverter
from strawberry.schema.types.scalar import DEFAULT_SCALAR_REGISTRY
-from strawberry.type import has_object_definition
from strawberry.types import ExecutionContext
+from strawberry.types.base import StrawberryObjectDefinition, has_object_definition
from strawberry.types.graphql import OperationType
-from strawberry.types.types import StrawberryObjectDefinition
from ..printer import print_schema
+from ..utils.await_maybe import await_maybe
from . import compat
from .base import BaseSchema
from .config import StrawberryConfig
@@ -49,13 +51,16 @@
from graphql import ExecutionContext as GraphQLExecutionContext
from graphql import ExecutionResult as GraphQLExecutionResult
- from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
from strawberry.directive import StrawberryDirective
- from strawberry.enum import EnumDefinition
from strawberry.extensions import SchemaExtension
from strawberry.field import StrawberryField
from strawberry.type import StrawberryType
from strawberry.types import ExecutionResult, SubscriptionExecutionResult
+ from strawberry.types.base import StrawberryType
+ from strawberry.types.enum import EnumDefinition
+ from strawberry.types.field import StrawberryField
+ from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
+ from strawberry.types.union import StrawberryUnion
from strawberry.union import StrawberryUnion
DEFAULT_ALLOWED_OPERATION_TYPES = {
@@ -79,10 +84,45 @@ def __init__(
execution_context_class: Optional[Type[GraphQLExecutionContext]] = None,
config: Optional[StrawberryConfig] = None,
scalar_overrides: Optional[
- Dict[object, Union[Type, ScalarWrapper, ScalarDefinition]]
+ Dict[object, Union[Type, ScalarWrapper, ScalarDefinition]],
] = None,
schema_directives: Iterable[object] = (),
- ):
+ ) -> None:
+ """Default Schema to be to be used in a Strawberry application.
+
+ A GraphQL Schema class used to define the structure and configuration
+ of GraphQL queries, mutations, and subscriptions.
+
+ This class allows the creation of a GraphQL schema by specifying the types
+ for queries, mutations, and subscriptions, along with various configuration
+ options such as directives, extensions, and scalar overrides.
+
+ Args:
+ query: The entry point for queries.
+ mutation: The entry point for mutations.
+ subscription: The entry point for subscriptions.
+ directives: A list of operation directives that clients can use.
+ The bult-in `@include` and `@skip` are included by default.
+ types: A list of additional types that will be included in the schema.
+ extensions: A list of Strawberry extensions.
+ execution_context_class: The execution context class.
+ config: The configuration for the schema.
+ scalar_overrides: A dictionary of overrides for scalars.
+ schema_directives: A list of schema directives for the schema.
+
+ Example:
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Query:
+ name: str = "Patrick"
+
+
+ schema = strawberry.Schema(query=Query)
+ ```
+ """
self.query = query
self.mutation = mutation
self.subscription = subscription
@@ -168,6 +208,7 @@ def __init__(
self._warn_for_federation_directives()
self._resolve_node_ids()
+ self._extend_introspection()
# Validate schema early because we want developers to know about
# possible issues as soon as possible
@@ -266,7 +307,7 @@ async def execute(
execution_context_class=self.execution_context_class,
execution_context=execution_context,
allowed_operation_types=allowed_operation_types,
- process_errors=self.process_errors,
+ process_errors=self._process_errors,
)
return result
@@ -298,7 +339,7 @@ def execute_sync(
execution_context_class=self.execution_context_class,
execution_context=execution_context,
allowed_operation_types=allowed_operation_types,
- process_errors=self.process_errors,
+ process_errors=self._process_errors,
)
return result
@@ -312,16 +353,18 @@ async def subscribe(
root_value: Optional[Any] = None,
operation_name: Optional[str] = None,
) -> Union[AsyncIterator[GraphQLExecutionResult], GraphQLExecutionResult]:
- return await subscribe(
- self._schema,
- parse(query),
- root_value=root_value,
- context_value=context_value,
- variable_values=variable_values,
- operation_name=operation_name,
+ return await await_maybe(
+ subscribe(
+ self._schema,
+ parse(query),
+ root_value=root_value,
+ context_value=context_value,
+ variable_values=variable_values,
+ operation_name=operation_name,
+ )
)
- def _resolve_node_ids(self):
+ def _resolve_node_ids(self) -> None:
for concrete_type in self.schema_converter.type_map.values():
type_def = concrete_type.definition
@@ -352,7 +395,7 @@ def _resolve_node_ids(self):
if not has_custom_resolve_id:
origin.resolve_id_attr()
- def _warn_for_federation_directives(self):
+ def _warn_for_federation_directives(self) -> None:
"""Raises a warning if the schema has any federation directives."""
from strawberry.federation.schema_directives import FederationDirective
@@ -375,13 +418,24 @@ def _warn_for_federation_directives(self):
stacklevel=3,
)
+ def _extend_introspection(self) -> None:
+ def _resolve_is_one_of(obj: Any, info: Any) -> bool:
+ if "strawberry-definition" not in obj.extensions:
+ return False
+
+ return obj.extensions["strawberry-definition"].is_one_of
+
+ instrospection_type = self._schema.type_map["__Type"]
+ instrospection_type.fields["isOneOf"] = GraphQLField(GraphQLBoolean) # type: ignore[attr-defined]
+ instrospection_type.fields["isOneOf"].resolve = _resolve_is_one_of # type: ignore[attr-defined]
+
def as_str(self) -> str:
return print_schema(self)
__str__ = as_str
def introspect(self) -> Dict[str, Any]:
- """Return the introspection query result for the current schema
+ """Return the introspection query result for the current schema.
Raises:
ValueError: If the introspection query fails due to an invalid schema
@@ -391,3 +445,6 @@ def introspect(self) -> Dict[str, Any]:
raise ValueError(f"Invalid Schema. Errors {introspection.errors!r}")
return introspection.data
+
+
+__all__ = ["Schema"]
diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py
index f89c7b1d99..9d19ad0345 100644
--- a/strawberry/schema/schema_converter.py
+++ b/strawberry/schema/schema_converter.py
@@ -26,13 +26,16 @@
GraphQLDirective,
GraphQLEnumType,
GraphQLEnumValue,
+ GraphQLError,
GraphQLField,
GraphQLInputField,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLList,
+ GraphQLNamedType,
GraphQLNonNull,
GraphQLObjectType,
+ GraphQLType,
GraphQLUnionType,
Undefined,
ValueNode,
@@ -41,9 +44,6 @@
from graphql.language.directive_locations import DirectiveLocation
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument, convert_arguments
-from strawberry.custom_scalar import ScalarWrapper
-from strawberry.enum import EnumDefinition
from strawberry.exceptions import (
DuplicatedTypeName,
InvalidTypeInputForUnion,
@@ -52,21 +52,24 @@
ScalarAlreadyRegisteredError,
UnresolvedFieldTypeError,
)
-from strawberry.field import UNRESOLVED
-from strawberry.lazy_type import LazyType
-from strawberry.private import is_private
from strawberry.schema.types.scalar import _make_scalar_type
-from strawberry.type import (
+from strawberry.types.arguments import StrawberryArgument, convert_arguments
+from strawberry.types.base import (
StrawberryList,
+ StrawberryObjectDefinition,
StrawberryOptional,
StrawberryType,
get_object_definition,
has_object_definition,
)
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.field import UNRESOLVED
from strawberry.types.info import Info
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
-from strawberry.unset import UNSET
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.private import is_private
+from strawberry.types.scalar import ScalarWrapper
+from strawberry.types.union import StrawberryUnion
+from strawberry.types.unset import UNSET
from strawberry.utils.await_maybe import await_maybe
from ..extensions.field_extension import build_field_extension_resolvers
@@ -82,12 +85,12 @@
GraphQLScalarType,
)
- from strawberry.custom_scalar import ScalarDefinition
from strawberry.directive import StrawberryDirective
- from strawberry.enum import EnumValue
- from strawberry.field import StrawberryField
from strawberry.schema.config import StrawberryConfig
from strawberry.schema_directive import StrawberrySchemaDirective
+ from strawberry.types.enum import EnumValue
+ from strawberry.types.field import StrawberryField
+ from strawberry.types.scalar import ScalarDefinition
FieldType = TypeVar(
@@ -101,8 +104,7 @@ def __call__( # pragma: no cover
field: StrawberryField,
*,
type_definition: Optional[StrawberryObjectDefinition] = None,
- ) -> FieldType:
- ...
+ ) -> FieldType: ...
def _get_thunk_mapping(
@@ -146,7 +148,12 @@ def _get_thunk_mapping(
# subclass the GraphQLEnumType class to enable returning Enum members from
# resolvers.
class CustomGraphQLEnumType(GraphQLEnumType):
- def __init__(self, enum: EnumDefinition, *args: Any, **kwargs: Any):
+ def __init__(
+ self,
+ enum: EnumDefinition,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(*args, **kwargs)
self.wrapped_cls = enum.wrapped_cls
@@ -171,6 +178,61 @@ def parse_literal(
return self.wrapped_cls(super().parse_literal(value_node, _variables))
+def get_arguments(
+ *,
+ field: StrawberryField,
+ source: Any,
+ info: Info,
+ kwargs: Any,
+ config: StrawberryConfig,
+ scalar_registry: Dict[object, Union[ScalarWrapper, ScalarDefinition]],
+) -> Tuple[List[Any], Dict[str, Any]]:
+ # TODO: An extension might have changed the resolver arguments,
+ # but we need them here since we are calling it.
+ # This is a bit of a hack, but it's the easiest way to get the arguments
+ # This happens in mutation.InputMutationExtension
+ field_arguments = field.arguments[:]
+ if field.base_resolver:
+ existing = {arg.python_name for arg in field_arguments}
+ field_arguments.extend(
+ [
+ arg
+ for arg in field.base_resolver.arguments
+ if arg.python_name not in existing
+ ]
+ )
+
+ kwargs = convert_arguments(
+ kwargs,
+ field_arguments,
+ scalar_registry=scalar_registry,
+ config=config,
+ )
+
+ # the following code allows to omit info and root arguments
+ # by inspecting the original resolver arguments,
+ # if it asks for self, the source will be passed as first argument
+ # if it asks for root or parent, the source will be passed as kwarg
+ # if it asks for info, the info will be passed as kwarg
+
+ args = []
+
+ if field.base_resolver:
+ if field.base_resolver.self_parameter:
+ args.append(source)
+
+ if parent_parameter := field.base_resolver.parent_parameter:
+ kwargs[parent_parameter.name] = source
+
+ if root_parameter := field.base_resolver.root_parameter:
+ kwargs[root_parameter.name] = source
+
+ if info_parameter := field.base_resolver.info_parameter:
+ kwargs[info_parameter.name] = info
+
+ return args, kwargs
+
+
class GraphQLCoreConverter:
# TODO: Make abstract
@@ -182,7 +244,7 @@ def __init__(
config: StrawberryConfig,
scalar_registry: Dict[object, Union[ScalarWrapper, ScalarDefinition]],
get_fields: Callable[[StrawberryObjectDefinition], List[StrawberryField]],
- ):
+ ) -> None:
self.type_map: Dict[str, ConcreteType] = {}
self.config = config
self.scalar_registry = scalar_registry
@@ -405,6 +467,27 @@ def from_input_object(self, object_type: type) -> GraphQLInputObjectType:
assert isinstance(graphql_object_type, GraphQLInputObjectType) # For mypy
return graphql_object_type
+ def check_one_of(value: dict[str, Any]) -> dict[str, Any]:
+ if len(value) != 1:
+ raise GraphQLError(
+ f"OneOf Input Object '{type_name}' must specify exactly one key."
+ )
+
+ first_key, first_value = next(iter(value.items()))
+
+ if first_value is None or first_value is UNSET:
+ raise GraphQLError(
+ f"Value for member field '{first_key}' must be non-null"
+ )
+
+ return value
+
+ out_type = (
+ check_one_of
+ if type_definition.is_input and type_definition.is_one_of
+ else None
+ )
+
graphql_object_type = GraphQLInputObjectType(
name=type_name,
fields=lambda: self.get_graphql_input_fields(type_definition),
@@ -412,6 +495,7 @@ def from_input_object(self, object_type: type) -> GraphQLInputObjectType:
extensions={
GraphQLCoreConverter.DEFINITION_BACKREF: type_definition,
},
+ out_type=out_type,
)
self.type_map[type_name] = ConcreteType(
@@ -425,7 +509,7 @@ def from_interface(
) -> GraphQLInterfaceType:
interface_name = self.config.name_converter.from_type(interface)
- # Don't reevaluate known types
+ # Don't re-evaluate known types
cached_type = self.type_map.get(interface_name, None)
if cached_type:
self.validate_same_type_definition(interface_name, interface, cached_type)
@@ -433,7 +517,12 @@ def from_interface(
assert isinstance(graphql_interface, GraphQLInterfaceType) # For mypy
return graphql_interface
- def _get_resolve_type():
+ def _get_resolve_type() -> (
+ Callable[
+ [Any, GraphQLResolveInfo, GraphQLAbstractType],
+ Union[Awaitable[Optional[str]], str, None],
+ ]
+ ):
if interface.resolve_type:
return interface.resolve_type
@@ -446,7 +535,32 @@ def resolve_type(
# TODO: we should find the correct type here from the
# generic
if not type_definition.is_graphql_generic:
- return obj.__strawberry_definition__.name
+ return type_definition.name
+
+ # here we don't all the implementations of the generic
+ # we need to find a way to find them, for now maybe
+ # we can follow the union's approach and iterate over
+ # all the types in the schema, but we should probably
+ # optimize this
+
+ return_type: Optional[GraphQLType] = None
+
+ for possible_concrete_type in self.type_map.values():
+ possible_type = possible_concrete_type.definition
+
+ if not isinstance(
+ possible_type, StrawberryObjectDefinition
+ ): # pragma: no cover
+ continue
+
+ if possible_type.is_implemented_by(obj):
+ return_type = possible_concrete_type.implementation
+ break
+
+ if return_type:
+ assert isinstance(return_type, GraphQLNamedType)
+
+ return return_type.name
# Revert to calling is_type_of for cases where a direct subclass
# of the interface is not returned (i.e. an ORM object)
@@ -540,7 +654,7 @@ def from_resolver(
if field.is_basic_field:
- def _get_basic_result(_source: Any, *args: str, **kwargs: Any):
+ def _get_basic_result(_source: Any, *args: str, **kwargs: Any) -> Any:
# Call `get_result` without an info object or any args or
# kwargs because this is a basic field with no resolver.
return field.get_result(_source, info=None, args=[], kwargs={})
@@ -549,56 +663,6 @@ def _get_basic_result(_source: Any, *args: str, **kwargs: Any):
return _get_basic_result
- def _get_arguments(
- source: Any,
- info: Info,
- kwargs: Any,
- ) -> Tuple[List[Any], Dict[str, Any]]:
- # TODO: An extension might have changed the resolver arguments,
- # but we need them here since we are calling it.
- # This is a bit of a hack, but it's the easiest way to get the arguments
- # This happens in mutation.InputMutationExtension
- field_arguments = field.arguments[:]
- if field.base_resolver:
- existing = {arg.python_name for arg in field_arguments}
- field_arguments.extend(
- [
- arg
- for arg in field.base_resolver.arguments
- if arg.python_name not in existing
- ]
- )
-
- kwargs = convert_arguments(
- kwargs,
- field_arguments,
- scalar_registry=self.scalar_registry,
- config=self.config,
- )
-
- # the following code allows to omit info and root arguments
- # by inspecting the original resolver arguments,
- # if it asks for self, the source will be passed as first argument
- # if it asks for root or parent, the source will be passed as kwarg
- # if it asks for info, the info will be passed as kwarg
-
- args = []
-
- if field.base_resolver:
- if field.base_resolver.self_parameter:
- args.append(source)
-
- if parent_parameter := field.base_resolver.parent_parameter:
- kwargs[parent_parameter.name] = source
-
- if root_parameter := field.base_resolver.root_parameter:
- kwargs[root_parameter.name] = source
-
- if info_parameter := field.base_resolver.info_parameter:
- kwargs[info_parameter.name] = info
-
- return args, kwargs
-
def _strawberry_info_from_graphql(info: GraphQLResolveInfo) -> Info:
return Info(
_raw_info=info,
@@ -610,14 +674,13 @@ def _get_result(
info: Info,
field_args: List[Any],
field_kwargs: Dict[str, Any],
- ):
+ ) -> Any:
return field.get_result(
_source, info=info, args=field_args, kwargs=field_kwargs
)
def wrap_field_extensions() -> Callable[..., Any]:
"""Wrap the provided field resolver with the middleware."""
-
for extension in field.extensions:
extension.apply(field)
@@ -627,11 +690,16 @@ def extension_resolver(
_source: Any,
info: Info,
**kwargs: Any,
- ):
+ ) -> Any:
# parse field arguments into Strawberry input types and convert
# field names to Python equivalents
- field_args, field_kwargs = _get_arguments(
- source=_source, info=info, kwargs=kwargs
+ field_args, field_kwargs = get_arguments(
+ field=field,
+ source=_source,
+ info=info,
+ kwargs=kwargs,
+ config=self.config,
+ scalar_registry=self.scalar_registry,
)
resolver_requested_info = False
@@ -644,7 +712,7 @@ def extension_resolver(
# `_get_result` expects `field_args` and `field_kwargs` as
# separate arguments so we have to wrap the function so that we
# can pass them in
- def wrapped_get_result(_source: Any, info: Info, **kwargs: Any):
+ def wrapped_get_result(_source: Any, info: Info, **kwargs: Any) -> Any:
# if the resolver function requested the info object info
# then put it back in the kwargs dictionary
if resolver_requested_info:
@@ -665,7 +733,7 @@ def wrapped_get_result(_source: Any, info: Info, **kwargs: Any):
_get_result_with_extensions = wrap_field_extensions()
- def _resolver(_source: Any, info: GraphQLResolveInfo, **kwargs: Any):
+ def _resolver(_source: Any, info: GraphQLResolveInfo, **kwargs: Any) -> Any:
strawberry_info = _strawberry_info_from_graphql(info)
return _get_result_with_extensions(
@@ -676,7 +744,7 @@ def _resolver(_source: Any, info: GraphQLResolveInfo, **kwargs: Any):
async def _async_resolver(
_source: Any, info: GraphQLResolveInfo, **kwargs: Any
- ):
+ ) -> Any:
strawberry_info = _strawberry_info_from_graphql(info)
return await await_maybe(
@@ -790,7 +858,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType:
if not StrawberryUnion.is_valid_union_type(type_):
raise InvalidUnionTypeError(union_name, type_, union_definition=union)
- # Don't reevaluate known types
+ # Don't re-evaluate known types
if union_name in self.type_map:
graphql_union = self.type_map[union_name].implementation
assert isinstance(graphql_union, GraphQLUnionType) # For mypy
@@ -930,3 +998,6 @@ def validate_same_type_definition(
second_origin = None
raise DuplicatedTypeName(first_origin, second_origin, name)
+
+
+__all__ = ["GraphQLCoreConverter"]
diff --git a/strawberry/schema/types/base_scalars.py b/strawberry/schema/types/base_scalars.py
index 78d9b5598d..8e73a1383a 100644
--- a/strawberry/schema/types/base_scalars.py
+++ b/strawberry/schema/types/base_scalars.py
@@ -7,11 +7,11 @@
import dateutil.parser
from graphql import GraphQLError
-from strawberry.custom_scalar import scalar
+from strawberry.types.scalar import scalar
def wrap_parser(parser: Callable, type_: str) -> Callable:
- def inner(value: str):
+ def inner(value: str) -> object:
try:
return parser(value)
except ValueError as e:
@@ -80,3 +80,5 @@ def _verify_void(x: None) -> None:
parse_value=_verify_void,
description="Represents NULL values",
)
+
+__all__ = ["Date", "DateTime", "Time", "Decimal", "UUID", "Void"]
diff --git a/strawberry/schema/types/concrete_type.py b/strawberry/schema/types/concrete_type.py
index 460f1d35b8..5507209e59 100644
--- a/strawberry/schema/types/concrete_type.py
+++ b/strawberry/schema/types/concrete_type.py
@@ -6,10 +6,10 @@
from graphql import GraphQLField, GraphQLInputField, GraphQLType
if TYPE_CHECKING:
- from strawberry.custom_scalar import ScalarDefinition
- from strawberry.enum import EnumDefinition
- from strawberry.types.types import StrawberryObjectDefinition
- from strawberry.union import StrawberryUnion
+ from strawberry.types.base import StrawberryObjectDefinition
+ from strawberry.types.enum import EnumDefinition
+ from strawberry.types.scalar import ScalarDefinition
+ from strawberry.types.union import StrawberryUnion
Field = Union[GraphQLInputField, GraphQLField]
diff --git a/strawberry/schema/types/scalar.py b/strawberry/schema/types/scalar.py
index 3ef47f7fda..0e89ad4476 100644
--- a/strawberry/schema/types/scalar.py
+++ b/strawberry/schema/types/scalar.py
@@ -12,11 +12,11 @@
GraphQLString,
)
-from strawberry.custom_scalar import ScalarDefinition, scalar
from strawberry.file_uploads.scalars import Upload
from strawberry.relay.types import GlobalID
from strawberry.scalars import ID
from strawberry.schema.types import base_scalars
+from strawberry.types.scalar import ScalarDefinition, scalar
def _make_scalar_type(definition: ScalarDefinition) -> GraphQLScalarType:
@@ -78,3 +78,10 @@ def _get_scalar_definition(scalar: Type) -> ScalarDefinition:
)
),
}
+
+__all__ = [
+ "DEFAULT_SCALAR_REGISTRY",
+ "_get_scalar_definition",
+ "_make_scalar_definition",
+ "_make_scalar_type",
+]
diff --git a/strawberry/schema/validation_rules/__init__.py b/strawberry/schema/validation_rules/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/strawberry/schema/validation_rules/one_of.py b/strawberry/schema/validation_rules/one_of.py
new file mode 100644
index 0000000000..eca1e4a9ff
--- /dev/null
+++ b/strawberry/schema/validation_rules/one_of.py
@@ -0,0 +1,83 @@
+from typing import Any
+
+from graphql import (
+ ExecutableDefinitionNode,
+ GraphQLError,
+ GraphQLNamedType,
+ ObjectValueNode,
+ ValidationContext,
+ ValidationRule,
+ VariableDefinitionNode,
+ get_named_type,
+)
+
+
+class OneOfInputValidationRule(ValidationRule):
+ def __init__(self, validation_context: ValidationContext) -> None:
+ super().__init__(validation_context)
+
+ def enter_operation_definition(
+ self, node: ExecutableDefinitionNode, *_args: Any
+ ) -> None:
+ self.variable_definitions: dict[str, VariableDefinitionNode] = {}
+
+ def enter_variable_definition(
+ self, node: VariableDefinitionNode, *_args: Any
+ ) -> None:
+ self.variable_definitions[node.variable.name.value] = node
+
+ def enter_object_value(self, node: ObjectValueNode, *_args: Any) -> None:
+ type_ = get_named_type(self.context.get_input_type())
+
+ if not type_:
+ return
+
+ strawberry_type = type_.extensions.get("strawberry-definition")
+
+ if strawberry_type and strawberry_type.is_one_of:
+ self.validate_one_of(node, type_)
+
+ def validate_one_of(self, node: ObjectValueNode, type: GraphQLNamedType) -> None:
+ field_node_map = {field.name.value: field for field in node.fields}
+ keys = list(field_node_map.keys())
+ is_not_exactly_one_field = len(keys) != 1
+
+ if is_not_exactly_one_field:
+ self.report_error(
+ GraphQLError(
+ f"OneOf Input Object '{type.name}' must specify exactly one key.",
+ nodes=[node],
+ )
+ )
+
+ return
+
+ value = field_node_map[keys[0]].value
+ is_null_literal = not value or value.kind == "null_value"
+ is_variable = value.kind == "variable"
+
+ if is_null_literal:
+ self.report_error(
+ GraphQLError(
+ f"Field '{type.name}.{keys[0]}' must be non-null.",
+ nodes=[node],
+ )
+ )
+
+ return
+
+ if is_variable:
+ variable_name = value.name.value # type: ignore
+ definition = self.variable_definitions[variable_name]
+ is_nullable_variable = definition.type.kind != "non_null_type"
+
+ if is_nullable_variable:
+ self.report_error(
+ GraphQLError(
+ f"Variable '{variable_name}' must be non-nullable to be used for OneOf Input Object '{type.name}'.",
+ nodes=[node],
+ )
+ )
+
+
+__all__ = ["OneOfInputValidationRule"]
diff --git a/strawberry/schema_codegen/__init__.py b/strawberry/schema_codegen/__init__.py
index 75f17d1709..5ce5c1de11 100644
--- a/strawberry/schema_codegen/__init__.py
+++ b/strawberry/schema_codegen/__init__.py
@@ -2,8 +2,12 @@
import dataclasses
import keyword
+from collections import defaultdict
+from typing import TYPE_CHECKING, List, Tuple, Union
+from typing_extensions import Protocol, TypeAlias
import libcst as cst
+from graphlib import TopologicalSorter
from graphql import (
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
@@ -19,14 +23,28 @@
OperationType,
ScalarTypeDefinitionNode,
SchemaDefinitionNode,
+ SchemaExtensionNode,
StringValueNode,
TypeNode,
UnionTypeDefinitionNode,
parse,
)
+from graphql.language.ast import (
+ BooleanValueNode,
+ ConstValueNode,
+ ListValueNode,
+)
from strawberry.utils.str_converters import to_snake_case
+if TYPE_CHECKING:
+ from graphql.language.ast import ConstDirectiveNode
+
+
+class HasDirectives(Protocol):
+ directives: Tuple[ConstDirectiveNode, ...]
+
+
_SCALAR_MAP = {
"Int": cst.Name("int"),
"Float": cst.Name("float"),
@@ -48,6 +66,48 @@
}
+@dataclasses.dataclass(frozen=True)
+class Import:
+ module: str | None
+ imports: tuple[str]
+
+ def module_path_to_cst(self, module_path: str) -> cst.Name | cst.Attribute:
+ parts = module_path.split(".")
+
+ module_name: cst.Name | cst.Attribute = cst.Name(parts[0])
+
+ for part in parts[1:]:
+ module_name = cst.Attribute(value=module_name, attr=cst.Name(part))
+
+ return module_name
+
+ def to_cst(self) -> cst.Import | cst.ImportFrom:
+ if self.module is None:
+ return cst.Import(
+ names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports]
+ )
+
+ return cst.ImportFrom(
+ module=self.module_path_to_cst(self.module),
+ names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports],
+ )
+
+
+def _is_federation_link_directive(directive: ConstDirectiveNode) -> bool:
+ if directive.name.value != "link":
+ return False
+
+ return next(
+ (
+ argument.value.value
+ for argument in directive.arguments
+ if argument.name.value == "url"
+ if isinstance(argument.value, StringValueNode)
+ ),
+ "",
+ ).startswith("https://specs.apollo.dev/federation")
+
+
def _get_field_type(
field_type: TypeNode, was_non_nullable: bool = False
) -> cst.BaseExpression:
@@ -85,7 +145,19 @@ def _get_field_type(
)
-def _get_argument(name: str, value: str) -> cst.Arg:
+def _sanitize_argument(value: ArgumentValue) -> cst.SimpleString | cst.Name | cst.List:
+ if isinstance(value, bool):
+ return cst.Name(value=str(value))
+
+ if isinstance(value, list):
+ return cst.List(
+ elements=[
+ cst.Element(value=_sanitize_argument(item))
+ for item in value
+ if item is not None
+ ],
+ )
+
if "\n" in value:
argument_value = cst.SimpleString(f'"""\n{value}\n"""')
elif '"' in value:
@@ -93,6 +165,12 @@ def _get_argument(name: str, value: str) -> cst.Arg:
else:
argument_value = cst.SimpleString(f'"{value}"')
+ return argument_value
+
+
+def _get_argument(name: str, value: ArgumentValue) -> cst.Arg:
+ argument_value = _sanitize_argument(value)
+
return cst.Arg(
value=argument_value,
keyword=cst.Name(name),
@@ -100,7 +178,14 @@ def _get_argument(name: str, value: str) -> cst.Arg:
)
-def _get_field_value(description: str | None, alias: str | None) -> cst.Call | None:
+def _get_field_value(
+ field: FieldDefinitionNode | InputValueDefinitionNode,
+ alias: str | None,
+ is_apollo_federation: bool,
+ imports: set[Import],
+) -> cst.Call | None:
+ description = field.description.value if field.description else None
+
args = list(
filter(
None,
@@ -111,6 +196,24 @@ def _get_field_value(description: str | None, alias: str | None) -> cst.Call | N
)
)
+ directives = _get_directives(field)
+
+ apollo_federation_args = _get_federation_arguments(directives, imports)
+
+ if is_apollo_federation and apollo_federation_args:
+ args.extend(apollo_federation_args)
+
+ return cst.Call(
+ func=cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name("strawberry"),
+ attr=cst.Name("federation"),
+ ),
+ attr=cst.Name("field"),
+ ),
+ args=args,
+ )
+
if args:
return cst.Call(
func=cst.Attribute(
@@ -125,6 +228,8 @@ def _get_field_value(description: str | None, alias: str | None) -> cst.Call | N
def _get_field(
field: FieldDefinitionNode | InputValueDefinitionNode,
+ is_apollo_federation: bool,
+ imports: set[Import],
) -> cst.SimpleStatementLine:
name = to_snake_case(field.name.value)
alias: str | None = None
@@ -141,19 +246,131 @@ def _get_field(
_get_field_type(field.type),
),
value=_get_field_value(
- description=field.description.value if field.description else None,
- alias=alias if alias != name else None,
+ field,
+ alias=alias,
+ is_apollo_federation=is_apollo_federation,
+ imports=imports,
),
)
]
)
+ArgumentValue: TypeAlias = Union[str, bool, List["ArgumentValue"]]
+
+
+def _get_argument_value(argument_value: ConstValueNode) -> ArgumentValue:
+ if isinstance(argument_value, StringValueNode):
+ return argument_value.value
+ elif isinstance(argument_value, EnumValueDefinitionNode):
+ return argument_value.name.value
+ elif isinstance(argument_value, ListValueNode):
+ return [_get_argument_value(arg) for arg in argument_value.values]
+ elif isinstance(argument_value, BooleanValueNode):
+ return argument_value.value
+ else:
+ raise NotImplementedError(f"Unknown argument value {argument_value}")
+
+
+def _get_directives(
+ definition: HasDirectives,
+) -> dict[str, list[dict[str, ArgumentValue]]]:
+ directives: dict[str, list[dict[str, ArgumentValue]]] = defaultdict(list)
+
+ for directive in definition.directives:
+ directive_name = directive.name.value
+
+ directives[directive_name].append(
+ {
+ argument.name.value: _get_argument_value(argument.value)
+ for argument in directive.arguments
+ }
+ )
+
+ return directives
+
+
+def _get_federation_arguments(
+ directives: dict[str, list[dict[str, ArgumentValue]]],
+ imports: set[Import],
+) -> list[cst.Arg]:
+ def append_arg_from_directive(
+ directive: str,
+ argument_name: str,
+ keyword_name: str | None = None,
+ flatten: bool = True,
+ ) -> None:
+ keyword_name = keyword_name or directive
+
+ if directive in directives:
+ values = [item[argument_name] for item in directives[directive]]
+
+ if flatten:
+ arguments.append(_get_argument(keyword_name, values))
+ else:
+ arguments.extend(_get_argument(keyword_name, value) for value in values)
+
+ arguments: list[cst.Arg] = []
+
+ append_arg_from_directive("key", "fields", "keys")
+ append_arg_from_directive("requires", "fields")
+ append_arg_from_directive("provides", "fields")
+ append_arg_from_directive(
+ "requiresScopes", "scopes", "requires_scopes", flatten=False
+ )
+ append_arg_from_directive("policy", "policies", "policy", flatten=False)
+ append_arg_from_directive("tag", "name", "tags")
+
+ boolean_keys = (
+ "shareable",
+ "inaccessible",
+ "external",
+ "authenticated",
+ )
+
+ arguments.extend(
+ _get_argument(key, True) for key in boolean_keys if directives.get(key, False)
+ )
+
+ if overrides := directives.get("override"):
+ override = overrides[0]
+
+ if "label" not in override:
+ arguments.append(_get_argument("override", override["from"]))
+ else:
+ imports.add(
+ Import(
+ module="strawberry.federation.schema_directives",
+ imports=("Override",),
+ )
+ )
+
+ arguments.append(
+ cst.Arg(
+ keyword=cst.Name("override"),
+ value=cst.Call(
+ func=cst.Name("Override"),
+ args=[
+ _get_argument("override_from", override["from"]),
+ _get_argument("label", override["label"]),
+ ],
+ ),
+ equal=cst.AssignEqual(
+ cst.SimpleWhitespace(""), cst.SimpleWhitespace("")
+ ),
+ )
+ )
+
+ return arguments
+
+
def _get_strawberry_decorator(
definition: ObjectTypeDefinitionNode
| ObjectTypeExtensionNode
| InterfaceTypeDefinitionNode
| InputObjectTypeDefinitionNode,
+ is_apollo_federation: bool,
+ imports: set[Import],
) -> cst.Decorator:
type_ = {
ObjectTypeDefinitionNode: "type",
@@ -168,15 +385,36 @@ def _get_strawberry_decorator(
else None
)
+ directives = _get_directives(definition)
+
decorator: cst.BaseExpression = cst.Attribute(
value=cst.Name("strawberry"),
attr=cst.Name(type_),
)
+ arguments: list[cst.Arg] = []
+
if description is not None:
+ arguments.append(_get_argument("description", description.value))
+
+ federation_arguments = _get_federation_arguments(directives, imports)
+
+ # and has any directive that is a federation directive
+ if is_apollo_federation and federation_arguments:
+ decorator = cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name("strawberry"),
+ attr=cst.Name("federation"),
+ ),
+ attr=cst.Name(type_),
+ )
+
+ arguments.extend(federation_arguments)
+
+ if arguments:
decorator = cst.Call(
func=decorator,
- args=[_get_argument("description", description.value)],
+ args=arguments,
)
return cst.Decorator(
@@ -189,11 +427,13 @@ def _get_class_definition(
| ObjectTypeExtensionNode
| InterfaceTypeDefinitionNode
| InputObjectTypeDefinitionNode,
-) -> cst.ClassDef:
- decorator = _get_strawberry_decorator(definition)
+ is_apollo_federation: bool,
+ imports: set[Import],
+) -> Definition:
+ decorator = _get_strawberry_decorator(definition, is_apollo_federation, imports)
- bases = (
- [cst.Arg(cst.Name(interface.name.value)) for interface in definition.interfaces]
+ interfaces = (
+ [interface.name.value for interface in definition.interfaces]
if isinstance(
definition, (ObjectTypeDefinitionNode, InterfaceTypeDefinitionNode)
)
@@ -201,16 +441,24 @@ def _get_class_definition(
else []
)
- return cst.ClassDef(
+ class_definition = cst.ClassDef(
name=cst.Name(definition.name.value),
- bases=bases,
- body=cst.IndentedBlock(body=[_get_field(field) for field in definition.fields]),
+ body=cst.IndentedBlock(
+ body=[
+ _get_field(field, is_apollo_federation, imports)
+ for field in definition.fields
+ ]
+ ),
+ bases=[cst.Arg(cst.Name(interface)) for interface in interfaces],
decorators=[decorator],
)
+ return Definition(class_definition, interfaces, definition.name.value)
+
def _get_enum_value(enum_value: EnumValueDefinitionNode) -> cst.SimpleStatementLine:
name = enum_value.name.value
+
return cst.SimpleStatementLine(
body=[
cst.Assign(
@@ -221,7 +469,7 @@ def _get_enum_value(enum_value: EnumValueDefinitionNode) -> cst.SimpleStatementL
)
-def _get_enum_definition(definition: EnumTypeDefinitionNode) -> cst.ClassDef:
+def _get_enum_definition(definition: EnumTypeDefinitionNode) -> Definition:
decorator = cst.Decorator(
decorator=cst.Attribute(
value=cst.Name("strawberry"),
@@ -229,7 +477,7 @@ def _get_enum_definition(definition: EnumTypeDefinitionNode) -> cst.ClassDef:
),
)
- return cst.ClassDef(
+ class_definition = cst.ClassDef(
name=cst.Name(definition.name.value),
bases=[cst.Arg(cst.Name("Enum"))],
body=cst.IndentedBlock(
@@ -238,18 +486,25 @@ def _get_enum_definition(definition: EnumTypeDefinitionNode) -> cst.ClassDef:
decorators=[decorator],
)
+ return Definition(
+ class_definition,
+ [],
+ definition.name.value,
+ )
+
def _get_schema_definition(
root_query_name: str | None,
root_mutation_name: str | None,
root_subscription_name: str | None,
+ is_apollo_federation: bool,
) -> cst.SimpleStatementLine | None:
if not any([root_query_name, root_mutation_name, root_subscription_name]):
return None
args: list[cst.Arg] = []
- def _get_arg(name: str, value: str):
+ def _get_arg(name: str, value: str) -> cst.Arg:
return cst.Arg(
keyword=cst.Name(name),
value=cst.Name(value),
@@ -265,71 +520,93 @@ def _get_arg(name: str, value: str):
if root_subscription_name:
args.append(_get_arg("subscription", root_subscription_name))
+ schema_call = cst.Call(
+ func=cst.Attribute(
+ value=cst.Name("strawberry"),
+ attr=cst.Name("Schema"),
+ ),
+ args=args,
+ )
+
+ if is_apollo_federation:
+ args.append(
+ cst.Arg(
+ keyword=cst.Name("enable_federation_2"),
+ value=cst.Name("True"),
+ equal=cst.AssignEqual(
+ cst.SimpleWhitespace(""), cst.SimpleWhitespace("")
+ ),
+ )
+ )
+ schema_call = cst.Call(
+ func=cst.Attribute(
+ value=cst.Attribute(
+ value=cst.Name(value="strawberry"),
+ attr=cst.Name(value="federation"),
+ ),
+ attr=cst.Name(value="Schema"),
+ ),
+ args=args,
+ )
+
return cst.SimpleStatementLine(
body=[
cst.Assign(
targets=[cst.AssignTarget(cst.Name("schema"))],
- value=cst.Call(
- func=cst.Attribute(
- value=cst.Name("strawberry"),
- attr=cst.Name("Schema"),
- ),
- args=args,
- ),
+ value=schema_call,
)
]
)
@dataclasses.dataclass(frozen=True)
-class Import:
- module: str | None
- imports: tuple[str]
+class Definition:
+ code: cst.CSTNode
+ dependencies: list[str]
+ name: str
- def to_cst(self) -> cst.Import | cst.ImportFrom:
- if self.module is None:
- return cst.Import(
- names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports]
- )
-
- return cst.ImportFrom(
- module=cst.Name(self.module),
- names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports],
- )
-
-def _get_union_definition(definition: UnionTypeDefinitionNode) -> cst.Assign:
+def _get_union_definition(definition: UnionTypeDefinitionNode) -> Definition:
name = definition.name.value
types = cst.parse_expression(
" | ".join([type_.name.value for type_ in definition.types])
)
- return cst.Assign(
- targets=[cst.AssignTarget(cst.Name(name))],
- value=cst.Subscript(
- value=cst.Name("Annotated"),
- slice=[
- cst.SubscriptElement(slice=cst.Index(types)),
- cst.SubscriptElement(
- slice=cst.Index(
- cst.Call(
- cst.Attribute(
- value=cst.Name("strawberry"),
- attr=cst.Name("union"),
- ),
- args=[_get_argument("name", name)],
- )
- )
+ simple_statement = cst.SimpleStatementLine(
+ body=[
+ cst.Assign(
+ targets=[cst.AssignTarget(cst.Name(name))],
+ value=cst.Subscript(
+ value=cst.Name("Annotated"),
+ slice=[
+ cst.SubscriptElement(slice=cst.Index(types)),
+ cst.SubscriptElement(
+ slice=cst.Index(
+ cst.Call(
+ cst.Attribute(
+ value=cst.Name("strawberry"),
+ attr=cst.Name("union"),
+ ),
+ args=[_get_argument("name", name)],
+ )
+ )
+ ),
+ ],
),
- ],
- ),
+ )
+ ]
+ )
+ return Definition(
+ simple_statement,
+ [],
+ definition.name.value,
)
def _get_scalar_definition(
definition: ScalarTypeDefinitionNode, imports: set[Import]
-) -> cst.SimpleStatementLine | None:
+) -> Definition | None:
name = definition.name.value
if name == "Date":
@@ -388,7 +665,7 @@ def _get_scalar_definition(
),
]
- return cst.SimpleStatementLine(
+ statement_definition = cst.SimpleStatementLine(
body=[
cst.Assign(
targets=[cst.AssignTarget(cst.Name(name))],
@@ -413,12 +690,13 @@ def _get_scalar_definition(
)
]
)
+ return Definition(statement_definition, [], name=definition.name.value)
def codegen(schema: str) -> str:
document = parse(schema)
- definitions: list[cst.CSTNode] = []
+ definitions: dict[str, Definition] = {}
root_query_name: str | None = None
root_mutation_name: str | None = None
@@ -428,11 +706,17 @@ def codegen(schema: str) -> str:
Import(module=None, imports=("strawberry",)),
}
- object_types: dict[str, cst.ClassDef] = {}
+ # when we encounter a extend schema @link ..., we check if is an apollo federation schema
+ # and we use this variable to keep track of it, but at the moment the assumption is that
+ # the schema extension is always done at the top, this might not be the case all the
+ # time
+ is_apollo_federation = False
+
+ for graphql_definition in document.definitions:
+ definition: Definition | None = None
- for definition in document.definitions:
if isinstance(
- definition,
+ graphql_definition,
(
ObjectTypeDefinitionNode,
InterfaceTypeDefinitionNode,
@@ -440,21 +724,17 @@ def codegen(schema: str) -> str:
ObjectTypeExtensionNode,
),
):
- class_definition = _get_class_definition(definition)
-
- object_types[definition.name.value] = class_definition
-
- definitions.append(cst.EmptyLine())
- definitions.append(class_definition)
+ definition = _get_class_definition(
+ graphql_definition, is_apollo_federation, imports
+ )
- elif isinstance(definition, EnumTypeDefinitionNode):
+ elif isinstance(graphql_definition, EnumTypeDefinitionNode):
imports.add(Import(module="enum", imports=("Enum",)))
- definitions.append(cst.EmptyLine())
- definitions.append(_get_enum_definition(definition))
+ definition = _get_enum_definition(graphql_definition)
- elif isinstance(definition, SchemaDefinitionNode):
- for operation_type_definition in definition.operation_types:
+ elif isinstance(graphql_definition, SchemaDefinitionNode):
+ for operation_type_definition in graphql_definition.operation_types:
if operation_type_definition.operation == OperationType.QUERY:
root_query_name = operation_type_definition.type.name.value
elif operation_type_definition.operation == OperationType.MUTATION:
@@ -465,53 +745,63 @@ def codegen(schema: str) -> str:
raise NotImplementedError(
f"Unknown operation {operation_type_definition.operation}"
)
- elif isinstance(definition, UnionTypeDefinitionNode):
+ elif isinstance(graphql_definition, UnionTypeDefinitionNode):
imports.add(Import(module="typing", imports=("Annotated",)))
- definitions.append(cst.EmptyLine())
- definitions.append(_get_union_definition(definition))
- definitions.append(cst.EmptyLine())
- elif isinstance(definition, ScalarTypeDefinitionNode):
- scalar_definition = _get_scalar_definition(definition, imports)
+ definition = _get_union_definition(graphql_definition)
+ elif isinstance(graphql_definition, ScalarTypeDefinitionNode):
+ definition = _get_scalar_definition(graphql_definition, imports)
- if scalar_definition is not None:
- definitions.append(cst.EmptyLine())
- definitions.append(scalar_definition)
- definitions.append(cst.EmptyLine())
+ elif isinstance(graphql_definition, SchemaExtensionNode):
+ is_apollo_federation = any(
+ _is_federation_link_directive(directive)
+ for directive in graphql_definition.directives
+ )
else:
raise NotImplementedError(f"Unknown definition {definition}")
+ if definition is not None:
+ definitions[definition.name] = definition
+
if root_query_name is None:
- root_query_name = "Query" if "Query" in object_types else None
+ root_query_name = "Query" if "Query" in definitions else None
if root_mutation_name is None:
- root_mutation_name = "Mutation" if "Mutation" in object_types else None
+ root_mutation_name = "Mutation" if "Mutation" in definitions else None
if root_subscription_name is None:
root_subscription_name = (
- "Subscription" if "Subscription" in object_types else None
+ "Subscription" if "Subscription" in definitions else None
)
schema_definition = _get_schema_definition(
root_query_name=root_query_name,
root_mutation_name=root_mutation_name,
root_subscription_name=root_subscription_name,
+ is_apollo_federation=is_apollo_federation,
)
if schema_definition:
- definitions.append(cst.EmptyLine())
- definitions.append(schema_definition)
+ definitions["Schema"] = Definition(schema_definition, [], "schema")
- module = cst.Module(
- body=[
- *[
- cst.SimpleStatementLine(body=[import_.to_cst()])
- for import_ in sorted(
- imports, key=lambda i: (i.module or "", i.imports)
- )
- ],
- *definitions, # type: ignore
- ]
- )
+ body: list[cst.CSTNode] = [
+ cst.SimpleStatementLine(body=[import_.to_cst()])
+ for import_ in sorted(imports, key=lambda i: (i.module or "", i.imports))
+ ]
+
+ # DAG to sort definitions based on dependencies
+ graph = {name: definition.dependencies for name, definition in definitions.items()}
+ ts = TopologicalSorter(graph)
+
+ for definition_name in tuple(ts.static_order()):
+ definition = definitions[definition_name]
+
+ body.append(cst.EmptyLine())
+ body.append(definition.code)
+
+ module = cst.Module(body=body) # type: ignore
return module.code
+
+
+__all__ = ["codegen"]
diff --git a/strawberry/schema_directive.py b/strawberry/schema_directive.py
index 9f455eb752..f519a7a766 100644
--- a/strawberry/schema_directive.py
+++ b/strawberry/schema_directive.py
@@ -3,11 +3,11 @@
from typing import Callable, List, Optional, Type, TypeVar
from typing_extensions import dataclass_transform
-from strawberry.object_type import _wrap_dataclass
+from strawberry.types.field import StrawberryField, field
+from strawberry.types.object_type import _wrap_dataclass
from strawberry.types.type_resolver import _get_fields
from .directive import directive_field
-from .field import StrawberryField, field
class Location(Enum):
@@ -53,8 +53,8 @@ def schema_directive(
print_definition: bool = True,
) -> Callable[..., T]:
def _wrap(cls: T) -> T:
- cls = _wrap_dataclass(cls)
- fields = _get_fields(cls)
+ cls = _wrap_dataclass(cls) # type: ignore
+ fields = _get_fields(cls, {})
cls.__strawberry_directive__ = StrawberrySchemaDirective(
python_name=cls.__name__,
diff --git a/strawberry/schema_directives.py b/strawberry/schema_directives.py
new file mode 100644
index 0000000000..245450126f
--- /dev/null
+++ b/strawberry/schema_directives.py
@@ -0,0 +1,8 @@
+from strawberry.schema_directive import Location, schema_directive
+
+
+@schema_directive(locations=[Location.INPUT_OBJECT], name="oneOf")
+class OneOf: ...
+
+
+__all__ = ["OneOf"]
diff --git a/strawberry/starlite/controller.py b/strawberry/starlite/controller.py
index 92cc86734b..3376b1782b 100644
--- a/strawberry/starlite/controller.py
+++ b/strawberry/starlite/controller.py
@@ -6,6 +6,7 @@
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union, cast
+from typing_extensions import deprecated
from starlite import (
BackgroundTasks,
@@ -118,8 +119,12 @@ async def get_root_value(self) -> Any:
return await self._get_root_value()
+@deprecated(
+ "The `starlite` integration is deprecated in favor of `litestar` integration",
+ stacklevel=2,
+)
class StarliteRequestAdapter(AsyncHTTPRequestAdapter):
- def __init__(self, request: Request[Any, Any]):
+ def __init__(self, request: Request[Any, Any]) -> None:
self.request = request
@property
@@ -148,11 +153,15 @@ async def get_form_data(self) -> FormData:
class BaseContext:
- def __init__(self):
+ def __init__(self) -> None:
self.request: Optional[Union[Request, WebSocket]] = None
self.response: Optional[Response] = None
+@deprecated(
+ "The `starlite` integration is deprecated in favor of `litestar` integration",
+ stacklevel=2,
+)
def make_graphql_controller(
schema: BaseSchema,
path: str = "",
@@ -176,7 +185,7 @@ def make_graphql_controller(
if context_getter is None:
- def custom_context_getter_():
+ def custom_context_getter_() -> None:
return None
else:
@@ -184,7 +193,7 @@ def custom_context_getter_():
if root_value_getter is None:
- def root_value_getter_():
+ def root_value_getter_() -> None:
return None
else:
@@ -222,9 +231,9 @@ class GraphQLController(
"response": Provide(response_getter),
}
graphql_ws_handler_class: Type[GraphQLWSHandler] = GraphQLWSHandler
- graphql_transport_ws_handler_class: Type[
+ graphql_transport_ws_handler_class: Type[GraphQLTransportWSHandler] = (
GraphQLTransportWSHandler
- ] = GraphQLTransportWSHandler
+ )
_keep_alive: bool = keep_alive
_keep_alive_interval: float = keep_alive_interval
@@ -331,10 +340,10 @@ async def websocket_endpoint(
context: CustomContext,
root_value: Any,
) -> None:
- async def _get_context():
+ async def _get_context() -> CustomContext:
return context
- async def _get_root_value():
+ async def _get_root_value() -> Any:
return root_value
preferred_protocol = self.pick_preferred_protocol(socket)
@@ -373,3 +382,6 @@ def pick_preferred_protocol(self, socket: WebSocket) -> Optional[str]:
)
return GraphQLController
+
+
+__all__ = ["make_graphql_controller"]
diff --git a/strawberry/starlite/handlers/__init__.py b/strawberry/starlite/handlers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/strawberry/starlite/handlers/graphql_transport_ws_handler.py b/strawberry/starlite/handlers/graphql_transport_ws_handler.py
index 19f0e192b9..5d65671831 100644
--- a/strawberry/starlite/handlers/graphql_transport_ws_handler.py
+++ b/strawberry/starlite/handlers/graphql_transport_ws_handler.py
@@ -19,7 +19,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, connection_init_wait_timeout)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -54,3 +54,6 @@ async def handle_request(self) -> None:
pass
finally:
await self.shutdown()
+
+
+__all__ = ["GraphQLTransportWSHandler"]
diff --git a/strawberry/starlite/handlers/graphql_ws_handler.py b/strawberry/starlite/handlers/graphql_ws_handler.py
index e1285ed1df..237ac95e14 100644
--- a/strawberry/starlite/handlers/graphql_ws_handler.py
+++ b/strawberry/starlite/handlers/graphql_ws_handler.py
@@ -19,7 +19,7 @@ def __init__(
get_context: Callable,
get_root_value: Callable,
ws: WebSocket,
- ):
+ ) -> None:
super().__init__(schema, debug, keep_alive, keep_alive_interval)
self._get_context = get_context
self._get_root_value = get_root_value
@@ -59,3 +59,6 @@ async def handle_request(self) -> Any:
for operation_id in list(self.subscriptions.keys()):
await self.cleanup_operation(operation_id)
+
+
+__all__ = ["GraphQLWSHandler"]
diff --git a/strawberry/subscriptions/__init__.py b/strawberry/subscriptions/__init__.py
index 61a8c08f9f..7d38a1a90a 100644
--- a/strawberry/subscriptions/__init__.py
+++ b/strawberry/subscriptions/__init__.py
@@ -1,2 +1,8 @@
GRAPHQL_TRANSPORT_WS_PROTOCOL = "graphql-transport-ws"
GRAPHQL_WS_PROTOCOL = "graphql-ws"
+
+
+__all__ = [
+ "GRAPHQL_TRANSPORT_WS_PROTOCOL",
+ "GRAPHQL_WS_PROTOCOL",
+]
diff --git a/strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py b/strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py
index 1368214127..c17c6ee143 100644
--- a/strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py
+++ b/strawberry/subscriptions/protocols/graphql_transport_ws/__init__.py
@@ -1,2 +1,7 @@
# Code 4406 is "Subprotocol not acceptable"
WS_4406_PROTOCOL_NOT_ACCEPTABLE = 4406
+
+
+__all__ = [
+ "WS_4406_PROTOCOL_NOT_ACCEPTABLE",
+]
diff --git a/strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py b/strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py
index 1d3f19af18..fa0cb7c177 100644
--- a/strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py
+++ b/strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py
@@ -4,7 +4,16 @@
import logging
from abc import ABC, abstractmethod
from contextlib import suppress
-from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, List, Optional
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncGenerator,
+ AsyncIterator,
+ Callable,
+ Dict,
+ List,
+ Optional,
+)
from graphql import ExecutionResult as GraphQLExecutionResult
from graphql import GraphQLError, GraphQLSyntaxError, parse
@@ -21,7 +30,7 @@
SubscribeMessagePayload,
)
from strawberry.types.graphql import OperationType
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
from strawberry.utils.debug import pretty_print_graphql_operation
from strawberry.utils.operation import get_operation_type
@@ -32,6 +41,7 @@
from strawberry.subscriptions.protocols.graphql_transport_ws.types import (
GraphQLTransportMessage,
)
+ from strawberry.types import ExecutionResult
class BaseGraphQLTransportWSHandler(ABC):
@@ -42,7 +52,7 @@ def __init__(
schema: BaseSchema,
debug: bool,
connection_init_wait_timeout: timedelta,
- ):
+ ) -> None:
self.schema = schema
self.debug = debug
self.connection_init_wait_timeout = connection_init_wait_timeout
@@ -56,23 +66,23 @@ def __init__(
@abstractmethod
async def get_context(self) -> Any:
- """Return the operations context"""
+ """Return the operations context."""
@abstractmethod
async def get_root_value(self) -> Any:
- """Return the schemas root value"""
+ """Return the schemas root value."""
@abstractmethod
async def send_json(self, data: dict) -> None:
- """Send the data JSON encoded to the WebSocket client"""
+ """Send the data JSON encoded to the WebSocket client."""
@abstractmethod
async def close(self, code: int, reason: str) -> None:
- """Close the WebSocket with the passed code and reason"""
+ """Close the WebSocket with the passed code and reason."""
@abstractmethod
async def handle_request(self) -> Any:
- """Handle the request this instance was created for"""
+ """Handle the request this instance was created for."""
async def handle(self) -> Any:
return await self.handle_request()
@@ -245,7 +255,7 @@ async def handle_subscribe(self, message: SubscribeMessage) -> None:
)
else:
# create AsyncGenerator returning a single result
- async def get_result_source():
+ async def get_result_source() -> AsyncIterator[ExecutionResult]:
yield await self.schema.execute(
query=message.payload.query,
variable_values=message.payload.variables,
@@ -276,10 +286,7 @@ async def get_result_source():
async def operation_task(
self, result_source: AsyncGenerator, operation: Operation
) -> None:
- """
- Operation task top level method. Cleans up and de-registers the operation
- once it is done.
- """
+ """The operation task's top level method. Cleans-up and de-registers the operation once it is done."""
# TODO: Handle errors in this method using self.handle_task_exception()
try:
await self.handle_async_results(result_source, operation)
@@ -361,9 +368,7 @@ async def cleanup_operation(self, operation_id: str) -> None:
# websocket handler Task.
async def reap_completed_tasks(self) -> None:
- """
- Await tasks that have completed
- """
+ """Await tasks that have completed."""
tasks, self.completed_tasks = self.completed_tasks, []
for task in tasks:
with suppress(BaseException):
@@ -371,10 +376,7 @@ async def reap_completed_tasks(self) -> None:
class Operation:
- """
- A class encapsulating a single operation with its id.
- Helps enforce protocol state transition.
- """
+ """A class encapsulating a single operation with its id. Helps enforce protocol state transition."""
__slots__ = ["handler", "id", "operation_type", "completed", "task"]
@@ -383,7 +385,7 @@ def __init__(
handler: BaseGraphQLTransportWSHandler,
id: str,
operation_type: OperationType,
- ):
+ ) -> None:
self.handler = handler
self.id = id
self.operation_type = operation_type
@@ -398,3 +400,6 @@ async def send_message(self, message: GraphQLTransportMessage) -> None:
# de-register the operation _before_ sending the final message
self.handler.forget_id(self.id)
await self.handler.send_message(message)
+
+
+__all__ = ["BaseGraphQLTransportWSHandler", "Operation"]
diff --git a/strawberry/subscriptions/protocols/graphql_transport_ws/types.py b/strawberry/subscriptions/protocols/graphql_transport_ws/types.py
index c982ccc463..76260fabee 100644
--- a/strawberry/subscriptions/protocols/graphql_transport_ws/types.py
+++ b/strawberry/subscriptions/protocols/graphql_transport_ws/types.py
@@ -3,7 +3,7 @@
from dataclasses import asdict, dataclass
from typing import TYPE_CHECKING, Any, Dict, List, Optional
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
if TYPE_CHECKING:
from graphql import GraphQLFormattedError
@@ -21,9 +21,7 @@ def as_dict(self) -> dict:
@dataclass
class ConnectionInitMessage(GraphQLTransportMessage):
- """
- Direction: Client -> Server
- """
+ """Direction: Client -> Server."""
payload: Optional[Dict[str, Any]] = UNSET
type: str = "connection_init"
@@ -31,9 +29,7 @@ class ConnectionInitMessage(GraphQLTransportMessage):
@dataclass
class ConnectionAckMessage(GraphQLTransportMessage):
- """
- Direction: Server -> Client
- """
+ """Direction: Server -> Client."""
payload: Optional[Dict[str, Any]] = UNSET
type: str = "connection_ack"
@@ -41,9 +37,7 @@ class ConnectionAckMessage(GraphQLTransportMessage):
@dataclass
class PingMessage(GraphQLTransportMessage):
- """
- Direction: bidirectional
- """
+ """Direction: bidirectional."""
payload: Optional[Dict[str, Any]] = UNSET
type: str = "ping"
@@ -51,9 +45,7 @@ class PingMessage(GraphQLTransportMessage):
@dataclass
class PongMessage(GraphQLTransportMessage):
- """
- Direction: bidirectional
- """
+ """Direction: bidirectional."""
payload: Optional[Dict[str, Any]] = UNSET
type: str = "pong"
@@ -69,9 +61,7 @@ class SubscribeMessagePayload:
@dataclass
class SubscribeMessage(GraphQLTransportMessage):
- """
- Direction: Client -> Server
- """
+ """Direction: Client -> Server."""
id: str
payload: SubscribeMessagePayload
@@ -80,9 +70,7 @@ class SubscribeMessage(GraphQLTransportMessage):
@dataclass
class NextMessage(GraphQLTransportMessage):
- """
- Direction: Server -> Client
- """
+ """Direction: Server -> Client."""
id: str
payload: Dict[str, Any] # TODO: shape like FormattedExecutionResult
@@ -94,9 +82,7 @@ def as_dict(self) -> dict:
@dataclass
class ErrorMessage(GraphQLTransportMessage):
- """
- Direction: Server -> Client
- """
+ """Direction: Server -> Client."""
id: str
payload: List[GraphQLFormattedError]
@@ -105,9 +91,19 @@ class ErrorMessage(GraphQLTransportMessage):
@dataclass
class CompleteMessage(GraphQLTransportMessage):
- """
- Direction: bidirectional
- """
+ """Direction: bidirectional."""
id: str
type: str = "complete"
+
+
+__all__ = [
+ "ConnectionInitMessage",
+ "ConnectionAckMessage",
+ "PingMessage",
+ "PongMessage",
+ "SubscribeMessage",
+ "NextMessage",
+ "ErrorMessage",
+ "CompleteMessage",
+]
diff --git a/strawberry/subscriptions/protocols/graphql_ws/__init__.py b/strawberry/subscriptions/protocols/graphql_ws/__init__.py
index 8b884cf45f..2b3696e822 100644
--- a/strawberry/subscriptions/protocols/graphql_ws/__init__.py
+++ b/strawberry/subscriptions/protocols/graphql_ws/__init__.py
@@ -8,3 +8,17 @@
GQL_ERROR = "error"
GQL_COMPLETE = "complete"
GQL_STOP = "stop"
+
+
+__all__ = [
+ "GQL_CONNECTION_INIT",
+ "GQL_CONNECTION_ACK",
+ "GQL_CONNECTION_ERROR",
+ "GQL_CONNECTION_TERMINATE",
+ "GQL_CONNECTION_KEEP_ALIVE",
+ "GQL_START",
+ "GQL_DATA",
+ "GQL_ERROR",
+ "GQL_COMPLETE",
+ "GQL_STOP",
+]
diff --git a/strawberry/subscriptions/protocols/graphql_ws/handlers.py b/strawberry/subscriptions/protocols/graphql_ws/handlers.py
index 609f9fdab8..382167304f 100644
--- a/strawberry/subscriptions/protocols/graphql_ws/handlers.py
+++ b/strawberry/subscriptions/protocols/graphql_ws/handlers.py
@@ -39,7 +39,7 @@ def __init__(
debug: bool,
keep_alive: bool,
keep_alive_interval: float,
- ):
+ ) -> None:
self.schema = schema
self.debug = debug
self.keep_alive = keep_alive
@@ -51,23 +51,23 @@ def __init__(
@abstractmethod
async def get_context(self) -> Any:
- """Return the operations context"""
+ """Return the operations context."""
@abstractmethod
async def get_root_value(self) -> Any:
- """Return the schemas root value"""
+ """Return the schemas root value."""
@abstractmethod
async def send_json(self, data: OperationMessage) -> None:
- """Send the data JSON encoded to the WebSocket client"""
+ """Send the data JSON encoded to the WebSocket client."""
@abstractmethod
async def close(self, code: int = 1000, reason: Optional[str] = None) -> None:
- """Close the WebSocket with the passed code and reason"""
+ """Close the WebSocket with the passed code and reason."""
@abstractmethod
async def handle_request(self) -> Any:
- """Handle the request this instance was created for"""
+ """Handle the request this instance was created for."""
async def handle(self) -> Any:
return await self.handle_request()
@@ -190,7 +190,8 @@ async def handle_async_results(
await self.send_message(GQL_COMPLETE, operation_id, None)
async def cleanup_operation(self, operation_id: str) -> None:
- await self.subscriptions[operation_id].aclose()
+ with suppress(RuntimeError):
+ await self.subscriptions[operation_id].aclose()
del self.subscriptions[operation_id]
self.tasks[operation_id].cancel()
@@ -208,3 +209,6 @@ async def send_message(
if payload is not None:
data["payload"] = payload
await self.send_json(data)
+
+
+__all__ = ["BaseGraphQLWSHandler"]
diff --git a/strawberry/subscriptions/protocols/graphql_ws/types.py b/strawberry/subscriptions/protocols/graphql_ws/types.py
index abd0ce785e..4b7d35d278 100644
--- a/strawberry/subscriptions/protocols/graphql_ws/types.py
+++ b/strawberry/subscriptions/protocols/graphql_ws/types.py
@@ -38,3 +38,14 @@ class OperationMessage(TypedDict, total=False):
type: str
id: str
payload: OperationMessagePayload
+
+
+__all__ = [
+ "ConnectionInitPayload",
+ "ConnectionErrorPayload",
+ "StartPayload",
+ "DataPayload",
+ "ErrorPayload",
+ "OperationMessagePayload",
+ "OperationMessage",
+]
diff --git a/strawberry/test/client.py b/strawberry/test/client.py
index 4ef79354e4..9127799763 100644
--- a/strawberry/test/client.py
+++ b/strawberry/test/client.py
@@ -23,7 +23,11 @@ class Body(TypedDict, total=False):
class BaseGraphQLTestClient(ABC):
- def __init__(self, client, url: str = "/graphql/"): # noqa: ANN001
+ def __init__(
+ self,
+ client: Any,
+ url: str = "/graphql/",
+ ) -> None:
self._client = client
self.url = url
@@ -87,47 +91,59 @@ def _build_body(
def _build_multipart_file_map(
variables: Dict[str, Mapping], files: Dict[str, object]
) -> Dict[str, List[str]]:
- """Creates the file mapping between the variables and the files objects passed
- as key arguments
+ """Creates the file mapping between the variables and the files objects passed as key arguments.
+
+ Args:
+ variables: A dictionary with the variables that are going to be passed to the
+ query.
+ files: A dictionary with the files that are going to be passed to the query.
Example usages:
- >>> _build_multipart_file_map(
- >>> variables={"textFile": None}, files={"textFile": f}
- >>> )
- ... {"textFile": ["variables.textFile"]}
+ ```python
+ _build_multipart_file_map(variables={"textFile": None}, files={"textFile": f})
+ # {"textFile": ["variables.textFile"]}
+ ```
If the variable is a list we have to enumerate files in the mapping
- >>> _build_multipart_file_map(
- >>> variables={"files": [None, None]},
- >>> files={"file1": file1, "file2": file2},
- >>> )
- ... {"file1": ["variables.files.0"], "file2": ["variables.files.1"]}
+
+ ```python
+ _build_multipart_file_map(
+ variables={"files": [None, None]},
+ files={"file1": file1, "file2": file2},
+ )
+ # {"file1": ["variables.files.0"], "file2": ["variables.files.1"]}
+ ```
If `variables` contains another keyword (a folder) we must include that keyword
in the mapping
- >>> _build_multipart_file_map(
- >>> variables={"folder": {"files": [None, None]}},
- >>> files={"file1": file1, "file2": file2},
- >>> )
- ... {
- ... "file1": ["variables.files.folder.files.0"],
- ... "file2": ["variables.files.folder.files.1"]
- ... }
+
+ ```python
+ _build_multipart_file_map(
+ variables={"folder": {"files": [None, None]}},
+ files={"file1": file1, "file2": file2},
+ )
+ # {
+ # "file1": ["variables.files.folder.files.0"],
+ # "file2": ["variables.files.folder.files.1"]
+ # }
+ ```
If `variables` includes both a list of files and other single values, we must
map them accordingly
- >>> _build_multipart_file_map(
- >>> variables={"files": [None, None], "textFile": None},
- >>> files={"file1": file1, "file2": file2, "textFile": file3},
- >>> )
- ... {
- ... "file1": ["variables.files.0"],
- ... "file2": ["variables.files.1"],
- ... "textFile": ["variables.textFile"],
- ... }
- """
+ ```python
+ _build_multipart_file_map(
+ variables={"files": [None, None], "textFile": None},
+ files={"file1": file1, "file2": file2, "textFile": file3},
+ )
+ # {
+ # "file1": ["variables.files.0"],
+ # "file2": ["variables.files.1"],
+ # "textFile": ["variables.textFile"],
+ # }
+ ```
+ """
map: Dict[str, List[str]] = {}
for key, values in variables.items():
reference = key
@@ -159,7 +175,10 @@ def _build_multipart_file_map(
map_without_vars = {k: v for k, v in map.items() if k in files}
return map_without_vars
- def _decode(self, response: Any, type: Literal["multipart", "json"]):
+ def _decode(self, response: Any, type: Literal["multipart", "json"]) -> Any:
if type == "multipart":
return json.loads(response.content.decode())
return response.json()
+
+
+__all__ = ["BaseGraphQLTestClient", "Response", "Body"]
diff --git a/strawberry/tools/create_type.py b/strawberry/tools/create_type.py
index 2ba4d17c21..8436a5969c 100644
--- a/strawberry/tools/create_type.py
+++ b/strawberry/tools/create_type.py
@@ -2,7 +2,7 @@
from typing import List, Optional, Sequence, Type
import strawberry
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
def create_type(
@@ -14,15 +14,31 @@ def create_type(
directives: Optional[Sequence[object]] = (),
extend: bool = False,
) -> Type:
- """Create a Strawberry type from a list of StrawberryFields
+ """Create a Strawberry type from a list of StrawberryFields.
- >>> @strawberry.field
- >>> def hello(info) -> str:
- >>> return "World"
- >>>
- >>> Query = create_type(name="Query", fields=[hello])
- """
+ Args:
+ name: The GraphQL name of the type.
+ fields: The fields of the type.
+ is_input: Whether the type is an input type.
+ is_interface: Whether the type is an interface.
+ description: The GraphQL description of the type.
+ directives: The directives to attach to the type.
+ extend: Whether the type is an extension.
+
+ Example usage:
+
+ ```python
+ import strawberry
+
+
+ @strawberry.field
+ def hello(info) -> str:
+ return "World"
+
+ Query = create_type(name="Query", fields=[hello])
+ ```
+ """
if not fields:
raise ValueError(f'Can\'t create type "{name}" with no fields')
@@ -55,3 +71,6 @@ def create_type(
directives=directives,
extend=extend,
)
+
+
+__all__ = ["create_type"]
diff --git a/strawberry/tools/merge_types.py b/strawberry/tools/merge_types.py
index 4dea67b3fc..84095d7086 100644
--- a/strawberry/tools/merge_types.py
+++ b/strawberry/tools/merge_types.py
@@ -4,11 +4,11 @@
from typing import Tuple
import strawberry
-from strawberry.type import has_object_definition
+from strawberry.types.base import has_object_definition
def merge_types(name: str, types: Tuple[type, ...]) -> type:
- """Merge multiple Strawberry types into one
+ """Merge multiple Strawberry types into one.
For example, given two queries `A` and `B`, one can merge them into a
super type as follows:
@@ -20,7 +20,6 @@ def merge_types(name: str, types: Tuple[type, ...]) -> type:
class SuperQuery(B, A):
...
"""
-
if not types:
raise ValueError("Can't merge types if none are supplied")
@@ -35,3 +34,6 @@ class SuperQuery(B, A):
)
return strawberry.type(type(name, types, {}))
+
+
+__all__ = ["merge_types"]
diff --git a/strawberry/type.py b/strawberry/type.py
deleted file mode 100644
index bc4fa89fd7..0000000000
--- a/strawberry/type.py
+++ /dev/null
@@ -1,232 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC, abstractmethod
-from typing import (
- TYPE_CHECKING,
- Any,
- ClassVar,
- List,
- Mapping,
- Optional,
- Type,
- TypeVar,
- Union,
- overload,
-)
-from typing_extensions import Literal, Protocol, Self
-
-from strawberry.utils.typing import is_concrete_generic
-
-if TYPE_CHECKING:
- from typing_extensions import TypeGuard
-
- from strawberry.types.types import StrawberryObjectDefinition
-
-
-class StrawberryType(ABC):
- """
- Every type that is decorated by strawberry should have a dunder
- `__strawberry_definition__` with instance of a StrawberryType that contains
- the parsed information that strawberry created.
-
- NOTE: ATM this is only true for @type @interface @input follow https://github.com/strawberry-graphql/strawberry/issues/2841
- to see progress.
- """
-
- @property
- def type_params(self) -> List[TypeVar]:
- return []
-
- @abstractmethod
- def copy_with(
- self,
- type_var_map: Mapping[
- str, Union[StrawberryType, Type[WithStrawberryObjectDefinition]]
- ],
- ) -> Union[StrawberryType, Type[WithStrawberryObjectDefinition]]:
- raise NotImplementedError()
-
- @property
- @abstractmethod
- def is_graphql_generic(self) -> bool:
- raise NotImplementedError()
-
- def has_generic(self, type_var: TypeVar) -> bool:
- return False
-
- def __eq__(self, other: object) -> bool:
- from strawberry.annotation import StrawberryAnnotation
-
- if isinstance(other, StrawberryType):
- return self is other
-
- elif isinstance(other, StrawberryAnnotation):
- return self == other.resolve()
-
- else:
- # This could be simplified if StrawberryAnnotation.resolve() always returned
- # a StrawberryType
- resolved = StrawberryAnnotation(other).resolve()
- if isinstance(resolved, StrawberryType):
- return self == resolved
- else:
- return NotImplemented
-
- def __hash__(self) -> int:
- # TODO: Is this a bad idea? __eq__ objects are supposed to have the same hash
- return id(self)
-
-
-class StrawberryContainer(StrawberryType):
- def __init__(
- self, of_type: Union[StrawberryType, Type[WithStrawberryObjectDefinition], type]
- ):
- self.of_type = of_type
-
- def __hash__(self) -> int:
- return hash((self.__class__, self.of_type))
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, StrawberryType):
- if isinstance(other, StrawberryContainer):
- return self.of_type == other.of_type
- else:
- return False
-
- return super().__eq__(other)
-
- @property
- def type_params(self) -> List[TypeVar]:
- if has_object_definition(self.of_type):
- parameters = getattr(self.of_type, "__parameters__", None)
-
- return list(parameters) if parameters else []
-
- elif isinstance(self.of_type, StrawberryType):
- return self.of_type.type_params
-
- else:
- return []
-
- def copy_with(
- self,
- type_var_map: Mapping[
- str, Union[StrawberryType, Type[WithStrawberryObjectDefinition]]
- ],
- ) -> Self:
- of_type_copy = self.of_type
-
- if has_object_definition(self.of_type):
- type_definition = self.of_type.__strawberry_definition__
-
- if type_definition.is_graphql_generic:
- of_type_copy = type_definition.copy_with(type_var_map)
-
- elif (
- isinstance(self.of_type, StrawberryType) and self.of_type.is_graphql_generic
- ):
- of_type_copy = self.of_type.copy_with(type_var_map)
-
- return type(self)(of_type_copy)
-
- @property
- def is_graphql_generic(self) -> bool:
- from strawberry.schema.compat import is_graphql_generic
-
- type_ = self.of_type
-
- return is_graphql_generic(type_)
-
- def has_generic(self, type_var: TypeVar) -> bool:
- if isinstance(self.of_type, StrawberryType):
- return self.of_type.has_generic(type_var)
- return False
-
-
-class StrawberryList(StrawberryContainer):
- ...
-
-
-class StrawberryOptional(StrawberryContainer):
- ...
-
-
-class StrawberryTypeVar(StrawberryType):
- def __init__(self, type_var: TypeVar):
- self.type_var = type_var
-
- def copy_with(
- self, type_var_map: Mapping[str, Union[StrawberryType, type]]
- ) -> Union[StrawberryType, type]:
- return type_var_map[self.type_var.__name__]
-
- @property
- def is_graphql_generic(self) -> bool:
- return True
-
- def has_generic(self, type_var: TypeVar) -> bool:
- return self.type_var == type_var
-
- @property
- def type_params(self) -> List[TypeVar]:
- return [self.type_var]
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, StrawberryTypeVar):
- return self.type_var == other.type_var
- if isinstance(other, TypeVar):
- return self.type_var == other
-
- return super().__eq__(other)
-
- def __hash__(self):
- return hash(self.type_var)
-
-
-class WithStrawberryObjectDefinition(Protocol):
- __strawberry_definition__: ClassVar[StrawberryObjectDefinition]
-
-
-def has_object_definition(
- obj: Any,
-) -> TypeGuard[Type[WithStrawberryObjectDefinition]]:
- if hasattr(obj, "__strawberry_definition__"):
- return True
- # TODO: Generics remove dunder members here, so we inject it here.
- # Would be better to avoid it somehow.
- # https://github.com/python/cpython/blob/3a314f7c3df0dd7c37da7d12b827f169ee60e1ea/Lib/typing.py#L1152
- if is_concrete_generic(obj):
- concrete = obj.__origin__
- if hasattr(concrete, "__strawberry_definition__"):
- obj.__strawberry_definition__ = concrete.__strawberry_definition__
- return True
- return False
-
-
-@overload
-def get_object_definition(
- obj: Any,
- *,
- strict: Literal[True],
-) -> StrawberryObjectDefinition:
- ...
-
-
-@overload
-def get_object_definition(
- obj: Any,
- *,
- strict: bool = False,
-) -> Optional[StrawberryObjectDefinition]:
- ...
-
-
-def get_object_definition(
- obj: Any,
- *,
- strict: bool = False,
-) -> Optional[StrawberryObjectDefinition]:
- definition = obj.__strawberry_definition__ if has_object_definition(obj) else None
- if strict and definition is None:
- raise TypeError(f"{obj!r} does not have a StrawberryObjectDefinition")
- return definition
diff --git a/strawberry/types/__init__.py b/strawberry/types/__init__.py
index 72f735d229..65f055865c 100644
--- a/strawberry/types/__init__.py
+++ b/strawberry/types/__init__.py
@@ -1,3 +1,4 @@
+from .base import get_object_definition, has_object_definition
from .execution import ExecutionContext, ExecutionResult, SubscriptionExecutionResult
from .info import Info
@@ -6,4 +7,7 @@
"ExecutionResult",
"SubscriptionExecutionResult",
"Info",
+ "Info",
+ "get_object_definition",
+ "has_object_definition",
]
diff --git a/strawberry/arguments.py b/strawberry/types/arguments.py
similarity index 81%
rename from strawberry/arguments.py
rename to strawberry/types/arguments.py
index e1e5a44099..a48f0e417d 100644
--- a/strawberry/arguments.py
+++ b/strawberry/types/arguments.py
@@ -16,25 +16,28 @@
from typing_extensions import Annotated, get_args, get_origin
from strawberry.annotation import StrawberryAnnotation
-from strawberry.enum import EnumDefinition
-from strawberry.lazy_type import LazyType, StrawberryLazyReference
-from strawberry.type import StrawberryList, StrawberryOptional, has_object_definition
-
-from .exceptions import MultipleStrawberryArgumentsError, UnsupportedTypeError
-from .scalars import is_scalar
-from .unset import UNSET as _deprecated_UNSET
-from .unset import _deprecated_is_unset # noqa # type: ignore
+from strawberry.exceptions import MultipleStrawberryArgumentsError, UnsupportedTypeError
+from strawberry.scalars import is_scalar
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryOptional,
+ has_object_definition,
+)
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.lazy_type import LazyType, StrawberryLazyReference
+from strawberry.types.unset import UNSET as _deprecated_UNSET
+from strawberry.types.unset import _deprecated_is_unset # noqa # type: ignore
if TYPE_CHECKING:
- from strawberry.custom_scalar import ScalarDefinition, ScalarWrapper
from strawberry.schema.config import StrawberryConfig
- from strawberry.type import StrawberryType
+ from strawberry.types.base import StrawberryType
+ from strawberry.types.scalar import ScalarDefinition, ScalarWrapper
DEPRECATED_NAMES: Dict[str, str] = {
"UNSET": (
"importing `UNSET` from `strawberry.arguments` is deprecated, "
- "import instead from `strawberry` or from `strawberry.unset`"
+ "import instead from `strawberry` or from `strawberry.types.unset`"
),
"is_unset": "`is_unset` is deprecated use `value is UNSET` instead",
}
@@ -54,7 +57,7 @@ def __init__(
deprecation_reason: Optional[str] = None,
directives: Iterable[object] = (),
metadata: Optional[Mapping[Any, Any]] = None,
- ):
+ ) -> None:
self.description = description
self.name = name
self.deprecation_reason = deprecation_reason
@@ -129,7 +132,6 @@ def type(self) -> Union[StrawberryType, type]:
@property
def is_graphql_generic(self) -> bool:
- # TODO: double check this
from strawberry.schema.compat import is_graphql_generic
return is_graphql_generic(self.type)
@@ -203,8 +205,8 @@ def convert_arguments(
"""Converts a nested dictionary to a dictionary of actual types.
It deals with conversion of input types to proper dataclasses and
- also uses a sentinel value for unset values."""
-
+ also uses a sentinel value for unset values.
+ """
if not arguments:
return {}
@@ -235,6 +237,34 @@ def argument(
directives: Iterable[object] = (),
metadata: Optional[Mapping[Any, Any]] = None,
) -> StrawberryArgumentAnnotation:
+ """Function to add metadata to an argument, like a description or deprecation reason.
+
+ Args:
+ description: The GraphQL description of the argument
+ name: The GraphQL name of the argument
+ deprecation_reason: The reason why this argument is deprecated,
+ setting this will mark the argument as deprecated
+ directives: The directives to attach to the argument
+ metadata: Metadata to attach to the argument, this can be used
+ to store custom data that can be used by custom logic or plugins
+
+ Returns:
+ A StrawberryArgumentAnnotation object that can be used to customise an argument
+
+ Example:
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def example(
+ self, info, value: int = strawberry.argument(description="The value")
+ ) -> int:
+ return value
+ ```
+ """
return StrawberryArgumentAnnotation(
description=description,
name=name,
diff --git a/strawberry/auto.py b/strawberry/types/auto.py
similarity index 77%
rename from strawberry/auto.py
rename to strawberry/types/auto.py
index 63fdb45aaa..9de85b1e34 100644
--- a/strawberry/auto.py
+++ b/strawberry/types/auto.py
@@ -3,9 +3,8 @@
from typing import Any, Optional, Union, cast
from typing_extensions import Annotated, get_args, get_origin
-from strawberry.type import StrawberryType
-
-from .annotation import StrawberryAnnotation
+from strawberry.annotation import StrawberryAnnotation
+from strawberry.types.base import StrawberryType
class StrawberryAutoMeta(type):
@@ -24,11 +23,11 @@ class StrawberryAutoMeta(type):
"""
- def __init__(self, *args: str, **kwargs: Any):
+ def __init__(self, *args: str, **kwargs: Any) -> None:
self._instance: Optional[StrawberryAuto] = None
super().__init__(*args, **kwargs)
- def __call__(cls, *args: str, **kwargs: Any):
+ def __call__(cls, *args: str, **kwargs: Any) -> Any:
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
@@ -37,7 +36,7 @@ def __call__(cls, *args: str, **kwargs: Any):
def __instancecheck__(
self,
instance: Union[StrawberryAuto, StrawberryAnnotation, StrawberryType, type],
- ):
+ ) -> bool:
if isinstance(instance, StrawberryAnnotation):
resolved = instance.raw_annotation
if isinstance(resolved, str):
@@ -69,11 +68,30 @@ def __instancecheck__(
class StrawberryAuto(metaclass=StrawberryAutoMeta):
- def __str__(self):
+ def __str__(self) -> str:
return "auto"
- def __repr__(self):
+ def __repr__(self) -> str:
return ""
auto = Annotated[Any, StrawberryAuto()]
+"""Special marker for automatic annotation.
+
+A special value that can be used to automatically infer the type of a field
+when using integrations like Strawberry Django or Strawberry Pydantic.
+
+Example:
+```python
+import strawberry
+
+from my_user_app import models
+
+
+@strawberry.django.type(models.User)
+class User:
+ name: strawberry.auto
+```
+"""
+
+__all__ = ["auto"]
diff --git a/strawberry/types/base.py b/strawberry/types/base.py
new file mode 100644
index 0000000000..9c235093d7
--- /dev/null
+++ b/strawberry/types/base.py
@@ -0,0 +1,473 @@
+from __future__ import annotations
+
+import dataclasses
+from abc import ABC, abstractmethod
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ ClassVar,
+ Dict,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Type,
+ TypeVar,
+ Union,
+ overload,
+)
+from typing_extensions import Literal, Protocol, Self, deprecated
+
+from strawberry.utils.deprecations import DEPRECATION_MESSAGES, DeprecatedDescriptor
+from strawberry.utils.inspect import get_specialized_type_var_map
+from strawberry.utils.typing import is_concrete_generic
+from strawberry.utils.typing import is_generic as is_type_generic
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeGuard
+
+ from graphql import GraphQLAbstractType, GraphQLResolveInfo
+
+ from strawberry.types.field import StrawberryField
+
+
+class StrawberryType(ABC):
+ """The base class for all types that Strawberry uses.
+
+ Every type that is decorated by strawberry should have a dunder
+ `__strawberry_definition__` with an instance of a StrawberryType that contains
+ the parsed information that strawberry created.
+
+ NOTE: ATM this is only true for @type @interface @input follow
+ https://github.com/strawberry-graphql/strawberry/issues/2841 to see progress.
+ """
+
+ @property
+ def type_params(self) -> List[TypeVar]:
+ return []
+
+ @property
+ def is_one_of(self) -> bool:
+ return False
+
+ @abstractmethod
+ def copy_with(
+ self,
+ type_var_map: Mapping[
+ str, Union[StrawberryType, Type[WithStrawberryObjectDefinition]]
+ ],
+ ) -> Union[StrawberryType, Type[WithStrawberryObjectDefinition]]:
+ raise NotImplementedError()
+
+ @property
+ @abstractmethod
+ def is_graphql_generic(self) -> bool:
+ raise NotImplementedError()
+
+ def has_generic(self, type_var: TypeVar) -> bool:
+ return False
+
+ def __eq__(self, other: object) -> bool:
+ from strawberry.annotation import StrawberryAnnotation
+
+ if isinstance(other, StrawberryType):
+ return self is other
+
+ elif isinstance(other, StrawberryAnnotation):
+ return self == other.resolve()
+
+ else:
+ # This could be simplified if StrawberryAnnotation.resolve() always returned
+ # a StrawberryType
+ resolved = StrawberryAnnotation(other).resolve()
+ if isinstance(resolved, StrawberryType):
+ return self == resolved
+ else:
+ return NotImplemented
+
+ def __hash__(self) -> int:
+ # TODO: Is this a bad idea? __eq__ objects are supposed to have the same hash
+ return id(self)
+
+
+class StrawberryContainer(StrawberryType):
+ def __init__(
+ self, of_type: Union[StrawberryType, Type[WithStrawberryObjectDefinition], type]
+ ) -> None:
+ self.of_type = of_type
+
+ def __hash__(self) -> int:
+ return hash((self.__class__, self.of_type))
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, StrawberryType):
+ if isinstance(other, StrawberryContainer):
+ return self.of_type == other.of_type
+ else:
+ return False
+
+ return super().__eq__(other)
+
+ @property
+ def type_params(self) -> List[TypeVar]:
+ if has_object_definition(self.of_type):
+ parameters = getattr(self.of_type, "__parameters__", None)
+
+ return list(parameters) if parameters else []
+
+ elif isinstance(self.of_type, StrawberryType):
+ return self.of_type.type_params
+
+ else:
+ return []
+
+ def copy_with(
+ self,
+ type_var_map: Mapping[
+ str, Union[StrawberryType, Type[WithStrawberryObjectDefinition]]
+ ],
+ ) -> Self:
+ of_type_copy = self.of_type
+
+ if has_object_definition(self.of_type):
+ type_definition = self.of_type.__strawberry_definition__
+
+ if type_definition.is_graphql_generic:
+ of_type_copy = type_definition.copy_with(type_var_map)
+
+ elif (
+ isinstance(self.of_type, StrawberryType) and self.of_type.is_graphql_generic
+ ):
+ of_type_copy = self.of_type.copy_with(type_var_map)
+
+ return type(self)(of_type_copy)
+
+ @property
+ def is_graphql_generic(self) -> bool:
+ from strawberry.schema.compat import is_graphql_generic
+
+ type_ = self.of_type
+
+ return is_graphql_generic(type_)
+
+ def has_generic(self, type_var: TypeVar) -> bool:
+ if isinstance(self.of_type, StrawberryType):
+ return self.of_type.has_generic(type_var)
+ return False
+
+
+class StrawberryList(StrawberryContainer): ...
+
+
+class StrawberryOptional(StrawberryContainer): ...
+
+
+class StrawberryTypeVar(StrawberryType):
+ def __init__(self, type_var: TypeVar) -> None:
+ self.type_var = type_var
+
+ def copy_with(
+ self, type_var_map: Mapping[str, Union[StrawberryType, type]]
+ ) -> Union[StrawberryType, type]:
+ return type_var_map[self.type_var.__name__]
+
+ @property
+ def is_graphql_generic(self) -> bool:
+ return True
+
+ def has_generic(self, type_var: TypeVar) -> bool:
+ return self.type_var == type_var
+
+ @property
+ def type_params(self) -> List[TypeVar]:
+ return [self.type_var]
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, StrawberryTypeVar):
+ return self.type_var == other.type_var
+ if isinstance(other, TypeVar):
+ return self.type_var == other
+
+ return super().__eq__(other)
+
+ def __hash__(self) -> int:
+ return hash(self.type_var)
+
+
+class WithStrawberryObjectDefinition(Protocol):
+ __strawberry_definition__: ClassVar[StrawberryObjectDefinition]
+
+
+def has_object_definition(
+ obj: Any,
+) -> TypeGuard[Type[WithStrawberryObjectDefinition]]:
+ if hasattr(obj, "__strawberry_definition__"):
+ return True
+ # TODO: Generics remove dunder members here, so we inject it here.
+ # Would be better to avoid it somehow.
+ # https://github.com/python/cpython/blob/3a314f7c3df0dd7c37da7d12b827f169ee60e1ea/Lib/typing.py#L1152
+ if is_concrete_generic(obj):
+ concrete = obj.__origin__
+ if hasattr(concrete, "__strawberry_definition__"):
+ obj.__strawberry_definition__ = concrete.__strawberry_definition__
+ return True
+ return False
+
+
+@overload
+def get_object_definition(
+ obj: Any,
+ *,
+ strict: Literal[True],
+) -> StrawberryObjectDefinition: ...
+
+
+@overload
+def get_object_definition(
+ obj: Any,
+ *,
+ strict: bool = False,
+) -> Optional[StrawberryObjectDefinition]: ...
+
+
+def get_object_definition(
+ obj: Any,
+ *,
+ strict: bool = False,
+) -> Optional[StrawberryObjectDefinition]:
+ definition = obj.__strawberry_definition__ if has_object_definition(obj) else None
+ if strict and definition is None:
+ raise TypeError(f"{obj!r} does not have a StrawberryObjectDefinition")
+
+ return definition
+
+
+@dataclasses.dataclass(eq=False)
+class StrawberryObjectDefinition(StrawberryType):
+ """Encapsulates definitions for Input / Object / interface GraphQL Types.
+
+ In order get the definition from a decorated object you can use
+ `has_object_definition` or `get_object_definition` as a shortcut.
+ """
+
+ name: str
+ is_input: bool
+ is_interface: bool
+ origin: Type[Any]
+ description: Optional[str]
+ interfaces: List[StrawberryObjectDefinition]
+ extend: bool
+ directives: Optional[Sequence[object]]
+ is_type_of: Optional[Callable[[Any, GraphQLResolveInfo], bool]]
+ resolve_type: Optional[
+ Callable[[Any, GraphQLResolveInfo, GraphQLAbstractType], str]
+ ]
+
+ fields: List[StrawberryField]
+
+ concrete_of: Optional[StrawberryObjectDefinition] = None
+ """Concrete implementations of Generic TypeDefinitions fill this in"""
+ type_var_map: Mapping[str, Union[StrawberryType, type]] = dataclasses.field(
+ default_factory=dict
+ )
+
+ def __post_init__(self) -> None:
+ # resolve `Self` annotation with the origin type
+ for index, field in enumerate(self.fields):
+ if isinstance(field.type, StrawberryType) and field.type.has_generic(Self): # type: ignore
+ self.fields[index] = field.copy_with({Self.__name__: self.origin}) # type: ignore
+
+ def resolve_generic(self, wrapped_cls: type) -> type:
+ from strawberry.annotation import StrawberryAnnotation
+
+ passed_types = wrapped_cls.__args__ # type: ignore
+ params = wrapped_cls.__origin__.__parameters__ # type: ignore
+
+ # Make sure all passed_types are turned into StrawberryTypes
+ resolved_types = []
+ for passed_type in passed_types:
+ resolved_type = StrawberryAnnotation(passed_type).resolve()
+ resolved_types.append(resolved_type)
+
+ type_var_map = dict(zip((param.__name__ for param in params), resolved_types))
+
+ return self.copy_with(type_var_map)
+
+ def copy_with(
+ self, type_var_map: Mapping[str, Union[StrawberryType, type]]
+ ) -> Type[WithStrawberryObjectDefinition]:
+ fields = [field.copy_with(type_var_map) for field in self.fields]
+
+ new_type_definition = StrawberryObjectDefinition(
+ name=self.name,
+ is_input=self.is_input,
+ origin=self.origin,
+ is_interface=self.is_interface,
+ directives=self.directives and self.directives[:],
+ interfaces=self.interfaces and self.interfaces[:],
+ description=self.description,
+ extend=self.extend,
+ is_type_of=self.is_type_of,
+ resolve_type=self.resolve_type,
+ fields=fields,
+ concrete_of=self,
+ type_var_map=type_var_map,
+ )
+
+ new_type = type(
+ new_type_definition.name,
+ (self.origin,),
+ {"__strawberry_definition__": new_type_definition},
+ )
+ # TODO: remove when deprecating _type_definition
+ DeprecatedDescriptor(
+ DEPRECATION_MESSAGES._TYPE_DEFINITION,
+ new_type.__strawberry_definition__, # type: ignore
+ "_type_definition",
+ ).inject(new_type)
+
+ new_type_definition.origin = new_type
+
+ return new_type
+
+ def get_field(self, python_name: str) -> Optional[StrawberryField]:
+ return next(
+ (field for field in self.fields if field.python_name == python_name), None
+ )
+
+ @property
+ def is_graphql_generic(self) -> bool:
+ if not is_type_generic(self.origin):
+ return False
+
+ # here we are checking if any exposed field is generic
+ # a Strawberry class can be "generic", but not expose any
+ # generic field to GraphQL
+ return any(field.is_graphql_generic for field in self.fields)
+
+ @property
+ def is_specialized_generic(self) -> bool:
+ return self.is_graphql_generic and not getattr(
+ self.origin, "__parameters__", None
+ )
+
+ @property
+ def specialized_type_var_map(self) -> Optional[Dict[str, type]]:
+ return get_specialized_type_var_map(self.origin)
+
+ @property
+ def is_object_type(self) -> bool:
+ return not self.is_input and not self.is_interface
+
+ @property
+ def type_params(self) -> List[TypeVar]:
+ type_params: List[TypeVar] = []
+ for field in self.fields:
+ type_params.extend(field.type_params)
+
+ return type_params
+
+ def is_implemented_by(self, root: Type[WithStrawberryObjectDefinition]) -> bool:
+ # TODO: Support dicts
+ if isinstance(root, dict):
+ raise NotImplementedError
+
+ type_definition = root.__strawberry_definition__
+
+ if type_definition is self:
+ # No generics involved. Exact type match
+ return True
+
+ if type_definition is not self.concrete_of:
+ # Either completely different type, or concrete type of a different generic
+ return False
+
+ # Check the mapping of all fields' TypeVars
+ for field in type_definition.fields:
+ if not field.is_graphql_generic:
+ continue
+
+ value = getattr(root, field.name)
+ generic_field_type = field.type
+
+ while isinstance(generic_field_type, StrawberryList):
+ generic_field_type = generic_field_type.of_type
+
+ assert isinstance(value, (list, tuple))
+
+ if len(value) == 0:
+ # We can't infer the type of an empty list, so we just
+ # return the first one we find
+ return True
+
+ value = value[0]
+
+ if isinstance(generic_field_type, StrawberryTypeVar):
+ type_var = generic_field_type.type_var
+ # TODO: I don't think we support nested types properly
+ # if there's a union that has two nested types that
+ # are have the same field with different types, we might
+ # not be able to differentiate them
+ else:
+ continue
+
+ # For each TypeVar found, get the expected type from the copy's type map
+ expected_concrete_type = self.type_var_map.get(type_var.__name__)
+
+ # this shouldn't happen, but we do a defensive check just in case
+ if expected_concrete_type is None:
+ continue
+
+ # Check if the expected type matches the type found on the type_map
+ real_concrete_type = type(value)
+
+ # TODO: uniform type var map, at the moment we map object types
+ # to their class (not to TypeDefinition) while we map enum to
+ # the EnumDefinition class. This is why we do this check here:
+ if hasattr(real_concrete_type, "_enum_definition"):
+ real_concrete_type = real_concrete_type._enum_definition
+
+ if isinstance(expected_concrete_type, type) and issubclass(
+ real_concrete_type, expected_concrete_type
+ ):
+ return True
+
+ if real_concrete_type is not expected_concrete_type:
+ return False
+
+ # All field mappings succeeded. This is a match
+ return True
+
+ @property
+ def is_one_of(self) -> bool:
+ from strawberry.schema_directives import OneOf
+
+ if not self.is_input or not self.directives:
+ return False
+
+ return any(isinstance(directive, OneOf) for directive in self.directives)
+
+
+# TODO: remove when deprecating _type_definition
+if TYPE_CHECKING:
+
+ @deprecated("Use StrawberryObjectDefinition instead")
+ class TypeDefinition(StrawberryObjectDefinition): ...
+
+else:
+ TypeDefinition = StrawberryObjectDefinition
+
+
+__all__ = [
+ "StrawberryContainer",
+ "StrawberryList",
+ "StrawberryObjectDefinition",
+ "StrawberryOptional",
+ "StrawberryType",
+ "StrawberryTypeVar",
+ "TypeDefinition",
+ "WithStrawberryObjectDefinition",
+ "get_object_definition",
+ "has_object_definition",
+]
diff --git a/strawberry/enum.py b/strawberry/types/enum.py
similarity index 65%
rename from strawberry/enum.py
rename to strawberry/types/enum.py
index b746087eaf..4ec01be188 100644
--- a/strawberry/enum.py
+++ b/strawberry/types/enum.py
@@ -12,9 +12,8 @@
overload,
)
-from strawberry.type import StrawberryType
-
-from .exceptions import ObjectIsNotAnEnumError
+from strawberry.exceptions import ObjectIsNotAnEnumError
+from strawberry.types.base import StrawberryType
@dataclasses.dataclass
@@ -67,6 +66,30 @@ def enum_value(
directives: Iterable[object] = (),
description: Optional[str] = None,
) -> EnumValueDefinition:
+ """Function to customise an enum value, for example to add a description or deprecation reason.
+
+ Args:
+ value: The value of the enum member.
+ deprecation_reason: The deprecation reason of the enum member,
+ setting this will mark the enum member as deprecated.
+ directives: The directives to attach to the enum member.
+ description: The GraphQL description of the enum member.
+
+ Returns:
+ An EnumValueDefinition object that can be used to customise an enum member.
+
+ Example:
+ ```python
+ from enum import Enum
+ import strawberry
+
+
+ @strawberry.enum
+ class MyEnum(Enum):
+ FIRST_VALUE = strawberry.enum_value(description="The first value")
+ SECOND_VALUE = strawberry.enum_value(description="The second value")
+ ```
+ """
return EnumValueDefinition(
value=value,
deprecation_reason=deprecation_reason,
@@ -131,34 +154,66 @@ def _process_enum(
@overload
def enum(
- _cls: EnumType,
+ cls: EnumType,
*,
name: Optional[str] = None,
description: Optional[str] = None,
directives: Iterable[object] = (),
-) -> EnumType:
- ...
+) -> EnumType: ...
@overload
def enum(
- _cls: None = None,
+ cls: None = None,
*,
name: Optional[str] = None,
description: Optional[str] = None,
directives: Iterable[object] = (),
-) -> Callable[[EnumType], EnumType]:
- ...
+) -> Callable[[EnumType], EnumType]: ...
def enum(
- _cls: Optional[EnumType] = None,
+ cls: Optional[EnumType] = None,
*,
name: Optional[str] = None,
description: Optional[str] = None,
directives: Iterable[object] = (),
) -> Union[EnumType, Callable[[EnumType], EnumType]]:
- """Registers the enum in the GraphQL type system.
+ """Annotates an Enum class a GraphQL enum.
+
+ GraphQL enums only have names, while Python enums have names and values,
+ Strawberry will use the names of the Python enum as the names of the
+ GraphQL enum values.
+
+ Args:
+ cls: The Enum class to be annotated.
+ name: The name of the GraphQL enum.
+ description: The description of the GraphQL enum.
+ directives: The directives to attach to the GraphQL enum.
+
+ Returns:
+ The decorated Enum class.
+
+ Example:
+ ```python
+ from enum import Enum
+ import strawberry
+
+
+ @strawberry.enum
+ class MyEnum(Enum):
+ FIRST_VALUE = "first_value"
+ SECOND_VALUE = "second_value"
+ ```
+
+ The above code will generate the following GraphQL schema:
+
+ ```graphql
+ enum MyEnum {
+ FIRST_VALUE
+ SECOND_VALUE
+ }
+ ```
If name is passed, the name of the GraphQL type will be
the value passed of name instead of the Enum class name.
@@ -167,7 +222,10 @@ def enum(
def wrap(cls: EnumType) -> EnumType:
return _process_enum(cls, name, description, directives=directives)
- if not _cls:
+ if not cls:
return wrap
- return wrap(_cls)
+ return wrap(cls)
+
+
+__all__ = ["EnumValue", "EnumDefinition", "EnumValueDefinition", "enum", "enum_value"]
diff --git a/strawberry/types/execution.py b/strawberry/types/execution.py
index e3d6518e55..e1f88dbf21 100644
--- a/strawberry/types/execution.py
+++ b/strawberry/types/execution.py
@@ -53,7 +53,7 @@ class ExecutionContext:
errors: Optional[List[GraphQLError]] = None
result: Optional[GraphQLExecutionResult] = None
- def __post_init__(self, provided_operation_name: str | None):
+ def __post_init__(self, provided_operation_name: str | None) -> None:
self._provided_operation_name = provided_operation_name
@property
@@ -104,3 +104,11 @@ def __aiter__(self) -> SubscriptionExecutionResult: # pragma: no cover
async def __anext__(self) -> Any: # pragma: no cover
...
+
+
+__all__ = [
+ "ExecutionContext",
+ "ExecutionResult",
+ "ParseOptions",
+ "SubscriptionExecutionResult",
+]
diff --git a/strawberry/field.py b/strawberry/types/field.py
similarity index 82%
rename from strawberry/field.py
rename to strawberry/types/field.py
index 2724fea618..4c1fb8c812 100644
--- a/strawberry/field.py
+++ b/strawberry/types/field.py
@@ -24,42 +24,46 @@
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import InvalidArgumentTypeError, InvalidDefaultFactoryError
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryType,
WithStrawberryObjectDefinition,
has_object_definition,
)
-from strawberry.union import StrawberryUnion
+from strawberry.types.union import StrawberryUnion
-from .types.fields.resolver import StrawberryResolver
+from .fields.resolver import StrawberryResolver
if TYPE_CHECKING:
import builtins
from typing_extensions import Literal, Self
- from strawberry.arguments import StrawberryArgument
from strawberry.extensions.field_extension import FieldExtension
+ from strawberry.permission import BasePermission
+ from strawberry.types.arguments import StrawberryArgument
+ from strawberry.types.base import StrawberryObjectDefinition
from strawberry.types.info import Info
- from strawberry.types.types import StrawberryObjectDefinition
-
- from .permission import BasePermission
T = TypeVar("T")
-_RESOLVER_TYPE = Union[
+_RESOLVER_TYPE_SYNC = Union[
StrawberryResolver[T],
Callable[..., T],
- Callable[..., Coroutine[T, Any, Any]],
- Callable[..., Awaitable[T]],
"staticmethod[Any, T]",
"classmethod[Any, Any, T]",
]
+_RESOLVER_TYPE_ASYNC = Union[
+ Callable[..., Coroutine[Any, Any, T]],
+ Callable[..., Awaitable[T]],
+]
+
+_RESOLVER_TYPE = Union[_RESOLVER_TYPE_SYNC[T], _RESOLVER_TYPE_ASYNC[T]]
+
UNRESOLVED = object()
def _is_generic(resolver_type: Union[StrawberryType, type]) -> bool:
- """Returns True if `resolver_type` is generic else False"""
+ """Returns True if `resolver_type` is generic else False."""
if isinstance(resolver_type, StrawberryType):
return resolver_type.is_graphql_generic
@@ -90,7 +94,7 @@ def __init__(
deprecation_reason: Optional[str] = None,
directives: Sequence[object] = (),
extensions: List[FieldExtension] = (), # type: ignore
- ):
+ ) -> None:
# basic fields are fields with no provided resolver
is_basic_field = not base_resolver
@@ -144,7 +148,7 @@ def __init__(
# Automatically add the permissions extension
if len(self.permission_classes):
- from .permission import PermissionExtension
+ from strawberry.permission import PermissionExtension
if not self.extensions:
self.extensions = []
@@ -184,8 +188,7 @@ def __copy__(self) -> Self:
return new_field
def __call__(self, resolver: _RESOLVER_TYPE) -> Self:
- """Add a resolver to the field"""
-
+ """Add a resolver to the field."""
# Allow for StrawberryResolvers or bare functions to be provided
if not isinstance(resolver, StrawberryResolver):
resolver = StrawberryResolver(resolver)
@@ -212,12 +215,11 @@ def __call__(self, resolver: _RESOLVER_TYPE) -> Self:
def get_result(
self, source: Any, info: Optional[Info], args: List[Any], kwargs: Any
) -> Union[Awaitable[Any], Any]:
- """
- Calls the resolver defined for the StrawberryField.
+ """Calls the resolver defined for the StrawberryField.
+
If the field doesn't have a resolver defined we default
to using the default resolver specified in StrawberryConfig.
"""
-
if self.base_resolver:
return self.base_resolver(*args, **kwargs)
@@ -225,8 +227,9 @@ def get_result(
@property
def is_basic_field(self) -> bool:
- """
- Flag indicating if this is a "basic" field that has no resolver or
+ """Returns a boolean indicating if the field is a basic field.
+
+ A "basic" field us a field that has no resolver or
permission classes, i.e. it just returns the relevant attribute from
the source object. If it is a basic field we can avoid constructing
an `Info` object and running any permission checks in the resolver
@@ -242,7 +245,7 @@ def arguments(self) -> List[StrawberryArgument]:
return self._arguments
@arguments.setter
- def arguments(self, value: List[StrawberryArgument]):
+ def arguments(self, value: List[StrawberryArgument]) -> None:
self._arguments = value
@property
@@ -416,10 +419,33 @@ def is_async(self) -> bool:
return self._has_async_base_resolver
+# NOTE: we are separating the sync and async resolvers because using both
+# in the same function will cause mypy to raise an error. Not sure if it is a bug
+
+
+@overload
+def field(
+ *,
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ name: Optional[str] = None,
+ is_subscription: bool = False,
+ description: Optional[str] = None,
+ init: Literal[False] = False,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> T: ...
+
+
@overload
def field(
*,
- resolver: _RESOLVER_TYPE[T],
+ resolver: _RESOLVER_TYPE_SYNC[T],
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
@@ -432,8 +458,7 @@ def field(
directives: Optional[Sequence[object]] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> T:
- ...
+) -> T: ...
@overload
@@ -451,13 +476,30 @@ def field(
directives: Optional[Sequence[object]] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> Any:
- ...
+) -> Any: ...
+
+
+@overload
+def field(
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ *,
+ name: Optional[str] = None,
+ is_subscription: bool = False,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> StrawberryField: ...
@overload
def field(
- resolver: _RESOLVER_TYPE[T],
+ resolver: _RESOLVER_TYPE_SYNC[T],
*,
name: Optional[str] = None,
is_subscription: bool = False,
@@ -470,8 +512,7 @@ def field(
directives: Optional[Sequence[object]] = (),
extensions: Optional[List[FieldExtension]] = None,
graphql_type: Optional[Any] = None,
-) -> StrawberryField:
- ...
+) -> StrawberryField: ...
def field(
@@ -495,19 +536,44 @@ def field(
) -> Any:
"""Annotates a method or property as a GraphQL field.
+ Args:
+ resolver: The resolver for the field. This can be a function or a `StrawberryResolver`.
+ name: The GraphQL name of the field.
+ is_subscription: Whether the field is a subscription field.
+ description: The GraphQL description of the field.
+ permission_classes: The permission classes required to access the field.
+ deprecation_reason: The deprecation reason for the field.
+ default: The default value for the field.
+ default_factory: The default factory for the field.
+ metadata: The metadata for the field.
+ directives: The directives for the field.
+ extensions: The extensions for the field.
+ graphql_type: The GraphQL type for the field, useful when you want to use a
+ different type in the resolver than the one in the schema.
+ init: This parameter is used by PyRight to determine whether this field is
+ added in the constructor or not. It is not used to change any behavior
+ at the moment.
+
+ Returns:
+ The field.
+
This is normally used inside a type declaration:
- >>> @strawberry.type
- >>> class X:
- >>> field_abc: str = strawberry.field(description="ABC")
+ ```python
+ import strawberry
- >>> @strawberry.field(description="ABC")
- >>> def field_with_resolver(self) -> str:
- >>> return "abc"
+
+ @strawberry.type
+ class X:
+ field_abc: str = strawberry.field(description="ABC")
+
+ @strawberry.field(description="ABC")
+ def field_with_resolver(self) -> str:
+ return "abc"
+ ```
it can be used both as decorator and as a normal function.
"""
-
type_annotation = StrawberryAnnotation.from_annotation(graphql_type)
field_ = StrawberryField(
diff --git a/strawberry/types/fields/resolver.py b/strawberry/types/fields/resolver.py
index 3e5d46f531..ae0d1d98a7 100644
--- a/strawberry/types/fields/resolver.py
+++ b/strawberry/types/fields/resolver.py
@@ -23,13 +23,13 @@
from typing_extensions import Annotated, Protocol, get_origin
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument
from strawberry.exceptions import (
ConflictingArgumentsError,
MissingArgumentsAnnotationsError,
)
from strawberry.parent import StrawberryParent
-from strawberry.type import StrawberryType, has_object_definition
+from strawberry.types.arguments import StrawberryArgument
+from strawberry.types.base import StrawberryType, has_object_definition
from strawberry.types.info import Info
from strawberry.utils.typing import type_has_annotation
@@ -38,7 +38,7 @@
class Parameter(inspect.Parameter):
- def __hash__(self):
+ def __hash__(self) -> int:
"""Override to exclude default value from hash.
This adds compatibility for using unhashable default values in resolvers such as
@@ -155,7 +155,7 @@ def is_reserved_type(self, other: builtins.type) -> bool:
# Handle annotated arguments such as Private[str] and DirectiveValue[str]
return type_has_annotation(other, self.type)
else:
- # Handle both concrete and generic types (i.e Info, and Info[Any, Any])
+ # Handle both concrete and generic types (i.e Info, and Info)
return (
issubclass(origin, self.type)
if isinstance(origin, type)
@@ -187,7 +187,7 @@ def __init__(
*,
description: Optional[str] = None,
type_override: Optional[Union[StrawberryType, type]] = None,
- ):
+ ) -> None:
self.wrapped_func = func
self._description = description
self._type_override = type_override
@@ -231,30 +231,25 @@ def reserved_parameters(
@cached_property
def arguments(self) -> List[StrawberryArgument]:
"""Resolver arguments exposed in the GraphQL Schema."""
- parameters = self.signature.parameters.values()
- reserved_parameters = set(self.reserved_parameters.values())
- populated_reserved_parameters = set(
- key for key, value in self.reserved_parameters.items() if value is not None
- )
+ root_parameter = self.reserved_parameters.get(ROOT_PARAMSPEC)
+ parent_parameter = self.reserved_parameters.get(PARENT_PARAMSPEC)
+ # TODO: Maybe use SELF_PARAMSPEC in the future? Right now
+ # it would prevent some common pattern for integrations
+ # (e.g. django) of typing the `root` parameters as the
+ # type of the real object being used
if (
- conflicting_arguments := (
- populated_reserved_parameters
- # TODO: Maybe use SELF_PARAMSPEC in the future? Right now
- # it would prevent some common pattern for integrations
- # (e.g. django) of typing the `root` parameters as the
- # type of the real object being used
- & {ROOT_PARAMSPEC, PARENT_PARAMSPEC}
- )
- ) and len(conflicting_arguments) > 1:
+ root_parameter is not None
+ and parent_parameter is not None
+ and root_parameter.name != parent_parameter.name
+ ):
raise ConflictingArgumentsError(
self,
- [
- cast(Parameter, self.reserved_parameters[key]).name
- for key in conflicting_arguments
- ],
+ [root_parameter.name, parent_parameter.name],
)
+ parameters = self.signature.parameters.values()
+ reserved_parameters = set(self.reserved_parameters.values())
missing_annotations: List[str] = []
arguments: List[StrawberryArgument] = []
user_parameters = (p for p in parameters if p not in reserved_parameters)
@@ -396,7 +391,7 @@ def _unbound_wrapped_func(self) -> Callable[..., T]:
class UncallableResolverError(Exception):
- def __init__(self, resolver: StrawberryResolver):
+ def __init__(self, resolver: StrawberryResolver) -> None:
message = (
f"Attempted to call resolver {resolver} with uncallable function "
f"{resolver.wrapped_func}"
diff --git a/strawberry/types/graphql.py b/strawberry/types/graphql.py
index 35417aa109..d0bbd0237d 100644
--- a/strawberry/types/graphql.py
+++ b/strawberry/types/graphql.py
@@ -29,3 +29,6 @@ def from_http(method: HTTPMethod) -> Set[OperationType]:
}
raise ValueError(f"Unsupported HTTP method: {method}") # pragma: no cover
+
+
+__all__ = ["OperationType"]
diff --git a/strawberry/types/info.py b/strawberry/types/info.py
index 288957540d..b5a95c924a 100644
--- a/strawberry/types/info.py
+++ b/strawberry/types/info.py
@@ -10,10 +10,11 @@
Generic,
List,
Optional,
+ Tuple,
Type,
- TypeVar,
Union,
)
+from typing_extensions import TypeVar
from .nodes import convert_selections
@@ -22,28 +23,77 @@
from graphql.language import FieldNode
from graphql.pyutils.path import Path
- from strawberry.arguments import StrawberryArgument
- from strawberry.field import StrawberryField
from strawberry.schema import Schema
- from strawberry.type import StrawberryType, WithStrawberryObjectDefinition
+ from strawberry.types.arguments import StrawberryArgument
+ from strawberry.types.base import (
+ StrawberryType,
+ WithStrawberryObjectDefinition,
+ )
+ from strawberry.types.field import StrawberryField
from .nodes import Selection
-ContextType = TypeVar("ContextType")
-RootValueType = TypeVar("RootValueType")
+ContextType = TypeVar("ContextType", default=Any)
+RootValueType = TypeVar("RootValueType", default=Any)
@dataclasses.dataclass
class Info(Generic[ContextType, RootValueType]):
+ """Class containing information about the current execution.
+
+ This class is passed to resolvers when there's an argument with type `Info`.
+
+ Example:
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def hello(self, info: strawberry.Info) -> str:
+ return f"Hello, {info.context['name']}!"
+ ```
+
+ It also supports passing the type of the context and root types:
+
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def hello(self, info: strawberry.Info[str, str]) -> str:
+ return f"Hello, {info.context}!"
+ ```
+ """
+
_raw_info: GraphQLResolveInfo
_field: StrawberryField
+ def __class_getitem__(cls, types: Union[type, Tuple[type, ...]]) -> Type[Info]:
+ """Workaround for when passing only one type.
+
+ Python doesn't yet support directly passing only one type to a generic class
+ that has typevars with defaults. This is a workaround for that.
+
+ See:
+ https://discuss.python.org/t/passing-only-one-typevar-of-two-when-using-defaults/49134
+ """
+ if not isinstance(types, tuple):
+ types = (types, Any) # type: ignore
+
+ return super().__class_getitem__(types) # type: ignore
+
@property
def field_name(self) -> str:
+ """The name of the current field being resolved."""
return self._raw_info.field_name
@property
def schema(self) -> Schema:
+ """The schema of the current execution."""
return self._raw_info.schema._strawberry_schema # type: ignore
@property
@@ -58,48 +108,57 @@ def field_nodes(self) -> List[FieldNode]: # deprecated
@cached_property
def selected_fields(self) -> List[Selection]:
+ """The fields that were selected on the current field's type."""
info = self._raw_info
return convert_selections(info, info.field_nodes)
@property
def context(self) -> ContextType:
+ """The context passed to the query execution."""
return self._raw_info.context
@property
def root_value(self) -> RootValueType:
+ """The root value passed to the query execution."""
return self._raw_info.root_value
@property
def variable_values(self) -> Dict[str, Any]:
+ """The variable values passed to the query execution."""
return self._raw_info.variable_values
@property
def return_type(
self,
) -> Optional[Union[Type[WithStrawberryObjectDefinition], StrawberryType]]:
+ """The return type of the current field being resolved."""
return self._field.type
@property
def python_name(self) -> str:
+ """The name of the current field being resolved in Python format."""
return self._field.python_name
# TODO: create an abstraction on these fields
@property
def operation(self) -> OperationDefinitionNode:
+ """The operation being executed."""
return self._raw_info.operation
@property
def path(self) -> Path:
+ """The path of the current field being resolved."""
return self._raw_info.path
# TODO: parent_type as strawberry types
# Helper functions
def get_argument_definition(self, name: str) -> Optional[StrawberryArgument]:
- """
- Get the StrawberryArgument definition for the current field by name.
- """
+ """Get the StrawberryArgument definition for the current field by name."""
try:
return next(arg for arg in self._field.arguments if arg.python_name == name)
except StopIteration:
return None
+
+
+__all__ = ["Info"]
diff --git a/strawberry/lazy_type.py b/strawberry/types/lazy_type.py
similarity index 63%
rename from strawberry/lazy_type.py
rename to strawberry/types/lazy_type.py
index b75deb94fa..776ad52d4a 100644
--- a/strawberry/lazy_type.py
+++ b/strawberry/types/lazy_type.py
@@ -4,19 +4,39 @@
import warnings
from dataclasses import dataclass
from pathlib import Path
-from typing import Any, ForwardRef, Generic, Optional, Tuple, Type, TypeVar, cast
+from typing import (
+ Any,
+ ForwardRef,
+ Generic,
+ Optional,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ cast,
+)
+from typing_extensions import Self
TypeName = TypeVar("TypeName")
Module = TypeVar("Module")
+Other = TypeVar("Other")
@dataclass(frozen=True)
class LazyType(Generic[TypeName, Module]):
+ """A class that represents a type that will be resolved at runtime.
+
+ This is useful when you have circular dependencies between types.
+
+ This class is not meant to be used directly, instead use the `strawberry.lazy`
+ function.
+ """
+
type_name: str
module: str
package: Optional[str] = None
- def __class_getitem__(cls, params: Tuple[str, str]):
+ def __class_getitem__(cls, params: Tuple[str, str]) -> "Self":
warnings.warn(
(
"LazyType is deprecated, use "
@@ -38,6 +58,9 @@ def __class_getitem__(cls, params: Tuple[str, str]):
return cls(type_name, module, package)
+ def __or__(self, other: Other) -> object:
+ return Union[self, other]
+
def resolve_type(self) -> Type[Any]:
module = importlib.import_module(self.module, self.package)
main_module = sys.modules.get("__main__", None)
@@ -63,11 +86,19 @@ def resolve_type(self) -> Type[Any]:
# this empty call method allows LazyTypes to be used in generic types
# for example: List[LazyType["A", "module"]]
- def __call__(self): # pragma: no cover
+ def __call__(self) -> None: # pragma: no cover
return None
class StrawberryLazyReference:
+ """A class that represents a lazy reference to a type in another module.
+
+ This is useful when you have circular dependencies between types.
+
+ This class is not meant to be used directly, instead use the `strawberry.lazy`
+ function.
+ """
+
def __init__(self, module: str) -> None:
self.module = module
self.package = None
@@ -92,4 +123,38 @@ def __hash__(self) -> int:
def lazy(module_path: str) -> StrawberryLazyReference:
+ """Creates a lazy reference to a type in another module.
+
+ Args:
+ module_path: The path to the module containing the type, supports relative paths
+ starting with `.`
+
+ Returns:
+ A `StrawberryLazyReference` object that can be used to reference a type in another
+ module.
+
+ This is useful when you have circular dependencies between types.
+
+ For example, assuming you have a `Post` type that has a field `author` that
+ references a `User` type (which also has a field `posts` that references a list of
+ `Post`), you can use `strawberry.lazy` to avoid the circular dependency:
+
+ ```python
+ from typing import TYPE_CHECKING, Annotated
+
+ import strawberry
+
+ if TYPE_CHECKING:
+ from .users import User
+
+
+ @strawberry.type
+ class Post:
+ title: str
+ author: Annotated["User", strawberry.lazy(".users")]
+ ```
+ """
return StrawberryLazyReference(module_path)
+
+
+__all__ = ["LazyType", "StrawberryLazyReference", "lazy"]
diff --git a/strawberry/types/mutation.py b/strawberry/types/mutation.py
new file mode 100644
index 0000000000..bf0bb2b792
--- /dev/null
+++ b/strawberry/types/mutation.py
@@ -0,0 +1,351 @@
+from __future__ import annotations
+
+import dataclasses
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Type,
+ Union,
+ overload,
+)
+from typing_extensions import Literal
+
+from strawberry.types.field import (
+ _RESOLVER_TYPE,
+ _RESOLVER_TYPE_ASYNC,
+ _RESOLVER_TYPE_SYNC,
+ StrawberryField,
+ T,
+ field,
+)
+
+if TYPE_CHECKING:
+ from typing_extensions import Literal
+
+ from strawberry.extensions.field_extension import FieldExtension
+ from strawberry.permission import BasePermission
+
+# NOTE: we are separating the sync and async resolvers because using both
+# in the same function will cause mypy to raise an error. Not sure if it is a bug
+
+
+@overload
+def mutation(
+ *,
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[False] = False,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> T: ...
+
+
+@overload
+def mutation(
+ *,
+ resolver: _RESOLVER_TYPE_SYNC[T],
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[False] = False,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> T: ...
+
+
+@overload
+def mutation(
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[True] = True,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> Any: ...
+
+
+@overload
+def mutation(
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> StrawberryField: ...
+
+
+@overload
+def mutation(
+ resolver: _RESOLVER_TYPE_SYNC[T],
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> StrawberryField: ...
+
+
+def mutation(
+ resolver: Optional[_RESOLVER_TYPE[Any]] = None,
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+ # This init parameter is used by PyRight to determine whether this field
+ # is added in the constructor or not. It is not used to change
+ # any behavior at the moment.
+ init: Literal[True, False, None] = None,
+) -> Any:
+ """Annotates a method or property as a GraphQL mutation.
+
+ Args:
+ resolver: The resolver for the field. It can be a sync or async function.
+ name: The GraphQL name of the field.
+ description: The GraphQL description of the field.
+ permission_classes: The permission classes required to access the field.
+ deprecation_reason: The deprecation reason for the field.
+ default: The default value for the field.
+ default_factory: The default factory for the field.
+ metadata: The metadata for the field.
+ directives: The directives for the field.
+ extensions: The extensions for the field.
+ graphql_type: The GraphQL type for the field, useful when you want to use a
+ different type in the resolver than the one in the schema.
+ init: This parameter is used by PyRight to determine whether this field is
+ added in the constructor or not. It is not used to change any behavior at
+ the moment.
+
+ Returns:
+ The field object.
+
+ This is normally used inside a type declaration:
+
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Mutation:
+ @strawberry.mutation
+ def create_post(self, title: str, content: str) -> Post: ...
+ ```
+
+ It can be used both as decorator and as a normal function.
+ """
+ return field(
+ resolver=resolver, # type: ignore
+ name=name,
+ description=description,
+ permission_classes=permission_classes,
+ deprecation_reason=deprecation_reason,
+ default=default,
+ default_factory=default_factory,
+ metadata=metadata,
+ directives=directives,
+ extensions=extensions,
+ graphql_type=graphql_type,
+ )
+
+
+# NOTE: we are separating the sync and async resolvers because using both
+# in the same function will cause mypy to raise an error. Not sure if it is a bug
+
+
+@overload
+def subscription(
+ *,
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[False] = False,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> T: ...
+
+
+@overload
+def subscription(
+ *,
+ resolver: _RESOLVER_TYPE_SYNC[T],
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[False] = False,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> T: ...
+
+
+@overload
+def subscription(
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ init: Literal[True] = True,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> Any: ...
+
+
+@overload
+def subscription(
+ resolver: _RESOLVER_TYPE_ASYNC[T],
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> StrawberryField: ...
+
+
+@overload
+def subscription(
+ resolver: _RESOLVER_TYPE_SYNC[T],
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+) -> StrawberryField: ...
+
+
+def subscription(
+ resolver: Optional[_RESOLVER_TYPE[Any]] = None,
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ permission_classes: Optional[List[Type[BasePermission]]] = None,
+ deprecation_reason: Optional[str] = None,
+ default: Any = dataclasses.MISSING,
+ default_factory: Union[Callable[..., object], object] = dataclasses.MISSING,
+ metadata: Optional[Mapping[Any, Any]] = None,
+ directives: Optional[Sequence[object]] = (),
+ extensions: Optional[List[FieldExtension]] = None,
+ graphql_type: Optional[Any] = None,
+ init: Literal[True, False, None] = None,
+) -> Any:
+ """Annotates a method or property as a GraphQL subscription.
+
+ Args:
+ resolver: The resolver for the field.
+ name: The GraphQL name of the field.
+ description: The GraphQL description of the field.
+ permission_classes: The permission classes required to access the field.
+ deprecation_reason: The deprecation reason for the field.
+ default: The default value for the field.
+ default_factory: The default factory for the field.
+ metadata: The metadata for the field.
+ directives: The directives for the field.
+ extensions: The extensions for the field.
+ graphql_type: The GraphQL type for the field, useful when you want to use a
+ different type in the resolver than the one in the schema.
+ init: This parameter is used by PyRight to determine whether this field is
+ added in the constructor or not. It is not used to change any behavior at
+ the moment.
+
+ Returns:
+ The field for the subscription.
+
+ This is normally used inside a type declaration:
+
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Subscription:
+ @strawberry.subscription
+ def post_created(self, title: str, content: str) -> Post: ...
+ ```
+
+ it can be used both as decorator and as a normal function.
+ """
+ return field(
+ resolver=resolver, # type: ignore
+ name=name,
+ description=description,
+ is_subscription=True,
+ permission_classes=permission_classes,
+ deprecation_reason=deprecation_reason,
+ default=default,
+ default_factory=default_factory,
+ metadata=metadata,
+ directives=directives,
+ extensions=extensions,
+ graphql_type=graphql_type,
+ )
+
+
+__all__ = ["mutation", "subscription"]
diff --git a/strawberry/types/nodes.py b/strawberry/types/nodes.py
index 7a938a872d..20a8407a8e 100644
--- a/strawberry/types/nodes.py
+++ b/strawberry/types/nodes.py
@@ -1,5 +1,4 @@
-"""
-Abstraction layer for graphql-core field nodes.
+"""Abstraction layer for graphql-core field nodes.
Call `convert_sections` on a list of GraphQL `FieldNode`s,
such as in `info.field_nodes`.
@@ -9,6 +8,7 @@
If a list of nodes have unique names, it's transformed into a mapping.
Note Python dicts maintain ordering (for all supported versions).
"""
+
from __future__ import annotations
import dataclasses
@@ -150,3 +150,6 @@ def from_node(cls, info: GraphQLResolveInfo, node: GQLFieldNode) -> SelectedFiel
info, getattr(node.selection_set, "selections", [])
),
)
+
+
+__all__ = ["convert_selections", "FragmentSpread", "InlineFragment", "SelectedField"]
diff --git a/strawberry/object_type.py b/strawberry/types/object_type.py
similarity index 67%
rename from strawberry/object_type.py
rename to strawberry/types/object_type.py
index 94f9a891b4..5a9cea739e 100644
--- a/strawberry/object_type.py
+++ b/strawberry/types/object_type.py
@@ -16,20 +16,19 @@
)
from typing_extensions import dataclass_transform
-from .exceptions import (
+from strawberry.exceptions import (
MissingFieldAnnotationError,
MissingReturnAnnotationError,
ObjectIsNotClassError,
)
+from strawberry.types.base import get_object_definition
+from strawberry.utils.dataclasses import add_custom_init_fn
+from strawberry.utils.deprecations import DEPRECATION_MESSAGES, DeprecatedDescriptor
+from strawberry.utils.str_converters import to_camel_case
+
+from .base import StrawberryObjectDefinition
from .field import StrawberryField, field
-from .type import get_object_definition
-from .types.type_resolver import _get_fields
-from .types.types import (
- StrawberryObjectDefinition,
-)
-from .utils.dataclasses import add_custom_init_fn
-from .utils.deprecations import DEPRECATION_MESSAGES, DeprecatedDescriptor
-from .utils.str_converters import to_camel_case
+from .type_resolver import _get_fields
T = TypeVar("T", bound=Type)
@@ -44,7 +43,7 @@ def _get_interfaces(cls: Type[Any]) -> List[StrawberryObjectDefinition]:
return interfaces
-def _check_field_annotations(cls: Type[Any]):
+def _check_field_annotations(cls: Type[Any]) -> None:
"""Are any of the dataclass Fields missing type annotations?
This is similar to the check that dataclasses do during creation, but allows us to
@@ -67,7 +66,8 @@ def _check_field_annotations(cls: Type[Any]):
# If the field has a type override then use that instead of using
# the class annotations or resolver annotation
if field_.type_annotation is not None:
- cls_annotations[field_name] = field_.type_annotation.annotation
+ if field_name not in cls_annotations:
+ cls_annotations[field_name] = field_.type_annotation.annotation
continue
# Make sure the cls has an annotation
@@ -85,7 +85,8 @@ def _check_field_annotations(cls: Type[Any]):
field_name, resolver=field_.base_resolver
)
- cls_annotations[field_name] = field_.base_resolver.type_annotation
+ if field_name not in cls_annotations:
+ cls_annotations[field_name] = field_.base_resolver.type_annotation
# TODO: Make sure the cls annotation agrees with the field's type
# >>> if cls_annotations[field_name] != field.base_resolver.type:
@@ -100,10 +101,8 @@ def _check_field_annotations(cls: Type[Any]):
raise MissingFieldAnnotationError(field_name, cls)
-def _wrap_dataclass(cls: Type[Any]):
- """Wrap a strawberry.type class with a dataclass and check for any issues
- before doing so"""
-
+def _wrap_dataclass(cls: Type[T]) -> Type[T]:
+ """Wrap a strawberry.type class with a dataclass and check for any issues before doing so."""
# Ensure all Fields have been properly type-annotated
_check_field_annotations(cls)
@@ -125,7 +124,7 @@ def _wrap_dataclass(cls: Type[Any]):
def _process_type(
- cls: Type,
+ cls: T,
*,
name: Optional[str] = None,
is_input: bool = False,
@@ -133,11 +132,13 @@ def _process_type(
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
extend: bool = False,
-):
+ original_type_annotations: Optional[Dict[str, Any]] = None,
+) -> T:
name = name or to_camel_case(cls.__name__)
+ original_type_annotations = original_type_annotations or {}
interfaces = _get_interfaces(cls)
- fields = _get_fields(cls)
+ fields = _get_fields(cls, original_type_annotations)
is_type_of = getattr(cls, "is_type_of", None)
resolve_type = getattr(cls, "resolve_type", None)
@@ -198,8 +199,7 @@ def type(
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
extend: bool = False,
-) -> T:
- ...
+) -> T: ...
@overload
@@ -214,8 +214,7 @@ def type(
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
extend: bool = False,
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
def type(
@@ -230,14 +229,39 @@ def type(
) -> Union[T, Callable[[T], T]]:
"""Annotates a class as a GraphQL type.
+ Similar to `dataclasses.dataclass`, but with additional functionality for
+ defining GraphQL types.
+
+ Args:
+ cls: The class we want to create a GraphQL type from.
+ name: The name of the GraphQL type.
+ is_input: Whether the class is an input type. Used internally, use `@strawerry.input` instead of passing this flag.
+ is_interface: Whether the class is an interface. Used internally, use `@strawerry.interface` instead of passing this flag.
+ description: The description of the GraphQL type.
+ directives: The directives of the GraphQL type.
+ extend: Whether the class is extending an existing type.
+
+ Returns:
+ The class.
+
Example usage:
- >>> @strawberry.type
- >>> class X:
- >>> field_abc: str = "ABC"
+ ```python
+ @strawberry.type
+ class User:
+ name: str = "A name"
+ ```
+
+ You can also pass parameters to the decorator:
+
+ ```python
+ @strawberry.type(name="UserType", description="A user type")
+ class MyUser:
+ name: str = "A name"
+ ```
"""
- def wrap(cls: Type):
+ def wrap(cls: T) -> T:
if not inspect.isclass(cls):
if is_input:
exc = ObjectIsNotClassError.input
@@ -247,8 +271,26 @@ def wrap(cls: Type):
exc = ObjectIsNotClassError.type
raise exc(cls)
+ # when running `_wrap_dataclass` we lose some of the information about the
+ # the passed types, especially the type_annotation inside the StrawberryField
+ # this makes it impossible to customise the field type, like this:
+ # >>> @strawberry.type
+ # >>> class Query:
+ # >>> a: int = strawberry.field(graphql_type=str)
+ # so we need to extract the information before running `_wrap_dataclass`
+ original_type_annotations: Dict[str, Any] = {}
+
+ annotations = getattr(cls, "__annotations__", {})
+
+ for field_name in annotations:
+ field = getattr(cls, field_name, None)
+
+ if field and isinstance(field, StrawberryField) and field.type_annotation:
+ original_type_annotations[field_name] = field.type_annotation.annotation
+
wrapped = _wrap_dataclass(cls)
- return _process_type(
+
+ return _process_type( # type: ignore
wrapped,
name=name,
is_input=is_input,
@@ -256,6 +298,7 @@ def wrap(cls: Type):
description=description,
directives=directives,
extend=extend,
+ original_type_annotations=original_type_annotations,
)
if cls is None:
@@ -272,10 +315,10 @@ def input(
cls: T,
*,
name: Optional[str] = None,
+ one_of: Optional[bool] = None,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
-) -> T:
- ...
+) -> T: ...
@overload
@@ -285,25 +328,54 @@ def input(
def input(
*,
name: Optional[str] = None,
+ one_of: Optional[bool] = None,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
def input(
cls: Optional[T] = None,
*,
name: Optional[str] = None,
+ one_of: Optional[bool] = None,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
):
"""Annotates a class as a GraphQL Input type.
+
+ Similar to `@strawberry.type`, but for input types.
+
+ Args:
+ cls: The class we want to create a GraphQL input type from.
+ name: The name of the GraphQL input type.
+ description: The description of the GraphQL input type.
+ directives: The directives of the GraphQL input type.
+ one_of: Whether the input type is a `oneOf` type.
+
+ Returns:
+ The class.
+
Example usage:
- >>> @strawberry.input
- >>> class X:
- >>> field_abc: str = "ABC"
+
+ ```python
+ @strawberry.input
+ class UserInput:
+ name: str
+ ```
+
+ You can also pass parameters to the decorator:
+
+ ```python
+ @strawberry.input(name="UserInputType", description="A user input type")
+ class MyUserInput:
+ name: str
+ ```
"""
+ from strawberry.schema_directives import OneOf
+
+ if one_of:
+ directives = (*(directives or ()), OneOf())
return type( # type: ignore # not sure why mypy complains here
cls,
@@ -324,8 +396,7 @@ def interface(
name: Optional[str] = None,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
-) -> T:
- ...
+) -> T: ...
@overload
@@ -337,8 +408,7 @@ def interface(
name: Optional[str] = None,
description: Optional[str] = None,
directives: Optional[Sequence[object]] = (),
-) -> Callable[[T], T]:
- ...
+) -> Callable[[T], T]: ...
@dataclass_transform(
@@ -352,12 +422,34 @@ def interface(
directives: Optional[Sequence[object]] = (),
):
"""Annotates a class as a GraphQL Interface.
+
+ Similar to `@strawberry.type`, but for interfaces.
+
+ Args:
+ cls: The class we want to create a GraphQL interface from.
+ name: The name of the GraphQL interface.
+ description: The description of the GraphQL interface.
+ directives: The directives of the GraphQL interface.
+
+ Returns:
+ The class.
+
Example usage:
- >>> @strawberry.interface
- >>> class X:
- >>> field_abc: str
- """
+ ```python
+ @strawberry.interface
+ class Node:
+ id: str
+ ```
+
+ You can also pass parameters to the decorator:
+
+ ```python
+ @strawberry.interface(name="NodeType", description="A node type")
+ class MyNode:
+ id: str
+ ```
+ """
return type( # type: ignore # not sure why mypy complains here
cls,
name=name,
@@ -369,15 +461,27 @@ def interface(
def asdict(obj: Any) -> Dict[str, object]:
"""Convert a strawberry object into a dictionary.
+
This wraps the dataclasses.asdict function to strawberry.
+ Args:
+ obj: The object to convert into a dictionary.
+
+ Returns:
+ A dictionary representation of the object.
+
Example usage:
- >>> @strawberry.type
- >>> class User:
- >>> name: str
- >>> age: int
- >>> # should be {"name": "Lorem", "age": 25}
- >>> user_dict = strawberry.asdict(User(name="Lorem", age=25))
+
+ ```python
+ @strawberry.type
+ class User:
+ name: str
+ age: int
+
+
+ strawberry.asdict(User(name="Lorem", age=25))
+ # {"name": "Lorem", "age": 25}
+ ```
"""
return dataclasses.asdict(obj)
diff --git a/strawberry/private.py b/strawberry/types/private.py
similarity index 55%
rename from strawberry/private.py
rename to strawberry/types/private.py
index e1ce953537..cd35d6ac0d 100644
--- a/strawberry/private.py
+++ b/strawberry/types/private.py
@@ -4,24 +4,30 @@
from strawberry.utils.typing import type_has_annotation
-class StrawberryPrivate:
- ...
+class StrawberryPrivate: ...
T = TypeVar("T")
Private = Annotated[T, StrawberryPrivate()]
-Private.__doc__ = """Represents a field that won't be exposed in the GraphQL schema
+"""Represents a field that won't be exposed in the GraphQL schema.
Example:
->>> import strawberry
->>> @strawberry.type
-... class User:
-... name: str
-... age: strawberry.Private[int]
+```python
+import strawberry
+
+
+@strawberry.type
+class User:
+ name: str
+ age: strawberry.Private[int]
+```
"""
def is_private(type_: object) -> bool:
return type_has_annotation(type_, StrawberryPrivate)
+
+
+__all__ = ["Private", "is_private"]
diff --git a/strawberry/custom_scalar.py b/strawberry/types/scalar.py
similarity index 75%
rename from strawberry/custom_scalar.py
rename to strawberry/types/scalar.py
index 6fe86d1c9f..c5adf22ae8 100644
--- a/strawberry/custom_scalar.py
+++ b/strawberry/types/scalar.py
@@ -10,16 +10,14 @@
Mapping,
NewType,
Optional,
- Type,
TypeVar,
Union,
overload,
)
from strawberry.exceptions import InvalidUnionTypeError
-from strawberry.type import StrawberryType
-
-from .utils.str_converters import to_camel_case
+from strawberry.types.base import StrawberryType
+from strawberry.utils.str_converters import to_camel_case
if TYPE_CHECKING:
from graphql import GraphQLScalarType
@@ -67,10 +65,10 @@ def is_graphql_generic(self) -> bool:
class ScalarWrapper:
_scalar_definition: ScalarDefinition
- def __init__(self, wrap: Callable[[Any], Any]):
+ def __init__(self, wrap: Callable[[Any], Any]) -> None:
self.wrap = wrap
- def __call__(self, *args: str, **kwargs: Any):
+ def __call__(self, *args: str, **kwargs: Any) -> Any:
return self.wrap(*args, **kwargs)
def __or__(self, other: Union[StrawberryType, type]) -> StrawberryType:
@@ -85,7 +83,7 @@ def __or__(self, other: Union[StrawberryType, type]) -> StrawberryType:
def _process_scalar(
- cls: Type[_T],
+ cls: _T,
*,
name: Optional[str] = None,
description: Optional[str] = None,
@@ -94,10 +92,10 @@ def _process_scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
-):
+) -> ScalarWrapper:
from strawberry.exceptions.handler import should_use_rich_exceptions
- name = name or to_camel_case(cls.__name__)
+ name = name or to_camel_case(cls.__name__) # type: ignore[union-attr]
_source_file = None
_source_line = None
@@ -134,8 +132,7 @@ def scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
-) -> Callable[[_T], _T]:
- ...
+) -> Callable[[_T], _T]: ...
@overload
@@ -149,15 +146,14 @@ def scalar(
parse_value: Optional[Callable] = None,
parse_literal: Optional[Callable] = None,
directives: Iterable[object] = (),
-) -> _T:
- ...
+) -> _T: ...
# TODO: We are tricking pyright into thinking that we are returning the given type
# here or else it won't let us use any custom scalar to annotate attributes in
# dataclasses/types. This should be properly solved when implementing StrawberryScalar
def scalar(
- cls=None,
+ cls: Optional[_T] = None,
*,
name: Optional[str] = None,
description: Optional[str] = None,
@@ -169,34 +165,48 @@ def scalar(
) -> Any:
"""Annotates a class or type as a GraphQL custom scalar.
+ Args:
+ cls: The class or type to annotate.
+ name: The GraphQL name of the scalar.
+ description: The description of the scalar.
+ specified_by_url: The URL of the specification.
+ serialize: The function to serialize the scalar.
+ parse_value: The function to parse the value.
+ parse_literal: The function to parse the literal.
+ directives: The directives to apply to the scalar.
+
+ Returns:
+ The decorated class or type.
+
Example usages:
- >>> strawberry.scalar(
- >>> datetime.date,
- >>> serialize=lambda value: value.isoformat(),
- >>> parse_value=datetime.parse_date
- >>> )
-
- >>> Base64Encoded = strawberry.scalar(
- >>> NewType("Base64Encoded", bytes),
- >>> serialize=base64.b64encode,
- >>> parse_value=base64.b64decode
- >>> )
-
- >>> @strawberry.scalar(
- >>> serialize=lambda value: ",".join(value.items),
- >>> parse_value=lambda value: CustomList(value.split(","))
- >>> )
- >>> class CustomList:
- >>> def __init__(self, items):
- >>> self.items = items
+ ```python
+ strawberry.scalar(
+ datetime.date,
+ serialize=lambda value: value.isoformat(),
+ parse_value=datetime.parse_date,
+ )
+
+ Base64Encoded = strawberry.scalar(
+ NewType("Base64Encoded", bytes),
+ serialize=base64.b64encode,
+ parse_value=base64.b64decode,
+ )
- """
+ @strawberry.scalar(
+ serialize=lambda value: ",".join(value.items),
+ parse_value=lambda value: CustomList(value.split(",")),
+ )
+ class CustomList:
+ def __init__(self, items):
+ self.items = items
+ ```
+ """
if parse_value is None:
parse_value = cls
- def wrap(cls: Type):
+ def wrap(cls: _T) -> ScalarWrapper:
return _process_scalar(
cls,
name=name,
@@ -212,3 +222,6 @@ def wrap(cls: Type):
return wrap
return wrap(cls)
+
+
+__all__ = ["ScalarDefinition", "ScalarWrapper", "scalar"]
diff --git a/strawberry/types/type_resolver.py b/strawberry/types/type_resolver.py
index 65ad6c16ae..975980880e 100644
--- a/strawberry/types/type_resolver.py
+++ b/strawberry/types/type_resolver.py
@@ -2,7 +2,7 @@
import dataclasses
import sys
-from typing import Dict, List, Type
+from typing import Any, Dict, List, Type
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import (
@@ -10,14 +10,16 @@
FieldWithResolverAndDefaultValueError,
PrivateStrawberryFieldError,
)
-from strawberry.field import StrawberryField
-from strawberry.private import is_private
-from strawberry.type import has_object_definition
-from strawberry.unset import UNSET
+from strawberry.types.base import has_object_definition
+from strawberry.types.field import StrawberryField
+from strawberry.types.private import is_private
+from strawberry.types.unset import UNSET
-def _get_fields(cls: Type) -> List[StrawberryField]:
- """Get all the strawberry fields off a strawberry.type cls
+def _get_fields(
+ cls: Type[Any], original_type_annotations: Dict[str, Type[Any]]
+) -> List[StrawberryField]:
+ """Get all the strawberry fields off a strawberry.type cls.
This function returns a list of StrawberryFields (one for each field item), while
also paying attention the name and typing of the field.
@@ -25,16 +27,19 @@ def _get_fields(cls: Type) -> List[StrawberryField]:
StrawberryFields can be defined on a strawberry.type class as either a dataclass-
style field or using strawberry.field as a decorator.
- >>> import strawberry
- >>> @strawberry.type
- ... class Query:
- ... type_1a: int = 5
- ... type_1b: int = strawberry.field(...)
- ... type_1c: int = strawberry.field(resolver=...)
- ...
- ... @strawberry.field
- ... def type_2(self) -> int:
- ... ...
+ ```python
+ import strawberry
+
+
+ @strawberry.type
+ class Query:
+ type_1a: int = 5
+ type_1b: int = strawberry.field(...)
+ type_1c: int = strawberry.field(resolver=...)
+
+ @strawberry.field
+ def type_2(self) -> int: ...
+ ```
Type #1:
A pure dataclass-style field. Will not have a StrawberryField; one will need to
@@ -152,7 +157,14 @@ class if one is not set by either using an explicit strawberry.field(name=...) o
assert_message = "Field must have a name by the time the schema is generated"
assert field_name is not None, assert_message
+ if field.name in original_type_annotations:
+ field.type = original_type_annotations[field.name]
+ field.type_annotation = StrawberryAnnotation(annotation=field.type)
+
# TODO: Raise exception if field_name already in fields
fields[field_name] = field
return list(fields.values())
+
+
+__all__ = ["_get_fields"]
diff --git a/strawberry/types/types.py b/strawberry/types/types.py
deleted file mode 100644
index ca667b8ff4..0000000000
--- a/strawberry/types/types.py
+++ /dev/null
@@ -1,209 +0,0 @@
-from __future__ import annotations
-
-import dataclasses
-from typing import (
- TYPE_CHECKING,
- Any,
- Callable,
- Dict,
- List,
- Mapping,
- Optional,
- Sequence,
- Type,
- TypeVar,
- Union,
-)
-from typing_extensions import Self, deprecated
-
-from strawberry.type import (
- StrawberryType,
- StrawberryTypeVar,
- WithStrawberryObjectDefinition,
-)
-from strawberry.utils.deprecations import DEPRECATION_MESSAGES, DeprecatedDescriptor
-from strawberry.utils.inspect import get_specialized_type_var_map
-from strawberry.utils.typing import is_generic as is_type_generic
-
-if TYPE_CHECKING:
- from graphql import GraphQLAbstractType, GraphQLResolveInfo
-
- from strawberry.field import StrawberryField
-
-
-@dataclasses.dataclass(eq=False)
-class StrawberryObjectDefinition(StrawberryType):
- """
- Encapsulates definitions for Input / Object / interface GraphQL Types.
-
- In order get the definition from a decorated object you can use
- `has_object_definition` or `get_object_definition` as a shortcut.
- """
-
- name: str
- is_input: bool
- is_interface: bool
- origin: Type[Any]
- description: Optional[str]
- interfaces: List[StrawberryObjectDefinition]
- extend: bool
- directives: Optional[Sequence[object]]
- is_type_of: Optional[Callable[[Any, GraphQLResolveInfo], bool]]
- resolve_type: Optional[
- Callable[[Any, GraphQLResolveInfo, GraphQLAbstractType], str]
- ]
-
- fields: List[StrawberryField]
-
- concrete_of: Optional[StrawberryObjectDefinition] = None
- """Concrete implementations of Generic TypeDefinitions fill this in"""
- type_var_map: Mapping[str, Union[StrawberryType, type]] = dataclasses.field(
- default_factory=dict
- )
-
- def __post_init__(self):
- # resolve `Self` annotation with the origin type
- for index, field in enumerate(self.fields):
- if isinstance(field.type, StrawberryType) and field.type.has_generic(Self): # type: ignore
- self.fields[index] = field.copy_with({Self.__name__: self.origin}) # type: ignore
-
- def resolve_generic(self, wrapped_cls: type) -> type:
- from strawberry.annotation import StrawberryAnnotation
-
- passed_types = wrapped_cls.__args__ # type: ignore
- params = wrapped_cls.__origin__.__parameters__ # type: ignore
-
- # Make sure all passed_types are turned into StrawberryTypes
- resolved_types = []
- for passed_type in passed_types:
- resolved_type = StrawberryAnnotation(passed_type).resolve()
- resolved_types.append(resolved_type)
-
- type_var_map = dict(zip((param.__name__ for param in params), resolved_types))
-
- return self.copy_with(type_var_map)
-
- def copy_with(
- self, type_var_map: Mapping[str, Union[StrawberryType, type]]
- ) -> Type[WithStrawberryObjectDefinition]:
- fields = [field.copy_with(type_var_map) for field in self.fields]
-
- new_type_definition = StrawberryObjectDefinition(
- name=self.name,
- is_input=self.is_input,
- origin=self.origin,
- is_interface=self.is_interface,
- directives=self.directives and self.directives[:],
- interfaces=self.interfaces and self.interfaces[:],
- description=self.description,
- extend=self.extend,
- is_type_of=self.is_type_of,
- resolve_type=self.resolve_type,
- fields=fields,
- concrete_of=self,
- type_var_map=type_var_map,
- )
-
- new_type = type(
- new_type_definition.name,
- (self.origin,),
- {"__strawberry_definition__": new_type_definition},
- )
- # TODO: remove when deprecating _type_definition
- DeprecatedDescriptor(
- DEPRECATION_MESSAGES._TYPE_DEFINITION,
- new_type.__strawberry_definition__, # type: ignore
- "_type_definition",
- ).inject(new_type)
-
- new_type_definition.origin = new_type
-
- return new_type
-
- def get_field(self, python_name: str) -> Optional[StrawberryField]:
- return next(
- (field for field in self.fields if field.python_name == python_name), None
- )
-
- @property
- def is_graphql_generic(self) -> bool:
- if not is_type_generic(self.origin):
- return False
-
- # here we are checking if any exposed field is generic
- # a Strawberry class can be "generic", but not expose any
- # generic field to GraphQL
- return any(field.is_graphql_generic for field in self.fields)
-
- @property
- def is_specialized_generic(self) -> bool:
- return self.is_graphql_generic and not getattr(
- self.origin, "__parameters__", None
- )
-
- @property
- def specialized_type_var_map(self) -> Optional[Dict[str, type]]:
- return get_specialized_type_var_map(self.origin)
-
- @property
- def type_params(self) -> List[TypeVar]:
- type_params: List[TypeVar] = []
- for field in self.fields:
- type_params.extend(field.type_params)
-
- return type_params
-
- def is_implemented_by(self, root: Type[WithStrawberryObjectDefinition]) -> bool:
- # TODO: Support dicts
- if isinstance(root, dict):
- raise NotImplementedError
-
- type_definition = root.__strawberry_definition__
-
- if type_definition is self:
- # No generics involved. Exact type match
- return True
-
- if type_definition is not self.concrete_of:
- # Either completely different type, or concrete type of a different generic
- return False
-
- # Check the mapping of all fields' TypeVars
- for generic_field in type_definition.fields:
- generic_field_type = generic_field.type
- if not isinstance(generic_field_type, StrawberryTypeVar):
- continue
-
- # For each TypeVar found, get the expected type from the copy's type map
- expected_concrete_type = self.type_var_map.get(
- generic_field_type.type_var.__name__
- )
- if expected_concrete_type is None:
- # TODO: Should this return False?
- continue
-
- # Check if the expected type matches the type found on the type_map
- real_concrete_type = type(getattr(root, generic_field.name))
-
- # TODO: uniform type var map, at the moment we map object types
- # to their class (not to TypeDefinition) while we map enum to
- # the EnumDefinition class. This is why we do this check here:
- if hasattr(real_concrete_type, "_enum_definition"):
- real_concrete_type = real_concrete_type._enum_definition
-
- if real_concrete_type is not expected_concrete_type:
- return False
-
- # All field mappings succeeded. This is a match
- return True
-
-
-# TODO: remove when deprecating _type_definition
-if TYPE_CHECKING:
-
- @deprecated("Use StrawberryObjectDefinition instead")
- class TypeDefinition(StrawberryObjectDefinition):
- ...
-
-else:
- TypeDefinition = StrawberryObjectDefinition
diff --git a/strawberry/union.py b/strawberry/types/union.py
similarity index 90%
rename from strawberry/union.py
rename to strawberry/types/union.py
index 1e007540b9..f1e32fba68 100644
--- a/strawberry/union.py
+++ b/strawberry/types/union.py
@@ -31,12 +31,12 @@
WrongReturnTypeForUnion,
)
from strawberry.exceptions.handler import should_use_rich_exceptions
-from strawberry.lazy_type import LazyType
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryOptional,
StrawberryType,
has_object_definition,
)
+from strawberry.types.lazy_type import LazyType
if TYPE_CHECKING:
from graphql import (
@@ -60,7 +60,7 @@ def __init__(
type_annotations: Tuple[StrawberryAnnotation, ...] = tuple(),
description: Optional[str] = None,
directives: Iterable[object] = (),
- ):
+ ) -> None:
self.graphql_name = name
self.type_annotations = type_annotations
self.description = description
@@ -102,7 +102,7 @@ def types(self) -> Tuple[StrawberryType, ...]:
@property
def type_params(self) -> List[TypeVar]:
- def _get_type_params(type_: StrawberryType):
+ def _get_type_params(type_: StrawberryType) -> list[TypeVar]:
if isinstance(type_, LazyType):
type_ = cast("StrawberryType", type_.resolve_type())
@@ -173,7 +173,7 @@ def _resolve_union_type(
) -> str:
assert isinstance(type_, GraphQLUnionType)
- from strawberry.types.types import StrawberryObjectDefinition
+ from strawberry.types.base import StrawberryObjectDefinition
# If the type given is not an Object type, try resolving using `is_type_of`
# defined on the union's inner types
@@ -194,7 +194,6 @@ def _resolve_union_type(
# Union in case a nested generic object matches against more than one type.
concrete_types_for_union = (type_map[x.name] for x in type_.types)
- # TODO: do we still need to iterate over all types in `type_map`?
for possible_concrete_type in chain(
concrete_types_for_union, type_map.values()
):
@@ -213,13 +212,9 @@ def _resolve_union_type(
info.field_name, str(type(root)), set(type_.types)
)
- # Return the name of the type. Returning the actual type is now deprecated
- if isinstance(return_type, GraphQLNamedType):
- # TODO: Can return_type ever _not_ be a GraphQLNamedType?
- return return_type.name
- else:
- # TODO: check if this is correct
- return return_type.__name__ # type: ignore
+ assert isinstance(return_type, GraphQLNamedType)
+
+ return return_type.name
return _resolve_union_type
@@ -255,15 +250,27 @@ def union(
) -> StrawberryUnion:
"""Creates a new named Union type.
+ Args:
+ name: The GraphQL name of the Union type.
+ types: The types that the Union can be.
+ (Deprecated, use `Annotated[U, strawberry.union("Name")]` instead)
+ description: The GraphQL description of the Union type.
+ directives: The directives to attach to the Union type.
+
Example usages:
- >>> @strawberry.type
- ... class A: ...
- >>> @strawberry.type
- ... class B: ...
- >>> Annotated[A | B, strawberry.union("Name")]
- """
+ ```python
+ import strawberry
+ from typing import Annotated
+
+ @strawberry.type
+ class A: ...
+ @strawberry.type
+ class B: ...
+
+ MyUnion = Annotated[A | B, strawberry.union("Name")]
+ """
if types is None:
union = StrawberryUnion(
name=name,
@@ -304,3 +311,6 @@ def union(
description=description,
directives=directives,
)
+
+
+__all__ = ["StrawberryUnion", "union"]
diff --git a/strawberry/unset.py b/strawberry/types/unset.py
similarity index 61%
rename from strawberry/unset.py
rename to strawberry/types/unset.py
index ecba41b7c2..31c8012b73 100644
--- a/strawberry/unset.py
+++ b/strawberry/types/unset.py
@@ -17,17 +17,37 @@ def __new__(cls: Type["UnsetType"]) -> "UnsetType":
else:
return cls.__instance
- def __str__(self):
+ def __str__(self) -> str:
return ""
def __repr__(self) -> str:
return "UNSET"
- def __bool__(self):
+ def __bool__(self) -> bool:
return False
UNSET: Any = UnsetType()
+"""A special value that can be used to represent an unset value in a field or argument.
+Similar to `undefined` in JavaScript, this value can be used to differentiate between
+a field that was not set and a field that was set to `None` or `null`.
+
+Example:
+
+```python
+import strawberry
+
+
+@strawberry.input
+class UserInput:
+ name: str | None = strawberry.UNSET
+ age: int | None = strawberry.UNSET
+```
+
+In the example above, if `name` or `age` are not provided when creating a `UserInput`
+object, they will be set to `UNSET` instead of `None`. Use `is UNSET` to check
+whether a value is unset.
+"""
def _deprecated_is_unset(value: Any) -> bool:
diff --git a/strawberry/utils/aio.py b/strawberry/utils/aio.py
index c6a0068269..7ffd8f9e67 100644
--- a/strawberry/utils/aio.py
+++ b/strawberry/utils/aio.py
@@ -24,7 +24,7 @@ async def aenumerate(
i = 0
async for element in iterable:
yield i, element
- i += 1 # noqa: SIM113
+ i += 1
async def aislice(
@@ -67,3 +67,11 @@ async def resolve_awaitable(
) -> _R:
"""Resolves an awaitable object and calls a callback with the resolved value."""
return callback(await awaitable)
+
+
+__all__ = [
+ "aenumerate",
+ "aislice",
+ "asyncgen_to_list",
+ "resolve_awaitable",
+]
diff --git a/strawberry/utils/await_maybe.py b/strawberry/utils/await_maybe.py
index ee79716082..65b1a23bd6 100644
--- a/strawberry/utils/await_maybe.py
+++ b/strawberry/utils/await_maybe.py
@@ -1,5 +1,5 @@
import inspect
-from typing import AsyncIterator, Awaitable, Iterator, TypeVar, Union, cast
+from typing import AsyncIterator, Awaitable, Iterator, TypeVar, Union
T = TypeVar("T")
@@ -11,4 +11,7 @@ async def await_maybe(value: AwaitableOrValue[T]) -> T:
if inspect.isawaitable(value):
return await value
- return cast(T, value)
+ return value
+
+
+__all__ = ["await_maybe", "AwaitableOrValue", "AsyncIteratorOrIterator"]
diff --git a/strawberry/utils/dataclasses.py b/strawberry/utils/dataclasses.py
index 62e835f6e8..eadd0ed1be 100644
--- a/strawberry/utils/dataclasses.py
+++ b/strawberry/utils/dataclasses.py
@@ -32,3 +32,6 @@ def add_custom_init_fn(cls: Any) -> None:
globals_=globals_,
),
)
+
+
+__all__ = ["add_custom_init_fn"]
diff --git a/strawberry/utils/debug.py b/strawberry/utils/debug.py
index 1a59086fb3..825a2ccc24 100644
--- a/strawberry/utils/debug.py
+++ b/strawberry/utils/debug.py
@@ -14,8 +14,8 @@ def pretty_print_graphql_operation(
) -> None:
"""Pretty print a GraphQL operation using pygments.
- Won't print introspection operation to prevent noise in the output."""
-
+ Won't print introspection operation to prevent noise in the output.
+ """
try:
from pygments import highlight, lexers
from pygments.formatters import Terminal256Formatter
@@ -41,3 +41,6 @@ def pretty_print_graphql_operation(
print( # noqa: T201
highlight(variables_json, lexers.JsonLexer(), Terminal256Formatter())
)
+
+
+__all__ = ["pretty_print_graphql_operation"]
diff --git a/strawberry/utils/deprecations.py b/strawberry/utils/deprecations.py
index aa722c92d6..4e802c5b43 100644
--- a/strawberry/utils/deprecations.py
+++ b/strawberry/utils/deprecations.py
@@ -25,3 +25,6 @@ def __get__(self, obj: Optional[object], type: Optional[Type] = None) -> Any:
def inject(self, klass: type) -> None:
setattr(klass, self.attr_name, self)
+
+
+__all__ = ["DEPRECATION_MESSAGES", "DeprecatedDescriptor"]
diff --git a/strawberry/utils/graphql_lexer.py b/strawberry/utils/graphql_lexer.py
index ffe02937c6..4e23668dd7 100644
--- a/strawberry/utils/graphql_lexer.py
+++ b/strawberry/utils/graphql_lexer.py
@@ -28,3 +28,6 @@ class GraphQLLexer(RegexLexer):
(r"(\s|,)", token.Text),
]
}
+
+
+__all__ = ["GraphQLLexer"]
diff --git a/strawberry/utils/importer.py b/strawberry/utils/importer.py
index 90dc81f02d..7179245c61 100644
--- a/strawberry/utils/importer.py
+++ b/strawberry/utils/importer.py
@@ -19,3 +19,6 @@ def import_module_symbol(
symbol = getattr(symbol, attribute_name)
return symbol
+
+
+__all__ = ["import_module_symbol"]
diff --git a/strawberry/utils/inspect.py b/strawberry/utils/inspect.py
index 629052e086..4ed67c6be4 100644
--- a/strawberry/utils/inspect.py
+++ b/strawberry/utils/inspect.py
@@ -8,10 +8,11 @@
List,
Optional,
TypeVar,
+ get_origin,
)
from typing_extensions import get_args
-from strawberry.type import has_object_definition
+from strawberry.utils.typing import is_generic_alias
def in_async_context() -> bool:
@@ -27,8 +28,7 @@ def in_async_context() -> bool:
@lru_cache(maxsize=250)
def get_func_args(func: Callable[[Any], Any]) -> List[str]:
- """Returns a list of arguments for the function"""
-
+ """Returns a list of arguments for the function."""
sig = inspect.signature(func)
return [
@@ -43,42 +43,54 @@ def get_specialized_type_var_map(cls: type) -> Optional[Dict[str, type]]:
Consider the following:
- >>> class Foo(Generic[T]):
- ... ...
- ...
- >>> class Bar(Generic[K]):
- ... ...
- ...
- >>> class IntBar(Bar[int]):
- ... ...
- ...
- >>> class IntBarSubclass(IntBar):
- ... ...
- ...
- >>> class IntBarFoo(IntBar, Foo[str]):
- ... ...
- ...
+ ```python
+ class Foo(Generic[T]): ...
+
+
+ class Bar(Generic[K]): ...
+
+
+ class IntBar(Bar[int]): ...
+
+
+ class IntBarSubclass(IntBar): ...
+
+
+ class IntBarFoo(IntBar, Foo[str]): ...
+ ```
This would return:
- >>> get_specialized_type_var_map(object)
- None
- >>> get_specialized_type_var_map(Foo)
- {}
- >>> get_specialized_type_var_map(Bar)
- {~T: ~T}
- >>> get_specialized_type_var_map(IntBar)
- {~T: int}
- >>> get_specialized_type_var_map(IntBarSubclass)
- {~T: int}
- >>> get_specialized_type_var_map(IntBarFoo)
- {~T: int, ~K: str}
+ ```python
+ get_specialized_type_var_map(object)
+ # None
+
+ get_specialized_type_var_map(Foo)
+ # {}
+
+ get_specialized_type_var_map(Bar)
+ # {~T: ~T}
+
+ get_specialized_type_var_map(IntBar)
+ # {~T: int}
+ get_specialized_type_var_map(IntBarSubclass)
+ # {~T: int}
+
+ get_specialized_type_var_map(IntBarFoo)
+ # {~T: int, ~K: str}
+ ```
"""
+ from strawberry.types.base import has_object_definition
+
orig_bases = getattr(cls, "__orig_bases__", None)
if orig_bases is None:
- # Not a specialized type
- return None
+ # Specialized generic aliases will not have __orig_bases__
+ if get_origin(cls) is not None and is_generic_alias(cls):
+ orig_bases = (cls,)
+ else:
+ # Not a specialized type
+ return None
type_var_map = {}
@@ -88,9 +100,10 @@ def get_specialized_type_var_map(cls: type) -> Optional[Dict[str, type]]:
for base in orig_bases:
# Recursively get type var map from base classes
- base_type_var_map = get_specialized_type_var_map(base)
- if base_type_var_map is not None:
- type_var_map.update(base_type_var_map)
+ if base is not cls:
+ base_type_var_map = get_specialized_type_var_map(base)
+ if base_type_var_map is not None:
+ type_var_map.update(base_type_var_map)
args = get_args(base)
origin = getattr(base, "__origin__", None)
@@ -107,3 +120,6 @@ def get_specialized_type_var_map(cls: type) -> Optional[Dict[str, type]]:
)
return type_var_map
+
+
+__all__ = ["in_async_context", "get_func_args", "get_specialized_type_var_map"]
diff --git a/strawberry/utils/logging.py b/strawberry/utils/logging.py
index d19fd49bce..0a701a7701 100644
--- a/strawberry/utils/logging.py
+++ b/strawberry/utils/logging.py
@@ -23,3 +23,6 @@ def error(
**logger_kwargs: Any,
) -> None:
cls.logger.error(error, exc_info=error.original_error, **logger_kwargs)
+
+
+__all__ = ["StrawberryLogger"]
diff --git a/strawberry/utils/operation.py b/strawberry/utils/operation.py
index dcca27a907..78eb95119e 100644
--- a/strawberry/utils/operation.py
+++ b/strawberry/utils/operation.py
@@ -38,3 +38,6 @@ def get_operation_type(
raise RuntimeError("Can't get GraphQL operation type")
return OperationType(definition.operation.value)
+
+
+__all__ = ["get_first_operation", "get_operation_type"]
diff --git a/strawberry/utils/str_converters.py b/strawberry/utils/str_converters.py
index 7bad6fc751..9c52a7ed7d 100644
--- a/strawberry/utils/str_converters.py
+++ b/strawberry/utils/str_converters.py
@@ -24,3 +24,6 @@ def capitalize_first(name: str) -> str:
def to_snake_case(name: str) -> str:
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
+
+
+__all__ = ["to_camel_case", "to_kebab_case", "capitalize_first", "to_snake_case"]
diff --git a/strawberry/utils/typing.py b/strawberry/utils/typing.py
index f066de3de3..82712f29ab 100644
--- a/strawberry/utils/typing.py
+++ b/strawberry/utils/typing.py
@@ -23,7 +23,7 @@
cast,
overload,
)
-from typing_extensions import Annotated, get_args, get_origin
+from typing_extensions import Annotated, TypeGuard, get_args, get_origin
ast_unparse = getattr(ast, "unparse", None)
# ast.unparse is only available on python 3.9+. For older versions we will
@@ -44,10 +44,13 @@ def get_generic_alias(type_: Type) -> Type:
Given a type, its generic alias from `typing` module will be returned
if it exists. For example:
- >>> get_generic_alias(list)
- typing.List
- >>> get_generic_alias(dict)
- typing.Dict
+ ```python
+ get_generic_alias(list)
+ # typing.List
+
+ get_generic_alias(dict)
+ # typing.Dict
+ ```
This is mostly useful for python versions prior to 3.9, to get a version
of a concrete type which supports `__class_getitem__`. In 3.9+ types like
@@ -63,26 +66,29 @@ def get_generic_alias(type_: Type) -> Type:
continue
attr = getattr(typing, attr_name)
- # _GenericAlias overrides all the methods that we can use to know if
- # this is a subclass of it. But if it has an "_inst" attribute
- # then it for sure is a _GenericAlias
- if hasattr(attr, "_inst") and attr.__origin__ is type_:
+ if is_generic_alias(attr) and attr.__origin__ is type_:
return attr
raise AssertionError(f"No GenericAlias available for {type_}") # pragma: no cover
-def is_list(annotation: object) -> bool:
- """Returns True if annotation is a List"""
+def is_generic_alias(type_: Any) -> TypeGuard[_GenericAlias]:
+ """Returns True if the type is a generic alias."""
+ # _GenericAlias overrides all the methods that we can use to know if
+ # this is a subclass of it. But if it has an "_inst" attribute
+ # then it for sure is a _GenericAlias
+ return hasattr(type_, "_inst")
+
+def is_list(annotation: object) -> bool:
+ """Returns True if annotation is a List."""
annotation_origin = getattr(annotation, "__origin__", None)
return annotation_origin == list
def is_union(annotation: object) -> bool:
- """Returns True if annotation is a Union"""
-
+ """Returns True if annotation is a Union."""
# this check is needed because unions declared with the new syntax `A | B`
# don't have a `__origin__` property on them, but they are instances of
# `UnionType`, which is only available in Python 3.10+
@@ -100,8 +106,7 @@ def is_union(annotation: object) -> bool:
def is_optional(annotation: Type) -> bool:
- """Returns True if the annotation is Optional[SomeType]"""
-
+ """Returns True if the annotation is Optional[SomeType]."""
# Optionals are represented as unions
if not is_union(annotation):
@@ -148,7 +153,6 @@ def is_generic_subclass(annotation: type) -> bool:
def is_generic(annotation: type) -> bool:
"""Returns True if the annotation is or extends a generic."""
-
return (
# TODO: These two lines appear to have the same effect. When will an
# annotation have parameters but not satisfy the first condition?
@@ -159,7 +163,6 @@ def is_generic(annotation: type) -> bool:
def is_type_var(annotation: Type) -> bool:
"""Returns True if the annotation is a TypeVar."""
-
return isinstance(annotation, TypeVar)
@@ -204,13 +207,11 @@ def get_parameters(annotation: Type) -> Union[Tuple[object], Tuple[()]]:
@overload
-def _ast_replace_union_operation(expr: ast.expr) -> ast.expr:
- ...
+def _ast_replace_union_operation(expr: ast.expr) -> ast.expr: ...
@overload
-def _ast_replace_union_operation(expr: ast.Expr) -> ast.Expr:
- ...
+def _ast_replace_union_operation(expr: ast.Expr) -> ast.Expr: ...
def _ast_replace_union_operation(
@@ -238,7 +239,7 @@ def _ast_replace_union_operation(
expr = ast.Subscript(
expr.value,
# The cast is required for mypy on python 3.7 and 3.8
- ast.Index(_ast_replace_union_operation(cast(Any, expr.slice).value)),
+ ast.Index(_ast_replace_union_operation(cast(Any, expr.slice).value)), # type: ignore
ast.Load(),
)
elif isinstance(expr.slice, (ast.BinOp, ast.Tuple)):
@@ -256,7 +257,7 @@ def _get_namespace_from_ast(
globalns: Optional[Dict] = None,
localns: Optional[Dict] = None,
) -> Dict[str, Type]:
- from strawberry.lazy_type import StrawberryLazyReference
+ from strawberry.types.lazy_type import StrawberryLazyReference
extra = {}
@@ -280,6 +281,12 @@ def _get_namespace_from_ast(
for elt in cast(ast.Tuple, expr_slice).elts:
extra.update(_get_namespace_from_ast(elt, globalns, localns))
+ elif (
+ isinstance(expr, ast.Subscript)
+ and isinstance(expr.value, ast.Name)
+ and expr.value.id in {"list", "List"}
+ ):
+ extra.update(_get_namespace_from_ast(expr.slice, globalns, localns))
elif (
isinstance(expr, ast.Subscript)
and isinstance(expr.value, ast.Name)
@@ -304,7 +311,7 @@ def _get_namespace_from_ast(
# here to resolve lazy types by execing the annotated args, resolving the
# type directly and then adding it to extra namespace, so that _eval_type
# can properly resolve it later
- type_name = args[0].strip()
+ type_name = args[0].strip(" '\"\n")
for arg in args[1:]:
evaled_arg = eval(arg, globalns, localns) # noqa: PGH001, S307
if isinstance(evaled_arg, StrawberryLazyReference):
@@ -319,9 +326,9 @@ def eval_type(
localns: Optional[Dict] = None,
) -> Type:
"""Evaluates a type, resolving forward references."""
- from strawberry.auto import StrawberryAuto
- from strawberry.lazy_type import StrawberryLazyReference
- from strawberry.private import StrawberryPrivate
+ from strawberry.types.auto import StrawberryAuto
+ from strawberry.types.lazy_type import StrawberryLazyReference
+ from strawberry.types.private import StrawberryPrivate
globalns = globalns or {}
# If this is not a string, maybe its args are (e.g. List["Foo"])
@@ -402,3 +409,22 @@ def eval_type(
)
return type_
+
+
+__all__ = [
+ "get_generic_alias",
+ "is_generic_alias",
+ "is_list",
+ "is_union",
+ "is_optional",
+ "get_optional_annotation",
+ "get_list_annotation",
+ "is_concrete_generic",
+ "is_generic_subclass",
+ "is_generic",
+ "is_type_var",
+ "is_classvar",
+ "type_has_annotation",
+ "get_parameters",
+ "eval_type",
+]
diff --git a/tests/b.py b/tests/b.py
index f646816b0c..b291c107ba 100644
--- a/tests/b.py
+++ b/tests/b.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, List, Optional
from typing_extensions import Annotated
import strawberry
@@ -19,6 +19,14 @@ async def a(self) -> Annotated[A, strawberry.lazy("tests.a"), object()]:
return A(id=self.id)
+ @strawberry.field
+ async def a_list(
+ self,
+ ) -> List[Annotated[A, strawberry.lazy("tests.a")]]: # pragma: no cover
+ from tests.a import A
+
+ return [A(id=self.id)]
+
@strawberry.field
async def optional_a(
self,
diff --git a/tests/benchmarks/api.py b/tests/benchmarks/api.py
index 157b18b556..4584d944e2 100644
--- a/tests/benchmarks/api.py
+++ b/tests/benchmarks/api.py
@@ -73,6 +73,11 @@ class Subscription:
async def something(self) -> AsyncIterator[str]:
yield "Hello World!"
+ @strawberry.subscription
+ async def long_running(self, count: int) -> AsyncIterator[int]:
+ for i in range(count):
+ yield i
+
@strawberry.directive(locations=[DirectiveLocation.FIELD])
def uppercase(value: str) -> str:
diff --git a/tests/benchmarks/test_generic_input.py b/tests/benchmarks/test_generic_input.py
new file mode 100644
index 0000000000..bc937c4605
--- /dev/null
+++ b/tests/benchmarks/test_generic_input.py
@@ -0,0 +1,75 @@
+import asyncio
+from typing import Generic, List, Optional, TypeVar
+
+from pytest_codspeed.plugin import BenchmarkFixture
+
+import strawberry
+
+T = TypeVar("T")
+
+
+@strawberry.input(description="Filter for GraphQL queries")
+class GraphQLFilter(Generic[T]):
+ """EXTERNAL Filter for GraphQL queries"""
+
+ eq: Optional[T] = None
+ in_: Optional[List[T]] = None
+ nin: Optional[List[T]] = None
+ gt: Optional[T] = None
+ gte: Optional[T] = None
+ lt: Optional[T] = None
+ lte: Optional[T] = None
+ contains: Optional[T] = None
+ icontains: Optional[T] = None
+
+
+@strawberry.type
+class Author:
+ name: str
+
+
+@strawberry.type
+class Book:
+ title: str
+
+ @strawberry.field
+ async def authors(
+ self,
+ name: Optional[GraphQLFilter[str]] = None,
+ ) -> List[Author]:
+ return [Author(name="F. Scott Fitzgerald")]
+
+
+def get_books():
+ return [
+ Book(title="The Great Gatsby"),
+ ] * 1000
+
+
+@strawberry.type
+class Query:
+ books: List[Book] = strawberry.field(resolver=get_books)
+
+
+schema = strawberry.Schema(query=Query)
+
+query = """{
+ books {
+ title
+ authors(name: {eq: "F. Scott Fitzgerald"}) {
+ name
+ }
+ }
+}
+"""
+
+
+def test_execute_generic_input(benchmark: BenchmarkFixture):
+ def run():
+ coroutine = schema.execute(query)
+
+ return asyncio.run(coroutine)
+
+ result = benchmark(run)
+
+ assert not result.errors
diff --git a/tests/benchmarks/test_subscriptions.py b/tests/benchmarks/test_subscriptions.py
index 075c993904..0bbd512835 100644
--- a/tests/benchmarks/test_subscriptions.py
+++ b/tests/benchmarks/test_subscriptions.py
@@ -1,6 +1,8 @@
import asyncio
+from typing import AsyncIterator
import pytest
+from graphql import ExecutionResult
from pytest_codspeed.plugin import BenchmarkFixture
from .api import schema
@@ -24,3 +26,25 @@ async def _run():
assert value.data["something"] == "Hello World!"
benchmark(lambda: asyncio.run(_run()))
+
+
+@pytest.mark.benchmark
+@pytest.mark.parametrize("count", [1000, 20000])
+def test_subscription_long_run(benchmark: BenchmarkFixture, count: int) -> None:
+ s = """#graphql
+ subscription LongRunning($count: Int!) {
+ longRunning(count: $count)
+ }
+ """
+
+ async def _run():
+ i = 0
+ aiterator: AsyncIterator[ExecutionResult] = await schema.subscribe(
+ s, variable_values={"count": count}
+ ) # type: ignore[assignment]
+ async for res in aiterator:
+ assert res.data is not None
+ assert res.data["longRunning"] == i
+ i += 1
+
+ benchmark(lambda: asyncio.run(_run()))
diff --git a/tests/c.py b/tests/c.py
new file mode 100644
index 0000000000..c365cd792b
--- /dev/null
+++ b/tests/c.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+import strawberry
+
+
+@strawberry.type
+class C:
+ id: strawberry.ID
diff --git a/tests/channels/test_testing.py b/tests/channels/test_testing.py
index e691abac33..921d8abf35 100644
--- a/tests/channels/test_testing.py
+++ b/tests/channels/test_testing.py
@@ -21,7 +21,10 @@ async def communicator(
application = GraphQLWSConsumer.as_asgi(schema=schema, keep_alive_interval=50)
async with GraphQLWebsocketCommunicator(
- protocol=request.param, application=application, path="/graphql"
+ protocol=request.param,
+ application=application,
+ path="/graphql",
+ connection_params={"strawberry": "Hi"},
) as client:
yield client
@@ -45,3 +48,8 @@ async def test_graphql_error(communicator):
query='subscription { error(message: "Hi") }'
):
assert res.errors[0].message == "Hi"
+
+
+async def test_simple_connection_params(communicator):
+ async for res in communicator.subscribe(query="subscription { connectionParams }"):
+ assert res.data["connectionParams"] == "Hi"
diff --git a/tests/codemods/test_imports.py b/tests/codemods/test_imports.py
new file mode 100644
index 0000000000..896fe325e8
--- /dev/null
+++ b/tests/codemods/test_imports.py
@@ -0,0 +1,176 @@
+from libcst.codemod import CodemodTest
+
+from strawberry.codemods.update_imports import UpdateImportsCodemod
+
+
+class TestConvertConstantCommand(CodemodTest):
+ TRANSFORM = UpdateImportsCodemod
+
+ def test_update_field(self) -> None:
+ before = """
+ from strawberry.field import something
+ """
+
+ after = """
+ from strawberry.types.field import something
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_strawberry_type(self) -> None:
+ before = """
+ from strawberry.type import (
+ StrawberryContainer,
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryType,
+ WithStrawberryObjectDefinition,
+ )
+ """
+
+ after = """
+ from strawberry.types.base import (
+ StrawberryContainer,
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryType,
+ WithStrawberryObjectDefinition,
+ )
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_strawberry_type_object_definition(self) -> None:
+ before = """
+ from strawberry.type import (
+ StrawberryContainer,
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryType,
+ WithStrawberryObjectDefinition,
+ get_object_definition,
+ has_object_definition,
+ )
+ """
+
+ after = """
+ from strawberry.types.base import (
+ StrawberryContainer,
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryType,
+ WithStrawberryObjectDefinition)
+ from strawberry.types import get_object_definition, has_object_definition
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_strawberry_type_object_definition_only(self) -> None:
+ before = """
+ from strawberry.type import get_object_definition
+ """
+
+ after = """
+ from strawberry.types import get_object_definition
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_union(self) -> None:
+ before = """
+ from strawberry.union import StrawberryUnion
+ """
+
+ after = """
+ from strawberry.types.union import StrawberryUnion
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_auto(self) -> None:
+ before = """
+ from strawberry.auto import auto
+ """
+
+ after = """
+ from strawberry.types.auto import auto
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_unset(self) -> None:
+ before = """
+ from strawberry.unset import UNSET
+ """
+
+ after = """
+ from strawberry.types.unset import UNSET
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_arguments(self) -> None:
+ before = """
+ from strawberry.arguments import StrawberryArgument
+ """
+
+ after = """
+ from strawberry.types.arguments import StrawberryArgument
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_lazy_type(self) -> None:
+ before = """
+ from strawberry.lazy_type import LazyType
+ """
+
+ after = """
+ from strawberry.types.lazy_type import LazyType
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_object_type(self) -> None:
+ before = """
+ from strawberry.object_type import StrawberryObjectDefinition
+ """
+
+ after = """
+ from strawberry.types.object_type import StrawberryObjectDefinition
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_import_enum(self) -> None:
+ before = """
+ from strawberry.enum import StrawberryEnum
+ """
+
+ after = """
+ from strawberry.types.enum import StrawberryEnum
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_types_types(self) -> None:
+ before = """
+ from strawberry.types.types import StrawberryObjectDefinition
+ """
+
+ after = """
+ from strawberry.types.base import StrawberryObjectDefinition
+ """
+
+ self.assertCodemod(before, after)
+
+ def test_update_is_private(self) -> None:
+ before = """
+ from strawberry.private import is_private
+ """
+
+ after = """
+ from strawberry.types.private import is_private
+ """
+
+ self.assertCodemod(before, after)
diff --git a/tests/conftest.py b/tests/conftest.py
index 7317d45710..4c128018a3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,7 @@
import sys
from typing import Any, List, Tuple
+import graphql
import pytest
@@ -57,3 +58,13 @@ def pytest_ignore_collect(
# we're running the tests for it
if "starlite" not in markers and "starlite" in collection_path.parts:
return True
+
+
+IS_GQL_32 = "3.3" not in graphql.__version__
+
+
+def skip_if_gql_32(reason: str) -> pytest.MarkDecorator:
+ return pytest.mark.skipif(
+ IS_GQL_32,
+ reason=reason,
+ )
diff --git a/tests/d.py b/tests/d.py
new file mode 100644
index 0000000000..44678fdc98
--- /dev/null
+++ b/tests/d.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+from typing_extensions import Annotated
+
+import strawberry
+
+if TYPE_CHECKING:
+ from tests.c import C
+
+
+@strawberry.type
+class D:
+ id: strawberry.ID
+
+ @strawberry.field
+ async def c_list(
+ self,
+ ) -> List[Annotated[C, strawberry.lazy("tests.c")]]: # pragma: no cover
+ from tests.c import C
+
+ return [C(id=self.id)]
diff --git a/tests/enums/test_enum.py b/tests/enums/test_enum.py
index 827e282909..2189fba3d6 100644
--- a/tests/enums/test_enum.py
+++ b/tests/enums/test_enum.py
@@ -3,9 +3,9 @@
import pytest
import strawberry
-from strawberry.enum import EnumDefinition
from strawberry.exceptions import ObjectIsNotAnEnumError
from strawberry.exceptions.not_a_strawberry_enum import NotAStrawberryEnumError
+from strawberry.types.enum import EnumDefinition
def test_basic_enum():
diff --git a/tests/exceptions/classes/test_exception_class_missing_optional_dependencies_error.py b/tests/exceptions/classes/test_exception_class_missing_optional_dependencies_error.py
new file mode 100644
index 0000000000..74e740a07c
--- /dev/null
+++ b/tests/exceptions/classes/test_exception_class_missing_optional_dependencies_error.py
@@ -0,0 +1,57 @@
+import pytest
+
+from strawberry.exceptions import MissingOptionalDependenciesError
+
+
+def test_missing_optional_dependencies_error():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError()
+
+ assert exc_info.value.message == "Some optional dependencies are missing"
+
+
+def test_missing_optional_dependencies_error_packages():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError(packages=["a", "b"])
+
+ assert (
+ exc_info.value.message
+ == "Some optional dependencies are missing (hint: pip install a b)"
+ )
+
+
+def test_missing_optional_dependencies_error_empty_packages():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError(packages=[])
+
+ assert exc_info.value.message == "Some optional dependencies are missing"
+
+
+def test_missing_optional_dependencies_error_extras():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError(extras=["dev", "test"])
+
+ assert (
+ exc_info.value.message
+ == "Some optional dependencies are missing (hint: pip install 'strawberry-graphql[dev,test]')"
+ )
+
+
+def test_missing_optional_dependencies_error_empty_extras():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError(extras=[])
+
+ assert exc_info.value.message == "Some optional dependencies are missing"
+
+
+def test_missing_optional_dependencies_error_packages_and_extras():
+ with pytest.raises(MissingOptionalDependenciesError) as exc_info:
+ raise MissingOptionalDependenciesError(
+ packages=["a", "b"],
+ extras=["dev", "test"],
+ )
+
+ assert (
+ exc_info.value.message
+ == "Some optional dependencies are missing (hint: pip install a b 'strawberry-graphql[dev,test]')"
+ )
diff --git a/tests/experimental/pydantic/schema/test_1_and_2.py b/tests/experimental/pydantic/schema/test_1_and_2.py
new file mode 100644
index 0000000000..5e8b3fea72
--- /dev/null
+++ b/tests/experimental/pydantic/schema/test_1_and_2.py
@@ -0,0 +1,83 @@
+import textwrap
+from typing import Optional, Union
+
+import strawberry
+from tests.experimental.pydantic.utils import needs_pydantic_v2
+
+
+@needs_pydantic_v2
+def test_can_use_both_pydantic_1_and_2():
+ import pydantic
+ from pydantic import v1 as pydantic_v1
+
+ class UserModel(pydantic.BaseModel):
+ age: int
+ name: Optional[str]
+
+ @strawberry.experimental.pydantic.type(UserModel)
+ class User:
+ age: strawberry.auto
+ name: strawberry.auto
+
+ class LegacyUserModel(pydantic_v1.BaseModel):
+ age: int
+ name: Optional[str]
+ int_field: pydantic.v1.NonNegativeInt = 1
+
+ @strawberry.experimental.pydantic.type(LegacyUserModel)
+ class LegacyUser:
+ age: strawberry.auto
+ name: strawberry.auto
+ int_field: strawberry.auto
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def user(self, id: strawberry.ID) -> Union[User, LegacyUser]:
+ if id == "legacy":
+ return LegacyUser(age=1, name="legacy")
+
+ return User(age=1, name="ABC")
+
+ schema = strawberry.Schema(query=Query)
+
+ expected_schema = """
+ type LegacyUser {
+ age: Int!
+ name: String
+ intField: Int!
+ }
+
+ type Query {
+ user(id: ID!): UserLegacyUser!
+ }
+
+ type User {
+ age: Int!
+ name: String
+ }
+
+ union UserLegacyUser = User | LegacyUser
+ """
+
+ assert str(schema) == textwrap.dedent(expected_schema).strip()
+
+ query = """
+ query ($id: ID!) {
+ user(id: $id) {
+ __typename
+ ... on User { name }
+ ... on LegacyUser { name }
+ }
+ }
+ """
+
+ result = schema.execute_sync(query, variable_values={"id": "new"})
+
+ assert not result.errors
+ assert result.data == {"user": {"__typename": "User", "name": "ABC"}}
+
+ result = schema.execute_sync(query, variable_values={"id": "legacy"})
+
+ assert not result.errors
+ assert result.data == {"user": {"__typename": "LegacyUser", "name": "legacy"}}
diff --git a/tests/experimental/pydantic/schema/test_defaults.py b/tests/experimental/pydantic/schema/test_defaults.py
index c798c89b15..2412e6b04c 100644
--- a/tests/experimental/pydantic/schema/test_defaults.py
+++ b/tests/experimental/pydantic/schema/test_defaults.py
@@ -5,6 +5,7 @@
import strawberry
from strawberry.printer import print_schema
+from tests.conftest import skip_if_gql_32
from tests.experimental.pydantic.utils import needs_pydantic_v2
@@ -14,8 +15,7 @@ class User(pydantic.BaseModel):
nickname: Optional[str] = "Jim"
@strawberry.experimental.pydantic.type(User, all_fields=True)
- class PydanticUser:
- ...
+ class PydanticUser: ...
@strawberry.type
class StrawberryUser:
@@ -58,8 +58,7 @@ class UserPydantic(pydantic.BaseModel):
name: Optional[str] = None
@strawberry.experimental.pydantic.type(UserPydantic, all_fields=True)
- class User:
- ...
+ class User: ...
@strawberry.type
class Query:
@@ -90,8 +89,7 @@ class UserPydantic(pydantic.BaseModel):
name: Optional[str]
@strawberry.experimental.pydantic.type(UserPydantic, all_fields=True)
- class User:
- ...
+ class User: ...
@strawberry.type
class Query:
@@ -121,8 +119,7 @@ class User(pydantic.BaseModel):
name: str = "James"
@strawberry.experimental.pydantic.type(User, all_fields=True, is_input=True)
- class PydanticUser:
- ...
+ class PydanticUser: ...
@strawberry.type(is_input=True)
class StrawberryUser:
@@ -165,8 +162,7 @@ class User(pydantic.BaseModel):
name: Optional[str]
@strawberry.experimental.pydantic.type(User, all_fields=True)
- class PydanticUser:
- ...
+ class PydanticUser: ...
@strawberry.type
class Query:
@@ -188,3 +184,84 @@ def a(self) -> PydanticUser:
"""
assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+
+@skip_if_gql_32("formatting is different in gql 3.2")
+def test_v2_input_with_nonscalar_default():
+ class NonScalarType(pydantic.BaseModel):
+ id: int = 10
+ nullable_field: Optional[int] = None
+
+ class Owning(pydantic.BaseModel):
+ non_scalar_type: NonScalarType = NonScalarType()
+ id: int = 10
+
+ @strawberry.experimental.pydantic.type(
+ model=NonScalarType, all_fields=True, is_input=True
+ )
+ class NonScalarTypeInput: ...
+
+ @strawberry.experimental.pydantic.type(model=Owning, all_fields=True, is_input=True)
+ class OwningInput: ...
+
+ @strawberry.type
+ class ExampleOutput:
+ owning_id: int
+ non_scalar_id: int
+ non_scalar_nullable_field: Optional[int]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field()
+ def test(self, x: OwningInput) -> ExampleOutput:
+ return ExampleOutput(
+ owning_id=x.id,
+ non_scalar_id=x.non_scalar_type.id,
+ non_scalar_nullable_field=x.non_scalar_type.nullable_field,
+ )
+
+ schema = strawberry.Schema(Query)
+
+ expected = """
+ type ExampleOutput {
+ owningId: Int!
+ nonScalarId: Int!
+ nonScalarNullableField: Int
+ }
+
+ input NonScalarTypeInput {
+ id: Int! = 10
+ nullableField: Int = null
+ }
+
+ input OwningInput {
+ nonScalarType: NonScalarTypeInput! = { id: 10 }
+ id: Int! = 10
+ }
+
+ type Query {
+ test(x: OwningInput!): ExampleOutput!
+ }
+ """
+
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+ query = """
+ query($input_data: OwningInput!)
+ {
+ test(x: $input_data) {
+ owningId nonScalarId nonScalarNullableField
+ }
+ }
+ """
+ result = schema.execute_sync(
+ query, variable_values=dict(input_data=dict(nonScalarType={}))
+ )
+
+ assert not result.errors
+ expected_result = {
+ "owningId": 10,
+ "nonScalarId": 10,
+ "nonScalarNullableField": None,
+ }
+ assert result.data["test"] == expected_result
diff --git a/tests/experimental/pydantic/test_basic.py b/tests/experimental/pydantic/test_basic.py
index 1b70b6d53e..9556d3f617 100644
--- a/tests/experimental/pydantic/test_basic.py
+++ b/tests/experimental/pydantic/test_basic.py
@@ -7,12 +7,15 @@
import pytest
import strawberry
-from strawberry.enum import EnumDefinition
from strawberry.experimental.pydantic.exceptions import MissingFieldsListError
from strawberry.schema_directive import Location
-from strawberry.type import StrawberryList, StrawberryOptional
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryObjectDefinition,
+ StrawberryOptional,
+)
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.union import StrawberryUnion
def test_basic_type_field_list():
diff --git a/tests/experimental/pydantic/test_conversion.py b/tests/experimental/pydantic/test_conversion.py
index ec9ba5495f..cc9b5a81e0 100644
--- a/tests/experimental/pydantic/test_conversion.py
+++ b/tests/experimental/pydantic/test_conversion.py
@@ -11,23 +11,21 @@
import strawberry
from strawberry.experimental.pydantic._compat import (
IS_PYDANTIC_V2,
- PYDANTIC_MISSING_TYPE,
CompatModelField,
+ PydanticCompat,
)
from strawberry.experimental.pydantic.exceptions import (
AutoFieldsNotInBaseModelError,
BothDefaultAndDefaultFactoryDefinedError,
)
from strawberry.experimental.pydantic.utils import get_default_factory_for_field
-from strawberry.type import StrawberryList, StrawberryOptional
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryObjectDefinition,
+ StrawberryOptional,
+)
from tests.experimental.pydantic.utils import needs_pydantic_v1
-if IS_PYDANTIC_V2:
- pass
-else:
- pass
-
def test_can_use_type_standalone():
class User(BaseModel):
@@ -149,8 +147,7 @@ class UserModel(BaseModel):
@strawberry.experimental.pydantic.type(
UserModel, all_fields=True, use_pydantic_alias=True
)
- class User:
- ...
+ class User: ...
origin_user = UserModel(age=1, password="abc")
user = User.from_pydantic(origin_user)
@@ -168,8 +165,7 @@ class UserModel(BaseModel):
@strawberry.experimental.pydantic.type(
UserModel, all_fields=True, use_pydantic_alias=False
)
- class User:
- ...
+ class User: ...
origin_user = UserModel(age=1, password="abc")
user = User.from_pydantic(origin_user)
@@ -847,9 +843,15 @@ class UserType:
def test_get_default_factory_for_field():
+ class User(BaseModel):
+ pass
+
+ compat = PydanticCompat.from_model(User)
+ MISSING_TYPE = compat.PYDANTIC_MISSING_TYPE
+
def _get_field(
- default: Any = PYDANTIC_MISSING_TYPE,
- default_factory: Any = PYDANTIC_MISSING_TYPE,
+ default: Any = MISSING_TYPE,
+ default_factory: Any = MISSING_TYPE,
) -> CompatModelField:
return CompatModelField(
name="a",
@@ -862,11 +864,13 @@ def _get_field(
description="",
has_alias=False,
required=True,
+ _missing_type=MISSING_TYPE,
+ is_v1=not IS_PYDANTIC_V2,
)
field = _get_field()
- assert get_default_factory_for_field(field) is dataclasses.MISSING
+ assert get_default_factory_for_field(field, compat) is dataclasses.MISSING
def factory_func():
return "strawberry"
@@ -874,13 +878,13 @@ def factory_func():
field = _get_field(default_factory=factory_func)
# should return the default_factory unchanged
- assert get_default_factory_for_field(field) is factory_func
+ assert get_default_factory_for_field(field, compat) is factory_func
mutable_default = [123, "strawberry"]
field = _get_field(mutable_default)
- created_factory = get_default_factory_for_field(field)
+ created_factory = get_default_factory_for_field(field, compat)
# should return a factory that copies the default parameter
assert created_factory() == mutable_default
@@ -892,7 +896,7 @@ def factory_func():
BothDefaultAndDefaultFactoryDefinedError,
match=("Not allowed to specify both default and default_factory."),
):
- get_default_factory_for_field(field)
+ get_default_factory_for_field(field, compat)
def test_convert_input_types_to_pydantic_default_and_default_factory():
diff --git a/tests/experimental/pydantic/test_error_type.py b/tests/experimental/pydantic/test_error_type.py
index 969c61b87a..8e37c6402c 100644
--- a/tests/experimental/pydantic/test_error_type.py
+++ b/tests/experimental/pydantic/test_error_type.py
@@ -5,8 +5,11 @@
import strawberry
from strawberry.experimental.pydantic.exceptions import MissingFieldsListError
-from strawberry.type import StrawberryList, StrawberryOptional
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryObjectDefinition,
+ StrawberryOptional,
+)
def test_basic_error_type_fields():
diff --git a/tests/experimental/pydantic/test_fields.py b/tests/experimental/pydantic/test_fields.py
index 5fb15deea7..878969b9af 100644
--- a/tests/experimental/pydantic/test_fields.py
+++ b/tests/experimental/pydantic/test_fields.py
@@ -8,8 +8,7 @@
import strawberry
from strawberry.experimental.pydantic._compat import IS_PYDANTIC_V1
-from strawberry.type import StrawberryOptional
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import StrawberryObjectDefinition, StrawberryOptional
from tests.experimental.pydantic.utils import needs_pydantic_v1, needs_pydantic_v2
@@ -150,8 +149,7 @@ class User(BaseModel):
friends: conlist(str, min_items=1)
@strawberry.experimental.pydantic.type(model=User, all_fields=True)
- class UserType:
- ...
+ class UserType: ...
assert UserType.__strawberry_definition__.fields[0].name == "friends"
assert (
@@ -178,8 +176,7 @@ class User(BaseModel):
friends: conlist(conlist(int, min_items=1), min_items=1)
@strawberry.experimental.pydantic.type(model=User, all_fields=True)
- class UserType:
- ...
+ class UserType: ...
assert UserType.__strawberry_definition__.fields[0].name == "friends"
assert (
diff --git a/tests/fastapi/test_context.py b/tests/fastapi/test_context.py
index ffd7ad64f9..a5b6ea9ec8 100644
--- a/tests/fastapi/test_context.py
+++ b/tests/fastapi/test_context.py
@@ -23,7 +23,6 @@
GQL_START,
GQL_STOP,
)
-from strawberry.types import Info
def test_base_context():
@@ -43,7 +42,7 @@ def test_with_explicit_class_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, None]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.request is not None
assert info.context.strawberry == "explicitly rocks"
assert info.context.connection_params is None
@@ -79,7 +78,7 @@ def test_with_implicit_class_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, None]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.request is not None
assert info.context.strawberry == "implicitly rocks"
assert info.context.connection_params is None
@@ -113,7 +112,7 @@ def test_with_dict_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, None]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert "connection_params" not in info.context
assert info.context.get("strawberry") == "rocks"
@@ -145,7 +144,7 @@ def test_without_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, None]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
return "abc"
@@ -170,7 +169,7 @@ def test_with_invalid_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, None]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
return "abc"
@@ -210,7 +209,7 @@ class Query:
class Subscription:
@strawberry.subscription
async def connection_params(
- self, info: Info[Any, None], delay: float = 0
+ self, info: strawberry.Info, delay: float = 0
) -> AsyncGenerator[str, None]:
assert info.context.request is not None
await asyncio.sleep(delay)
@@ -276,7 +275,7 @@ class Query:
class Subscription:
@strawberry.subscription
async def connection_params(
- self, info: Info[Any, None], delay: float = 0
+ self, info: strawberry.Info, delay: float = 0
) -> AsyncGenerator[str, None]:
assert info.context.request is not None
await asyncio.sleep(delay)
diff --git a/tests/fastapi/test_openapi.py b/tests/fastapi/test_openapi.py
index ede69ace39..d30f88abe9 100644
--- a/tests/fastapi/test_openapi.py
+++ b/tests/fastapi/test_openapi.py
@@ -32,3 +32,16 @@ def test_disable_graphiql_view_and_allow_queries_via_get():
assert "get" not in app.openapi()["paths"]["/graphql"]
assert "post" in app.openapi()["paths"]["/graphql"]
+
+
+def test_graphql_router_with_tags():
+ from fastapi import FastAPI
+ from strawberry.fastapi import GraphQLRouter
+
+ app = FastAPI()
+ schema = strawberry.Schema(query=Query)
+ graphql_app = GraphQLRouter[None, None](schema, tags=["abc"])
+ app.include_router(graphql_app, prefix="/graphql")
+
+ assert "abc" in app.openapi()["paths"]["/graphql"]["get"]["tags"]
+ assert "abc" in app.openapi()["paths"]["/graphql"]["post"]["tags"]
diff --git a/tests/federation/printer/test_authenticated.py b/tests/federation/printer/test_authenticated.py
new file mode 100644
index 0000000000..db9ef10a22
--- /dev/null
+++ b/tests/federation/printer/test_authenticated.py
@@ -0,0 +1,122 @@
+import textwrap
+from enum import Enum
+from typing import List
+from typing_extensions import Annotated
+
+import strawberry
+
+
+def test_field_authenticated_printed_correctly():
+ @strawberry.federation.interface(authenticated=True)
+ class SomeInterface:
+ id: strawberry.ID
+
+ @strawberry.federation.type(authenticated=True)
+ class Product(SomeInterface):
+ upc: str = strawberry.federation.field(authenticated=True)
+
+ @strawberry.federation.type
+ class Query:
+ @strawberry.federation.field(authenticated=True)
+ def top_products(
+ self, first: Annotated[int, strawberry.federation.argument()]
+ ) -> List[Product]:
+ return []
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@authenticated"]) {
+ query: Query
+ }
+
+ type Product implements SomeInterface @authenticated {
+ id: ID!
+ upc: String! @authenticated
+ }
+
+ type Query {
+ _service: _Service!
+ topProducts(first: Int!): [Product!]! @authenticated
+ }
+
+ interface SomeInterface @authenticated {
+ id: ID!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_authenticated_printed_correctly_on_scalar():
+ @strawberry.federation.scalar(authenticated=True)
+ class SomeScalar(str):
+ __slots__ = ()
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeScalar
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@authenticated"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeScalar!
+ }
+
+ scalar SomeScalar @authenticated
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_authenticated_printed_correctly_on_enum():
+ @strawberry.federation.enum(authenticated=True)
+ class SomeEnum(Enum):
+ A = "A"
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeEnum
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@authenticated"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeEnum!
+ }
+
+ enum SomeEnum @authenticated {
+ A
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
diff --git a/tests/federation/printer/test_compose_directive.py b/tests/federation/printer/test_compose_directive.py
index a9ce64979f..ddbc0463c9 100644
--- a/tests/federation/printer/test_compose_directive.py
+++ b/tests/federation/printer/test_compose_directive.py
@@ -37,7 +37,7 @@ class Query:
directive @sensitive(reason: String!) on OBJECT
- schema @composeDirective(name: "@cacheControl") @link(url: "https://directives.strawberry.rocks/cacheControl/v0.1", import: ["@cacheControl"]) @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective", "@key", "@shareable"]) {
+ schema @composeDirective(name: "@cacheControl") @link(url: "https://directives.strawberry.rocks/cacheControl/v0.1", import: ["@cacheControl"]) @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@composeDirective", "@key", "@shareable"]) {
query: Query
}
@@ -102,7 +102,7 @@ class Query:
directive @sensitive(reason: String!) on OBJECT
- schema @composeDirective(name: "@cacheControl") @link(url: "https://f.strawberry.rocks/cacheControl/v1.0", import: ["@cacheControl"]) @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective", "@key", "@shareable"]) {
+ schema @composeDirective(name: "@cacheControl") @link(url: "https://f.strawberry.rocks/cacheControl/v1.0", import: ["@cacheControl"]) @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@composeDirective", "@key", "@shareable"]) {
query: Query
}
diff --git a/tests/federation/printer/test_entities.py b/tests/federation/printer/test_entities.py
index 9d7bf01c02..eb5866a849 100644
--- a/tests/federation/printer/test_entities.py
+++ b/tests/federation/printer/test_entities.py
@@ -33,7 +33,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external"]) {
query: Query
}
@@ -96,7 +96,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key"]) {
query: Query
}
diff --git a/tests/federation/printer/test_inaccessible.py b/tests/federation/printer/test_inaccessible.py
index 43c21d3223..e8801d3e64 100644
--- a/tests/federation/printer/test_inaccessible.py
+++ b/tests/federation/printer/test_inaccessible.py
@@ -44,7 +44,7 @@ def top_products(
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@inaccessible", "@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@inaccessible", "@key"]) {
query: Query
}
@@ -107,7 +107,7 @@ def hello(self) -> str:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
mutation: Mutation
}
@@ -144,7 +144,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
}
@@ -180,7 +180,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
}
@@ -218,7 +218,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
}
@@ -259,7 +259,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
}
diff --git a/tests/federation/printer/test_interface.py b/tests/federation/printer/test_interface.py
index 7215e45fc3..00d2464052 100644
--- a/tests/federation/printer/test_interface.py
+++ b/tests/federation/printer/test_interface.py
@@ -22,7 +22,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key"]) {
query: Query
}
diff --git a/tests/federation/printer/test_interface_object.py b/tests/federation/printer/test_interface_object.py
index a37f726421..07d7a7406b 100644
--- a/tests/federation/printer/test_interface_object.py
+++ b/tests/federation/printer/test_interface_object.py
@@ -13,7 +13,7 @@ class SomeInterface:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@interfaceObject", "@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@interfaceObject", "@key"]) {
query: Query
}
diff --git a/tests/federation/printer/test_keys.py b/tests/federation/printer/test_keys.py
index 81dd5de248..5cf411aa4c 100644
--- a/tests/federation/printer/test_keys.py
+++ b/tests/federation/printer/test_keys.py
@@ -96,7 +96,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key"]) {
query: Query
}
diff --git a/tests/federation/printer/test_link.py b/tests/federation/printer/test_link.py
index 2b89062614..fffca9423d 100644
--- a/tests/federation/printer/test_link.py
+++ b/tests/federation/printer/test_link.py
@@ -2,6 +2,7 @@
import strawberry
from strawberry.federation.schema_directives import Link
+from tests.conftest import skip_if_gql_32
def test_link_directive():
@@ -38,6 +39,7 @@ class Query:
assert schema.as_str() == textwrap.dedent(expected).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_link_directive_imports():
@strawberry.type
class Query:
@@ -47,7 +49,7 @@ class Query:
query=Query,
schema_directives=[
Link(
- url="https://specs.apollo.dev/federation/v2.3",
+ url="https://specs.apollo.dev/federation/v2.7",
import_=[
"@key",
"@requires",
@@ -64,20 +66,30 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@requires", "@provides", "@external", {name: "@tag", as: "@mytag"}, "@extends", "@shareable", "@inaccessible", "@override"]) {
- query: Query
- }
-
- type Query {
- _service: _Service!
- hello: String!
- }
-
- scalar _Any
-
- type _Service {
- sdl: String!
- }
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: [
+ "@key"
+ "@requires"
+ "@provides"
+ "@external"
+ { name: "@tag", as: "@mytag" }
+ "@extends"
+ "@shareable"
+ "@inaccessible"
+ "@override"
+ ]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: String!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
"""
assert schema.as_str() == textwrap.dedent(expected).strip()
@@ -95,7 +107,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key"]) {
query: Query
}
@@ -139,7 +151,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key"]) {
query: Query
}
@@ -184,7 +196,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@inaccessible"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@inaccessible"]) {
query: Query
}
@@ -224,7 +236,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key", "@tag"]) {
query: Query
}
@@ -303,7 +315,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key"]) {
query: Query
}
diff --git a/tests/federation/printer/test_one_of.py b/tests/federation/printer/test_one_of.py
new file mode 100644
index 0000000000..5de1938007
--- /dev/null
+++ b/tests/federation/printer/test_one_of.py
@@ -0,0 +1,45 @@
+import textwrap
+from typing import Optional
+
+import strawberry
+
+
+def test_prints_one_of_directive():
+ @strawberry.federation.input(one_of=True, tags=["myTag", "anotherTag"])
+ class Input:
+ a: Optional[str] = strawberry.UNSET
+ b: Optional[int] = strawberry.UNSET
+
+ @strawberry.federation.type
+ class Query:
+ hello: str
+
+ schema = strawberry.federation.Schema(
+ query=Query, types=[Input], enable_federation_2=True
+ )
+
+ expected = """
+ directive @oneOf on INPUT_OBJECT
+
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
+ query: Query
+ }
+
+ input Input @tag(name: "myTag") @tag(name: "anotherTag") @oneOf {
+ a: String
+ b: Int
+ }
+
+ type Query {
+ _service: _Service!
+ hello: String!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
diff --git a/tests/federation/printer/test_override.py b/tests/federation/printer/test_override.py
index eb01edcbbb..bec1f6f95a 100644
--- a/tests/federation/printer/test_override.py
+++ b/tests/federation/printer/test_override.py
@@ -4,6 +4,7 @@
from typing import List
import strawberry
+from strawberry.federation.schema_directives import Override
def test_field_override_printed_correctly():
@@ -24,7 +25,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key", "@override"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@override"]) {
query: Query
}
@@ -53,3 +54,55 @@ def top_products(self, first: int) -> List[Product]:
"""
assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_override_label_printed_correctly():
+ @strawberry.interface
+ class SomeInterface:
+ id: strawberry.ID
+
+ @strawberry.federation.type(keys=["upc"], extend=True)
+ class Product(SomeInterface):
+ upc: str = strawberry.federation.field(
+ external=True,
+ override=Override(override_from="mySubGraph", label="percent(1)"),
+ )
+
+ @strawberry.federation.type
+ class Query:
+ @strawberry.field
+ def top_products(self, first: int) -> List[Product]:
+ return []
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@override"]) {
+ query: Query
+ }
+
+ extend type Product implements SomeInterface @key(fields: "upc") {
+ id: ID!
+ upc: String! @external @override(from: "mySubGraph", label: "percent(1)")
+ }
+
+ type Query {
+ _entities(representations: [_Any!]!): [_Entity]!
+ _service: _Service!
+ topProducts(first: Int!): [Product!]!
+ }
+
+ interface SomeInterface {
+ id: ID!
+ }
+
+ scalar _Any
+
+ union _Entity = Product
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
diff --git a/tests/federation/printer/test_policy.py b/tests/federation/printer/test_policy.py
new file mode 100644
index 0000000000..05dae2eb11
--- /dev/null
+++ b/tests/federation/printer/test_policy.py
@@ -0,0 +1,132 @@
+import textwrap
+from enum import Enum
+from typing import List
+from typing_extensions import Annotated
+
+import strawberry
+
+
+def test_field_policy_printed_correctly():
+ @strawberry.federation.interface(
+ policy=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeInterface:
+ id: strawberry.ID
+
+ @strawberry.federation.type(
+ policy=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class Product(SomeInterface):
+ upc: str = strawberry.federation.field(policy=[["productowner"]])
+
+ @strawberry.federation.type
+ class Query:
+ @strawberry.federation.field(
+ policy=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ def top_products(
+ self, first: Annotated[int, strawberry.federation.argument()]
+ ) -> List[Product]:
+ return []
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@policy"]) {
+ query: Query
+ }
+
+ type Product implements SomeInterface @policy(policies: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ id: ID!
+ upc: String! @policy(policies: [["productowner"]])
+ }
+
+ type Query {
+ _service: _Service!
+ topProducts(first: Int!): [Product!]! @policy(policies: [["client", "poweruser"], ["admin"], ["productowner"]])
+ }
+
+ interface SomeInterface @policy(policies: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ id: ID!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_policy_printed_correctly_on_scalar():
+ @strawberry.federation.scalar(
+ policy=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeScalar(str):
+ __slots__ = ()
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeScalar
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@policy"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeScalar!
+ }
+
+ scalar SomeScalar @policy(policies: [["client", "poweruser"], ["admin"], ["productowner"]])
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_policy_printed_correctly_on_enum():
+ @strawberry.federation.enum(
+ policy=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeEnum(Enum):
+ A = "A"
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeEnum
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@policy"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeEnum!
+ }
+
+ enum SomeEnum @policy(policies: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ A
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
diff --git a/tests/federation/printer/test_provides.py b/tests/federation/printer/test_provides.py
index b067669ab2..a13130bc82 100644
--- a/tests/federation/printer/test_provides.py
+++ b/tests/federation/printer/test_provides.py
@@ -39,7 +39,7 @@ def top_products(self, first: int) -> List[Product]:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key", "@provides"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@provides"]) {
query: Query
}
@@ -111,7 +111,7 @@ def top_products(self, first: int) -> List[Product]:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key", "@provides"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@provides"]) {
query: Query
}
diff --git a/tests/federation/printer/test_requires.py b/tests/federation/printer/test_requires.py
index 0e6edb93db..fef6011949 100644
--- a/tests/federation/printer/test_requires.py
+++ b/tests/federation/printer/test_requires.py
@@ -39,7 +39,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key", "@requires"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@requires"]) {
query: Query
}
diff --git a/tests/federation/printer/test_requires_scopes.py b/tests/federation/printer/test_requires_scopes.py
new file mode 100644
index 0000000000..2c42eec0bf
--- /dev/null
+++ b/tests/federation/printer/test_requires_scopes.py
@@ -0,0 +1,132 @@
+import textwrap
+from enum import Enum
+from typing import List
+from typing_extensions import Annotated
+
+import strawberry
+
+
+def test_field_requires_scopes_printed_correctly():
+ @strawberry.federation.interface(
+ requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeInterface:
+ id: strawberry.ID
+
+ @strawberry.federation.type(
+ requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class Product(SomeInterface):
+ upc: str = strawberry.federation.field(requires_scopes=[["productowner"]])
+
+ @strawberry.federation.type
+ class Query:
+ @strawberry.federation.field(
+ requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ def top_products(
+ self, first: Annotated[int, strawberry.federation.argument()]
+ ) -> List[Product]:
+ return []
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@requiresScopes"]) {
+ query: Query
+ }
+
+ type Product implements SomeInterface @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ id: ID!
+ upc: String! @requiresScopes(scopes: [["productowner"]])
+ }
+
+ type Query {
+ _service: _Service!
+ topProducts(first: Int!): [Product!]! @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]])
+ }
+
+ interface SomeInterface @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ id: ID!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_requires_scopes_printed_correctly_on_scalar():
+ @strawberry.federation.scalar(
+ requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeScalar(str):
+ __slots__ = ()
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeScalar
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@requiresScopes"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeScalar!
+ }
+
+ scalar SomeScalar @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]])
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
+
+
+def test_field_requires_scopes_printed_correctly_on_enum():
+ @strawberry.federation.enum(
+ requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]]
+ )
+ class SomeEnum(Enum):
+ A = "A"
+
+ @strawberry.federation.type
+ class Query:
+ hello: SomeEnum
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ expected = """
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@requiresScopes"]) {
+ query: Query
+ }
+
+ type Query {
+ _service: _Service!
+ hello: SomeEnum!
+ }
+
+ enum SomeEnum @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]]) {
+ A
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """
+
+ assert schema.as_str() == textwrap.dedent(expected).strip()
diff --git a/tests/federation/printer/test_shareable.py b/tests/federation/printer/test_shareable.py
index ff636dc3dc..6212b8425b 100644
--- a/tests/federation/printer/test_shareable.py
+++ b/tests/federation/printer/test_shareable.py
@@ -24,7 +24,7 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@key", "@shareable"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@key", "@shareable"]) {
query: Query
}
diff --git a/tests/federation/printer/test_tag.py b/tests/federation/printer/test_tag.py
index 89446347f5..388c0adf93 100644
--- a/tests/federation/printer/test_tag.py
+++ b/tests/federation/printer/test_tag.py
@@ -28,7 +28,7 @@ def top_products(
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@external", "@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@external", "@tag"]) {
query: Query
}
@@ -68,7 +68,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
query: Query
}
@@ -101,7 +101,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
query: Query
}
@@ -136,7 +136,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
query: Query
}
@@ -177,7 +177,7 @@ class Query:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
query: Query
}
@@ -220,7 +220,7 @@ class Query:
)
expected = """
- schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@tag"]) {
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@tag"]) {
query: Query
}
diff --git a/tests/federation/test_entities.py b/tests/federation/test_entities.py
index 89df89fe5d..4793c0aeb0 100644
--- a/tests/federation/test_entities.py
+++ b/tests/federation/test_entities.py
@@ -1,6 +1,9 @@
import typing
+from graphql import located_error
+
import strawberry
+from strawberry.types import Info
def test_fetch_entities():
@@ -46,11 +49,11 @@ def test_info_param_in_resolve_reference():
@strawberry.federation.type(keys=["upc"])
class Product:
upc: str
- info: str
+ debug_field_name: str
@classmethod
- def resolve_reference(cls, info, upc) -> "Product":
- return Product(upc=upc, info=info)
+ def resolve_reference(cls, info: strawberry.Info, upc: str) -> "Product":
+ return Product(upc=upc, debug_field_name=info.field_name)
@strawberry.federation.type(extend=True)
class Query:
@@ -65,7 +68,7 @@ def top_products(self, first: int) -> typing.List[Product]:
_entities(representations: $representations) {
... on Product {
upc
- info
+ debugFieldName
}
}
}
@@ -80,10 +83,15 @@ def top_products(self, first: int) -> typing.List[Product]:
assert not result.errors
- assert (
- "GraphQLResolveInfo(field_name='_entities', field_nodes=[FieldNode"
- in result.data["_entities"][0]["info"]
- )
+ assert result.data == {
+ "_entities": [
+ {
+ "upc": "B00005N5PF",
+ # _entities is the field that's called by federation
+ "debugFieldName": "_entities",
+ }
+ ]
+ }
def test_does_not_need_custom_resolve_reference_for_basic_things():
@@ -168,6 +176,49 @@ def top_products(self, first: int) -> typing.List[Product]:
}
+def test_fails_properly_when_wrong_key_is_passed():
+ @strawberry.type
+ class Something:
+ id: str
+
+ @strawberry.federation.type(keys=["upc"])
+ class Product:
+ upc: str
+ something: Something
+
+ @strawberry.federation.type(extend=True)
+ class Query:
+ @strawberry.field
+ def top_products(self, first: int) -> typing.List[Product]:
+ return []
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ query = """
+ query ($representations: [_Any!]!) {
+ _entities(representations: $representations) {
+ ... on Product {
+ upc
+ something {
+ id
+ }
+ }
+ }
+ }
+ """
+
+ result = schema.execute_sync(
+ query,
+ variable_values={
+ "representations": [{"__typename": "Product", "not_upc": "B00005N5PF"}]
+ },
+ )
+
+ assert result.errors
+
+ assert result.errors[0].message == "Unable to resolve reference for Product"
+
+
def test_fails_properly_when_wrong_data_is_passed():
@strawberry.federation.type(keys=["id"])
class Something:
@@ -214,7 +265,155 @@ def top_products(self, first: int) -> typing.List[Product]:
assert result.errors
- assert result.errors[0].message.startswith("Unable to resolve reference for")
+ assert result.errors[0].message == "Unable to resolve reference for Product"
+
+
+def test_propagates_original_error_message_with_auto_graphql_error_metadata():
+ @strawberry.federation.type(keys=["id"])
+ class Product:
+ id: strawberry.ID
+
+ @classmethod
+ def resolve_reference(cls, id: strawberry.ID) -> "Product":
+ raise Exception("Foo bar")
+
+ @strawberry.federation.type(extend=True)
+ class Query:
+ @strawberry.field
+ def mock(self) -> typing.Optional[Product]:
+ return None
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ query = """
+ query ($representations: [_Any!]!) {
+ _entities(representations: $representations) {
+ ... on Product {
+ id
+ }
+ }
+ }
+ """
+
+ result = schema.execute_sync(
+ query,
+ variable_values={
+ "representations": [
+ {
+ "__typename": "Product",
+ "id": "B00005N5PF",
+ }
+ ]
+ },
+ )
+
+ assert len(result.errors) == 1
+ error = result.errors[0].formatted
+ assert error["message"] == "Foo bar"
+ assert error["path"] == ["_entities", 0]
+ assert error["locations"] == [{"column": 13, "line": 3}]
+ assert "extensions" not in error
+
+
+def test_propagates_custom_type_error_message_with_auto_graphql_error_metadata():
+ class MyTypeError(TypeError):
+ pass
+
+ @strawberry.federation.type(keys=["id"])
+ class Product:
+ id: strawberry.ID
+
+ @classmethod
+ def resolve_reference(cls, id: strawberry.ID) -> "Product":
+ raise MyTypeError("Foo bar")
+
+ @strawberry.federation.type(extend=True)
+ class Query:
+ @strawberry.field
+ def mock(self) -> typing.Optional[Product]:
+ return None
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ query = """
+ query ($representations: [_Any!]!) {
+ _entities(representations: $representations) {
+ ... on Product {
+ id
+ }
+ }
+ }
+ """
+
+ result = schema.execute_sync(
+ query,
+ variable_values={
+ "representations": [
+ {
+ "__typename": "Product",
+ "id": "B00005N5PF",
+ }
+ ]
+ },
+ )
+
+ assert len(result.errors) == 1
+ error = result.errors[0].formatted
+ assert error["message"] == "Foo bar"
+ assert error["path"] == ["_entities", 0]
+ assert error["locations"] == [{"column": 13, "line": 3}]
+ assert "extensions" not in error
+
+
+def test_propagates_original_error_message_and_graphql_error_metadata():
+ @strawberry.federation.type(keys=["id"])
+ class Product:
+ id: strawberry.ID
+
+ @classmethod
+ def resolve_reference(cls, info: Info, id: strawberry.ID) -> "Product":
+ exception = Exception("Foo bar")
+ exception.extensions = {"baz": "qux"}
+ raise located_error(
+ exception, nodes=info.field_nodes[0], path=["_entities_override", 0]
+ )
+
+ @strawberry.federation.type(extend=True)
+ class Query:
+ @strawberry.field
+ def mock(self) -> typing.Optional[Product]:
+ return None
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+
+ query = """
+ query ($representations: [_Any!]!) {
+ _entities(representations: $representations) {
+ ... on Product {
+ id
+ }
+ }
+ }
+ """
+
+ result = schema.execute_sync(
+ query,
+ variable_values={
+ "representations": [
+ {
+ "__typename": "Product",
+ "id": "B00005N5PF",
+ }
+ ]
+ },
+ )
+
+ assert len(result.errors) == 1
+ error = result.errors[0].formatted
+ assert error["message"] == "Foo bar"
+ assert error["path"] == ["_entities_override", 0]
+ assert error["locations"] == [{"column": 13, "line": 3}]
+ assert error["extensions"] == {"baz": "qux"}
async def test_can_use_async_resolve_reference():
diff --git a/tests/federation/test_schema.py b/tests/federation/test_schema.py
index 2b96a27f60..ade9a9e235 100644
--- a/tests/federation/test_schema.py
+++ b/tests/federation/test_schema.py
@@ -23,6 +23,28 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+ expected_sdl = textwrap.dedent("""
+ type Product {
+ upc: String!
+ name: String
+ price: Int
+ weight: Int
+ }
+
+ extend type Query {
+ _service: _Service!
+ topProducts(first: Int!): [Product!]!
+ }
+
+ scalar _Any
+
+ type _Service {
+ sdl: String!
+ }
+ """).strip()
+
+ assert str(schema) == expected_sdl
+
query = """
query {
__type(name: "_Entity") {
@@ -57,6 +79,35 @@ def top_products(self, first: int) -> List[Product]:
schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+ expected_sdl = textwrap.dedent("""
+ schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key"]) {
+ query: Query
+ }
+
+ type Product @key(fields: "upc") {
+ upc: String!
+ name: String
+ price: Int
+ weight: Int
+ }
+
+ extend type Query {
+ _entities(representations: [_Any!]!): [_Entity]!
+ _service: _Service!
+ topProducts(first: Int!): [Product!]!
+ }
+
+ scalar _Any
+
+ union _Entity = Product
+
+ type _Service {
+ sdl: String!
+ }
+ """).strip()
+
+ assert str(schema) == expected_sdl
+
query = """
query {
__type(name: "_Entity") {
diff --git a/tests/fields/test_arguments.py b/tests/fields/test_arguments.py
index c9ae8508b9..90cc15d2ed 100644
--- a/tests/fields/test_arguments.py
+++ b/tests/fields/test_arguments.py
@@ -11,7 +11,7 @@
InvalidArgumentTypeError,
MultipleStrawberryArgumentsError,
)
-from strawberry.type import StrawberryList, StrawberryOptional
+from strawberry.types.base import StrawberryList, StrawberryOptional
def test_basic_arguments():
@@ -482,14 +482,14 @@ class Mutation:
def test_unset_deprecation_warning():
with pytest.deprecated_call():
- from strawberry.arguments import UNSET # noqa: F401
+ from strawberry.types.arguments import UNSET # noqa: F401
with pytest.deprecated_call():
- from strawberry.arguments import is_unset # noqa: F401
+ from strawberry.types.arguments import is_unset # noqa: F401
def test_deprecated_unset():
with pytest.deprecated_call():
- from strawberry.unset import is_unset
+ from strawberry.types.unset import is_unset
with warnings.catch_warnings(record=False):
assert is_unset(UNSET)
diff --git a/tests/fields/test_field_defaults.py b/tests/fields/test_field_defaults.py
index a84928fc19..2ca92069c5 100644
--- a/tests/fields/test_field_defaults.py
+++ b/tests/fields/test_field_defaults.py
@@ -8,7 +8,7 @@
FieldWithResolverAndDefaultValueError,
InvalidDefaultFactoryError,
)
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
def test_field_with_default():
diff --git a/tests/fields/test_field_exceptions.py b/tests/fields/test_field_exceptions.py
index fc0fc4860b..7eeeb3f60b 100644
--- a/tests/fields/test_field_exceptions.py
+++ b/tests/fields/test_field_exceptions.py
@@ -10,7 +10,7 @@
FieldWithResolverAndDefaultValueError,
)
from strawberry.extensions.field_extension import FieldExtension
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
def test_field_with_resolver_default():
@@ -57,8 +57,7 @@ def resolve(self, next_, source, info, **kwargs: Any):
@strawberry.type
class Query:
@strawberry.field(extensions=[ChangeReturnTypeExtension()])
- def test_changing_return_type(self) -> bool:
- ...
+ def test_changing_return_type(self) -> bool: ...
schema = strawberry.Schema(query=Query)
expected = """\
diff --git a/tests/fields/test_permissions.py b/tests/fields/test_permissions.py
index 14741b06e4..581576018b 100644
--- a/tests/fields/test_permissions.py
+++ b/tests/fields/test_permissions.py
@@ -2,14 +2,15 @@
import strawberry
from strawberry.permission import BasePermission
-from strawberry.types import Info
def test_permission_classes_basic_fields():
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
- def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
+ def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: Any
+ ) -> bool:
return False
@strawberry.type
@@ -30,7 +31,9 @@ def test_permission_classes():
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
- def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
+ def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: Any
+ ) -> bool:
return False
@strawberry.type
diff --git a/tests/fields/test_resolvers.py b/tests/fields/test_resolvers.py
index f6bfde9184..3f6be393a6 100644
--- a/tests/fields/test_resolvers.py
+++ b/tests/fields/test_resolvers.py
@@ -1,6 +1,7 @@
import dataclasses
+import textwrap
import types
-from typing import ClassVar, List, no_type_check
+from typing import Any, ClassVar, List, no_type_check
import pytest
@@ -17,7 +18,6 @@
StrawberryResolver,
UncallableResolverError,
)
-from strawberry.types.info import Info
def test_resolver_as_argument():
@@ -222,7 +222,8 @@ class Query:
def test_raises_error_when_missing_type():
"""Test to make sure that if somehow a non-StrawberryField field is added to the cls
without annotations it raises an exception. This would occur if someone manually
- uses dataclasses.field"""
+ uses dataclasses.field
+ """
@strawberry.type
class Query:
@@ -279,8 +280,7 @@ class Query:
def test_raises_error_calling_uncallable_resolver():
@classmethod # type: ignore
- def class_func(cls) -> int:
- ...
+ def class_func(cls) -> int: ...
# Note that class_func is a raw classmethod object because it has not been bound
# to a class at this point
@@ -362,7 +362,7 @@ def root_and_info(
foo: str,
bar: float,
info: str,
- strawberry_info: Info,
+ strawberry_info: strawberry.Info,
) -> str:
raise AssertionError("Unreachable code.")
@@ -372,7 +372,7 @@ def self_and_info(
foo: str,
bar: float,
info: str,
- strawberry_info: Info,
+ strawberry_info: strawberry.Info,
) -> str:
raise AssertionError("Unreachable code.")
@@ -382,7 +382,7 @@ def parent_and_info(
foo: str,
bar: float,
info: str,
- strawberry_info: Info,
+ strawberry_info: strawberry.Info,
) -> str:
raise AssertionError("Unreachable code.")
@@ -455,3 +455,83 @@ def bar(x: str = "bar"):
assert len(parameters_map) == 2
assert len(parameters_set) == 2
+
+
+def test_annotation_using_parent_annotation():
+ @strawberry.type
+ class FruitType:
+ name: str
+
+ @strawberry.field
+ @staticmethod
+ def name_from_parent(parent: strawberry.Parent[Any]) -> str:
+ return f"Using 'parent': {parent.name}"
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ @staticmethod
+ def fruit() -> FruitType:
+ return FruitType(name="Strawberry")
+
+ schema = strawberry.Schema(query=Query)
+ expected = """\
+ type FruitType {
+ name: String!
+ nameFromParent: String!
+ }
+
+ type Query {
+ fruit: FruitType!
+ }
+ """
+
+ assert textwrap.dedent(str(schema)) == textwrap.dedent(expected).strip()
+
+ result = schema.execute_sync("query { fruit { name nameFromParent } }")
+ assert result.data == {
+ "fruit": {
+ "name": "Strawberry",
+ "nameFromParent": "Using 'parent': Strawberry",
+ }
+ }
+
+
+def test_annotation_using_parent_annotation_but_named_root():
+ @strawberry.type
+ class FruitType:
+ name: str
+
+ @strawberry.field
+ @staticmethod
+ def name_from_parent(root: strawberry.Parent[Any]) -> str:
+ return f"Using 'root': {root.name}"
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ @staticmethod
+ def fruit() -> FruitType:
+ return FruitType(name="Strawberry")
+
+ schema = strawberry.Schema(query=Query)
+ expected = """\
+ type FruitType {
+ name: String!
+ nameFromParent: String!
+ }
+
+ type Query {
+ fruit: FruitType!
+ }
+ """
+
+ assert textwrap.dedent(str(schema)) == textwrap.dedent(expected).strip()
+
+ result = schema.execute_sync("query { fruit { name nameFromParent } }")
+ assert result.data == {
+ "fruit": {
+ "name": "Strawberry",
+ "nameFromParent": "Using 'root': Strawberry",
+ }
+ }
diff --git a/tests/http/clients/base.py b/tests/http/clients/base.py
index a20d9d0c92..0ea062c34c 100644
--- a/tests/http/clients/base.py
+++ b/tests/http/clients/base.py
@@ -103,8 +103,7 @@ def __init__(
graphql_ide: Optional[GraphQL_IDE] = "graphiql",
allow_queries_via_get: bool = True,
result_override: ResultOverrideFunction = None,
- ):
- ...
+ ): ...
@abc.abstractmethod
async def _graphql_request(
@@ -115,8 +114,7 @@ async def _graphql_request(
files: Optional[Dict[str, BytesIO]] = None,
headers: Optional[Dict[str, str]] = None,
**kwargs: Any,
- ) -> Response:
- ...
+ ) -> Response: ...
@abc.abstractmethod
async def request(
@@ -124,16 +122,14 @@ async def request(
url: str,
method: Literal["get", "post", "patch", "put", "delete"],
headers: Optional[Dict[str, str]] = None,
- ) -> Response:
- ...
+ ) -> Response: ...
@abc.abstractmethod
async def get(
self,
url: str,
headers: Optional[Dict[str, str]] = None,
- ) -> Response:
- ...
+ ) -> Response: ...
@abc.abstractmethod
async def post(
@@ -142,8 +138,7 @@ async def post(
data: Optional[bytes] = None,
json: Optional[JSON] = None,
headers: Optional[Dict[str, str]] = None,
- ) -> Response:
- ...
+ ) -> Response: ...
async def query(
self,
@@ -238,9 +233,7 @@ def _build_multipart_file_map(
return files_map
def create_app(self, **kwargs: Any) -> None:
- """
- For use by websocket tests
- """
+ """For use by websocket tests."""
raise NotImplementedError
async def ws_connect(
@@ -267,37 +260,29 @@ def name(self) -> str:
return ""
@abc.abstractmethod
- async def send_json(self, payload: Dict[str, Any]) -> None:
- ...
+ async def send_json(self, payload: Dict[str, Any]) -> None: ...
@abc.abstractmethod
- async def send_bytes(self, payload: bytes) -> None:
- ...
+ async def send_bytes(self, payload: bytes) -> None: ...
@abc.abstractmethod
- async def receive(self, timeout: Optional[float] = None) -> Message:
- ...
+ async def receive(self, timeout: Optional[float] = None) -> Message: ...
- async def receive_json(self, timeout: Optional[float] = None) -> Any:
- ...
+ async def receive_json(self, timeout: Optional[float] = None) -> Any: ...
@abc.abstractmethod
- async def close(self) -> None:
- ...
+ async def close(self) -> None: ...
@property
@abc.abstractmethod
- def closed(self) -> bool:
- ...
+ def closed(self) -> bool: ...
@property
@abc.abstractmethod
- def close_code(self) -> int:
- ...
+ def close_code(self) -> int: ...
@abc.abstractmethod
- def assert_reason(self, reason: str) -> None:
- ...
+ def assert_reason(self, reason: str) -> None: ...
async def __aiter__(self) -> AsyncGenerator[Message, None]:
while not self.closed:
@@ -306,10 +291,9 @@ async def __aiter__(self) -> AsyncGenerator[Message, None]:
class DebuggableGraphQLTransportWSMixin:
@staticmethod
- def on_init(self):
- """
- This method can be patched by unittests to get the instance of the
- transport handler when it is initialized
+ def on_init(self) -> None:
+ """This method can be patched by unittests to get the instance of the
+ transport handler when it is initialized.
"""
def __init__(self, *args: Any, **kwargs: Any):
diff --git a/tests/http/clients/channels.py b/tests/http/clients/channels.py
index ee31c8e88b..da981403bb 100644
--- a/tests/http/clients/channels.py
+++ b/tests/http/clients/channels.py
@@ -131,9 +131,7 @@ def process_result(
class ChannelsHttpClient(HttpClient):
- """
- A client to test websockets over channels
- """
+ """A client to test websockets over channels."""
def __init__(
self,
diff --git a/tests/http/test_graphql_ide.py b/tests/http/test_graphql_ide.py
index 30dfde7c83..d9a85ad736 100644
--- a/tests/http/test_graphql_ide.py
+++ b/tests/http/test_graphql_ide.py
@@ -1,4 +1,4 @@
-from typing import Type
+from typing import Type, Union
from typing_extensions import Literal
import pytest
@@ -11,12 +11,9 @@
async def test_renders_graphql_ide(
header_value: str,
http_client_class: Type[HttpClient],
- graphql_ide: Literal["graphiql", "apollo-sandbox", "pathfinder", None],
+ graphql_ide: Literal["graphiql", "apollo-sandbox", "pathfinder"],
):
- if graphql_ide is None:
- http_client = http_client_class()
- else:
- http_client = http_client_class(graphql_ide=graphql_ide)
+ http_client = http_client_class(graphql_ide=graphql_ide)
response = await http_client.get("/graphql", headers={"Accept": header_value})
content_type = response.headers.get(
@@ -33,7 +30,7 @@ async def test_renders_graphql_ide(
if graphql_ide == "pathfinder":
assert "@pathfinder-ide/react" in response.text
- if graphql_ide == "graphiql" or graphql_ide is None:
+ if graphql_ide == "graphiql":
assert "unpkg.com/graphiql" in response.text
@@ -70,8 +67,12 @@ async def test_does_not_render_graphiql_if_wrong_accept(
assert response.status_code == 400
-async def test_renders_graphiql_disabled(http_client_class: Type[HttpClient]):
- http_client = http_client_class(graphql_ide=None)
+@pytest.mark.parametrize("graphql_ide", [False, None])
+async def test_renders_graphiql_disabled(
+ http_client_class: Type[HttpClient],
+ graphql_ide: Union[bool, None],
+):
+ http_client = http_client_class(graphql_ide=graphql_ide)
response = await http_client.get("/graphql", headers={"Accept": "text/html"})
assert response.status_code == 404
diff --git a/tests/http/test_query.py b/tests/http/test_query.py
index d7910fd220..85a9f46889 100644
--- a/tests/http/test_query.py
+++ b/tests/http/test_query.py
@@ -168,6 +168,15 @@ async def test_passing_invalid_json_get(http_client: HttpClient):
assert "Unable to parse request body as JSON" in response.text
+async def test_query_parameters_are_never_interpreted_as_list(http_client: HttpClient):
+ response = await http_client.get(
+ url='/graphql?query=query($name: String!) { hello(name: $name) }&variables={"name": "Jake"}&variables={"name": "Jake"}',
+ )
+
+ assert response.status_code == 200
+ assert response.json["data"] == {"hello": "Hello Jake"}
+
+
async def test_missing_query(http_client: HttpClient):
response = await http_client.post(
url="/graphql",
diff --git a/tests/http/test_query_via_get.py b/tests/http/test_query_via_get.py
index f86812c3a6..5e7557a197 100644
--- a/tests/http/test_query_via_get.py
+++ b/tests/http/test_query_via_get.py
@@ -1,6 +1,22 @@
from .clients.base import HttpClient
+async def test_sending_get_with_content_type_passes(http_client_class):
+ http_client = http_client_class()
+
+ response = await http_client.query(
+ method="get",
+ query="query {hello}",
+ headers={
+ "Content-Type": "application/json",
+ },
+ )
+ data = response.json["data"]
+
+ assert response.status_code == 200
+ assert data["hello"] == "Hello world"
+
+
async def test_sending_empty_query(http_client_class):
http_client = http_client_class()
diff --git a/tests/litestar/schema.py b/tests/litestar/schema.py
index 4d3faec477..5f00b1a2ee 100644
--- a/tests/litestar/schema.py
+++ b/tests/litestar/schema.py
@@ -9,13 +9,14 @@
from strawberry.file_uploads import Upload
from strawberry.permission import BasePermission
from strawberry.subscriptions.protocols.graphql_transport_ws.types import PingMessage
-from strawberry.types import Info
class AlwaysFailPermission(BasePermission):
message = "You are not authorized"
- def has_permission(self, source: Any, info: Info, **kwargs: typing.Any) -> bool:
+ def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: typing.Any
+ ) -> bool:
return False
diff --git a/tests/litestar/test_context.py b/tests/litestar/test_context.py
index 8ee8eac9d8..ce009ec346 100644
--- a/tests/litestar/test_context.py
+++ b/tests/litestar/test_context.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict
+from typing import Dict
import strawberry
@@ -15,12 +15,11 @@ def test_with_class_context_getter():
from litestar.di import Provide
from litestar.testing import TestClient
from strawberry.litestar import BaseContext, make_graphql_controller
- from strawberry.types import Info
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert isinstance(info.context, CustomContext)
assert info.context.request is not None
assert info.context.strawberry == "rocks"
@@ -60,12 +59,11 @@ def test_with_dict_context_getter():
from litestar.di import Provide
from litestar.testing import TestClient
from strawberry.litestar import make_graphql_controller
- from strawberry.types import Info
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert isinstance(info.context, dict)
assert info.context.get("request") is not None
assert info.context.get("strawberry") == "rocks"
@@ -100,12 +98,11 @@ def test_without_context_getter():
from litestar import Litestar
from litestar.testing import TestClient
from strawberry.litestar import make_graphql_controller
- from strawberry.types import Info
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert isinstance(info.context, dict)
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
@@ -128,12 +125,11 @@ def test_with_invalid_context_getter():
from litestar.di import Provide
from litestar.testing import TestClient
from strawberry.litestar import make_graphql_controller
- from strawberry.types import Info
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
return "abc"
@@ -171,13 +167,12 @@ async def get_context(custom_context_dependency: str) -> str:
def test_custom_context():
from litestar.testing import TestClient
- from strawberry.types import Info
from tests.litestar.app import create_app
@strawberry.type
class Query:
@strawberry.field
- def custom_context_value(self, info: Info[Any, Any]) -> str:
+ def custom_context_value(self, info: strawberry.Info) -> str:
return info.context["custom_value"]
schema = strawberry.Schema(query=Query)
@@ -192,7 +187,6 @@ def custom_context_value(self, info: Info[Any, Any]) -> str:
def test_can_set_background_task():
from litestar.testing import TestClient
- from strawberry.types import Info
from tests.litestar.app import create_app
task_complete = False
@@ -204,7 +198,7 @@ async def task():
@strawberry.type
class Query:
@strawberry.field
- def something(self, info: Info[Any, Any]) -> str:
+ def something(self, info: strawberry.Info) -> str:
response = info.context["response"]
response.background.tasks.append(task)
return "foo"
diff --git a/tests/litestar/test_response_headers.py b/tests/litestar/test_response_headers.py
index e9b541eb41..a606e9ad03 100644
--- a/tests/litestar/test_response_headers.py
+++ b/tests/litestar/test_response_headers.py
@@ -1,5 +1,4 @@
import strawberry
-from strawberry.types import Info
# TODO: move this to common tests
@@ -11,7 +10,7 @@ def test_set_response_headers():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].headers["X-Strawberry"] = "rocks"
return "abc"
@@ -37,7 +36,7 @@ def test_set_cookie_headers():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].set_cookie(
key="strawberry",
diff --git a/tests/litestar/test_response_status.py b/tests/litestar/test_response_status.py
index e0940e3161..879ac9a30a 100644
--- a/tests/litestar/test_response_status.py
+++ b/tests/litestar/test_response_status.py
@@ -1,5 +1,4 @@
import strawberry
-from strawberry.types import Info
# TODO: move this to common tests
@@ -11,7 +10,7 @@ def test_set_custom_http_response_status():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].status_code = 418
return "abc"
diff --git a/tests/mypy/enums.py b/tests/mypy/enums.py
deleted file mode 100644
index 5a3f2c8a32..0000000000
--- a/tests/mypy/enums.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from enum import Enum
-
-
-class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
diff --git a/tests/mypy/federation/test_decorators.yml b/tests/mypy/federation/test_decorators.yml
deleted file mode 100644
index ec4218b893..0000000000
--- a/tests/mypy/federation/test_decorators.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-- case: test_type
- main: |
- import strawberry
-
- @strawberry.federation.type
- class User:
- name: str
-
- User(name="Patrick")
- User(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "User" [call-arg]
-
-- case: test_input
- main: |
- import strawberry
-
- @strawberry.federation.input
- class EditUserInput:
- name: str
-
- EditUserInput(name="Patrick")
- EditUserInput(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "EditUserInput" [call-arg]
-
-- case: test_interface
- main: |
- import strawberry
-
- @strawberry.federation.interface
- class NameInterface:
- name: str
-
- NameInterface(name="Patrick")
- NameInterface(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "NameInterface" [call-arg]
diff --git a/tests/mypy/federation/test_fields.yml b/tests/mypy/federation/test_fields.yml
deleted file mode 100644
index 9b83024dd4..0000000000
--- a/tests/mypy/federation/test_fields.yml
+++ /dev/null
@@ -1,115 +0,0 @@
-- case: test_field
- main: |
- import strawberry
-
- @strawberry.federation.type
- class User:
- name: str = strawberry.federation.field(description="Example")
-
- User(name="Patrick")
- User(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "User" [call-arg]
-
-- case: test_all_field_usage
- main: |
- import strawberry
- from strawberry.types import Info
-
- def some_resolver() -> str:
- return ""
-
- def some_resolver_2(root: "Example") -> str:
- return ""
-
- @strawberry.type
- class Example:
- a: str
- b: str = strawberry.federation.field(name="b")
- c: str = strawberry.federation.field(name="c", resolver=some_resolver)
- d: str = strawberry.federation.field(resolver=some_resolver_2)
-
- @strawberry.federation.field(description="ABC")
- def e(self, info: Info) -> str:
- return ""
-
- @strawberry.federation.field(name="f")
- def f_resolver(self, info) -> str:
- return ""
-
- reveal_type(Example.a)
- reveal_type(Example.b)
- reveal_type(Example.c)
- reveal_type(Example.d)
- reveal_type(Example.e)
- reveal_type(Example.f_resolver)
- out: |
- main:25: note: Revealed type is "builtins.str"
- main:26: note: Revealed type is "builtins.str"
- main:27: note: Revealed type is "builtins.str"
- main:28: note: Revealed type is "builtins.str"
- main:29: note: Revealed type is "Any"
- main:30: note: Revealed type is "Any"
-
-- case: test_private_field
- main: |
- import strawberry
-
- @strawberry.type
- class User:
- age: strawberry.Private[int]
-
- @strawberry.field
- def age_in_months(self) -> int:
- return self.age * 12
-
- @strawberry.field
- def wrong_type(self) -> int:
- reveal_type(self.age)
- return self.age.trim()
- out: |
- main:13: note: Revealed type is "builtins.int"
- main:14: error: "int" has no attribute "trim" [attr-defined]
-
-- case: test_field_with_default_before_non_default
- main: |
- import strawberry
-
- @strawberry.type
- class Example:
- a: str = "ABC"
- b: str
-
- out: |
-
-- case: test_using_strawberry_field_does_not_break
- main: |
- import strawberry
-
- @strawberry.type
- class Example:
- a: str = strawberry.federation.field(description="Example")
- b: str
-
- reveal_type(Example.a)
- out: |
- main:8: note: Revealed type is "builtins.str"
-
-- case: test_does_not_put_fields_with_resolver_in_init
- main: |
- import strawberry
-
- def resolver() -> str:
- return "hi"
-
- @strawberry.type
- class Example:
- a: str = strawberry.federation.field(description="Example")
- b: str = strawberry.federation.field(resolver=resolver)
- c: str
-
- i = Example(a="a", c="c")
-
- reveal_type(i.a)
- out: |
- main:14: note: Revealed type is "builtins.str"
diff --git a/tests/mypy/test_create_type.yml b/tests/mypy/test_create_type.yml
deleted file mode 100644
index 38be8a7bc3..0000000000
--- a/tests/mypy/test_create_type.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-- case: test_create_type
- main: |
- import strawberry
- from strawberry.tools import create_type
-
- @strawberry.field
- def name() -> str:
- return "foo"
-
- MyType = create_type("MyType", [name])
-
- reveal_type(MyType)
- out: |
- main:10: note: Revealed type is "Any"
-
-- case: test_can_extend
- main: |
- import strawberry
- from strawberry.tools import create_type
-
- @strawberry.field
- def name() -> str:
- return "foo"
-
- MyType = create_type("MyType", [name])
-
- class Query(MyType):
- ...
-
- reveal_type(Query)
- out: |
- main:13: note: Revealed type is "def (*_args: Any, **_kwds: Any) -> main.Query"
diff --git a/tests/mypy/test_dataloaders.yml b/tests/mypy/test_dataloaders.yml
deleted file mode 100644
index aa70ab5ffb..0000000000
--- a/tests/mypy/test_dataloaders.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-- case: test_dataloader_no_types
- main: |
- from strawberry.dataloader import DataLoader
-
- def load(keys):
- return keys
-
- loader = DataLoader(load)
-
- reveal_type(loader)
-
- async def run() -> None:
- user = await loader.load(1)
-
- reveal_type(user)
- out: |
- main:8: note: Revealed type is "strawberry.dataloader.DataLoader[Any, Any]"
- main:13: note: Revealed type is "Any"
-
-- case: test_dataloader
- main: |
- from typing import List
-
- from strawberry.dataloader import DataLoader
-
- async def load(keys: List[int]) -> List[str]:
- return [str(k) for k in keys]
-
- loader = DataLoader(load)
-
- reveal_type(loader)
-
- async def run() -> None:
- user = await loader.load(1)
-
- reveal_type(user)
- out: |
- main:10: note: Revealed type is "strawberry.dataloader.DataLoader[builtins.int, builtins.str]"
- main:15: note: Revealed type is "builtins.str"
-
-- case: test_dataloader_exception
- main: |
- from typing import List, Union
-
- from strawberry.dataloader import DataLoader
-
- async def load(keys: List[int]) -> List[Union[str, ValueError, TypeError]]:
- return [ValueError("x") for k in keys]
-
- loader = DataLoader(load)
-
- reveal_type(loader)
-
- async def run() -> None:
- user = await loader.load(1)
-
- reveal_type(user)
- out: |
- main:10: note: Revealed type is "strawberry.dataloader.DataLoader[builtins.int, builtins.str]"
- main:15: note: Revealed type is "builtins.str"
-
-- case: test_dataloader_optional
- main: |
- from typing import List, Optional
-
- from strawberry.dataloader import DataLoader
-
- async def load(keys: List[int]) -> List[Optional[str]]:
- return [None for k in keys]
-
- loader = DataLoader(load)
-
- reveal_type(loader)
-
- async def run() -> None:
- user = await loader.load(1)
-
- reveal_type(user)
- out: |
- main:10: note: Revealed type is "strawberry.dataloader.DataLoader[builtins.int, Union[builtins.str, None]]"
- main:15: note: Revealed type is "Union[builtins.str, None]"
diff --git a/tests/mypy/test_decorators.yml b/tests/mypy/test_decorators.yml
deleted file mode 100644
index 37fccc8bbb..0000000000
--- a/tests/mypy/test_decorators.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-- case: test_type
- main: |
- import strawberry
-
- @strawberry.type
- class User:
- name: str
-
- User(name="Patrick")
- User(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "User" [call-arg]
-
-- case: test_input
- main: |
- import strawberry
-
- @strawberry.input
- class EditUserInput:
- name: str
-
- EditUserInput(name="Patrick")
- EditUserInput(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "EditUserInput" [call-arg]
-
-- case: test_interface
- main: |
- import strawberry
-
- @strawberry.interface
- class NameInterface:
- name: str
-
- NameInterface(name="Patrick")
- NameInterface(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "NameInterface" [call-arg]
diff --git a/tests/mypy/test_directives.yml b/tests/mypy/test_directives.yml
deleted file mode 100644
index 26a0421168..0000000000
--- a/tests/mypy/test_directives.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-- case: test_directive_decorator
- main: |
- import strawberry
- from strawberry.directive import DirectiveLocation
-
- @strawberry.directive(
- locations=[DirectiveLocation.FRAGMENT_DEFINITION],
- description="description.",
- )
- def make_int(value: str) -> int:
- """description."""
- try:
- return int(value)
- except ValueError:
- return 0
-
- reveal_type(make_int)
-
- out: |
- main:15: note: Revealed type is "strawberry.directive.StrawberryDirective[builtins.int]"
diff --git a/tests/mypy/test_enum.yml b/tests/mypy/test_enum.yml
deleted file mode 100644
index 46f585ce4c..0000000000
--- a/tests/mypy/test_enum.yml
+++ /dev/null
@@ -1,157 +0,0 @@
-- case: test_enum
- main: |
- from enum import Enum
-
- import strawberry
-
- class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- Flavour = strawberry.enum(IceCreamFlavour)
-
- a: Flavour
- reveal_type(Flavour)
- reveal_type(a)
- out: |
- main:13: note: Revealed type is "def (value: builtins.object) -> main.IceCreamFlavour"
- main:14: note: Revealed type is "main.IceCreamFlavour"
-
-- case: test_enum_from_var
- main: |
- from typing import Type
- from enum import Enum
-
- import strawberry
-
- def get_enum() -> Type[Enum]:
- class I(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- return I
-
- IceCreamFlavour = get_enum()
-
- Flavour = strawberry.enum(IceCreamFlavour)
-
- a: Flavour
- reveal_type(Flavour)
- reveal_type(a)
- out: |
- main:19: note: Revealed type is "Any"
- main:20: note: Revealed type is "Any"
-
-- case: test_enum_from_cast
- main: |
- from enum import Enum
- from typing import Type, cast
-
- import strawberry
-
- class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- Flavour = strawberry.enum(cast(Type[Enum], IceCreamFlavour))
-
- a: Flavour
- reveal_type(Flavour)
- reveal_type(a)
- out: |
- main:14: note: Revealed type is "typing._SpecialForm"
- main:15: note: Revealed type is "Type[enum.Enum]"
-
-- case: test_enum_with_decorator
- main: |
- from enum import Enum
-
- import strawberry
-
- @strawberry.enum
- class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- a: IceCreamFlavour
- reveal_type(IceCreamFlavour)
- reveal_type(a)
- reveal_type(IceCreamFlavour.VANILLA)
- out: |
- main:12: note: Revealed type is "def (value: builtins.object) -> main.IceCreamFlavour"
- main:13: note: Revealed type is "main.IceCreamFlavour"
- main:14: note: Revealed type is "Literal[main.IceCreamFlavour.VANILLA]?"
-- case: test_enum_with_decorator_and_name
- main: |
- from enum import Enum
-
- import strawberry
-
- @strawberry.enum(name="IceCreamFlavour")
- class Flavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- a: Flavour
- reveal_type(Flavour)
- reveal_type(a)
- reveal_type(Flavour.VANILLA)
- out: |
- main:12: note: Revealed type is "def (value: builtins.object) -> main.Flavour"
- main:13: note: Revealed type is "main.Flavour"
- main:14: note: Revealed type is "Literal[main.Flavour.VANILLA]?"
-- case: test_enum_with_manual_decorator
- main: |
- from enum import Enum
-
- import strawberry
-
- class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- reveal_type(strawberry.enum(IceCreamFlavour))
- reveal_type(strawberry.enum(IceCreamFlavour).VANILLA)
- out: |
- main:10: note: Revealed type is "def (value: builtins.object) -> main.IceCreamFlavour"
- main:11: note: Revealed type is "Literal[main.IceCreamFlavour.VANILLA]?"
-- case: test_enum_with_manual_decorator_and_name
- main: |
- from enum import Enum
-
- import strawberry
-
- class Flavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
- reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour))
- reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour).VANILLA)
- out: |
- main:10: note: Revealed type is "def (value: builtins.object) -> main.Flavour"
- main:11: note: Revealed type is "Literal[main.Flavour.VANILLA]?"
-- case: test_enum_with_deprecation_reason
- main: |
- from enum import Enum
-
- import strawberry
-
- class Flavour(Enum):
- VANILLA = strawberry.enum_value("vanilla")
- STRAWBERRY = strawberry.enum_value(
- "strawberry", deprecation_reason="We ran out"
- )
- CHOCOLATE = "chocolate"
-
- reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour))
- reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour).STRAWBERRY)
- out: |
- main:12: note: Revealed type is "def (value: builtins.object) -> main.Flavour"
- main:13: note: Revealed type is "Literal[main.Flavour.STRAWBERRY]?"
diff --git a/tests/mypy/test_fields.yml b/tests/mypy/test_fields.yml
deleted file mode 100644
index 1a4530fbdb..0000000000
--- a/tests/mypy/test_fields.yml
+++ /dev/null
@@ -1,131 +0,0 @@
-- case: test_field
- main: |
- import strawberry
-
- @strawberry.type
- class User:
- name: str = strawberry.field(description="Example")
-
- User(name="Patrick")
- User(n="Patrick")
- out: |
- main:8: error: Unexpected keyword argument "n" for "User" [call-arg]
-
-- case: test_all_field_usage
- main: |
- import strawberry
- from strawberry.types import Info
-
- def some_resolver() -> str:
- return ""
-
- @strawberry.type
- class Example:
- a: str
- b: str = strawberry.field(name="b")
- c: str = strawberry.field(name="c", resolver=some_resolver)
- d: str = strawberry.field(resolver=some_resolver)
-
- @strawberry.field(description="ABC")
- def e(self, info: Info) -> str:
- return ""
-
- @strawberry.field(name="f")
- def f_resolver(self, info) -> str:
- return ""
-
- reveal_type(Example.a)
- reveal_type(Example.b)
- reveal_type(Example.c)
- reveal_type(Example.d)
- reveal_type(Example.e)
- reveal_type(Example.f_resolver)
- out: |
- main:22: note: Revealed type is "builtins.str"
- main:23: note: Revealed type is "builtins.str"
- main:24: note: Revealed type is "builtins.str"
- main:25: note: Revealed type is "builtins.str"
- main:26: note: Revealed type is "Any"
- main:27: note: Revealed type is "Any"
-
-- case: test_private_field
- main: |
- import strawberry
-
- @strawberry.type
- class User:
- age: strawberry.Private[int]
-
- @strawberry.field
- def age_in_months(self) -> int:
- return self.age * 12
-
- @strawberry.field
- def wrong_type(self) -> int:
- reveal_type(self.age)
- return self.age.trim()
- out: |
- main:13: note: Revealed type is "builtins.int"
- main:14: error: "int" has no attribute "trim" [attr-defined]
-
-- case: test_field_with_default_before_non_default
- main: |
- import strawberry
-
- @strawberry.type
- class Example:
- a: str = "ABC"
- b: str
-
- out: |
-
-- case: test_using_strawberry_field_does_not_break
- main: |
- import strawberry
-
- @strawberry.type
- class Example:
- a: str = strawberry.field(description="Example")
- b: str
-
- reveal_type(Example.a)
- out: |
- main:8: note: Revealed type is "builtins.str"
-
-- case: test_does_not_put_fields_with_resolver_in_init
- main: |
- import strawberry
-
- def resolver() -> str:
- return "hi"
-
- @strawberry.type
- class Example:
- a: str = strawberry.field(description="Example")
- b: str = strawberry.field(resolver=resolver)
- c: str
-
- i = Example(a="a", c="c")
-
- reveal_type(i.a)
- out: |
- main:14: note: Revealed type is "builtins.str"
-
-- case: test_does_not_put_fields_with_async_resolver_in_init
- main: |
- import strawberry
-
- async def resolver() -> str:
- return "hi"
-
- @strawberry.type
- class Example:
- a: str = strawberry.field(description="Example")
- b: str = strawberry.field(resolver=resolver)
- c: str
-
- i = Example(a="a", c="c")
-
- reveal_type(i.a)
- out: |
- main:14: note: Revealed type is "builtins.str"
diff --git a/tests/mypy/test_merge_types.yml b/tests/mypy/test_merge_types.yml
deleted file mode 100644
index 406385cf99..0000000000
--- a/tests/mypy/test_merge_types.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-- case: test_merge_types
- main: |
- import strawberry
- from strawberry.tools import merge_types
-
- @strawberry.type
- class QueryA:
- field1: str
-
- @strawberry.type
- class QueryB:
- field2: int
-
- Query = merge_types("AwesomeQuery", (QueryA, QueryB))
-
- reveal_type(Query)
- out: |
- main:14: note: Revealed type is "builtins.type"
diff --git a/tests/mypy/test_mutation.yml b/tests/mypy/test_mutation.yml
deleted file mode 100644
index 5e5bc4a618..0000000000
--- a/tests/mypy/test_mutation.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-- case: test_mutation_decorator
- main: |
- import strawberry
-
- @strawberry.type
- class Mutation:
- @strawberry.mutation
- def set_name(self, name: str) -> None:
- self.name = name
-
- Mutation()
- Mutation(n="Patrick")
- out: |
- main:10: error: Unexpected keyword argument "n" for "Mutation" [call-arg]
-
-
-- case: test_mutation_field
- main: |
- import strawberry
-
- def set_name(self, name: str) -> None:
- self.name = name
-
- @strawberry.type
- class Mutation:
- set_name: None = strawberry.mutation(resolver=set_name)
-
- Mutation()
- Mutation(n="Patrick")
- out: |
- main:11: error: Unexpected keyword argument "n" for "Mutation" [call-arg]
diff --git a/tests/mypy/test_plugin.py b/tests/mypy/test_plugin.py
deleted file mode 100644
index ee4850b398..0000000000
--- a/tests/mypy/test_plugin.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from decimal import Decimal
-from typing import Generator
-
-import pytest
-
-from strawberry.ext.mypy_plugin import FALLBACK_VERSION, MypyVersion, plugin
-
-pytestmark = pytest.mark.usefixtures("maintain_version")
-
-
-@pytest.fixture
-def maintain_version() -> Generator[None, None, None]:
- """Clean-up side-effected version after tests"""
-
- yield
-
- del MypyVersion.VERSION
-
-
-@pytest.mark.parametrize(
- ("version", "expected"),
- [
- ("0.93", Decimal("0.93")),
- ("0.800", Decimal("0.800")),
- ("0.920", Decimal("0.920")),
- ("0.980+dev.d89b28d973c3036ef154c9551b961d9119761380", Decimal("0.980")),
- ("1.0.0", Decimal("1.0")),
- ("1.0.1", Decimal("1.0")),
- ("1.1.1", Decimal("1.1")),
- ("99.999", Decimal("99.999")),
- ],
-)
-def test_plugin(version, expected):
- plugin(version)
- assert expected == MypyVersion.VERSION
-
-
-def test_plugin_negative():
- invalid_version = "001.290"
- with pytest.warns(UserWarning, match=f"Mypy version {invalid_version} could not"):
- plugin(invalid_version)
- assert MypyVersion.VERSION == FALLBACK_VERSION
diff --git a/tests/mypy/test_pydantic.decorators.yml b/tests/mypy/test_pydantic.decorators.yml
deleted file mode 100644
index b5323ec9fd..0000000000
--- a/tests/mypy/test_pydantic.decorators.yml
+++ /dev/null
@@ -1,287 +0,0 @@
-
-- case: test_converted_pydantic_init_any_kwargs
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.type(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry)
- reveal_type(UserStrawberry(age=123))
- out: |
- main:11: note: Revealed type is "def (**kwargs: Any) -> main.UserStrawberry"
- main:12: note: Revealed type is "main.UserStrawberry"
-
-- case: test_converted_to_pydantic
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.type(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry(age=123).to_pydantic())
- out: |
- main:11: note: Revealed type is "main.UserPydantic"
-
-- case: test_converted_from_pydantic
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.type(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry.from_pydantic(UserPydantic(age=123)))
- out: |
- main:11: note: Revealed type is "main.UserStrawberry"
-
-- case: test_converted_to_pydantic_input
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.input(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry(age=123).to_pydantic())
- out: |
- main:11: note: Revealed type is "main.UserPydantic"
-
-- case: test_converted_from_pydantic_input
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.input(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry.from_pydantic(UserPydantic(age=123)))
- out: |
- main:11: note: Revealed type is "main.UserStrawberry"
-
-- case: test_converted_to_pydantic_interface
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.interface(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry(age=123).to_pydantic())
- out: |
- main:11: note: Revealed type is "main.UserPydantic"
-
-- case: test_converted_from_pydantic_interface
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.interface(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry.from_pydantic(UserPydantic(age=123)))
- out: |
- main:11: note: Revealed type is "main.UserStrawberry"
-
-- case: test_converted_from_pydantic_raise_error_wrong_instance
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.type(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- class AnotherModel(BaseModel):
- age: int
-
- UserStrawberry.from_pydantic(AnotherModel(age=123))
- out: |
- main:14: error: Argument 1 to "from_pydantic" of "UserStrawberry" has incompatible type "AnotherModel"; expected "UserPydantic" [arg-type]
-
-- case: test_converted_from_pydantic_chained
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class UserPydantic(BaseModel):
- age: int
-
- @strawberry.experimental.pydantic.type(UserPydantic)
- class UserStrawberry:
- age: strawberry.auto
-
- reveal_type(UserStrawberry.from_pydantic(UserPydantic(age=123)).to_pydantic())
- out: |
- main:11: note: Revealed type is "main.UserPydantic"
-
-- case: test_to_pydantic_kwargs
- main: |
- from pydantic import BaseModel
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- password: str
-
-
- @strawberry.experimental.pydantic.input(model=MyModel)
- class MyModelStrawberry:
- email: strawberry.auto
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
- main:15: error: Missing named argument "password" for "to_pydantic" of "MyModelStrawberry" [call-arg]
-- case: test_to_pydantic_kwargs_with_defaults
- main: |
- from pydantic import BaseModel
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- password: str = "hunter2"
-
-
- @strawberry.experimental.pydantic.input(model=MyModel)
- class MyModelStrawberry:
- email: strawberry.auto
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
-- case: test_to_pydantic_kwargs_with_default_none
- main: |
- from pydantic import BaseModel
- from typing import Optional
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- password: Optional[str] = None
-
-
- @strawberry.experimental.pydantic.input(model=MyModel)
- class MyModelStrawberry:
- email: strawberry.auto
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
-- case: test_to_pydantic_kwargs_with_all_fields
- main: |
- from pydantic import BaseModel
- from typing import Optional
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- password: Optional[str]
-
-
- @strawberry.experimental.pydantic.input(model=MyModel, all_fields=True)
- class MyModelStrawberry:
- ...
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
-- case: test_to_pydantic_kwargs_with_all_fields_adding_field
- main: |
- from pydantic import BaseModel
- from typing import Optional
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- password: Optional[str]
-
- @property
- def age(self) -> int:
- return 42
-
-
- @strawberry.experimental.pydantic.input(model=MyModel, all_fields=True)
- class MyModelStrawberry:
- age: int # read from the property
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
-- case: test_to_pydantic_kwargs_private_field
- main: |
- from pydantic import BaseModel
- from typing import Optional
- import strawberry
-
-
- class MyModel(BaseModel):
- email: str
- _age: int # for pydantic, underscore means private field
-
-
- @strawberry.experimental.pydantic.input(model=MyModel)
- class MyModelStrawberry:
- email: strawberry.auto
-
-
- MyModelStrawberry(email="").to_pydantic()
- out: |
-- case: test_to_pydantic_custom
- main: |
- from pydantic import BaseModel
- import strawberry
-
- class MyModel(BaseModel):
- email: str
-
- @strawberry.experimental.pydantic.input(model=MyModel)
- class MyModelStrawberry:
- email: strawberry.auto
-
- def to_pydantic(self, another_param: str):
- # custom to_pydantic overwrites default params
- ...
-
- MyModelStrawberry(email="").to_pydantic()
-
- out: |
- main:15: error: Missing positional argument "another_param" in call to "to_pydantic" of "MyModelStrawberry" [call-arg]
diff --git a/tests/mypy/test_pydantic.yml b/tests/mypy/test_pydantic.yml
deleted file mode 100644
index 622ac47e67..0000000000
--- a/tests/mypy/test_pydantic.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-- case: test_type
- main: |
- import strawberry
- import pydantic
-
- class UserModel(pydantic.BaseModel):
- name: str
-
- @strawberry.experimental.pydantic.type(model=UserModel, fields=["name"])
- class User:
- pass
-
- User(n="Patrick")
- User(name="Patrick")
- out: |
diff --git a/tests/mypy/test_scalar.yml b/tests/mypy/test_scalar.yml
deleted file mode 100644
index dfdfb3df84..0000000000
--- a/tests/mypy/test_scalar.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-- case: test_scalar_decorator
- main: |
- import strawberry
-
- @strawberry.scalar()
- class X:
- pass
-
- @strawberry.scalar
- class Y:
- pass
-
- @strawberry.scalar(name="Zed")
- class Z:
- pass
-
- @strawberry.type
- class Me:
- z: Z
-
- reveal_type(X)
- reveal_type(Y)
- reveal_type(Z)
- reveal_type(X())
- reveal_type(Y())
- reveal_type(Z())
- reveal_type(Me(z=Z()).z)
- out: |
- main:19: note: Revealed type is "def () -> main.X"
- main:20: note: Revealed type is "def () -> main.Y"
- main:21: note: Revealed type is "def () -> main.Z"
- main:22: note: Revealed type is "main.X"
- main:23: note: Revealed type is "main.Y"
- main:24: note: Revealed type is "main.Z"
- main:25: note: Revealed type is "main.Z"
-
-- case: test_scalar_as_function
- main: |
- import strawberry
-
- X = strawberry.scalar(int)
- Y = strawberry.scalar(str, name="Y")
-
- @strawberry.type
- class Me:
- x: X
-
- reveal_type(X())
- reveal_type(Y())
- reveal_type(Me(x=X("1")).x)
- out: |
- main:10: note: Revealed type is "builtins.int"
- main:11: note: Revealed type is "builtins.str"
- main:12: note: Revealed type is "builtins.int"
-
-- case: test_scalar_as_function_new_type
- main: |
- from typing import NewType
- import strawberry
-
- Z = strawberry.scalar(NewType("X", int))
-
- @strawberry.type
- class Me:
- x: Z
-
- reveal_type(Z())
- reveal_type(Me(x=Z("1")).x)
- out: |
- main:10: note: Revealed type is "Any"
- main:11: note: Revealed type is "Any"
-
-- case: test_we_can_pass_scalar_overrides_to_schema
- main: |
- import strawberry
- from datetime import datetime, timezone
-
- EpochDateTime = strawberry.scalar(
- datetime,
- )
-
- @strawberry.type
- class Query:
- a: datetime
-
- schema = strawberry.Schema(query=Query, scalar_overrides={
- datetime: EpochDateTime,
- })
-
- reveal_type(EpochDateTime)
-
- out: |
- main:16: note: Revealed type is "def (year: typing_extensions.SupportsIndex, month: typing_extensions.SupportsIndex, day: typing_extensions.SupportsIndex, hour: typing_extensions.SupportsIndex =, minute: typing_extensions.SupportsIndex =, second: typing_extensions.SupportsIndex =, microsecond: typing_extensions.SupportsIndex =, tzinfo: Union[datetime.tzinfo, None] =, *, fold: builtins.int =) -> datetime.datetime"
diff --git a/tests/mypy/test_schema.yml b/tests/mypy/test_schema.yml
deleted file mode 100644
index eeeddaf8b2..0000000000
--- a/tests/mypy/test_schema.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-- case: test_passing_extensions
- main: |
- import strawberry
- from strawberry.extensions import ParserCache
-
- @strawberry.type
- class Query:
- b: str
-
- schema = strawberry.Schema(Query, extensions=[ParserCache()])
-
- reveal_type(schema)
- out: |
- main:10: note: Revealed type is "strawberry.schema.schema.Schema"
diff --git a/tests/mypy/test_union.yml b/tests/mypy/test_union.yml
deleted file mode 100644
index 9168292282..0000000000
--- a/tests/mypy/test_union.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-- case: test_union
- main: |
- from typing_extensions import Annotated
- from typing import Union
-
- import strawberry
-
- @strawberry.type
- class User:
- name: str
-
- @strawberry.type
- class Error:
- message: str
-
- Response = Annotated[Union[User, Error], strawberry.union(name="Response")]
-
- a: Response
- reveal_type(Response)
- reveal_type(a)
-
- a = User(name="abc")
- reveal_type(a)
- out: |
- main:17: note: Revealed type is "typing._SpecialForm"
- main:18: note: Revealed type is "Union[main.User, main.Error]"
- main:21: note: Revealed type is "main.User"
-
-- case: test_union_generics
- main: |
- from typing import Generic, TypeVar
- from typing_extensions import Annotated
- from typing import Union
-
- import strawberry
-
- T = TypeVar("T")
-
- @strawberry.type
- class Error:
- message: str
-
- @strawberry.type
- class Edge(Generic[T]):
- node: T
-
- Result = Annotated[Union[Error, Edge[str]], strawberry.union("Result")]
-
- reveal_type(Result)
-
- a: Result
- reveal_type(a)
- out: |
- main:19: note: Revealed type is "typing._SpecialForm"
- main:22: note: Revealed type is "Union[main.Error, main.Edge[builtins.str]]"
-
-- case: test_union_kwargs
- main: |
- from typing_extensions import Annotated
- from typing import Union
-
- import strawberry
-
- @strawberry.type
- class User:
- name: str
-
- @strawberry.type
- class Error:
- message: str
-
- Response = Annotated[Union[User, Error], strawberry.union("Response")]
-
- a: Response
- reveal_type(Response)
- reveal_type(a)
-
- a = User(name="abc")
- reveal_type(a)
- out: |
- main:17: note: Revealed type is "typing._SpecialForm"
- main:18: note: Revealed type is "Union[main.User, main.Error]"
- main:21: note: Revealed type is "main.User"
diff --git a/tests/mypy/test_unset.yml b/tests/mypy/test_unset.yml
deleted file mode 100644
index b1ae0f28f3..0000000000
--- a/tests/mypy/test_unset.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-- case: test_unset
- main: |
- import strawberry
- from typing import Optional
- from strawberry import UNSET
-
- @strawberry.type
- class User:
- name: Optional[str] = UNSET
-
- reveal_type(User().name)
- out: |
- main:9: note: Revealed type is "Union[builtins.str, None]"
diff --git a/tests/objects/generics/test_generic_objects.py b/tests/objects/generics/test_generic_objects.py
index 71a38a44b4..2dcf9d5fe3 100644
--- a/tests/objects/generics/test_generic_objects.py
+++ b/tests/objects/generics/test_generic_objects.py
@@ -5,13 +5,13 @@
import pytest
import strawberry
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
StrawberryOptional,
StrawberryTypeVar,
get_object_definition,
)
-from strawberry.union import StrawberryUnion
+from strawberry.types.union import StrawberryUnion
T = TypeVar("T")
@@ -117,7 +117,7 @@ class Connection(Generic[T]):
# let's make a copy of this generic type
definition_copy = get_object_definition(
- Connection.__strawberry_definition__.copy_with({"T": str}),
+ definition.copy_with({"T": str}),
strict=True,
)
@@ -144,8 +144,9 @@ class Foo:
optional_string: Value[Optional[str]]
optional_strings: Value[Optional[List[str]]]
- definition = Foo.__strawberry_definition__
+ definition = get_object_definition(Foo, strict=True)
assert not definition.is_graphql_generic
+
[
string_field,
strings_field,
@@ -163,7 +164,7 @@ def test_generic_with_optional():
class Edge(Generic[T]):
node: Optional[T]
- definition = Edge.__strawberry_definition__
+ definition = get_object_definition(Edge, strict=True)
assert definition.is_graphql_generic
assert definition.type_params == [T]
@@ -174,9 +175,9 @@ class Edge(Generic[T]):
assert field.type.of_type.type_var is T
# let's make a copy of this generic type
- definition_copy = Edge.__strawberry_definition__.copy_with(
- {"T": str}
- ).__strawberry_definition__
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
assert not definition_copy.is_graphql_generic
assert definition_copy.type_params == []
@@ -192,7 +193,7 @@ def test_generic_with_list():
class Connection(Generic[T]):
edges: List[T]
- definition = Connection.__strawberry_definition__
+ definition = get_object_definition(Connection, strict=True)
assert definition.is_graphql_generic
assert definition.type_params == [T]
@@ -203,9 +204,9 @@ class Connection(Generic[T]):
assert field.type.of_type.type_var is T
# let's make a copy of this generic type
- definition_copy = Connection.__strawberry_definition__.copy_with(
- {"T": str}
- ).__strawberry_definition__
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
assert not definition_copy.is_graphql_generic
assert definition_copy.type_params == []
@@ -221,7 +222,7 @@ def test_generic_with_list_of_optionals():
class Connection(Generic[T]):
edges: List[Optional[T]]
- definition = Connection.__strawberry_definition__
+ definition = get_object_definition(Connection, strict=True)
assert definition.is_graphql_generic
assert definition.type_params == [T]
@@ -233,9 +234,9 @@ class Connection(Generic[T]):
assert field.type.of_type.of_type.type_var is T
# let's make a copy of this generic type
- definition_copy = Connection.__strawberry_definition__.copy_with(
- {"T": str}
- ).__strawberry_definition__
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
assert not definition_copy.is_graphql_generic
assert definition_copy.type_params == []
@@ -256,7 +257,7 @@ class Error:
class Edge(Generic[T]):
node: Union[Error, T]
- definition = Edge.__strawberry_definition__
+ definition = get_object_definition(Edge, strict=True)
assert definition.type_params == [T]
[field] = definition.fields
@@ -269,9 +270,9 @@ class Edge(Generic[T]):
class Node:
name: str
- definition_copy = Edge.__strawberry_definition__.copy_with(
- {"T": Node}
- ).__strawberry_definition__
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": Node}), strict=True
+ )
assert not definition_copy.is_graphql_generic
assert definition_copy.type_params == []
@@ -325,7 +326,7 @@ class User:
class Query:
users: Connection[User]
- connection_definition = Connection.__strawberry_definition__
+ connection_definition = get_object_definition(Connection, strict=True)
assert connection_definition.is_graphql_generic
assert connection_definition.type_params == [T]
@@ -409,15 +410,11 @@ class Query:
assert field.python_name == "user"
assert isinstance(field.type, StrawberryOptional)
- str_edge_definition = field.type.of_type.__strawberry_definition__
+ str_edge_definition = get_object_definition(field.type.of_type, strict=True)
assert not str_edge_definition.is_graphql_generic
def test_generics_inside_list():
- @strawberry.type
- class Error:
- message: str
-
@strawberry.type
class Edge(Generic[T]):
node: T
@@ -433,7 +430,7 @@ class Query:
assert field.python_name == "user"
assert isinstance(field.type, StrawberryList)
- str_edge_definition = field.type.of_type.__strawberry_definition__
+ str_edge_definition = get_object_definition(field.type.of_type, strict=True)
assert not str_edge_definition.is_graphql_generic
@@ -459,7 +456,7 @@ class Query:
union = field.type
assert isinstance(union, StrawberryUnion)
- assert not union.types[0].__strawberry_definition__.is_graphql_generic
+ assert not get_object_definition(union.types[0], strict=True).is_graphql_generic
def test_multiple_generics_inside_unions():
@@ -481,11 +478,11 @@ class Query:
union = user_field.type
assert isinstance(union, StrawberryUnion)
- int_edge_definition = union.types[0].__strawberry_definition__
+ int_edge_definition = get_object_definition(union.types[0], strict=True)
assert not int_edge_definition.is_graphql_generic
assert int_edge_definition.fields[0].type is int
- str_edge_definition = union.types[1].__strawberry_definition__
+ str_edge_definition = get_object_definition(union.types[1], strict=True)
assert not str_edge_definition.is_graphql_generic
assert str_edge_definition.fields[0].type is str
@@ -629,13 +626,15 @@ class Edge(Generic[T]):
id: strawberry.ID
node_field: T
- definition_copy = Edge.__strawberry_definition__.copy_with(
- {"T": str}
- ).__strawberry_definition__
+ definition = get_object_definition(Edge, strict=True)
+
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
assert not definition_copy.is_graphql_generic
assert definition_copy.type_params == []
- assert definition_copy.directives == Edge.__strawberry_definition__.directives
+ assert definition_copy.directives == definition.directives
[field1_copy, field2_copy] = definition_copy.fields
diff --git a/tests/objects/generics/test_names.py b/tests/objects/generics/test_names.py
index 997f01a38c..fe5c747122 100644
--- a/tests/objects/generics/test_names.py
+++ b/tests/objects/generics/test_names.py
@@ -5,11 +5,11 @@
import pytest
import strawberry
-from strawberry.enum import EnumDefinition
-from strawberry.lazy_type import LazyType
from strawberry.schema.config import StrawberryConfig
-from strawberry.type import StrawberryList, StrawberryOptional
-from strawberry.union import StrawberryUnion
+from strawberry.types.base import StrawberryList, StrawberryOptional
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.union import StrawberryUnion
T = TypeVar("T")
K = TypeVar("K")
@@ -87,7 +87,8 @@ class Connection(Generic[T]):
def test_nested_generics_aliases_with_schema():
"""This tests is similar to the previous test, but it also tests against
- the schema, since the resolution of the type name might be different."""
+ the schema, since the resolution of the type name might be different.
+ """
config = StrawberryConfig()
@strawberry.type
diff --git a/tests/pyright/test_auto.py b/tests/pyright/test_auto.py
deleted file mode 100644
index 4ad54fa015..0000000000
--- a/tests/pyright/test_auto.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.type
-class SomeType:
- foobar: strawberry.auto
-
-
-obj1 = SomeType(foobar=1)
-obj2 = SomeType(foobar="some text")
-obj3 = SomeType(foobar={"some key": "some value"})
-
-reveal_type(obj1.foobar)
-reveal_type(obj2.foobar)
-reveal_type(obj3.foobar)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="information",
- message='Type of "obj1.foobar" is "Any"',
- line=14,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj2.foobar" is "Any"',
- line=15,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj3.foobar" is "Any"',
- line=16,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_directives.py b/tests/pyright/test_directives.py
deleted file mode 100644
index 28a76db9b0..0000000000
--- a/tests/pyright/test_directives.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-from strawberry.directive import DirectiveLocation
-
-@strawberry.directive(
- locations=[DirectiveLocation.FRAGMENT_DEFINITION],
- description="description.",
-)
-def make_int(value: str) -> int:
- '''description.'''
- try:
- return int(value)
- except ValueError:
- return 0
-
-reveal_type(make_int)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
- assert results == [
- Result(
- type="information",
- message='Type of "make_int" is "StrawberryDirective[int]"',
- line=16,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_enum.py b/tests/pyright/test_enum.py
deleted file mode 100644
index 49a69c3d45..0000000000
--- a/tests/pyright/test_enum.py
+++ /dev/null
@@ -1,196 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE_WITH_DECORATOR = """
-from enum import Enum
-
-import strawberry
-
-@strawberry.enum
-class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
-reveal_type(IceCreamFlavour)
-reveal_type(IceCreamFlavour.VANILLA)
-"""
-
-
-def test_enum_with_decorator():
- results = run_pyright(CODE_WITH_DECORATOR)
-
- assert results == [
- Result(
- type="information",
- message='Type of "IceCreamFlavour" is "type[IceCreamFlavour]"',
- line=12,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "IceCreamFlavour.VANILLA" is '
- '"Literal[IceCreamFlavour.VANILLA]"'
- ),
- line=13,
- column=13,
- ),
- ]
-
-
-CODE_WITH_DECORATOR_AND_NAME = """
-from enum import Enum
-
-import strawberry
-
-@strawberry.enum(name="IceCreamFlavour")
-class Flavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
-reveal_type(Flavour)
-reveal_type(Flavour.VANILLA)
-"""
-
-
-def test_enum_with_decorator_and_name():
- results = run_pyright(CODE_WITH_DECORATOR_AND_NAME)
-
- assert results == [
- Result(
- type="information",
- message='Type of "Flavour" is "type[Flavour]"',
- line=12,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Flavour.VANILLA" is "Literal[Flavour.VANILLA]"',
- line=13,
- column=13,
- ),
- ]
-
-
-CODE_WITH_MANUAL_DECORATOR = """
-from enum import Enum
-
-import strawberry
-
-class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
-reveal_type(strawberry.enum(IceCreamFlavour))
-reveal_type(strawberry.enum(IceCreamFlavour).VANILLA)
-"""
-
-
-def test_enum_with_manual_decorator():
- results = run_pyright(CODE_WITH_MANUAL_DECORATOR)
-
- assert results == [
- Result(
- type="information",
- message=(
- 'Type of "strawberry.enum(IceCreamFlavour)" '
- 'is "type[IceCreamFlavour]"'
- ),
- line=11,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "strawberry.enum(IceCreamFlavour).VANILLA" '
- 'is "Literal[IceCreamFlavour.VANILLA]"'
- ),
- line=12,
- column=13,
- ),
- ]
-
-
-CODE_WITH_MANUAL_DECORATOR_AND_NAME = """
-from enum import Enum
-
-import strawberry
-
-class Flavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = "strawberry"
- CHOCOLATE = "chocolate"
-
-reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour))
-reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour).VANILLA)
-"""
-
-
-def test_enum_with_manual_decorator_and_name():
- results = run_pyright(CODE_WITH_MANUAL_DECORATOR_AND_NAME)
-
- assert results == [
- Result(
- type="information",
- message=(
- 'Type of "strawberry.enum(name="IceCreamFlavour")(Flavour)" '
- 'is "type[Flavour]"'
- ),
- line=11,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "strawberry.enum(name="IceCreamFlavour")(Flavour).VANILLA" '
- 'is "Literal[Flavour.VANILLA]"'
- ),
- line=12,
- column=13,
- ),
- ]
-
-
-CODE_WITH_DEPRECATION_REASON = """
-from enum import Enum
-
-import strawberry
-
-@strawberry.enum
-class IceCreamFlavour(Enum):
- VANILLA = "vanilla"
- STRAWBERRY = strawberry.enum_value(
- "strawberry", deprecation_reason="We ran out"
- )
- CHOCOLATE = "chocolate"
-
-reveal_type(IceCreamFlavour)
-reveal_type(IceCreamFlavour.STRAWBERRY)
-"""
-
-
-def test_enum_deprecated():
- results = run_pyright(CODE_WITH_DEPRECATION_REASON)
-
- assert results == [
- Result(
- type="information",
- message='Type of "IceCreamFlavour" is "type[IceCreamFlavour]"',
- line=14,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "IceCreamFlavour.STRAWBERRY" is '
- '"Literal[IceCreamFlavour.STRAWBERRY]"'
- ),
- line=15,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_federation.py b/tests/pyright/test_federation.py
deleted file mode 100644
index 83040ecfce..0000000000
--- a/tests/pyright/test_federation.py
+++ /dev/null
@@ -1,147 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-def get_user_age() -> int:
- return 0
-
-
-@strawberry.federation.type
-class User:
- name: str
- age: int = strawberry.field(resolver=get_user_age)
- something_else: int = strawberry.federation.field(resolver=get_user_age)
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_federation_type():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=16,
- column=1,
- ),
- Result(type="error", message='No parameter named "n"', line=16, column=6),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=18,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=19,
- column=13,
- ),
- ]
-
-
-CODE_INTERFACE = """
-import strawberry
-
-
-@strawberry.federation.interface
-class User:
- name: str
- age: int
-
-
-User(name="Patrick", age=1)
-User(n="Patrick", age=1)
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_federation_interface():
- results = run_pyright(CODE_INTERFACE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=12,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=12,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=14,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str, age: int) -> None"',
- line=15,
- column=13,
- ),
- ]
-
-
-CODE_INPUT = """
-import strawberry
-
-@strawberry.federation.input
-class User:
- name: str
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_federation_input():
- results = run_pyright(CODE_INPUT)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=10,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=10,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=12,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=13,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_federation_fields.py b/tests/pyright/test_federation_fields.py
deleted file mode 100644
index 6981fdc849..0000000000
--- a/tests/pyright/test_federation_fields.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-CODE = """
-import strawberry
-
-def some_resolver(root: "User") -> str:
- return "An address"
-
-def some_resolver_2() -> str:
- return "Another address"
-
-@strawberry.federation.type
-class User:
- age: int = strawberry.federation.field(description="Age")
- name: str
- address: str = strawberry.federation.field(resolver=some_resolver)
- another_address: str = strawberry.federation.field(resolver=some_resolver_2)
-
-@strawberry.federation.input
-class UserInput:
- age: int = strawberry.federation.field(description="Age")
- name: str
-
-
-User(name="Patrick", age=1)
-User(n="Patrick", age=1)
-
-UserInput(name="Patrick", age=1)
-UserInput(n="Patrick", age=1)
-
-reveal_type(User)
-reveal_type(User.__init__)
-
-reveal_type(UserInput)
-reveal_type(UserInput.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=24,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=24,
- column=6,
- ),
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=27,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=27,
- column=11,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=29,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "User.__init__" is "(self: User, *, age: int, name: str) '
- '-> None"'
- ),
- line=30,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "UserInput" is "type[UserInput]"',
- line=32,
- column=13,
- ),
- Result(
- type="information",
- message=(
- 'Type of "UserInput.__init__" is "(self: UserInput, *, age: int, '
- 'name: str) -> None"'
- ),
- line=33,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_federation_params.py b/tests/pyright/test_federation_params.py
deleted file mode 100644
index 69ae95d839..0000000000
--- a/tests/pyright/test_federation_params.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.federation.type(name="User")
-class UserModel:
- name: str
-
-
-UserModel(name="Patrick")
-UserModel(n="Patrick")
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=11,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=11,
- column=11,
- ),
- ]
diff --git a/tests/pyright/test_fields.py b/tests/pyright/test_fields.py
deleted file mode 100644
index 445a048a47..0000000000
--- a/tests/pyright/test_fields.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.type
-class User:
- name: str
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=11,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=11,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=13,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=14,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_fields_input.py b/tests/pyright/test_fields_input.py
deleted file mode 100644
index 360ad6389b..0000000000
--- a/tests/pyright/test_fields_input.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-CODE = """
-import strawberry
-
-
-@strawberry.input
-class User:
- name: str
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=11,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=11,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=13,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=14,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_fields_keyword.py b/tests/pyright/test_fields_keyword.py
deleted file mode 100644
index b7e52f62d2..0000000000
--- a/tests/pyright/test_fields_keyword.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.type
-class User:
- name: str
-
-
-User("Patrick")
-
-reveal_type(User.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message="Expected 0 positional arguments",
- line=10,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=12,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_fields_resolver.py b/tests/pyright/test_fields_resolver.py
deleted file mode 100644
index 0888b7cc2a..0000000000
--- a/tests/pyright/test_fields_resolver.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-CODE = """
-import strawberry
-
-def get_user_age() -> int:
- return 0
-
-
-@strawberry.type
-class User:
- name: str
- age: int = strawberry.field(resolver=get_user_age)
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=15,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=15,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=17,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=18,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_fields_resolver_async.py b/tests/pyright/test_fields_resolver_async.py
deleted file mode 100644
index 6127d1384d..0000000000
--- a/tests/pyright/test_fields_resolver_async.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-CODE = """
-import strawberry
-
-async def get_user_age() -> int:
- return 0
-
-
-@strawberry.type
-class User:
- name: str
- age: int = strawberry.field(resolver=get_user_age)
-
-
-User(name="Patrick")
-User(n="Patrick")
-
-reveal_type(User)
-reveal_type(User.__init__)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=15,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=15,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "User" is "type[User]"',
- line=17,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
- line=18,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_interface.py b/tests/pyright/test_interface.py
deleted file mode 100644
index 85981546c0..0000000000
--- a/tests/pyright/test_interface.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-CODE = """
-import strawberry
-
-
-@strawberry.interface
-class Node:
- id: strawberry.ID
-
-reveal_type(Node)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="information",
- message='Type of "Node" is "type[Node]"',
- line=9,
- column=13,
- ),
- ]
-
-
-CODE_2 = """
-import strawberry
-
-
-@strawberry.interface(name="nodeinterface")
-class Node:
- id: strawberry.ID
-
-reveal_type(Node)
-"""
-
-
-def test_pyright_calling():
- results = run_pyright(CODE_2)
-
- assert results == [
- Result(
- type="information",
- message='Type of "Node" is "type[Node]"',
- line=9,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_params.py b/tests/pyright/test_params.py
deleted file mode 100644
index ec7e4aa495..0000000000
--- a/tests/pyright/test_params.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.type(name="User")
-class UserModel:
- name: str
-
-
-@strawberry.input(name="User")
-class UserInput:
- name: str
-
-
-UserModel(name="Patrick")
-UserModel(n="Patrick")
-
-UserInput(name="Patrick")
-UserInput(n="Patrick")
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=16,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=16,
- column=11,
- ),
- Result(
- type="error",
- message='Argument missing for parameter "name"',
- line=19,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=19,
- column=11,
- ),
- ]
diff --git a/tests/pyright/test_private.py b/tests/pyright/test_private.py
deleted file mode 100644
index ae228aadce..0000000000
--- a/tests/pyright/test_private.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-
-
-@strawberry.type
-class User:
- name: str
- age: strawberry.Private[int]
-
-
-patrick = User(name="Patrick", age=1)
-User(n="Patrick")
-
-reveal_type(patrick.name)
-reveal_type(patrick.age)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="error",
- message='Arguments missing for parameters "name", "age"',
- line=12,
- column=1,
- ),
- Result(
- type="error",
- message='No parameter named "n"',
- line=12,
- column=6,
- ),
- Result(
- type="information",
- message='Type of "patrick.name" is "str"',
- line=14,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "patrick.age" is "int"',
- line=15,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_relay.py b/tests/pyright/test_relay.py
deleted file mode 100644
index 7ef08bd58f..0000000000
--- a/tests/pyright/test_relay.py
+++ /dev/null
@@ -1,244 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-from typing import (
- Any,
- AsyncIterator,
- AsyncGenerator,
- AsyncIterable,
- Generator,
- Iterable,
- Iterator,
- List,
- Optional,
- Union,
-)
-
-import strawberry
-from strawberry import relay
-from strawberry.types import Info
-from typing_extensions import Self
-
-
-@strawberry.type
-class Fruit(relay.Node):
- id: relay.NodeID[int]
- name: str
- color: str
-
-
-@strawberry.type
-class FruitCustomPaginationConnection(relay.Connection[Fruit]):
- @strawberry.field
- def something(self) -> str:
- return "foobar"
-
- @classmethod
- def resolve_connection(
- cls,
- nodes: Union[
- Iterator[Fruit],
- AsyncIterator[Fruit],
- Iterable[Fruit],
- AsyncIterable[Fruit],
- ],
- *,
- info: Optional[Info[Any, Any]] = None,
- before: Optional[str] = None,
- after: Optional[str] = None,
- first: Optional[int] = None,
- last: Optional[int] = None,
- **kwargs: Any,
- ) -> Self:
- ...
-
-
-class FruitAlike:
- ...
-
-
-def fruits_resolver() -> List[Fruit]:
- ...
-
-
-@strawberry.type
-class Query:
- node: relay.Node
- nodes: List[relay.Node]
- node_optional: Optional[relay.Node]
- nodes_optional: List[Optional[relay.Node]]
- fruits: relay.Connection[Fruit] = strawberry.relay.connection(
- resolver=fruits_resolver,
- )
- fruits_conn: relay.Connection[Fruit] = relay.connection(
- resolver=fruits_resolver,
- )
- fruits_custom_pagination: FruitCustomPaginationConnection
-
- @relay.connection(relay.Connection[Fruit])
- def fruits_custom_resolver(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> List[Fruit]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- def fruits_custom_resolver_iterator(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> Iterator[Fruit]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- def fruits_custom_resolver_iterable(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> Iterable[Fruit]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- def fruits_custom_resolver_generator(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> Generator[Fruit, None, None]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- async def fruits_custom_resolver_async_iterator(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> AsyncIterator[Fruit]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- async def fruits_custom_resolver_async_iterable(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> AsyncIterable[Fruit]:
- ...
-
- @relay.connection(relay.Connection[Fruit])
- async def fruits_custom_resolver_async_generator(
- self,
- info: Info[Any, Any],
- name_endswith: Optional[str] = None,
- ) -> AsyncGenerator[Fruit, None]:
- ...
-
-reveal_type(Query.node)
-reveal_type(Query.nodes)
-reveal_type(Query.node_optional)
-reveal_type(Query.nodes_optional)
-reveal_type(Query.fruits)
-reveal_type(Query.fruits_conn)
-reveal_type(Query.fruits_custom_pagination)
-reveal_type(Query.fruits_custom_resolver)
-reveal_type(Query.fruits_custom_resolver_iterator)
-reveal_type(Query.fruits_custom_resolver_iterable)
-reveal_type(Query.fruits_custom_resolver_generator)
-reveal_type(Query.fruits_custom_resolver_async_iterator)
-reveal_type(Query.fruits_custom_resolver_async_iterable)
-reveal_type(Query.fruits_custom_resolver_async_generator)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="information",
- message='Type of "Query.node" is "Node"',
- line=132,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.nodes" is "List[Node]"',
- line=133,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.node_optional" is "Node | None"',
- line=134,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.nodes_optional" is "List[Node | None]"',
- line=135,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits" is "Connection[Fruit]"',
- line=136,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_conn" is "Connection[Fruit]"',
- line=137,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_pagination" is '
- '"FruitCustomPaginationConnection"',
- line=138,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver" is "Any"',
- line=139,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_iterator" is "Any"',
- line=140,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_iterable" is "Any"',
- line=141,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_generator" is "Any"',
- line=142,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_async_iterator" is ' '"Any"',
- line=143,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_async_iterable" is ' '"Any"',
- line=144,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "Query.fruits_custom_resolver_async_generator" is '
- '"Any"',
- line=145,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_scalars.py b/tests/pyright/test_scalars.py
deleted file mode 100644
index 5a86027ac6..0000000000
--- a/tests/pyright/test_scalars.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-from strawberry.scalars import ID, JSON, Base16, Base32, Base64
-
-
-@strawberry.type
-class SomeType:
- id: ID
- json: JSON
- base16: Base16
- base32: Base32
- base64: Base64
-
-
-obj = SomeType(
- id=ID("123"),
- json=JSON({"foo": "bar"}),
- base16=Base16(b""),
- base32=Base32(b""),
- base64=Base64(b""),
-)
-
-reveal_type(obj.id)
-reveal_type(obj.json)
-reveal_type(obj.base16)
-reveal_type(obj.base16)
-reveal_type(obj.base64)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- # NOTE: This is also guaranteeing that those scalars could be used to annotate
- # the attributes. Pyright 1.1.224+ doesn't allow non-types to be used there
- assert results == [
- Result(
- type="information",
- message='Type of "obj.id" is "ID"',
- line=23,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj.json" is "JSON"',
- line=24,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj.base16" is "Base16"',
- line=25,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj.base16" is "Base16"',
- line=26,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "obj.base64" is "Base64"',
- line=27,
- column=13,
- ),
- ]
-
-
-CODE_SCHEMA_OVERRIDES = """
-import strawberry
-from datetime import datetime, timezone
-
-EpochDateTime = strawberry.scalar(
- datetime,
-)
-
-@strawberry.type
-class Query:
- a: datetime
-
-schema = strawberry.Schema(query=Query, scalar_overrides={
- datetime: EpochDateTime,
-})
-
-reveal_type(EpochDateTime)
-"""
-
-
-def test_schema_overrides():
- # TODO: change strict to true when we improve type hints for scalar
- results = run_pyright(CODE_SCHEMA_OVERRIDES, strict=False)
-
- assert results == [
- Result(
- type="information",
- message='Type of "EpochDateTime" is "type[datetime]"',
- line=16,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_type.py b/tests/pyright/test_type.py
deleted file mode 100644
index 7e8d4e4b9f..0000000000
--- a/tests/pyright/test_type.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-from strawberry.type import StrawberryOptional, StrawberryList
-
-
-@strawberry.type
-class Fruit:
- name: str
-
-
-reveal_type(StrawberryOptional(Fruit))
-reveal_type(StrawberryList(Fruit))
-reveal_type(StrawberryOptional(StrawberryList(Fruit)))
-reveal_type(StrawberryList(StrawberryOptional(Fruit)))
-
-reveal_type(StrawberryOptional(str))
-reveal_type(StrawberryList(str))
-reveal_type(StrawberryOptional(StrawberryList(str)))
-reveal_type(StrawberryList(StrawberryOptional(str)))
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="information",
- message='Type of "StrawberryOptional(Fruit)" is "StrawberryOptional"',
- line=11,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryList(Fruit)" is "StrawberryList"',
- line=12,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryOptional(StrawberryList(Fruit))" is '
- '"StrawberryOptional"',
- line=13,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryList(StrawberryOptional(Fruit))" is '
- '"StrawberryList"',
- line=14,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryOptional(str)" is "StrawberryOptional"',
- line=16,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryList(str)" is "StrawberryList"',
- line=17,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryOptional(StrawberryList(str))" is '
- '"StrawberryOptional"',
- line=18,
- column=13,
- ),
- Result(
- type="information",
- message='Type of "StrawberryList(StrawberryOptional(str))" is '
- '"StrawberryList"',
- line=19,
- column=13,
- ),
- ]
diff --git a/tests/pyright/test_union.py b/tests/pyright/test_union.py
deleted file mode 100644
index e208c5c5ad..0000000000
--- a/tests/pyright/test_union.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from .utils import Result, requires_pyright, run_pyright, skip_on_windows
-
-pytestmark = [skip_on_windows, requires_pyright]
-
-
-CODE = """
-import strawberry
-from typing_extensions import TypeAlias, Annotated
-from typing import Union
-
-@strawberry.type
-class User:
- name: str
-
-
-@strawberry.type
-class Error:
- message: str
-
-UserOrError: TypeAlias = Annotated[
- Union[User, Error], strawberry.union("UserOrError")
-]
-
-reveal_type(UserOrError)
-
-x: UserOrError = User(name="Patrick")
-
-reveal_type(x)
-"""
-
-
-def test_pyright():
- results = run_pyright(CODE)
-
- assert results == [
- Result(
- type="information",
- message='Type of "UserOrError" is "type[User] | type[Error]"',
- line=19,
- column=13,
- ),
- Result(type="information", message='Type of "x" is "User"', line=23, column=13),
- ], f"Actual results:\n{results}"
diff --git a/tests/python_312/test_generic_objects.py b/tests/python_312/test_generic_objects.py
new file mode 100644
index 0000000000..107218e1fd
--- /dev/null
+++ b/tests/python_312/test_generic_objects.py
@@ -0,0 +1,646 @@
+# ruff: noqa: F821
+
+import datetime
+import sys
+from typing import List, Optional, Union
+from typing_extensions import Annotated
+
+import pytest
+
+import strawberry
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryTypeVar,
+ get_object_definition,
+)
+from strawberry.types.union import StrawberryUnion
+
+pytestmark = pytest.mark.skipif(
+ sys.version_info < (3, 12), reason="These are tests for Python 3.12+"
+)
+
+
+def test_basic_generic():
+ directive = object()
+
+ @strawberry.type
+ class Edge[T]:
+ node_field: T = strawberry.field(directives=[directive])
+
+ definition = get_object_definition(Edge, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "node_field"
+ assert isinstance(field.type, StrawberryTypeVar)
+ assert field.type.type_var.__name__ == "T"
+
+ # let's make a copy of this generic type
+ copy = get_object_definition(Edge, strict=True).copy_with({"T": str})
+
+ definition_copy = get_object_definition(copy, strict=True)
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "node_field"
+ assert field_copy.type is str
+ assert field_copy.directives == [directive]
+
+
+def test_generics_nested():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Connection[T]:
+ edge: Edge[T]
+
+ definition = get_object_definition(Connection, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "edge"
+ assert get_object_definition(field.type, strict=True).type_params[0].__name__ == "T"
+
+ # let's make a copy of this generic type
+ definition_copy = get_object_definition(
+ get_object_definition(Connection, strict=True).copy_with({"T": str}),
+ strict=True,
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "edge"
+
+
+def test_generics_name():
+ @strawberry.type(name="AnotherName")
+ class EdgeName:
+ node: str
+
+ @strawberry.type
+ class Connection[T]:
+ edge: T
+
+ definition_copy = get_object_definition(
+ get_object_definition(Connection, strict=True).copy_with({"T": EdgeName}),
+ strict=True,
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "edge"
+
+
+def test_generics_nested_in_list():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Connection[T]:
+ edges: List[Edge[T]]
+
+ definition = get_object_definition(Connection, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "edges"
+ assert isinstance(field.type, StrawberryList)
+ assert (
+ get_object_definition(field.type.of_type, strict=True).type_params[0].__name__
+ == "T"
+ )
+
+ # let's make a copy of this generic type
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}),
+ strict=True,
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "edges"
+ assert isinstance(field_copy.type, StrawberryList)
+
+
+def test_list_inside_generic():
+ @strawberry.type
+ class Value[T]:
+ valuation_date: datetime.date
+ value: T
+
+ @strawberry.type
+ class Foo:
+ string: Value[str]
+ strings: Value[List[str]]
+ optional_string: Value[Optional[str]]
+ optional_strings: Value[Optional[List[str]]]
+
+ definition = get_object_definition(Foo, strict=True)
+ assert not definition.is_graphql_generic
+ [
+ string_field,
+ strings_field,
+ optional_string_field,
+ optional_strings_field,
+ ] = definition.fields
+ assert string_field.python_name == "string"
+ assert strings_field.python_name == "strings"
+ assert optional_string_field.python_name == "optional_string"
+ assert optional_strings_field.python_name == "optional_strings"
+
+
+def test_generic_with_optional():
+ @strawberry.type
+ class Edge[T]:
+ node: Optional[T]
+
+ definition = get_object_definition(Edge, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "node"
+ assert isinstance(field.type, StrawberryOptional)
+ assert isinstance(field.type.of_type, StrawberryTypeVar)
+ assert field.type.of_type.type_var.__name__ == "T"
+
+ # let's make a copy of this generic type
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "node"
+ assert isinstance(field_copy.type, StrawberryOptional)
+ assert field_copy.type.of_type is str
+
+
+def test_generic_with_list():
+ @strawberry.type
+ class Connection[T]:
+ edges: List[T]
+
+ definition = get_object_definition(Connection, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "edges"
+ assert isinstance(field.type, StrawberryList)
+ assert isinstance(field.type.of_type, StrawberryTypeVar)
+ assert field.type.of_type.type_var.__name__ == "T"
+
+ # let's make a copy of this generic type
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "edges"
+ assert isinstance(field_copy.type, StrawberryList)
+ assert field_copy.type.of_type is str
+
+
+def test_generic_with_list_of_optionals():
+ @strawberry.type
+ class Connection[T]:
+ edges: List[Optional[T]]
+
+ definition = get_object_definition(Connection, strict=True)
+ assert definition.is_graphql_generic
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "edges"
+ assert isinstance(field.type, StrawberryList)
+ assert isinstance(field.type.of_type, StrawberryOptional)
+ assert isinstance(field.type.of_type.of_type, StrawberryTypeVar)
+ assert field.type.of_type.of_type.type_var.__name__ == "T"
+
+ # let's make a copy of this generic type
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "edges"
+ assert isinstance(field_copy.type, StrawberryList)
+ assert isinstance(field_copy.type.of_type, StrawberryOptional)
+ assert field_copy.type.of_type.of_type is str
+
+
+def test_generics_with_unions():
+ @strawberry.type
+ class Error:
+ message: str
+
+ @strawberry.type
+ class Edge[T]:
+ node: Union[Error, T]
+
+ definition = get_object_definition(Edge, strict=True)
+ assert definition.type_params[0].__name__ == "T"
+
+ [field] = definition.fields
+ assert field.python_name == "node"
+ assert isinstance(field.type, StrawberryUnion)
+ assert field.type.types[0] is Error
+ assert isinstance(field.type.types[1], StrawberryTypeVar)
+ assert field.type.types[1].type_var.__name__ == "T"
+
+ # let's make a copy of this generic type
+ @strawberry.type
+ class Node:
+ name: str
+
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": Node}), strict=True
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+
+ [field_copy] = definition_copy.fields
+ assert field_copy.python_name == "node"
+ assert isinstance(field_copy.type, StrawberryUnion)
+ assert field_copy.type.types == (Error, Node)
+
+
+def test_using_generics():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class User:
+ name: str
+
+ @strawberry.type
+ class Query:
+ user: Edge[User]
+
+ definition = get_object_definition(Query, strict=True)
+
+ [field] = definition.fields
+ assert field.python_name == "user"
+
+ user_edge_definition = get_object_definition(field.type, strict=True)
+ assert not user_edge_definition.is_graphql_generic
+
+ [node_field] = user_edge_definition.fields
+ assert node_field.python_name == "node"
+ assert node_field.type is User
+
+
+def test_using_generics_nested():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Connection[T]:
+ edges: Edge[T]
+
+ @strawberry.type
+ class User:
+ name: str
+
+ @strawberry.type
+ class Query:
+ users: Connection[User]
+
+ connection_definition = get_object_definition(Connection, strict=True)
+ assert connection_definition.is_graphql_generic
+ assert connection_definition.type_params[0].__name__ == "T"
+
+ query_definition = get_object_definition(Query, strict=True)
+
+ [user_field] = query_definition.fields
+ assert user_field.python_name == "users"
+
+ user_connection_definition = get_object_definition(user_field.type, strict=True)
+ assert not user_connection_definition.is_graphql_generic
+
+ [edges_field] = user_connection_definition.fields
+ assert edges_field.python_name == "edges"
+
+
+def test_using_generics_raises_when_missing_annotation():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ error_message = (
+ f'Query fields cannot be resolved. The type "{Edge!r}" '
+ "is generic, but no type has been passed"
+ )
+
+ @strawberry.type
+ class Query:
+ user: Edge
+
+ with pytest.raises(TypeError, match=error_message):
+ strawberry.Schema(Query)
+
+
+def test_using_generics_raises_when_missing_annotation_nested():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Connection[T]:
+ edges: List[Edge[T]]
+
+ @strawberry.type
+ class User:
+ name: str
+
+ error_message = (
+ f'Query fields cannot be resolved. The type "{Connection!r}" '
+ "is generic, but no type has been passed"
+ )
+
+ @strawberry.type
+ class Query:
+ users: Connection
+
+ with pytest.raises(TypeError, match=error_message):
+ strawberry.Schema(Query)
+
+
+def test_generics_inside_optional():
+ @strawberry.type
+ class Error:
+ message: str
+
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Query:
+ user: Optional[Edge[str]]
+
+ query_definition = get_object_definition(Query, strict=True)
+ assert query_definition.type_params == []
+
+ [field] = query_definition.fields
+ assert field.python_name == "user"
+ assert isinstance(field.type, StrawberryOptional)
+
+ str_edge_definition = get_object_definition(field.type.of_type, strict=True)
+ assert not str_edge_definition.is_graphql_generic
+
+
+def test_generics_inside_list():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Query:
+ user: List[Edge[str]]
+
+ query_definition = get_object_definition(Query, strict=True)
+ assert query_definition.type_params == []
+
+ [field] = query_definition.fields
+ assert field.python_name == "user"
+ assert isinstance(field.type, StrawberryList)
+
+ str_edge_definition = get_object_definition(field.type.of_type, strict=True)
+ assert not str_edge_definition.is_graphql_generic
+
+
+def test_generics_inside_unions():
+ @strawberry.type
+ class Error:
+ message: str
+
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Query:
+ user: Union[Edge[str], Error]
+
+ query_definition = get_object_definition(Query, strict=True)
+ assert query_definition.type_params == []
+
+ [field] = query_definition.fields
+ assert field.python_name == "user"
+ assert not isinstance(field.type, StrawberryOptional)
+
+ union = field.type
+ assert isinstance(union, StrawberryUnion)
+ assert not get_object_definition(union.types[0], strict=True).is_graphql_generic
+
+
+def test_multiple_generics_inside_unions():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.type
+ class Query:
+ user: Union[Edge[int], Edge[str]]
+
+ query_definition = get_object_definition(Query, strict=True)
+ assert query_definition.type_params == []
+
+ [user_field] = query_definition.fields
+ assert user_field.python_name == "user"
+ assert not isinstance(user_field.type, StrawberryOptional)
+
+ union = user_field.type
+ assert isinstance(union, StrawberryUnion)
+
+ int_edge_definition = get_object_definition(union.types[0], strict=True)
+ assert not int_edge_definition.is_graphql_generic
+ assert int_edge_definition.fields[0].type is int
+
+ str_edge_definition = get_object_definition(union.types[1], strict=True)
+ assert not str_edge_definition.is_graphql_generic
+ assert str_edge_definition.fields[0].type is str
+
+
+def test_union_inside_generics():
+ @strawberry.type
+ class Dog:
+ name: str
+
+ @strawberry.type
+ class Cat:
+ name: str
+
+ @strawberry.type
+ class Connection[T]:
+ nodes: List[T]
+
+ DogCat = Annotated[Union[Dog, Cat], strawberry.union("DogCat")]
+
+ @strawberry.type
+ class Query:
+ connection: Connection[DogCat]
+
+ query_definition = get_object_definition(Query, strict=True)
+ assert query_definition.type_params == []
+
+ [connection_field] = query_definition.fields
+ assert connection_field.python_name == "connection"
+ assert not isinstance(connection_field, StrawberryOptional)
+
+ dog_cat_connection_definition = get_object_definition(
+ connection_field.type, strict=True
+ )
+
+ [node_field] = dog_cat_connection_definition.fields
+ assert isinstance(node_field.type, StrawberryList)
+
+ union = dog_cat_connection_definition.fields[0].type.of_type
+ assert isinstance(union, StrawberryUnion)
+
+
+def test_anonymous_union_inside_generics():
+ @strawberry.type
+ class Dog:
+ name: str
+
+ @strawberry.type
+ class Cat:
+ name: str
+
+ @strawberry.type
+ class Connection[T]:
+ nodes: List[T]
+
+ @strawberry.type
+ class Query:
+ connection: Connection[Union[Dog, Cat]]
+
+ definition = get_object_definition(Query, strict=True)
+ assert definition.type_params == []
+
+ [connection_field] = definition.fields
+ assert connection_field.python_name == "connection"
+
+ dog_cat_connection_definition = get_object_definition(
+ connection_field.type, strict=True
+ )
+
+ [node_field] = dog_cat_connection_definition.fields
+ assert isinstance(node_field.type, StrawberryList)
+
+ union = node_field.type.of_type
+ assert isinstance(union, StrawberryUnion)
+
+
+def test_using_generics_with_interfaces():
+ @strawberry.type
+ class Edge[T]:
+ node: T
+
+ @strawberry.interface
+ class WithName:
+ name: str
+
+ @strawberry.type
+ class Query:
+ user: Edge[WithName]
+
+ query_definition = get_object_definition(Query, strict=True)
+
+ [user_field] = query_definition.fields
+ assert user_field.python_name == "user"
+
+ with_name_definition = get_object_definition(user_field.type, strict=True)
+ assert not with_name_definition.is_graphql_generic
+
+ [node_field] = with_name_definition.fields
+ assert node_field.python_name == "node"
+ assert node_field.type is WithName
+
+
+def test_generic_with_arguments():
+ @strawberry.type
+ class Collection[T]:
+ @strawberry.field
+ def by_id(self, ids: List[int]) -> List[T]:
+ return []
+
+ @strawberry.type
+ class Post:
+ name: str
+
+ @strawberry.type
+ class Query:
+ user: Collection[Post]
+
+ query_definition = get_object_definition(Query, strict=True)
+
+ [user_field] = query_definition.fields
+ assert user_field.python_name == "user"
+
+ post_collection_definition = get_object_definition(user_field.type, strict=True)
+ assert not post_collection_definition.is_graphql_generic
+
+ [by_id_field] = post_collection_definition.fields
+ assert by_id_field.python_name == "by_id"
+ assert isinstance(by_id_field.type, StrawberryList)
+ assert by_id_field.type.of_type is Post
+
+ [ids_argument] = by_id_field.arguments
+ assert ids_argument.python_name == "ids"
+ assert isinstance(ids_argument.type, StrawberryList)
+ assert ids_argument.type.of_type is int
+
+
+def test_federation():
+ @strawberry.federation.type(keys=["id"])
+ class Edge[T]:
+ id: strawberry.ID
+ node_field: T
+
+ definition = get_object_definition(Edge, strict=True)
+
+ definition_copy = get_object_definition(
+ definition.copy_with({"T": str}), strict=True
+ )
+
+ assert not definition_copy.is_graphql_generic
+ assert definition_copy.type_params == []
+ assert definition_copy.directives == definition.directives
+
+ [field1_copy, field2_copy] = definition_copy.fields
+
+ assert field1_copy.python_name == "id"
+ assert field1_copy.type is strawberry.ID
+
+ assert field2_copy.python_name == "node_field"
+ assert field2_copy.type is str
diff --git a/tests/python_312/test_generics.py b/tests/python_312/test_generics_schema.py
similarity index 98%
rename from tests/python_312/test_generics.py
rename to tests/python_312/test_generics_schema.py
index 932b670e56..fb8edb6131 100644
--- a/tests/python_312/test_generics.py
+++ b/tests/python_312/test_generics_schema.py
@@ -52,8 +52,7 @@ class Edge[T]:
node_field: T
@strawberry.type
- class IntEdge(Edge[int]):
- ...
+ class IntEdge(Edge[int]): ...
@strawberry.type
class Query:
@@ -86,12 +85,10 @@ class Edge[T]:
node_field: T
@strawberry.type
- class IntEdge(Edge[int]):
- ...
+ class IntEdge(Edge[int]): ...
@strawberry.type
- class IntEdgeSubclass(IntEdge):
- ...
+ class IntEdgeSubclass(IntEdge): ...
@strawberry.type
class Query:
@@ -128,8 +125,7 @@ class Edge[T]:
node_field: T
@strawberry.type
- class FruitEdge(Edge[Fruit]):
- ...
+ class FruitEdge(Edge[Fruit]): ...
@strawberry.type
class Query:
@@ -172,8 +168,7 @@ class Edge[T]:
nodes: List[T]
@strawberry.type
- class FruitEdge(Edge[Fruit]):
- ...
+ class FruitEdge(Edge[Fruit]): ...
@strawberry.type
class Query:
@@ -1029,8 +1024,7 @@ class INode:
fields: List[Self]
@strawberry.type
- class Node(INode):
- ...
+ class Node(INode): ...
schema = strawberry.Schema(query=Node)
diff --git a/tests/python_312/test_inspect.py b/tests/python_312/test_inspect.py
index 5d61db3eb4..e894e36dac 100644
--- a/tests/python_312/test_inspect.py
+++ b/tests/python_312/test_inspect.py
@@ -13,92 +13,96 @@ def test_get_specialized_type_var_map_non_generic(value: type):
def test_get_specialized_type_var_map_generic_not_specialized():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
assert get_specialized_type_var_map(Foo) == {}
def test_get_specialized_type_var_map_generic():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar(Foo[int]):
- ...
+ class Bar(Foo[int]): ...
+
+ assert get_specialized_type_var_map(Bar) == {"_T": int}
+
+
+def test_get_specialized_type_var_map_from_alias():
+ @strawberry.type
+ class Foo[_T]: ...
+
+ SpecializedFoo = Foo[int]
+
+ assert get_specialized_type_var_map(SpecializedFoo) == {"_T": int}
+
+
+def test_get_specialized_type_var_map_from_alias_with_inheritance():
+ @strawberry.type
+ class Foo[_T]: ...
+
+ SpecializedFoo = Foo[int]
+
+ @strawberry.type
+ class Bar(SpecializedFoo): ...
assert get_specialized_type_var_map(Bar) == {"_T": int}
def test_get_specialized_type_var_map_generic_subclass():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar(Foo[int]):
- ...
+ class Bar(Foo[int]): ...
@strawberry.type
- class BarSubclass(Bar):
- ...
+ class BarSubclass(Bar): ...
assert get_specialized_type_var_map(BarSubclass) == {"_T": int}
def test_get_specialized_type_var_map_double_generic():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar[_T](Foo[_T]):
- ...
+ class Bar[_T](Foo[_T]): ...
@strawberry.type
- class Bin(Bar[int]):
- ...
+ class Bin(Bar[int]): ...
assert get_specialized_type_var_map(Bin) == {"_T": int}
def test_get_specialized_type_var_map_double_generic_subclass():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar[_T](Foo[_T]):
- ...
+ class Bar[_T](Foo[_T]): ...
@strawberry.type
- class Bin(Bar[int]):
- ...
+ class Bin(Bar[int]): ...
@strawberry.type
- class BinSubclass(Bin):
- ...
+ class BinSubclass(Bin): ...
assert get_specialized_type_var_map(Bin) == {"_T": int}
def test_get_specialized_type_var_map_multiple_inheritance():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar[_K]:
- ...
+ class Bar[_K]: ...
@strawberry.type
- class Bin(Foo[int]):
- ...
+ class Bin(Foo[int]): ...
@strawberry.type
- class Baz(Bin, Bar[str]):
- ...
+ class Baz(Bin, Bar[str]): ...
assert get_specialized_type_var_map(Baz) == {
"_T": int,
@@ -108,24 +112,19 @@ class Baz(Bin, Bar[str]):
def test_get_specialized_type_var_map_multiple_inheritance_subclass():
@strawberry.type
- class Foo[_T]:
- ...
+ class Foo[_T]: ...
@strawberry.type
- class Bar[_K]:
- ...
+ class Bar[_K]: ...
@strawberry.type
- class Bin(Foo[int]):
- ...
+ class Bin(Foo[int]): ...
@strawberry.type
- class Baz(Bin, Bar[str]):
- ...
+ class Baz(Bin, Bar[str]): ...
@strawberry.type
- class BazSubclass(Baz):
- ...
+ class BazSubclass(Baz): ...
assert get_specialized_type_var_map(BazSubclass) == {
"_T": int,
diff --git a/tests/python_312/test_python_generics.py b/tests/python_312/test_python_generics.py
index 55de842d29..7f3a8b1b90 100644
--- a/tests/python_312/test_python_generics.py
+++ b/tests/python_312/test_python_generics.py
@@ -1,6 +1,4 @@
-"""
-These tests are for Generics that don't expose any generic parts to the schema.
-"""
+"""These tests are for Generics that don't expose any generic parts to the schema."""
import sys
import textwrap
diff --git a/tests/relay/schema.py b/tests/relay/schema.py
index 01abc1b0ac..5bd8cea2be 100644
--- a/tests/relay/schema.py
+++ b/tests/relay/schema.py
@@ -12,7 +12,7 @@
Optional,
cast,
)
-from typing_extensions import Annotated, Self
+from typing_extensions import Annotated, Self, TypeAlias
import strawberry
from strawberry import relay
@@ -31,7 +31,7 @@ class Fruit(relay.Node):
def resolve_nodes(
cls,
*,
- info: Info[None, None],
+ info: strawberry.Info,
node_ids: Iterable[str],
required: bool = False,
) -> Iterable[Optional[Self]]:
@@ -41,7 +41,7 @@ def resolve_nodes(
return fruits.values()
@classmethod
- def is_type_of(cls, obj: Any, _info: Info[None, None]) -> bool:
+ def is_type_of(cls, obj: Any, _info: strawberry.Info) -> bool:
# This is here to support FruitConcrete, which is mimicing an integration
# object which would return an object alike Fruit (e.g. the django integration)
return isinstance(obj, (cls, FruitConcrete))
@@ -64,7 +64,7 @@ class FruitAsync(relay.Node):
async def resolve_nodes(
cls,
*,
- info: Optional[Info[None, None]] = None,
+ info: Optional[Info] = None,
node_ids: Iterable[str],
required: bool = False,
) -> Iterable[Optional[Self]]:
@@ -77,7 +77,7 @@ async def resolve_nodes(
return fruits_async.values()
@classmethod
- async def resolve_id(cls, root: Self, *, info: Info[None, None]) -> str:
+ async def resolve_id(cls, root: Self, *, info: strawberry.Info) -> str:
return str(root.id)
@@ -92,7 +92,7 @@ def resolve_connection(
cls,
nodes: Iterable[Fruit],
*,
- info: Optional[Info[None, None]] = None,
+ info: Optional[Info] = None,
total_count: Optional[int] = None,
before: Optional[str] = None,
after: Optional[str] = None,
@@ -165,7 +165,7 @@ class FruitAlike(NamedTuple):
class FruitAlikeConnection(relay.ListConnection[Fruit]):
@classmethod
def resolve_node(
- cls, node: FruitAlike, *, info: Info[None, None], **kwargs: Any
+ cls, node: FruitAlike, *, info: strawberry.Info, **kwargs: Any
) -> Fruit:
return Fruit(
id=node.id,
@@ -185,10 +185,15 @@ async def fruits_async_resolver() -> Iterable[FruitAsync]:
class DummyPermission(BasePermission):
message = "Dummy message"
- async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
+ async def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: Any
+ ) -> bool:
return True
+FruitsListConnectionAlias: TypeAlias = relay.ListConnection[Fruit]
+
+
@strawberry.type
class Query:
node: relay.Node = relay.node()
@@ -202,6 +207,11 @@ class Query:
fruits_lazy: relay.ListConnection[
Annotated["Fruit", strawberry.lazy("tests.relay.schema")]
] = relay.connection(resolver=fruits_resolver)
+ fruits_alias: FruitsListConnectionAlias = relay.connection(resolver=fruits_resolver)
+ fruits_alias_lazy: Annotated[
+ "FruitsListConnectionAlias",
+ strawberry.lazy("tests.relay.schema"),
+ ] = relay.connection(resolver=fruits_resolver)
fruits_async: relay.ListConnection[FruitAsync] = relay.connection(
resolver=fruits_async_resolver
)
@@ -212,7 +222,7 @@ class Query:
@relay.connection(relay.ListConnection[Fruit])
def fruits_concrete_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Fruit]:
# This is mimicing integrations, like Django
@@ -232,7 +242,7 @@ def fruits_concrete_resolver(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Fruit]:
return [
@@ -244,7 +254,7 @@ def fruits_custom_resolver(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_lazy(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Annotated["Fruit", strawberry.lazy("tests.relay.schema")]]:
return [
@@ -256,7 +266,7 @@ def fruits_custom_resolver_lazy(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_iterator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Iterator[Fruit]:
for f in fruits.values():
@@ -266,7 +276,7 @@ def fruits_custom_resolver_iterator(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_iterable(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Iterator[Fruit]:
for f in fruits.values():
@@ -276,7 +286,7 @@ def fruits_custom_resolver_iterable(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_generator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Generator[Fruit, None, None]:
for f in fruits.values():
@@ -286,7 +296,7 @@ def fruits_custom_resolver_generator(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_iterable(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncIterable[Fruit]:
for f in fruits.values():
@@ -296,7 +306,7 @@ async def fruits_custom_resolver_async_iterable(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_iterator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncIterator[Fruit]:
for f in fruits.values():
@@ -306,7 +316,7 @@ async def fruits_custom_resolver_async_iterator(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_generator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncGenerator[Fruit, None]:
for f in fruits.values():
@@ -316,7 +326,7 @@ async def fruits_custom_resolver_async_generator(
@relay.connection(FruitAlikeConnection)
def fruit_alike_connection_custom_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[FruitAlike]:
return [
@@ -340,11 +350,10 @@ class Mutation:
@strawberry.mutation
def create_fruit(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name: str,
color: str,
- ) -> CreateFruitPayload:
- ...
+ ) -> CreateFruitPayload: ...
schema = strawberry.Schema(query=Query, mutation=Mutation)
diff --git a/tests/relay/schema_future_annotations.py b/tests/relay/schema_future_annotations.py
index 3c8e847cc3..464c197f0c 100644
--- a/tests/relay/schema_future_annotations.py
+++ b/tests/relay/schema_future_annotations.py
@@ -33,7 +33,7 @@ class Fruit(relay.Node):
def resolve_nodes(
cls,
*,
- info: Info[None, None],
+ info: strawberry.Info,
node_ids: Iterable[str],
required: bool = False,
) -> Iterable[Optional[Self]]:
@@ -43,7 +43,7 @@ def resolve_nodes(
return fruits.values()
@classmethod
- def is_type_of(cls, obj: Any, _info: Info[None, None]) -> bool:
+ def is_type_of(cls, obj: Any, _info: strawberry.Info) -> bool:
# This is here to support FruitConcrete, which is mimicing an integration
# object which would return an object alike Fruit (e.g. the django integration)
return isinstance(obj, (cls, FruitConcrete))
@@ -66,7 +66,7 @@ class FruitAsync(relay.Node):
async def resolve_nodes(
cls,
*,
- info: Optional[Info[None, None]] = None,
+ info: Optional[Info] = None,
node_ids: Iterable[str],
required: bool = False,
) -> Iterable[Optional[Self]]:
@@ -79,7 +79,7 @@ async def resolve_nodes(
return fruits_async.values()
@classmethod
- async def resolve_id(cls, root: Self, *, info: Info[None, None]) -> str:
+ async def resolve_id(cls, root: Self, *, info: strawberry.Info) -> str:
return str(root.id)
@@ -94,7 +94,7 @@ def resolve_connection(
cls,
nodes: Iterable[Fruit],
*,
- info: Optional[Info[None, None]] = None,
+ info: Optional[Info] = None,
total_count: Optional[int] = None,
before: Optional[str] = None,
after: Optional[str] = None,
@@ -167,7 +167,7 @@ class FruitAlike(NamedTuple):
class FruitAlikeConnection(relay.ListConnection[Fruit]):
@classmethod
def resolve_node(
- cls, node: FruitAlike, *, info: Info[None, None], **kwargs: Any
+ cls, node: FruitAlike, *, info: strawberry.Info, **kwargs: Any
) -> Fruit:
return Fruit(
id=node.id,
@@ -187,7 +187,9 @@ async def fruits_async_resolver() -> Iterable[FruitAsync]:
class DummyPermission(BasePermission):
message = "Dummy message"
- async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
+ async def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: Any
+ ) -> bool:
return True
@@ -214,7 +216,7 @@ class Query:
@relay.connection(relay.ListConnection[Fruit])
def fruits_concrete_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Fruit]:
# This is mimicing integrations, like Django
@@ -234,7 +236,7 @@ def fruits_concrete_resolver(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Fruit]:
return [
@@ -246,7 +248,7 @@ def fruits_custom_resolver(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_lazy(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[Annotated[Fruit, strawberry.lazy("tests.relay.schema")]]:
return [
@@ -258,7 +260,7 @@ def fruits_custom_resolver_lazy(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_iterator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Iterator[Fruit]:
for f in fruits.values():
@@ -268,7 +270,7 @@ def fruits_custom_resolver_iterator(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_iterable(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Iterator[Fruit]:
for f in fruits.values():
@@ -278,7 +280,7 @@ def fruits_custom_resolver_iterable(
@relay.connection(relay.ListConnection[Fruit])
def fruits_custom_resolver_generator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> Generator[Fruit, None, None]:
for f in fruits.values():
@@ -288,7 +290,7 @@ def fruits_custom_resolver_generator(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_iterable(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncIterable[Fruit]:
for f in fruits.values():
@@ -298,7 +300,7 @@ async def fruits_custom_resolver_async_iterable(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_iterator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncIterator[Fruit]:
for f in fruits.values():
@@ -308,7 +310,7 @@ async def fruits_custom_resolver_async_iterator(
@relay.connection(relay.ListConnection[Fruit])
async def fruits_custom_resolver_async_generator(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> AsyncGenerator[Fruit, None]:
for f in fruits.values():
@@ -318,7 +320,7 @@ async def fruits_custom_resolver_async_generator(
@relay.connection(FruitAlikeConnection)
def fruit_alike_connection_custom_resolver(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name_endswith: Optional[str] = None,
) -> List[FruitAlike]:
return [
@@ -342,11 +344,10 @@ class Mutation:
@strawberry.mutation
def create_fruit(
self,
- info: Info[None, None],
+ info: strawberry.Info,
name: str,
color: str,
- ) -> CreateFruitPayload:
- ...
+ ) -> CreateFruitPayload: ...
schema = strawberry.Schema(query=Query, mutation=Mutation)
diff --git a/tests/relay/snapshots/schema.gql b/tests/relay/snapshots/schema.gql
index 45eb0a8640..93c0fc7337 100644
--- a/tests/relay/snapshots/schema.gql
+++ b/tests/relay/snapshots/schema.gql
@@ -146,6 +146,32 @@ type Query {
"""Returns the items in the list that come after the specified cursor."""
last: Int = null
): FruitConnection!
+ fruitsAlias(
+ """Returns the items in the list that come before the specified cursor."""
+ before: String = null
+
+ """Returns the items in the list that come after the specified cursor."""
+ after: String = null
+
+ """Returns the first n items from the list."""
+ first: Int = null
+
+ """Returns the items in the list that come after the specified cursor."""
+ last: Int = null
+ ): FruitConnection!
+ fruitsAliasLazy(
+ """Returns the items in the list that come before the specified cursor."""
+ before: String = null
+
+ """Returns the items in the list that come after the specified cursor."""
+ after: String = null
+
+ """Returns the first n items from the list."""
+ first: Int = null
+
+ """Returns the items in the list that come after the specified cursor."""
+ last: Int = null
+ ): FruitConnection!
fruitsAsync(
"""Returns the items in the list that come before the specified cursor."""
before: String = null
diff --git a/tests/relay/test_exceptions.py b/tests/relay/test_exceptions.py
index f59b2eb9e1..174c3748f7 100644
--- a/tests/relay/test_exceptions.py
+++ b/tests/relay/test_exceptions.py
@@ -3,7 +3,8 @@
import pytest
import strawberry
-from strawberry import relay
+from strawberry import Info, relay
+from strawberry.relay import GlobalID
from strawberry.relay.exceptions import (
NodeIDAnnotationError,
RelayWrongAnnotationError,
@@ -16,6 +17,52 @@ class NonNodeType:
foo: str
+def test_raises_error_on_unknown_node_type_in_global_id():
+ @strawberry.type
+ class Query:
+ @strawberry.field()
+ def test(self, info: Info) -> GlobalID:
+ _id = GlobalID("foo", "bar")
+ _id.resolve_type(info)
+ return _id
+
+ schema = strawberry.Schema(query=Query)
+
+ result = schema.execute_sync("""
+ query TestQuery {
+ test
+ }
+ """)
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message
+ == "Cannot resolve. GlobalID requires a GraphQL type, received `foo`."
+ )
+
+
+def test_raises_error_on_non_node_type_in_global_id():
+ @strawberry.type
+ class Query:
+ @strawberry.field()
+ def test(self, info: Info) -> GlobalID:
+ _id = GlobalID("NonNodeType", "bar")
+ _id.resolve_type(info)
+ return _id
+
+ schema = strawberry.Schema(query=Query, types=(NonNodeType,))
+
+ result = schema.execute_sync("""
+ query TestQuery {
+ test
+ }
+ """)
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message == "Cannot resolve. GlobalID requires a GraphQL Node "
+ "type, received `NonNodeType`."
+ )
+
+
@pytest.mark.raises_strawberry_exception(
NodeIDAnnotationError,
match='No field annotated with `NodeID` found in "Fruit"',
@@ -28,8 +75,7 @@ class Fruit(relay.Node):
@strawberry.type
class Query:
@relay.connection(relay.ListConnection[Fruit])
- def fruits(self) -> List[Fruit]:
- ...
+ def fruits(self) -> List[Fruit]: ...
strawberry.Schema(query=Query)
@@ -47,8 +93,7 @@ class Fruit(relay.Node):
@strawberry.type
class Query:
@relay.connection(relay.ListConnection[Fruit])
- def fruits(self) -> List[Fruit]:
- ...
+ def fruits(self) -> List[Fruit]: ...
strawberry.Schema(query=Query)
@@ -87,8 +132,7 @@ class Fruit(relay.Node):
@strawberry.type
class Query:
@relay.connection(List[Fruit]) # type: ignore
- def custom_resolver(self) -> List[Fruit]:
- ...
+ def custom_resolver(self) -> List[Fruit]: ...
strawberry.Schema(query=Query)
@@ -108,7 +152,6 @@ class Fruit(relay.Node):
@strawberry.type
class Query:
@relay.connection(relay.Connection[Fruit]) # type: ignore
- def custom_resolver(self):
- ...
+ def custom_resolver(self): ...
strawberry.Schema(query=Query)
diff --git a/tests/relay/test_fields.py b/tests/relay/test_fields.py
index 0c0cd4722d..9993697d17 100644
--- a/tests/relay/test_fields.py
+++ b/tests/relay/test_fields.py
@@ -7,13 +7,12 @@
import strawberry
from strawberry import relay
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument
-from strawberry.field import StrawberryField
from strawberry.relay.fields import ConnectionExtension
from strawberry.relay.utils import to_base64
from strawberry.schema.types.scalar import DEFAULT_SCALAR_REGISTRY
+from strawberry.types.arguments import StrawberryArgument
+from strawberry.types.field import StrawberryField
from strawberry.types.fields.resolver import StrawberryResolver
-from strawberry.types.info import Info
from .schema import FruitAsync, schema
@@ -364,6 +363,8 @@ async def test_query_nodes_optional_async():
attrs = [
"fruits",
"fruitsLazy",
+ "fruitsAlias",
+ "fruitsAliasLazy",
"fruitsConcreteResolver",
"fruitsCustomResolver",
"fruitsCustomResolverLazy",
@@ -1479,8 +1480,7 @@ def arguments(self, value: List[StrawberryArgument]):
class Fruit(relay.Node):
code: relay.NodeID[str]
- def resolver(info: Info) -> List[Fruit]:
- ...
+ def resolver(info: strawberry.Info) -> List[Fruit]: ...
@strawberry.type
class Query:
@@ -1597,9 +1597,8 @@ class Query:
async def test_query_before_error():
- """
- Verify if the error raised on a non-existing before hash
- raises the correct error
+ """Verify if the error raised on a non-existing before hash
+ raises the correct error.
"""
# with pytest.raises(ValueError):
index = to_base64("Fake", 9292292)
@@ -1612,9 +1611,8 @@ async def test_query_before_error():
def test_query_after_error():
- """
- Verify if the error raised on a non-existing before hash
- raises the correct error
+ """Verify if the error raised on a non-existing before hash
+ raises the correct error.
"""
index = to_base64("Fake", 9292292)
result = schema.execute_sync(
diff --git a/tests/relay/test_schema.py b/tests/relay/test_schema.py
index dbe98aee03..a1bca4cb8a 100644
--- a/tests/relay/test_schema.py
+++ b/tests/relay/test_schema.py
@@ -149,8 +149,7 @@ class BaseFruit(relay.Node):
code: relay.NodeID[int]
@strawberry.type
- class Fruit(BaseFruit):
- ...
+ class Fruit(BaseFruit): ...
@strawberry.type
class Query:
diff --git a/tests/relay/test_types.py b/tests/relay/test_types.py
index 3887454b36..1171d7896a 100644
--- a/tests/relay/test_types.py
+++ b/tests/relay/test_types.py
@@ -1,5 +1,6 @@
from typing import Any, AsyncGenerator, AsyncIterable, Optional, Union, cast
from typing_extensions import assert_type
+from unittest.mock import MagicMock
import pytest
@@ -8,7 +9,7 @@
from strawberry.relay.utils import to_base64
from strawberry.types.info import Info
-from .schema import Fruit, FruitAsync, schema
+from .schema import Fruit, FruitAsync, fruits_resolver, schema
class FakeInfo:
@@ -80,8 +81,7 @@ def test_global_id_resolve_node_sync_ensure_type():
def test_global_id_resolve_node_sync_ensure_type_with_union():
- class Foo:
- ...
+ class Foo: ...
gid = relay.GlobalID(type_name="Fruit", node_id="1")
fruit = gid.resolve_node_sync(fake_info, ensure_type=Union[Fruit, Foo])
@@ -92,8 +92,7 @@ class Foo:
def test_global_id_resolve_node_sync_ensure_type_wrong_type():
- class Foo:
- ...
+ class Foo: ...
gid = relay.GlobalID(type_name="Fruit", node_id="1")
with pytest.raises(TypeError):
@@ -132,8 +131,7 @@ async def test_global_id_resolve_node_ensure_type():
async def test_global_id_resolve_node_ensure_type_with_union():
- class Foo:
- ...
+ class Foo: ...
gid = relay.GlobalID(type_name="FruitAsync", node_id="1")
fruit = await gid.resolve_node(fake_info, ensure_type=Union[FruitAsync, Foo])
@@ -144,8 +142,7 @@ class Foo:
async def test_global_id_resolve_node_ensure_type_wrong_type():
- class Foo:
- ...
+ class Foo: ...
gid = relay.GlobalID(type_name="FruitAsync", node_id="1")
with pytest.raises(TypeError):
@@ -259,3 +256,68 @@ def fruit(self) -> Fruit:
return Fruit(color="red") # pragma: no cover
strawberry.Schema(query=Query)
+
+
+def test_list_connection_without_edges_or_page_info(mocker: MagicMock):
+ @strawberry.type(name="Connection", description="A connection to a list of items.")
+ class DummyListConnectionWithTotalCount(relay.ListConnection[relay.NodeType]):
+ @strawberry.field(description="Total quantity of existing nodes.")
+ def total_count(self) -> int:
+ return -1
+
+ @strawberry.type
+ class Query:
+ fruits: DummyListConnectionWithTotalCount[Fruit] = relay.connection(
+ resolver=fruits_resolver
+ )
+
+ mock = mocker.patch("strawberry.relay.types.Edge.resolve_edge")
+ schema = strawberry.Schema(query=Query)
+ ret = schema.execute_sync(
+ """
+ query {
+ fruits {
+ totalCount
+ }
+ }
+ """
+ )
+ mock.assert_not_called()
+ assert ret.errors is None
+ assert ret.data == {
+ "fruits": {
+ "totalCount": -1,
+ }
+ }
+
+
+def test_list_connection_with_nested_fragments():
+ ret = schema.execute_sync(
+ """
+ query {
+ fruits {
+ ...FruitFragment
+ }
+ }
+
+ fragment FruitFragment on FruitConnection {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ """
+ )
+ assert ret.errors is None
+ assert ret.data == {
+ "fruits": {
+ "edges": [
+ {"node": {"id": "RnJ1aXQ6MQ=="}},
+ {"node": {"id": "RnJ1aXQ6Mg=="}},
+ {"node": {"id": "RnJ1aXQ6Mw=="}},
+ {"node": {"id": "RnJ1aXQ6NA=="}},
+ {"node": {"id": "RnJ1aXQ6NQ=="}},
+ ]
+ }
+ }
diff --git a/tests/relay/test_utils.py b/tests/relay/test_utils.py
index 8811df8f0a..e14a375522 100644
--- a/tests/relay/test_utils.py
+++ b/tests/relay/test_utils.py
@@ -1,9 +1,19 @@
+from __future__ import annotations
+
+import sys
from typing import Any
+from unittest import mock
import pytest
-from strawberry.relay.utils import from_base64, to_base64
-from strawberry.type import get_object_definition
+from strawberry.relay.types import PREFIX
+from strawberry.relay.utils import (
+ SliceMetadata,
+ from_base64,
+ to_base64,
+)
+from strawberry.schema.config import StrawberryConfig
+from strawberry.types.base import get_object_definition
from .schema import Fruit
@@ -61,3 +71,110 @@ def test_to_base64_with_typedef():
def test_to_base64_with_invalid_type(value: Any):
with pytest.raises(ValueError):
value = to_base64(value, "1")
+
+
+@pytest.mark.parametrize(
+ (
+ "before",
+ "after",
+ "first",
+ "last",
+ "max_results",
+ "expected",
+ "expected_overfetch",
+ ),
+ [
+ (
+ None,
+ None,
+ None,
+ None,
+ 100,
+ SliceMetadata(start=0, end=100, expected=100),
+ 101,
+ ),
+ (
+ None,
+ None,
+ None,
+ None,
+ 200,
+ SliceMetadata(start=0, end=200, expected=200),
+ 201,
+ ),
+ (
+ None,
+ None,
+ 10,
+ None,
+ 100,
+ SliceMetadata(start=0, end=10, expected=10),
+ 11,
+ ),
+ (
+ None,
+ None,
+ None,
+ 10,
+ 100,
+ SliceMetadata(start=0, end=sys.maxsize, expected=None),
+ sys.maxsize,
+ ),
+ (
+ 10,
+ None,
+ None,
+ None,
+ 100,
+ SliceMetadata(start=0, end=10, expected=10),
+ 11,
+ ),
+ (
+ None,
+ 10,
+ None,
+ None,
+ 100,
+ SliceMetadata(start=11, end=111, expected=100),
+ 112,
+ ),
+ (
+ 15,
+ None,
+ 10,
+ None,
+ 100,
+ SliceMetadata(start=14, end=24, expected=10),
+ 25,
+ ),
+ (
+ None,
+ 15,
+ None,
+ 10,
+ 100,
+ SliceMetadata(start=16, end=sys.maxsize, expected=None),
+ sys.maxsize,
+ ),
+ ],
+)
+def test_get_slice_metadata(
+ before: str | None,
+ after: str | None,
+ first: int | None,
+ last: int | None,
+ max_results: int,
+ expected: SliceMetadata,
+ expected_overfetch: int,
+):
+ info = mock.Mock()
+ info.schema.config = StrawberryConfig(relay_max_results=max_results)
+ slice_metadata = SliceMetadata.from_arguments(
+ info,
+ before=before and to_base64(PREFIX, before),
+ after=after and to_base64(PREFIX, after),
+ first=first,
+ last=last,
+ )
+ assert slice_metadata == expected
+ assert slice_metadata.overfetch == expected_overfetch
diff --git a/tests/schema/extensions/test_datadog.py b/tests/schema/extensions/test_datadog.py
index 936151b2f8..0af13877a7 100644
--- a/tests/schema/extensions/test_datadog.py
+++ b/tests/schema/extensions/test_datadog.py
@@ -289,3 +289,28 @@ def create_span(
await schema.execute(query)
mock.tracer.trace().set_tag.assert_any_call("graphql.query", query)
+
+
+@pytest.mark.asyncio
+async def test_uses_query_missing_operation_if_no_query(datadog_extension, mocker):
+ """Avoid regression of https://github.com/strawberry-graphql/strawberry/issues/3150"""
+ extension, mock = datadog_extension
+
+ schema = strawberry.Schema(query=Query, mutation=Mutation, extensions=[extension])
+
+ # A missing query error is expected here, but the extension will run anyways
+ with pytest.raises(strawberry.exceptions.MissingQueryError):
+ await schema.execute(None)
+
+ mock.tracer.assert_has_calls(
+ [
+ mocker.call.trace(
+ "Anonymous Query",
+ resource="query_missing",
+ span_type="graphql",
+ service="strawberry",
+ ),
+ mocker.call.trace().set_tag("graphql.operation_name", None),
+ mocker.call.trace().set_tag("graphql.operation_type", "query_missing"),
+ ]
+ )
diff --git a/tests/schema/extensions/test_extensions.py b/tests/schema/extensions/test_extensions.py
index 3deb9be140..fba3d400b4 100644
--- a/tests/schema/extensions/test_extensions.py
+++ b/tests/schema/extensions/test_extensions.py
@@ -50,8 +50,7 @@ def dont_call_me(self_):
nonlocal called
called = True
- class ExtensionNoHooks(SchemaExtension):
- ...
+ class ExtensionNoHooks(SchemaExtension): ...
for hook in (
ExtensionNoHooks.on_parse,
@@ -407,18 +406,18 @@ async def on_execute(self):
"ExtensionB, on_operation Entered",
"ExtensionA, on_parse Entered",
"ExtensionB, on_parse Entered",
- "ExtensionA, on_parse Exited",
"ExtensionB, on_parse Exited",
+ "ExtensionA, on_parse Exited",
"ExtensionA, on_validate Entered",
"ExtensionB, on_validate Entered",
- "ExtensionA, on_validate Exited",
"ExtensionB, on_validate Exited",
+ "ExtensionA, on_validate Exited",
"ExtensionA, on_execute Entered",
"ExtensionB, on_execute Entered",
- "ExtensionA, on_execute Exited",
"ExtensionB, on_execute Exited",
- "ExtensionA, on_operation Exited",
+ "ExtensionA, on_execute Exited",
"ExtensionB, on_operation Exited",
+ "ExtensionA, on_operation Exited",
]
@@ -461,14 +460,15 @@ class WrongUsageExtension(SchemaExtension):
def on_execute(self):
yield
- def on_executing_start(self):
- ...
+ def on_executing_start(self): ...
schema = strawberry.Schema(
query=default_query_types_and_query.query_type, extensions=[WrongUsageExtension]
)
- with pytest.raises(ValueError):
- schema.execute_sync(default_query_types_and_query.query)
+
+ result = schema.execute_sync(default_query_types_and_query.query)
+ assert len(result.errors) == 1
+ assert isinstance(result.errors[0].original_error, ValueError)
async def test_legacy_extension_supported():
@@ -628,8 +628,184 @@ def string(self) -> str:
schema.execute_sync(query)
+class ExceptionTestingExtension(SchemaExtension):
+ def __init__(self, failing_hook: str):
+ self.failing_hook = failing_hook
+ self.called_hooks = set()
+
+ def on_operation(self):
+ if self.failing_hook == "on_operation_start":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(1)
+
+ with contextlib.suppress(Exception):
+ yield
+
+ if self.failing_hook == "on_operation_end":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(8)
+
+ def on_parse(self):
+ if self.failing_hook == "on_parse_start":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(2)
+
+ with contextlib.suppress(Exception):
+ yield
+
+ if self.failing_hook == "on_parse_end":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(3)
+
+ def on_validate(self):
+ if self.failing_hook == "on_validate_start":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(4)
+
+ with contextlib.suppress(Exception):
+ yield
+
+ if self.failing_hook == "on_validate_end":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(5)
+
+ def on_execute(self):
+ if self.failing_hook == "on_execute_start":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(6)
+
+ with contextlib.suppress(Exception):
+ yield
+
+ if self.failing_hook == "on_execute_end":
+ raise Exception(self.failing_hook)
+ self.called_hooks.add(7)
+
+
+@pytest.mark.parametrize(
+ "failing_hook",
+ (
+ "on_operation_start",
+ "on_operation_end",
+ "on_parse_start",
+ "on_parse_end",
+ "on_validate_start",
+ "on_validate_end",
+ "on_execute_start",
+ "on_execute_end",
+ ),
+)
+@pytest.mark.asyncio
+async def test_exceptions_are_included_in_the_execution_result(failing_hook):
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def ping(self) -> str:
+ return "pong"
+
+ schema = strawberry.Schema(
+ query=Query,
+ extensions=[ExceptionTestingExtension(failing_hook)],
+ )
+ document = "query { ping }"
+
+ sync_result = schema.execute_sync(document)
+ assert sync_result.errors is not None
+ assert len(sync_result.errors) == 1
+ assert sync_result.errors[0].message == failing_hook
+
+ async_result = await schema.execute(document)
+ assert async_result.errors is not None
+ assert len(async_result.errors) == 1
+ assert sync_result.errors[0].message == failing_hook
+
+
+@pytest.mark.parametrize(
+ ("failing_hook", "expected_hooks"),
+ (
+ ("on_operation_start", set()),
+ ("on_parse_start", {1, 8}),
+ ("on_parse_end", {1, 2, 8}),
+ ("on_validate_start", {1, 2, 3, 8}),
+ ("on_validate_end", {1, 2, 3, 4, 8}),
+ ("on_execute_start", {1, 2, 3, 4, 5, 8}),
+ ("on_execute_end", {1, 2, 3, 4, 5, 6, 8}),
+ ("on_operation_end", {1, 2, 3, 4, 5, 6, 7}),
+ ),
+)
@pytest.mark.asyncio
-async def test_dont_swallow_errors_in_parsing_hooks():
+async def test_exceptions_abort_evaluation(failing_hook, expected_hooks):
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def ping(self) -> str:
+ return "pong"
+
+ extension = ExceptionTestingExtension(failing_hook)
+ schema = strawberry.Schema(query=Query, extensions=[extension])
+ document = "query { ping }"
+
+ extension.called_hooks = set()
+ schema.execute_sync(document)
+ assert extension.called_hooks == expected_hooks
+
+ extension.called_hooks = set()
+ await schema.execute(document)
+ assert extension.called_hooks == expected_hooks
+
+
+async def test_generic_exceptions_get_wrapped_in_a_graphql_error():
+ exception = Exception("This should be wrapped in a GraphQL error")
+
+ class MyExtension(SchemaExtension):
+ def on_parse(self):
+ raise exception
+
+ @strawberry.type
+ class Query:
+ ping: str = "pong"
+
+ schema = strawberry.Schema(query=Query, extensions=[MyExtension])
+ query = "query { ping }"
+
+ sync_result = schema.execute_sync(query)
+ assert len(sync_result.errors) == 1
+ assert isinstance(sync_result.errors[0], GraphQLError)
+ assert sync_result.errors[0].original_error == exception
+
+ async_result = await schema.execute(query)
+ assert len(async_result.errors) == 1
+ assert isinstance(async_result.errors[0], GraphQLError)
+ assert async_result.errors[0].original_error == exception
+
+
+async def test_graphql_errors_get_not_wrapped_in_a_graphql_error():
+ exception = GraphQLError("This should not be wrapped in a GraphQL error")
+
+ class MyExtension(SchemaExtension):
+ def on_parse(self):
+ raise exception
+
+ @strawberry.type
+ class Query:
+ ping: str = "pong"
+
+ schema = strawberry.Schema(query=Query, extensions=[MyExtension])
+ query = "query { ping }"
+
+ sync_result = schema.execute_sync(query)
+ assert len(sync_result.errors) == 1
+ assert sync_result.errors[0] == exception
+ assert sync_result.errors[0].original_error is None
+
+ async_result = await schema.execute(query)
+ assert len(async_result.errors) == 1
+ assert async_result.errors[0] == exception
+ assert async_result.errors[0].original_error is None
+
+
+@pytest.mark.asyncio
+async def test_non_parsing_errors_are_not_swallowed_by_parsing_hooks():
class MyExtension(SchemaExtension):
def on_parse(self):
raise Exception("This shouldn't be swallowed")
@@ -643,14 +819,16 @@ def ping(self) -> str:
schema = strawberry.Schema(query=Query, extensions=[MyExtension])
query = "query { string }"
- with pytest.raises(Exception, match="This shouldn't be swallowed"):
- schema.execute_sync(query)
+ sync_result = schema.execute_sync(query)
+ assert len(sync_result.errors) == 1
+ assert sync_result.errors[0].message == "This shouldn't be swallowed"
- with pytest.raises(Exception, match="This shouldn't be swallowed"):
- await schema.execute(query)
+ async_result = await schema.execute(query)
+ assert len(async_result.errors) == 1
+ assert async_result.errors[0].message == "This shouldn't be swallowed"
-def test_on_parsing_end_called_when_errors():
+def test_on_parsing_end_is_called_with_parsing_errors():
execution_errors = False
class MyExtension(SchemaExtension):
@@ -677,18 +855,19 @@ def ping(self) -> str:
def test_extension_execution_order_sync():
"""Ensure mixed hooks (async & sync) are called correctly."""
-
execution_order: List[Type[SchemaExtension]] = []
class ExtensionB(SchemaExtension):
def on_execute(self):
execution_order.append(type(self))
yield
+ execution_order.append(type(self))
class ExtensionC(SchemaExtension):
def on_execute(self):
execution_order.append(type(self))
yield
+ execution_order.append(type(self))
@strawberry.type
class Query:
@@ -707,7 +886,7 @@ class Query:
assert not result.errors
assert result.data == {"food": "strawberry"}
- assert execution_order == extensions
+ assert execution_order == [ExtensionB, ExtensionC, ExtensionC, ExtensionB]
def test_async_extension_in_sync_context():
@@ -721,8 +900,9 @@ class Query:
schema = strawberry.Schema(query=Query, extensions=[ExtensionA])
- with pytest.raises(RuntimeError, match="failed to complete synchronously"):
- schema.execute_sync("query { food }")
+ result = schema.execute_sync("query { food }")
+ assert len(result.errors) == 1
+ assert result.errors[0].message.endswith("failed to complete synchronously.")
def test_extension_override_execution():
@@ -1020,7 +1200,8 @@ def hi(self) -> str:
# Query not set on input
query = "{ hi }"
- with pytest.raises(
- ValueError, match="Hook on_operation on <(.*)> must be callable, received 'ABC'"
- ):
- schema.execute_sync(query)
+ result = schema.execute_sync(query)
+ assert len(result.errors) == 1
+ assert isinstance(result.errors[0].original_error, ValueError)
+ assert result.errors[0].message.startswith("Hook on_operation on <")
+ assert result.errors[0].message.endswith("> must be callable, received 'ABC'")
diff --git a/tests/schema/extensions/test_field_extensions.py b/tests/schema/extensions/test_field_extensions.py
index febe42f887..eea9e0e1ab 100644
--- a/tests/schema/extensions/test_field_extensions.py
+++ b/tests/schema/extensions/test_field_extensions.py
@@ -10,12 +10,15 @@
FieldExtension,
SyncExtensionResolver,
)
-from strawberry.types import Info
class UpperCaseExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = next_(source, info, **kwargs)
return str(result).upper()
@@ -23,7 +26,11 @@ def resolve(
class LowerCaseExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = next_(source, info, **kwargs)
return str(result).lower()
@@ -31,7 +38,11 @@ def resolve(
class AsyncUpperCaseExtension(FieldExtension):
async def resolve_async(
- self, next_: AsyncExtensionResolver, source: Any, info: Info, **kwargs: Any
+ self,
+ next_: AsyncExtensionResolver,
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = await next_(source, info, **kwargs)
return str(result).upper()
@@ -39,12 +50,20 @@ async def resolve_async(
class IdentityExtension(FieldExtension):
def resolve(
- self, next_: SyncExtensionResolver, source: Any, info: Info, **kwargs: Any
+ self,
+ next_: SyncExtensionResolver,
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
) -> Any:
return next_(source, info, **kwargs)
async def resolve_async(
- self, next_: AsyncExtensionResolver, source: Any, info: Info, **kwargs: Any
+ self,
+ next_: AsyncExtensionResolver,
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
) -> Any:
return await next_(source, info, **kwargs)
@@ -145,9 +164,7 @@ def string(self) -> str:
async def test_can_use_sync_only_and_sync_before_async_extensions():
- """
- Use Sync - Sync + Async - Sync - Async possible
- """
+ """Use Sync - Sync + Async - Sync - Async possible."""
@strawberry.type
class Query:
@@ -173,14 +190,22 @@ def string(self) -> str:
def test_fail_on_missing_async_extensions():
class LowerCaseExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = next_(source, info, **kwargs)
return str(result).lower()
class UpperCaseExtension(FieldExtension):
async def resolve_async(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = await next_(source, info, **kwargs)
return str(result).upper()
@@ -204,14 +229,22 @@ async def string(self) -> str:
def test_extension_order_respected():
class LowerCaseExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = next_(source, info, **kwargs)
return str(result).lower()
class UpperCaseExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
result = next_(source, info, **kwargs)
return str(result).upper()
@@ -231,9 +264,8 @@ def string(self) -> str:
def test_extension_argument_parsing():
- """
- Check that kwargs passed to field extensions have been converted into
- Strawberry types
+ """Check that kwargs passed to field extensions have been converted into
+ Strawberry types.
"""
@strawberry.input
@@ -244,7 +276,11 @@ class StringInput:
class CustomExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
nonlocal field_kwargs
field_kwargs = kwargs
@@ -273,7 +309,11 @@ def string(self, some_input: StringInput) -> str:
def test_extension_mutate_arguments():
class CustomExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
kwargs["some_input"] += 10
result = next_(source, info, **kwargs)
@@ -299,7 +339,11 @@ def test_extension_access_argument_metadata():
class CustomExtension(FieldExtension):
def resolve(
- self, next_: Callable[..., Any], source: Any, info: Info, **kwargs: Any
+ self,
+ next_: Callable[..., Any],
+ source: Any,
+ info: strawberry.Info,
+ **kwargs: Any,
):
nonlocal field_kwargs
field_kwargs = kwargs
diff --git a/tests/schema/extensions/test_parser_cache.py b/tests/schema/extensions/test_parser_cache.py
index 521c561ac7..20f7f540ad 100644
--- a/tests/schema/extensions/test_parser_cache.py
+++ b/tests/schema/extensions/test_parser_cache.py
@@ -1,7 +1,7 @@
from unittest.mock import patch
import pytest
-from graphql import parse
+from graphql import SourceLocation, parse
import strawberry
from strawberry.extensions import MaxTokensLimiter, ParserCache
@@ -75,6 +75,26 @@ def ping(self) -> str:
mock_parse.assert_called_with("query { hello }", max_tokens=20)
+@patch("strawberry.schema.execute.parse", wraps=parse)
+def test_parser_cache_extension_syntax_error(mock_parse):
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def hello(self) -> str: # pragma: no cover
+ return "world"
+
+ schema = strawberry.Schema(query=Query, extensions=[ParserCache()])
+
+ query = "query { hello"
+
+ result = schema.execute_sync(query)
+
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Syntax Error: Expected Name, found ."
+ assert result.errors[0].locations == [SourceLocation(line=1, column=14)]
+ assert mock_parse.call_count == 1
+
+
@patch("strawberry.schema.execute.parse", wraps=parse)
def test_parser_cache_extension_max_size(mock_parse):
@strawberry.type
diff --git a/tests/schema/test_annotated/type_a.py b/tests/schema/test_annotated/type_a.py
index e74b3a84f2..a6bb8146c0 100644
--- a/tests/schema/test_annotated/type_a.py
+++ b/tests/schema/test_annotated/type_a.py
@@ -5,7 +5,6 @@
from uuid import UUID
import strawberry
-from strawberry.types import Info
@strawberry.type
@@ -13,7 +12,6 @@ class Query:
@strawberry.field
def get_testing(
self,
- info: Info[None, None],
+ info: strawberry.Info,
id_: Annotated[UUID, strawberry.argument(name="id")],
- ) -> Optional[str]:
- ...
+ ) -> Optional[str]: ...
diff --git a/tests/schema/test_annotated/type_b.py b/tests/schema/test_annotated/type_b.py
index c03028f6ce..dfd9186876 100644
--- a/tests/schema/test_annotated/type_b.py
+++ b/tests/schema/test_annotated/type_b.py
@@ -5,7 +5,6 @@
from uuid import UUID
import strawberry
-from strawberry.types import Info
@strawberry.type
@@ -14,6 +13,5 @@ class Query:
def get_testing(
self,
id_: Annotated[UUID, strawberry.argument(name="id")],
- info: Info[None, None],
- ) -> Optional[str]:
- ...
+ info: strawberry.Info,
+ ) -> Optional[str]: ...
diff --git a/tests/schema/test_arguments.py b/tests/schema/test_arguments.py
index e6932b5321..7c1f2e32f9 100644
--- a/tests/schema/test_arguments.py
+++ b/tests/schema/test_arguments.py
@@ -4,7 +4,7 @@
from typing_extensions import Annotated
import strawberry
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
def test_argument_descriptions():
@@ -221,7 +221,6 @@ def test_argument_parse_order():
Refer to: https://github.com/strawberry-graphql/strawberry/issues/2855
"""
-
from tests.schema.test_annotated import type_a, type_b
expected = """
diff --git a/tests/schema/test_basic.py b/tests/schema/test_basic.py
index a07d57c4e0..47e248f2f8 100644
--- a/tests/schema/test_basic.py
+++ b/tests/schema/test_basic.py
@@ -10,12 +10,11 @@
from strawberry import ID
from strawberry.scalars import Base64
from strawberry.schema_directive import Location
-from strawberry.type import StrawberryList
+from strawberry.types.base import StrawberryList
def test_raises_exception_with_unsupported_types():
- class SomeType:
- ...
+ class SomeType: ...
@strawberry.type
class Query:
@@ -366,7 +365,8 @@ def example(self) -> Schema:
def test_can_return_compatible_type():
"""Test that we can return a different type that has the same fields,
- for example when returning a Django Model."""
+ for example when returning a Django Model.
+ """
@dataclass
class Example:
diff --git a/tests/schema/test_directives.py b/tests/schema/test_directives.py
index 10a7bb6329..9c82d483e0 100644
--- a/tests/schema/test_directives.py
+++ b/tests/schema/test_directives.py
@@ -8,8 +8,7 @@
from strawberry.directive import DirectiveLocation, DirectiveValue
from strawberry.extensions import SchemaExtension
from strawberry.schema.config import StrawberryConfig
-from strawberry.type import get_object_definition
-from strawberry.types.info import Info
+from strawberry.types.base import get_object_definition
from strawberry.utils.await_maybe import await_maybe
@@ -416,9 +415,9 @@ def greetingTemplate(self, locale: Locale = Locale.EN) -> str:
locations=[DirectiveLocation.FIELD],
description="Interpolate string on the server from context data",
)
- def interpolate(value: str, info: Info):
+ def interpolate(value: str, info: strawberry.Info):
try:
- assert isinstance(info, Info)
+ assert isinstance(info, strawberry.Info)
assert info._field is field
return value.format(**info.context["userdata"])
except KeyError:
@@ -567,8 +566,7 @@ class Query:
hello: str = "hello"
@strawberry.directive(locations=[DirectiveLocation.FIELD])
- def deprecated_value(value):
- ...
+ def deprecated_value(value): ...
strawberry.Schema(query=Query, directives=[deprecated_value])
diff --git a/tests/schema/test_enum.py b/tests/schema/test_enum.py
index 8b0df578fa..4c6d566e3c 100644
--- a/tests/schema/test_enum.py
+++ b/tests/schema/test_enum.py
@@ -7,7 +7,7 @@
import pytest
import strawberry
-from strawberry.lazy_type import lazy
+from strawberry.types.lazy_type import lazy
def test_enum_resolver():
diff --git a/tests/schema/test_execution_errors.py b/tests/schema/test_execution_errors.py
index e4fa50b2fe..1c93e4a4cb 100644
--- a/tests/schema/test_execution_errors.py
+++ b/tests/schema/test_execution_errors.py
@@ -1,6 +1,7 @@
import pytest
import strawberry
+from strawberry.schema.config import StrawberryConfig
def test_runs_parsing():
@@ -38,10 +39,13 @@ async def name(self) -> str:
}
"""
- with pytest.raises(RuntimeError) as e:
- schema.execute_sync(query)
-
- assert e.value.args[0] == "GraphQL execution failed to complete synchronously."
+ result = schema.execute_sync(query)
+ assert len(result.errors) == 1
+ assert isinstance(result.errors[0].original_error, RuntimeError)
+ assert (
+ result.errors[0].message
+ == "GraphQL execution failed to complete synchronously."
+ )
@pytest.mark.asyncio
@@ -61,3 +65,70 @@ class Query:
assert len(result.errors) == 1
assert result.errors[0].message == "Syntax Error: Expected Name, found ."
+
+
+def test_suggests_fields_by_default():
+ @strawberry.type
+ class Query:
+ name: str
+
+ schema = strawberry.Schema(query=Query)
+
+ query = """
+ query {
+ ample
+ }
+ """
+
+ result = schema.execute_sync(query)
+
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message
+ == "Cannot query field 'ample' on type 'Query'. Did you mean 'name'?"
+ )
+
+
+def test_can_disable_field_suggestions():
+ @strawberry.type
+ class Query:
+ name: str
+
+ schema = strawberry.Schema(
+ query=Query, config=StrawberryConfig(disable_field_suggestions=True)
+ )
+
+ query = """
+ query {
+ ample
+ }
+ """
+
+ result = schema.execute_sync(query)
+
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Cannot query field 'ample' on type 'Query'."
+
+
+def test_can_disable_field_suggestions_multiple_fields():
+ @strawberry.type
+ class Query:
+ name: str
+ age: str
+
+ schema = strawberry.Schema(
+ query=Query, config=StrawberryConfig(disable_field_suggestions=True)
+ )
+
+ query = """
+ query {
+ ample
+ ag
+ }
+ """
+
+ result = schema.execute_sync(query)
+
+ assert len(result.errors) == 2
+ assert result.errors[0].message == "Cannot query field 'ample' on type 'Query'."
+ assert result.errors[1].message == "Cannot query field 'ag' on type 'Query'."
diff --git a/tests/schema/test_extensions.py b/tests/schema/test_extensions.py
index 225f49cad4..131864142c 100644
--- a/tests/schema/test_extensions.py
+++ b/tests/schema/test_extensions.py
@@ -14,7 +14,7 @@
from strawberry.scalars import JSON
from strawberry.schema.schema_converter import GraphQLCoreConverter
from strawberry.schema_directive import Location
-from strawberry.type import get_object_definition
+from strawberry.types.base import get_object_definition
DEFINITION_BACKREF = GraphQLCoreConverter.DEFINITION_BACKREF
@@ -161,8 +161,7 @@ class Input:
@strawberry.type()
class Query:
@strawberry.field
- def hello(self, input: Input) -> str:
- ...
+ def hello(self, input: Input) -> str: ...
schema = strawberry.Schema(query=Query)
graphql_schema: GraphQLSchema = schema._schema
diff --git a/tests/schema/test_fields.py b/tests/schema/test_fields.py
index 58c58b4cb1..db0b3abe29 100644
--- a/tests/schema/test_fields.py
+++ b/tests/schema/test_fields.py
@@ -3,9 +3,9 @@
from operator import getitem
import strawberry
-from strawberry.field import StrawberryField
from strawberry.printer import print_schema
from strawberry.schema.config import StrawberryConfig
+from strawberry.types.field import StrawberryField
def test_custom_field():
@@ -84,9 +84,7 @@ class Query:
def test_field_type_priority():
- """
- Prioritise the field annotation on the class over the resolver annotation.
- """
+ """Prioritise the field annotation on the class over the resolver annotation."""
def my_resolver() -> str:
return "1.33"
diff --git a/tests/schema/test_generics.py b/tests/schema/test_generics.py
index 8eb8729933..17e582afd7 100644
--- a/tests/schema/test_generics.py
+++ b/tests/schema/test_generics.py
@@ -3,8 +3,6 @@
from typing import Any, Generic, List, Optional, TypeVar, Union
from typing_extensions import Self
-import pytest
-
import strawberry
@@ -49,8 +47,7 @@ class Edge(Generic[T]):
node_field: T
@strawberry.type
- class IntEdge(Edge[int]):
- ...
+ class IntEdge(Edge[int]): ...
@strawberry.type
class Query:
@@ -85,12 +82,10 @@ class Edge(Generic[T]):
node_field: T
@strawberry.type
- class IntEdge(Edge[int]):
- ...
+ class IntEdge(Edge[int]): ...
@strawberry.type
- class IntEdgeSubclass(IntEdge):
- ...
+ class IntEdgeSubclass(IntEdge): ...
@strawberry.type
class Query:
@@ -129,8 +124,7 @@ class Edge(Generic[T]):
node_field: T
@strawberry.type
- class FruitEdge(Edge[Fruit]):
- ...
+ class FruitEdge(Edge[Fruit]): ...
@strawberry.type
class Query:
@@ -175,8 +169,7 @@ class Edge(Generic[T]):
nodes: List[T]
@strawberry.type
- class FruitEdge(Edge[Fruit]):
- ...
+ class FruitEdge(Edge[Fruit]): ...
@strawberry.type
class Query:
@@ -289,52 +282,6 @@ def multiple(self) -> Multiple[int, str]:
}
-def test_support_nested_generics():
- T = TypeVar("T")
-
- @strawberry.type
- class User:
- name: str
-
- @strawberry.type
- class Edge(Generic[T]):
- node: T
-
- @strawberry.type
- class Connection(Generic[T]):
- edge: Edge[T]
-
- @strawberry.type
- class Query:
- @strawberry.field
- def users(self) -> Connection[User]:
- return Connection(edge=Edge(node=User(name="Patrick")))
-
- schema = strawberry.Schema(query=Query)
-
- query = """{
- users {
- __typename
- edge {
- __typename
- node {
- name
- }
- }
- }
- }"""
-
- result = schema.execute_sync(query)
-
- assert not result.errors
- assert result.data == {
- "users": {
- "__typename": "UserConnection",
- "edge": {"__typename": "UserEdge", "node": {"name": "Patrick"}},
- }
- }
-
-
def test_supports_optional():
T = TypeVar("T")
@@ -650,58 +597,6 @@ def example(self) -> Union[Fallback, Edge[int, str]]:
}
-def test_supports_generic_in_unions_with_nesting():
- T = TypeVar("T")
-
- @strawberry.type
- class User:
- name: str
-
- @strawberry.type
- class Edge(Generic[T]):
- node: T
-
- @strawberry.type
- class Connection(Generic[T]):
- edge: Edge[T]
-
- @strawberry.type
- class Fallback:
- node: str
-
- @strawberry.type
- class Query:
- @strawberry.field
- def users(self) -> Union[Connection[User], Fallback]:
- return Connection(edge=Edge(node=User(name="Patrick")))
-
- schema = strawberry.Schema(query=Query)
-
- query = """{
- users {
- __typename
- ... on UserConnection {
- edge {
- __typename
- node {
- name
- }
- }
- }
- }
- }"""
-
- result = schema.execute_sync(query)
-
- assert not result.errors
- assert result.data == {
- "users": {
- "__typename": "UserConnection",
- "edge": {"__typename": "UserEdge", "node": {"name": "Patrick"}},
- }
- }
-
-
def test_supports_multiple_generics_in_union():
T = TypeVar("T")
@@ -942,7 +837,6 @@ def user(self) -> Union[User, Edge[User]]:
assert result.data == {"user": {"__typename": "UserEdge", "nodes": []}}
-@pytest.mark.xfail()
def test_raises_error_when_unable_to_find_type():
T = TypeVar("T")
@@ -976,10 +870,9 @@ def user(self) -> Union[User, Edge[User]]:
result = schema.execute_sync(query)
- assert result.errors[0].message == (
- "Unable to find type for .Edge'> "
- "and (,)"
+ assert (
+ 'of the field "user" is not in the list of the types of the union'
+ in result.errors[0].message
)
@@ -1132,8 +1025,7 @@ class INode:
fields: List[Self]
@strawberry.type
- class Node(INode):
- ...
+ class Node(INode): ...
schema = strawberry.Schema(query=Node)
@@ -1281,3 +1173,40 @@ def real(self) -> Abstract:
assert not query_result.errors
assert query_result.data == {"real": {"__typename": "IntReal", "x": ""}}
+
+
+def test_generic_with_interface():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class Pagination(Generic[T]):
+ items: List[T]
+
+ @strawberry.interface
+ class TestInterface:
+ data: str
+
+ @strawberry.type
+ class Test1(TestInterface):
+ pass
+
+ @strawberry.type
+ class TestError:
+ reason: str
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def hello(
+ self, info: strawberry.Info
+ ) -> Union[Pagination[TestInterface], TestError]:
+ return Pagination(items=[Test1(data="test1")])
+
+ schema = strawberry.Schema(Query, types=[Test1])
+
+ query_result = schema.execute_sync(
+ "{ hello { ... on TestInterfacePagination { items { data }} } }"
+ )
+
+ assert not query_result.errors
+ assert query_result.data == {"hello": {"items": [{"data": "test1"}]}}
diff --git a/tests/schema/test_generics_nested.py b/tests/schema/test_generics_nested.py
new file mode 100644
index 0000000000..a4de77b29f
--- /dev/null
+++ b/tests/schema/test_generics_nested.py
@@ -0,0 +1,365 @@
+import textwrap
+from typing import Generic, List, Optional, TypeVar, Union
+
+import strawberry
+from strawberry.scalars import JSON
+
+
+def test_support_nested_generics():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class User:
+ name: str
+
+ @strawberry.type
+ class Edge(Generic[T]):
+ node: T
+
+ @strawberry.type
+ class Connection(Generic[T]):
+ edge: Edge[T]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def users(self) -> Connection[User]:
+ return Connection(edge=Edge(node=User(name="Patrick")))
+
+ schema = strawberry.Schema(query=Query)
+
+ query = """{
+ users {
+ __typename
+ edge {
+ __typename
+ node {
+ name
+ }
+ }
+ }
+ }"""
+
+ result = schema.execute_sync(query)
+
+ assert not result.errors
+ assert result.data == {
+ "users": {
+ "__typename": "UserConnection",
+ "edge": {"__typename": "UserEdge", "node": {"name": "Patrick"}},
+ }
+ }
+
+
+def test_unions_nested_inside_a_list():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class JsonBlock:
+ data: JSON
+
+ @strawberry.type
+ class BlockRowType(Generic[T]):
+ total: int
+ items: List[T]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def blocks(
+ self,
+ ) -> List[Union[BlockRowType[int], BlockRowType[str], JsonBlock]]:
+ return [
+ BlockRowType(total=3, items=["a", "b", "c"]),
+ BlockRowType(total=1, items=[1, 2, 3, 4]),
+ JsonBlock(data=JSON({"a": 1})),
+ ]
+
+ schema = strawberry.Schema(query=Query)
+
+ result = schema.execute_sync(
+ """query {
+ blocks {
+ __typename
+ ... on IntBlockRowType {
+ a: items
+ }
+ ... on StrBlockRowType {
+ b: items
+ }
+ ... on JsonBlock {
+ data
+ }
+ }
+ }"""
+ )
+
+ assert not result.errors
+
+ assert result.data == {
+ "blocks": [
+ {"__typename": "StrBlockRowType", "b": ["a", "b", "c"]},
+ {"__typename": "IntBlockRowType", "a": [1, 2, 3, 4]},
+ {"__typename": "JsonBlock", "data": {"a": 1}},
+ ]
+ }
+
+
+def test_unions_nested_inside_a_list_with_no_items():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class JsonBlock:
+ data: JSON
+
+ @strawberry.type
+ class BlockRowType(Generic[T]):
+ total: int
+ items: List[T]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def blocks(
+ self,
+ ) -> List[Union[BlockRowType[int], BlockRowType[str], JsonBlock]]:
+ return [
+ BlockRowType(total=3, items=[]),
+ BlockRowType(total=1, items=[]),
+ JsonBlock(data=JSON({"a": 1})),
+ ]
+
+ schema = strawberry.Schema(query=Query)
+
+ result = schema.execute_sync(
+ """query {
+ blocks {
+ __typename
+ ... on IntBlockRowType {
+ a: items
+ }
+ ... on StrBlockRowType {
+ b: items
+ }
+ ... on JsonBlock {
+ data
+ }
+ }
+ }"""
+ )
+
+ assert not result.errors
+
+ assert result.data == {
+ "blocks": [
+ {"__typename": "IntBlockRowType", "a": []},
+ {"__typename": "IntBlockRowType", "a": []},
+ {"__typename": "JsonBlock", "data": {"a": 1}},
+ ]
+ }
+
+
+def test_unions_nested_inside_a_list_of_lists():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class JsonBlock:
+ data: JSON
+
+ @strawberry.type
+ class BlockRowType(Generic[T]):
+ total: int
+ items: List[List[T]]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def blocks(
+ self,
+ ) -> List[Union[BlockRowType[int], BlockRowType[str], JsonBlock]]:
+ return [
+ BlockRowType(total=3, items=[["a", "b", "c"]]),
+ BlockRowType(total=1, items=[[1, 2, 3, 4]]),
+ JsonBlock(data=JSON({"a": 1})),
+ ]
+
+ schema = strawberry.Schema(query=Query)
+
+ result = schema.execute_sync(
+ """query {
+ blocks {
+ __typename
+ ... on IntBlockRowType {
+ a: items
+ }
+ ... on StrBlockRowType {
+ b: items
+ }
+ ... on JsonBlock {
+ data
+ }
+ }
+ }"""
+ )
+
+ assert not result.errors
+
+ assert result.data == {
+ "blocks": [
+ {"__typename": "StrBlockRowType", "b": [["a", "b", "c"]]},
+ {"__typename": "IntBlockRowType", "a": [[1, 2, 3, 4]]},
+ {"__typename": "JsonBlock", "data": {"a": 1}},
+ ]
+ }
+
+
+def test_using_generics_with_an_interface():
+ T = TypeVar("T")
+
+ @strawberry.interface
+ class BlockInterface:
+ id: strawberry.ID
+ disclaimer: Optional[str] = strawberry.field(default=None)
+
+ @strawberry.type
+ class JsonBlock(BlockInterface):
+ data: JSON
+
+ @strawberry.type
+ class BlockRowType(BlockInterface, Generic[T]):
+ total: int
+ items: List[T]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def blocks(self) -> List[BlockInterface]:
+ return [
+ BlockRowType(id=strawberry.ID("3"), total=3, items=["a", "b", "c"]),
+ BlockRowType(id=strawberry.ID("1"), total=1, items=[1, 2, 3, 4]),
+ JsonBlock(id=strawberry.ID("2"), data=JSON({"a": 1})),
+ ]
+
+ schema = strawberry.Schema(
+ query=Query, types=[BlockRowType[int], JsonBlock, BlockRowType[str]]
+ )
+
+ expected_schema = textwrap.dedent(
+ '''
+ interface BlockInterface {
+ id: ID!
+ disclaimer: String
+ }
+
+ type IntBlockRowType implements BlockInterface {
+ id: ID!
+ disclaimer: String
+ total: Int!
+ items: [Int!]!
+ }
+
+ """
+ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
+ """
+ scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
+
+ type JsonBlock implements BlockInterface {
+ id: ID!
+ disclaimer: String
+ data: JSON!
+ }
+
+ type Query {
+ blocks: [BlockInterface!]!
+ }
+
+ type StrBlockRowType implements BlockInterface {
+ id: ID!
+ disclaimer: String
+ total: Int!
+ items: [String!]!
+ }
+ '''
+ ).strip()
+
+ assert str(schema) == expected_schema
+
+ result = schema.execute_sync(
+ """query {
+ blocks {
+ id
+ __typename
+ ... on IntBlockRowType {
+ a: items
+ }
+ ... on StrBlockRowType {
+ b: items
+ }
+ ... on JsonBlock {
+ data
+ }
+ }
+ }"""
+ )
+
+ assert not result.errors
+
+ assert result.data == {
+ "blocks": [
+ {"id": "3", "__typename": "StrBlockRowType", "b": ["a", "b", "c"]},
+ {"id": "1", "__typename": "IntBlockRowType", "a": [1, 2, 3, 4]},
+ {"id": "2", "__typename": "JsonBlock", "data": {"a": 1}},
+ ]
+ }
+
+
+def test_supports_generic_in_unions_with_nesting():
+ T = TypeVar("T")
+
+ @strawberry.type
+ class User:
+ name: str
+
+ @strawberry.type
+ class Edge(Generic[T]):
+ node: T
+
+ @strawberry.type
+ class Connection(Generic[T]):
+ edge: Edge[T]
+
+ @strawberry.type
+ class Fallback:
+ node: str
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def users(self) -> Union[Connection[User], Fallback]:
+ return Connection(edge=Edge(node=User(name="Patrick")))
+
+ schema = strawberry.Schema(query=Query)
+
+ query = """{
+ users {
+ __typename
+ ... on UserConnection {
+ edge {
+ __typename
+ node {
+ name
+ }
+ }
+ }
+ }
+ }"""
+
+ result = schema.execute_sync(query)
+
+ assert not result.errors
+ assert result.data == {
+ "users": {
+ "__typename": "UserConnection",
+ "edge": {"__typename": "UserEdge", "node": {"name": "Patrick"}},
+ }
+ }
diff --git a/tests/schema/test_get_extensions.py b/tests/schema/test_get_extensions.py
index 6ee61e04ed..4a9d2ced67 100644
--- a/tests/schema/test_get_extensions.py
+++ b/tests/schema/test_get_extensions.py
@@ -17,8 +17,7 @@ def uppercase(value: str) -> str:
return value.upper()
-class MyExtension(SchemaExtension):
- ...
+class MyExtension(SchemaExtension): ...
def test_returns_empty_list_when_no_custom_directives():
diff --git a/tests/schema/test_info.py b/tests/schema/test_info.py
index 989f0d7632..0c3c23dc3c 100644
--- a/tests/schema/test_info.py
+++ b/tests/schema/test_info.py
@@ -6,10 +6,9 @@
import pytest
import strawberry
-from strawberry.type import StrawberryOptional
-from strawberry.types import Info
+from strawberry.types.base import StrawberryOptional
from strawberry.types.nodes import FragmentSpread, InlineFragment, SelectedField
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
def test_info_has_the_correct_shape():
@@ -32,7 +31,7 @@ class Result:
@strawberry.type
class Query:
@strawberry.field
- def hello_world(self, info: Info[str, str]) -> Result:
+ def hello_world(self, info: strawberry.Info[str, str]) -> Result:
return Result(
path="".join([str(p) for p in info.path.as_list()]),
operation=str(info.operation),
@@ -110,7 +109,7 @@ class Result:
@strawberry.type
class Query:
@strawberry.field
- def hello(self, info: Info[str, str]) -> Result:
+ def hello(self, info: strawberry.Info[str, str]) -> Result:
nonlocal selected_fields
selected_fields = info.selected_fields
return Result(ok=True)
@@ -186,7 +185,10 @@ class TestInput:
class Query:
@strawberry.field
def test_arg(
- self, info: Info[str, str], input: TestInput, another_arg: bool = True
+ self,
+ info: strawberry.Info[str, str],
+ input: TestInput,
+ another_arg: bool = True,
) -> str:
nonlocal selected_fields
selected_fields = info.selected_fields
@@ -254,7 +256,7 @@ class Result:
class Query:
@strawberry.field
def hello(
- self, info: Info[str, str], optional_input: Optional[str] = "hi"
+ self, info: strawberry.Info[str, str], optional_input: Optional[str] = "hi"
) -> Result:
nonlocal selected_fields
selected_fields = info.selected_fields
@@ -304,7 +306,7 @@ def test_return_type_from_resolver(return_type, return_value):
@strawberry.type
class Query:
@strawberry.field
- def field(self, info: Info) -> return_type:
+ def field(self, info: strawberry.Info) -> return_type:
assert info.return_type == return_type
return return_value
diff --git a/tests/schema/test_input.py b/tests/schema/test_input.py
index 4135b35913..5fe43244e1 100644
--- a/tests/schema/test_input.py
+++ b/tests/schema/test_input.py
@@ -1,6 +1,9 @@
+import textwrap
from typing import Optional
import strawberry
+from strawberry.printer import print_schema
+from tests.conftest import skip_if_gql_32
def test_renaming_input_fields():
@@ -27,3 +30,75 @@ def filter(self, input: FilterInput) -> str:
assert not result.errors
assert result.data
assert result.data["filter"] == "Hello nope"
+
+
+@skip_if_gql_32("formatting is different in gql 3.2")
+def test_input_with_nonscalar_field_default():
+ @strawberry.input
+ class NonScalarField:
+ id: int = 10
+ nullable_field: Optional[int] = None
+
+ @strawberry.input
+ class Input:
+ non_scalar_field: NonScalarField = strawberry.field(
+ default_factory=lambda: NonScalarField()
+ )
+ id: int = 10
+
+ @strawberry.type
+ class ExampleOutput:
+ input_id: int
+ non_scalar_id: int
+ non_scalar_nullable_field: Optional[int]
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def example(self, data: Input) -> ExampleOutput:
+ return ExampleOutput(
+ input_id=data.id,
+ non_scalar_id=data.non_scalar_field.id,
+ non_scalar_nullable_field=data.non_scalar_field.nullable_field,
+ )
+
+ schema = strawberry.Schema(query=Query)
+
+ expected = """
+ type ExampleOutput {
+ inputId: Int!
+ nonScalarId: Int!
+ nonScalarNullableField: Int
+ }
+
+ input Input {
+ nonScalarField: NonScalarField! = { id: 10 }
+ id: Int! = 10
+ }
+
+ input NonScalarField {
+ id: Int! = 10
+ nullableField: Int = null
+ }
+
+ type Query {
+ example(data: Input!): ExampleOutput!
+ }
+ """
+ assert print_schema(schema) == textwrap.dedent(expected).strip()
+
+ query = """
+ query($input_data: Input!)
+ {
+ example(data: $input_data) {
+ inputId nonScalarId nonScalarNullableField
+ }
+ }
+ """
+ result = schema.execute_sync(
+ query, variable_values=dict(input_data=dict(nonScalarField={}))
+ )
+
+ assert not result.errors
+ expected_result = {"inputId": 10, "nonScalarId": 10, "nonScalarNullableField": None}
+ assert result.data["example"] == expected_result
diff --git a/tests/schema/test_interface.py b/tests/schema/test_interface.py
index 2c8047adbb..31f106b09e 100644
--- a/tests/schema/test_interface.py
+++ b/tests/schema/test_interface.py
@@ -5,7 +5,7 @@
from pytest_mock import MockerFixture
import strawberry
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import StrawberryObjectDefinition
def test_query_interface():
@@ -390,12 +390,10 @@ def resolve_type(cls, obj: Any, *args: Any, **kwargs: Any) -> str:
return "Video" if obj.id == "1" else "Image"
@strawberry.type
- class Video(Node):
- ...
+ class Video(Node): ...
@strawberry.type
- class Image(Node):
- ...
+ class Image(Node): ...
@strawberry.type
class Query:
diff --git a/tests/schema/test_lazy/test_lazy.py b/tests/schema/test_lazy/test_lazy.py
index ce1c31f831..a89ad8e589 100644
--- a/tests/schema/test_lazy/test_lazy.py
+++ b/tests/schema/test_lazy/test_lazy.py
@@ -26,6 +26,12 @@ class Query:
type TypeB {
typeA: TypeA!
+ typeAList: [TypeA!]!
+ typeCList: [TypeC!]!
+ }
+
+ type TypeC {
+ name: String!
}
"""
diff --git a/tests/schema/test_lazy/test_lazy_generic.py b/tests/schema/test_lazy/test_lazy_generic.py
index 3bc8102d2b..e765e1ce63 100644
--- a/tests/schema/test_lazy/test_lazy_generic.py
+++ b/tests/schema/test_lazy/test_lazy_generic.py
@@ -12,7 +12,8 @@
import strawberry
if TYPE_CHECKING:
- from tests.schema.test_lazy.type_a import TypeA # noqa
+ from tests.schema.test_lazy.type_a import TypeA
+ from tests.schema.test_lazy.type_c import TypeC
STRAWBERRY_EXECUTABLE = next(
Path(sysconfig.get_path("scripts")).glob("strawberry*"), None
@@ -36,7 +37,6 @@ class Query:
def test_no_generic_type_duplication_with_lazy():
- from tests.schema.test_lazy.type_a import TypeB_abs, TypeB_rel
from tests.schema.test_lazy.type_b import TypeB
@strawberry.type
@@ -46,8 +46,10 @@ class Edge(Generic[T]):
@strawberry.type
class Query:
users: Edge[TypeB]
- relatively_lazy_users: Edge[TypeB_rel]
- absolutely_lazy_users: Edge[TypeB_abs]
+ relatively_lazy_users: Edge[Annotated["TypeB", strawberry.lazy(".type_b")]]
+ absolutely_lazy_users: Edge[
+ Annotated["TypeB", strawberry.lazy("tests.schema.test_lazy.type_b")]
+ ]
schema = strawberry.Schema(query=Query)
@@ -66,11 +68,17 @@ class Query:
type TypeB {
typeA: TypeA!
+ typeAList: [TypeA!]!
+ typeCList: [TypeC!]!
}
type TypeBEdge {
node: TypeB!
}
+
+ type TypeC {
+ name: String!
+ }
"""
).strip()
diff --git a/tests/schema/test_lazy/type_a.py b/tests/schema/test_lazy/type_a.py
index 65617b5f3b..1e5727b2f8 100644
--- a/tests/schema/test_lazy/type_a.py
+++ b/tests/schema/test_lazy/type_a.py
@@ -6,19 +6,15 @@
if TYPE_CHECKING:
from .type_b import TypeB
- TypeB_rel = TypeB
- TypeB_abs = TypeB
-else:
- TypeB_rel = Annotated["TypeB", strawberry.lazy(".type_b")]
- TypeB_abs = Annotated["TypeB", strawberry.lazy("tests.schema.test_lazy.type_b")]
-
@strawberry.type
class TypeA:
- list_of_b: Optional[List[TypeB_abs]] = None
+ list_of_b: Optional[
+ List[Annotated["TypeB", strawberry.lazy("tests.schema.test_lazy.type_b")]]
+ ] = None
@strawberry.field
- def type_b(self) -> TypeB_rel:
+ def type_b(self) -> Annotated["TypeB", strawberry.lazy(".type_b")]:
from .type_b import TypeB
return TypeB()
diff --git a/tests/schema/test_lazy/type_b.py b/tests/schema/test_lazy/type_b.py
index 7b43591634..12c4394d2c 100644
--- a/tests/schema/test_lazy/type_b.py
+++ b/tests/schema/test_lazy/type_b.py
@@ -1,12 +1,22 @@
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, List
from typing_extensions import Annotated
import strawberry
if TYPE_CHECKING:
from .type_a import TypeA
+ from .type_c import TypeC
+
+ ListTypeA = List[TypeA]
+ ListTypeC = List[TypeC]
else:
TypeA = Annotated["TypeA", strawberry.lazy("tests.schema.test_lazy.type_a")]
+ ListTypeA = List[
+ Annotated["TypeA", strawberry.lazy("tests.schema.test_lazy.type_a")]
+ ]
+ ListTypeC = List[
+ Annotated["TypeC", strawberry.lazy("tests.schema.test_lazy.type_c")]
+ ]
@strawberry.type
@@ -18,3 +28,19 @@ def type_a(
from .type_a import TypeA
return TypeA()
+
+ @strawberry.field()
+ def type_a_list(
+ self,
+ ) -> ListTypeA: # pragma: no cover
+ from .type_a import TypeA
+
+ return [TypeA()]
+
+ @strawberry.field()
+ def type_c_list(
+ self,
+ ) -> ListTypeC: # pragma: no cover
+ from .type_c import TypeC
+
+ return [TypeC()]
diff --git a/tests/schema/test_mutation.py b/tests/schema/test_mutation.py
index 2671cb3fd4..b41476018b 100644
--- a/tests/schema/test_mutation.py
+++ b/tests/schema/test_mutation.py
@@ -3,7 +3,7 @@
from textwrap import dedent
import strawberry
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
def test_mutation():
diff --git a/tests/schema/test_name_converter.py b/tests/schema/test_name_converter.py
index ba22e0b1d0..a406fbd532 100644
--- a/tests/schema/test_name_converter.py
+++ b/tests/schema/test_name_converter.py
@@ -3,17 +3,16 @@
from typing import Generic, List, Optional, TypeVar, Union
import strawberry
-from strawberry.arguments import StrawberryArgument
-from strawberry.custom_scalar import ScalarDefinition
from strawberry.directive import StrawberryDirective
-from strawberry.enum import EnumDefinition, EnumValue
-from strawberry.field import StrawberryField
from strawberry.schema.config import StrawberryConfig
from strawberry.schema.name_converter import NameConverter
from strawberry.schema_directive import Location, StrawberrySchemaDirective
-from strawberry.type import StrawberryType
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
+from strawberry.types.arguments import StrawberryArgument
+from strawberry.types.base import StrawberryObjectDefinition, StrawberryType
+from strawberry.types.enum import EnumDefinition, EnumValue
+from strawberry.types.field import StrawberryField
+from strawberry.types.scalar import ScalarDefinition
+from strawberry.types.union import StrawberryUnion
class AppendsNameConverter(NameConverter):
diff --git a/tests/schema/test_one_of.py b/tests/schema/test_one_of.py
new file mode 100644
index 0000000000..a1139a97f9
--- /dev/null
+++ b/tests/schema/test_one_of.py
@@ -0,0 +1,278 @@
+from __future__ import annotations
+
+from typing import Any
+
+import pytest
+
+import strawberry
+from strawberry.schema_directives import OneOf
+
+
+@strawberry.input(one_of=True)
+class ExampleInputTagged:
+ a: str | None = strawberry.UNSET
+ b: int | None = strawberry.UNSET
+
+
+@strawberry.type
+class ExampleResult:
+ a: str | None
+ b: int | None
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def test(self, input: ExampleInputTagged) -> ExampleResult:
+ return input # type: ignore
+
+
+schema = strawberry.Schema(query=Query)
+
+
+@pytest.mark.parametrize(
+ ("default_value", "variables"),
+ (
+ ("{a: null, b: null}", {}),
+ ('{ a: "abc", b: 123 }', {}),
+ ("{a: null, b: 123}", {}),
+ ("{}", {}),
+ ),
+)
+def test_must_specify_at_least_one_key_default(
+ default_value: str, variables: dict[str, Any]
+):
+ query = f"""
+ query ($input: ExampleInputTagged! = {default_value}) {{
+ test(input: $input) {{
+ a
+ b
+ }}
+ }}
+ """
+
+ result = schema.execute_sync(query, variable_values=variables)
+
+ assert result.errors
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message
+ == "OneOf Input Object 'ExampleInputTagged' must specify exactly one key."
+ )
+
+
+@pytest.mark.parametrize(
+ ("value", "variables"),
+ [
+ ("{a: null, b: null}", {}),
+ ('{ a: "abc", b: 123 }', {}),
+ ("{a: null, b: 123}", {}),
+ ("{}", {}),
+ ("{ a: $a, b: 123 }", {"a": "abc"}),
+ ("{ a: $a, b: 123 }", {}),
+ ("{ a: $a, b: $b }", {"a": "abc"}),
+ ("$input", {"input": {"a": "abc", "b": 123}}),
+ ("$input", {"input": {"a": "abc", "b": None}}),
+ ("$input", {"input": {}}),
+ ('{ a: "abc", b: null }', {}),
+ ],
+)
+def test_must_specify_at_least_one_key_literal(value: str, variables: dict[str, Any]):
+ variables_definitions = []
+
+ if "$a" in value:
+ variables_definitions.append("$a: String")
+
+ if "$b" in value:
+ variables_definitions.append("$b: Int")
+
+ if "$input" in value:
+ variables_definitions.append("$input: ExampleInputTagged!")
+
+ variables_definition_str = (
+ f'({", ".join(variables_definitions)})' if variables_definitions else ""
+ )
+
+ query = f"""
+ query {variables_definition_str} {{
+ test(input: {value}) {{
+ a
+ b
+ }}
+ }}
+ """
+
+ result = schema.execute_sync(query, variable_values=variables)
+
+ assert result.errors
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message
+ == "OneOf Input Object 'ExampleInputTagged' must specify exactly one key."
+ )
+
+
+def test_value_must_be_non_null_input():
+ query = """
+ query ($input: ExampleInputTagged!) {
+ test(input: $input) {
+ a
+ b
+ }
+ }
+ """
+
+ result = schema.execute_sync(query, variable_values={"input": {"a": None}})
+
+ assert result.errors
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Value for member field 'a' must be non-null"
+
+
+def test_value_must_be_non_null_literal():
+ query = """
+ query {
+ test(input: { a: null }) {
+ a
+ b
+ }
+ }
+ """
+
+ result = schema.execute_sync(query, variable_values={"input": {"a": None}})
+
+ assert result.errors
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Field 'ExampleInputTagged.a' must be non-null."
+
+
+def test_value_must_be_non_null_variable():
+ query = """
+ query ($b: Int) {
+ test(input: { b: $b }) {
+ b
+ }
+ }
+ """
+
+ result = schema.execute_sync(query, variable_values={})
+
+ assert result.errors
+ assert len(result.errors) == 1
+ assert (
+ result.errors[0].message
+ == "Variable 'b' must be non-nullable to be used for OneOf Input Object 'ExampleInputTagged'."
+ )
+
+
+@pytest.mark.parametrize(
+ ("value", "variables", "expected"),
+ [
+ ("{ b: $b }", {"b": 123}, {"b": 123}),
+ ("$input", {"input": {"b": 123}}, {"b": 123}),
+ ('{ a: "abc" }', {}, {"a": "abc"}),
+ ("$input", {"input": {"a": "abc"}}, {"a": "abc"}),
+ ],
+)
+def test_works(value: str, variables: dict[str, Any], expected: dict[str, Any]):
+ variables_definitions = []
+
+ if "$b" in value:
+ variables_definitions.append("$b: Int!")
+
+ if "$input" in value:
+ variables_definitions.append("$input: ExampleInputTagged!")
+
+ variables_definition_str = (
+ f'({", ".join(variables_definitions)})' if variables_definitions else ""
+ )
+
+ field = next(iter(expected.keys()))
+
+ query = f"""
+ query {variables_definition_str} {{
+ test(input: {value}) {{
+ {field}
+ }}
+ }}
+ """
+
+ result = schema.execute_sync(query, variable_values=variables)
+
+ assert not result.errors
+ assert result.data["test"] == expected
+
+
+def test_works_with_camelcasing():
+ global ExampleWithLongerNames, Result
+
+ @strawberry.input(directives=[OneOf()])
+ class ExampleWithLongerNames:
+ a_field: str | None = strawberry.UNSET
+ b_field: int | None = strawberry.UNSET
+
+ @strawberry.type
+ class Result:
+ a_field: str | None
+ b_field: int | None
+
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def test(self, input: ExampleWithLongerNames) -> Result:
+ return Result( # noqa
+ a_field=None if input.a_field is strawberry.UNSET else input.a_field,
+ b_field=None if input.b_field is strawberry.UNSET else input.b_field,
+ )
+
+ schema = strawberry.Schema(query=Query)
+
+ query = """
+ query ($input: ExampleWithLongerNames!) {
+ test(input: $input) {
+ aField
+ bField
+ }
+ }
+ """
+
+ result = schema.execute_sync(query, variable_values={"input": {"aField": "abc"}})
+
+ assert not result.errors
+ assert result.data["test"] == {"aField": "abc", "bField": None}
+
+ del ExampleWithLongerNames, Result
+
+
+def test_introspection():
+ query = """
+ query {
+ __type(name: "ExampleInputTagged") {
+ name
+ isOneOf
+ }
+ }
+ """
+
+ result = schema.execute_sync(query)
+
+ assert not result.errors
+
+ assert result.data == {"__type": {"name": "ExampleInputTagged", "isOneOf": True}}
+
+
+def test_introspection_builtin():
+ query = """
+ query {
+ __type(name: "String") {
+ name
+ isOneOf
+ }
+ }
+ """
+
+ result = schema.execute_sync(query)
+
+ assert not result.errors
+
+ assert result.data == {"__type": {"name": "String", "isOneOf": False}}
diff --git a/tests/schema/test_permission.py b/tests/schema/test_permission.py
index b9853187b4..0cec4cde60 100644
--- a/tests/schema/test_permission.py
+++ b/tests/schema/test_permission.py
@@ -12,7 +12,6 @@
)
from strawberry.permission import BasePermission, PermissionExtension
from strawberry.printer import print_schema
-from strawberry.types import Info
def test_raises_graphql_error_when_permission_method_is_missing():
@@ -37,7 +36,7 @@ class IsAuthenticated(BasePermission):
message = "User is not authenticated"
def has_permission(
- self, source: typing.Any, info: Info, **kwargs: typing.Any
+ self, source: typing.Any, info: strawberry.Info, **kwargs: typing.Any
) -> bool:
return False
@@ -61,7 +60,7 @@ class IsAdmin(BasePermission):
message = "You are not authorized"
def has_permission(
- self, source: typing.Any, info: Info, **kwargs: typing.Any
+ self, source: typing.Any, info: strawberry.Info, **kwargs: typing.Any
) -> bool:
return False
@@ -121,7 +120,7 @@ class CanSeeEmail(BasePermission):
message = "Cannot see email for this user"
def has_permission(
- self, source: typing.Any, info: Info, **kwargs: typing.Any
+ self, source: typing.Any, info: strawberry.Info, **kwargs: typing.Any
) -> bool:
return source.name.lower() == "patrick"
@@ -157,7 +156,7 @@ class CanSeeEmail(BasePermission):
message = "Cannot see email for this user"
def has_permission(
- self, source: typing.Any, info: Info, **kwargs: typing.Any
+ self, source: typing.Any, info: strawberry.Info, **kwargs: typing.Any
) -> bool:
return kwargs.get("secure", False)
@@ -193,7 +192,7 @@ class CanSeeEmail(BasePermission):
message = "Cannot see email for this user"
def has_permission(
- self, source: typing.Any, info: Info, **kwargs: typing.Any
+ self, source: typing.Any, info: strawberry.Info, **kwargs: typing.Any
) -> bool:
return source.name.lower() == "patrick"
diff --git a/tests/schema/test_private_field.py b/tests/schema/test_private_field.py
index 951a21eebf..554f6b59fa 100644
--- a/tests/schema/test_private_field.py
+++ b/tests/schema/test_private_field.py
@@ -6,7 +6,7 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import PrivateStrawberryFieldError
-from strawberry.field import StrawberryField
+from strawberry.types.field import StrawberryField
def test_private_field():
@@ -79,7 +79,6 @@ class SensitiveData:
def test_private_field_with_str_annotations():
"""Check compatibility of strawberry.Private with annotations as string."""
-
schema = strawberry.Schema(query=Query)
result = schema.execute_sync(
@@ -98,7 +97,6 @@ def test_private_field_with_str_annotations():
def test_private_field_defined_outside_module_scope():
"""Check compatibility of strawberry.Private when defined outside module scope."""
-
global LocallyScopedSensitiveData
@strawberry.type
@@ -126,7 +124,6 @@ def test_private_field_type_resolution_with_generic_type():
Refer to: https://github.com/strawberry-graphql/strawberry/issues/1938
"""
-
T = TypeVar("T")
class GenericPrivateType(Generic[T]):
diff --git a/tests/schema/test_pydantic.py b/tests/schema/test_pydantic.py
index 312ad00694..6e905b2403 100644
--- a/tests/schema/test_pydantic.py
+++ b/tests/schema/test_pydantic.py
@@ -14,8 +14,7 @@ class UserModel(BaseModel):
@strawberry.experimental.pydantic.type(
UserModel, all_fields=True, use_pydantic_alias=True
)
- class User:
- ...
+ class User: ...
@strawberry.type
class Query:
@@ -47,8 +46,7 @@ class UserModel(BaseModel):
@strawberry.experimental.pydantic.type(
UserModel, all_fields=True, use_pydantic_alias=False
)
- class User:
- ...
+ class User: ...
@strawberry.type
class Query:
diff --git a/tests/schema/test_resolvers.py b/tests/schema/test_resolvers.py
index b990fc3cbc..574955a954 100644
--- a/tests/schema/test_resolvers.py
+++ b/tests/schema/test_resolvers.py
@@ -176,10 +176,10 @@ def __post_init__(self):
def test_only_info_function_resolvers():
- def function_resolver(info: Info) -> str:
+ def function_resolver(info: strawberry.Info) -> str:
return f"I'm a function resolver for {info.field_name}"
- def function_resolver_with_params(info: Info, x: str) -> str:
+ def function_resolver_with_params(info: strawberry.Info, x: str) -> str:
return f"I'm {x} for {info.field_name}"
@strawberry.type
@@ -499,11 +499,11 @@ def name_based_info(info, icon: str) -> str:
return f"I'm a resolver for {icon} {info.field_name}"
-def type_based_info(info: Info, icon: str) -> str:
+def type_based_info(info: strawberry.Info, icon: str) -> str:
return f"I'm a resolver for {icon} {info.field_name}"
-def generic_type_based_info(icon: str, info: Info[Any, Any]) -> str:
+def generic_type_based_info(icon: str, info: strawberry.Info) -> str:
return f"I'm a resolver for {icon} {info.field_name}"
@@ -538,8 +538,7 @@ def test_name_based_info_is_deprecated():
@strawberry.type
class Query:
@strawberry.field
- def foo(info: Any) -> str:
- ...
+ def foo(info: Any) -> str: ...
strawberry.Schema(query=Query)
diff --git a/tests/schema/test_scalars.py b/tests/schema/test_scalars.py
index 920b1cb7d4..6a7a191182 100644
--- a/tests/schema/test_scalars.py
+++ b/tests/schema/test_scalars.py
@@ -176,9 +176,9 @@ def echo_json_nullable(data: Optional[JSON]) -> Optional[JSON]:
expected_schema = dedent(
'''
"""
- The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
+ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
- scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+ scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Query {
echoJson(data: JSON!): JSON!
diff --git a/tests/schema/test_schema_generation.py b/tests/schema/test_schema_generation.py
index 5770eb7349..6826159b3d 100644
--- a/tests/schema/test_schema_generation.py
+++ b/tests/schema/test_schema_generation.py
@@ -56,8 +56,7 @@ class Query:
def test_schema_fails_on_an_invalid_schema():
@strawberry.type
- class Query:
- ... # Type must have at least one field
+ class Query: ... # Type must have at least one field
with pytest.raises(ValueError, match="Invalid Schema. Errors.*"):
strawberry.Schema(query=Query)
diff --git a/tests/schema/test_schema_hooks.py b/tests/schema/test_schema_hooks.py
index 51038e2f49..c24b352e6e 100644
--- a/tests/schema/test_schema_hooks.py
+++ b/tests/schema/test_schema_hooks.py
@@ -2,8 +2,8 @@
from typing import List
import strawberry
-from strawberry.field import StrawberryField
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import StrawberryObjectDefinition
+from strawberry.types.field import StrawberryField
def test_can_change_which_fields_are_exposed():
diff --git a/tests/schema/test_subscription.py b/tests/schema/test_subscription.py
index 6850e3f974..b63af92dcc 100644
--- a/tests/schema/test_subscription.py
+++ b/tests/schema/test_subscription.py
@@ -15,7 +15,6 @@
import pytest
import strawberry
-from strawberry.types import Info
@pytest.mark.asyncio
@@ -48,7 +47,9 @@ async def test_subscription_with_permission():
class IsAuthenticated(BasePermission):
message = "Unauthorized"
- async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
+ async def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: Any
+ ) -> bool:
return True
@strawberry.type
diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py
index fe8e2ec98f..42392b9584 100644
--- a/tests/schema/test_union.py
+++ b/tests/schema/test_union.py
@@ -9,7 +9,7 @@
import strawberry
from strawberry.exceptions import InvalidUnionTypeError
-from strawberry.lazy_type import lazy
+from strawberry.types.lazy_type import lazy
def test_union_as_field():
@@ -467,8 +467,7 @@ def my_field(self) -> MyUnion:
reason="pipe syntax for union is only available on python 3.10+",
)
def test_union_optional_with_or_operator():
- """
- Verify that the `|` operator is supported when annotating unions as
+ """Verify that the `|` operator is supported when annotating unions as
optional in schemas.
"""
@@ -502,9 +501,7 @@ def animal(self) -> animal_union | None:
def test_union_with_input_types():
- """
- Verify that union of input types raises an error
- """
+ """Verify that union of input types raises an error."""
@strawberry.type
class User:
@@ -538,8 +535,7 @@ def user(self, data: Input) -> User:
def test_union_with_similar_nested_generic_types():
- """
- Previously this failed due to an edge case where Strawberry would choose AContainer
+ """Previously this failed due to an edge case where Strawberry would choose AContainer
as the resolved type for container_b due to the inability to exactly match the
nested generic `Container.items`.
"""
@@ -611,9 +607,7 @@ def container_b(self) -> Union[Container[B], B]:
def test_lazy_union():
- """
- Previously this failed to evaluate generic parameters on lazy types
- """
+ """Previously this failed to evaluate generic parameters on lazy types"""
TypeA = Annotated["TypeA", lazy("tests.schema.test_lazy_types.type_a")]
TypeB = Annotated["TypeB", lazy("tests.schema.test_lazy_types.type_b")]
diff --git a/tests/schema/test_union_deprecated.py b/tests/schema/test_union_deprecated.py
index 402a24d831..c20695f0b1 100644
--- a/tests/schema/test_union_deprecated.py
+++ b/tests/schema/test_union_deprecated.py
@@ -215,8 +215,7 @@ def my_field(self) -> MyUnion:
reason="pipe syntax for union is only available on python 3.10+",
)
def test_union_optional_with_or_operator():
- """
- Verify that the `|` operator is supported when annotating unions as
+ """Verify that the `|` operator is supported when annotating unions as
optional in schemas.
"""
diff --git a/tests/schema/types/test_date.py b/tests/schema/types/test_date.py
index f3232c7a5e..160d52f330 100644
--- a/tests/schema/types/test_date.py
+++ b/tests/schema/types/test_date.py
@@ -5,6 +5,7 @@
import strawberry
from strawberry.types.execution import ExecutionResult
+from tests.conftest import IS_GQL_32
def test_serialization():
@@ -99,23 +100,20 @@ def date_input(self, date_input: datetime.date) -> datetime.date:
),
)
def test_serialization_of_incorrect_date_string(value):
- """
- Test GraphQLError is raised for incorrect date.
+ """Test GraphQLError is raised for incorrect date.
The error should exclude "original_error".
"""
-
result = execute_mutation(value)
assert result.errors
assert isinstance(result.errors[0], GraphQLError)
- assert result.errors[0].original_error is None
+ if IS_GQL_32:
+ assert result.errors[0].original_error is None
def test_serialization_error_message_for_incorrect_date_string():
+ """Test if error message is using original error message from
+ date lib, and is properly formatted.
"""
- Test if error message is using original error message from
- date lib, and is properly formatted
- """
-
result = execute_mutation("2021-13-01")
assert result.errors
assert result.errors[0].message == (
diff --git a/tests/schema/types/test_datetime.py b/tests/schema/types/test_datetime.py
index 48741f38e6..bba400fde8 100644
--- a/tests/schema/types/test_datetime.py
+++ b/tests/schema/types/test_datetime.py
@@ -6,6 +6,7 @@
import strawberry
from strawberry.types.execution import ExecutionResult
+from tests.conftest import IS_GQL_32
@pytest.mark.parametrize(
@@ -149,23 +150,20 @@ def datetime_input(
),
)
def test_serialization_of_incorrect_datetime_string(value):
- """
- Test GraphQLError is raised for incorrect datetime.
+ """Test GraphQLError is raised for incorrect datetime.
The error should exclude "original_error".
"""
-
result = execute_mutation(value)
assert result.errors
assert isinstance(result.errors[0], GraphQLError)
- assert result.errors[0].original_error is None
+ if IS_GQL_32:
+ assert result.errors[0].original_error is None
def test_serialization_error_message_for_incorrect_datetime_string():
+ """Test if error message is using original error message
+ from datetime lib, and is properly formatted.
"""
- Test if error message is using original error message
- from datetime lib, and is properly formatted
- """
-
result = execute_mutation("2021-13-01T09:00:00")
assert result.errors
assert result.errors[0].message == (
diff --git a/tests/schema/types/test_decimal.py b/tests/schema/types/test_decimal.py
index 327cfddc92..b13571835d 100644
--- a/tests/schema/types/test_decimal.py
+++ b/tests/schema/types/test_decimal.py
@@ -3,6 +3,7 @@
from graphql import GraphQLError
import strawberry
+from tests.conftest import IS_GQL_32
def test_decimal():
@@ -36,8 +37,7 @@ def example_decimal(self, decimal: Decimal) -> Decimal:
def test_serialization_of_incorrect_decimal_string():
- """
- Test GraphQLError is raised for an invalid Decimal.
+ """Test GraphQLError is raised for an invalid Decimal.
The error should exclude "original_error".
"""
@@ -64,7 +64,8 @@ def decimal_input(self, decimal_input: Decimal) -> Decimal:
assert result.errors
assert isinstance(result.errors[0], GraphQLError)
- assert result.errors[0].original_error is None
+ if IS_GQL_32:
+ assert result.errors[0].original_error is None
assert result.errors[0].message == (
"Variable '$value' got invalid value 'fail'; Value cannot represent a "
'Decimal: "fail".'
diff --git a/tests/schema/types/test_time.py b/tests/schema/types/test_time.py
index 3f55dd7669..97c27bec8c 100644
--- a/tests/schema/types/test_time.py
+++ b/tests/schema/types/test_time.py
@@ -5,6 +5,7 @@
import strawberry
from strawberry.types.execution import ExecutionResult
+from tests.conftest import IS_GQL_32
def test_serialization():
@@ -98,23 +99,20 @@ def time_input(self, time_input: datetime.time) -> datetime.time:
),
)
def test_serialization_of_incorrect_time_string(value):
- """
- Test GraphQLError is raised for incorrect time.
+ """Test GraphQLError is raised for incorrect time.
The error should exclude "original_error".
"""
-
result = execute_mutation(value)
assert result.errors
assert isinstance(result.errors[0], GraphQLError)
- assert result.errors[0].original_error is None
+ if IS_GQL_32:
+ assert result.errors[0].original_error is None
def test_serialization_error_message_for_incorrect_time_string():
+ """Test if error message is using original error message
+ from time lib, and is properly formatted.
"""
- Test if error message is using original error message
- from time lib, and is properly formatted
- """
-
result = execute_mutation("25:00")
assert result.errors
assert result.errors[0].message == (
diff --git a/tests/schema/types/test_uuid.py b/tests/schema/types/test_uuid.py
index 6c44b56c05..38fa4daf24 100644
--- a/tests/schema/types/test_uuid.py
+++ b/tests/schema/types/test_uuid.py
@@ -3,6 +3,7 @@
from graphql import GraphQLError
import strawberry
+from tests.conftest import IS_GQL_32
def test_uuid():
@@ -36,8 +37,7 @@ def example_uuid_in(self, uid: uuid.UUID) -> uuid.UUID:
def test_serialization_of_incorrect_uuid_string():
- """
- Test GraphQLError is raised for an invalid UUID.
+ """Test GraphQLError is raised for an invalid UUID.
The error should exclude "original_error".
"""
@@ -64,7 +64,8 @@ def uuid_input(self, uuid_input: uuid.UUID) -> uuid.UUID:
assert result.errors
assert isinstance(result.errors[0], GraphQLError)
- assert result.errors[0].original_error is None
+ if IS_GQL_32:
+ assert result.errors[0].original_error is None
assert result.errors[0].message == (
"Variable '$value' got invalid value 'fail'; Value cannot represent a "
'UUID: "fail". badly formed hexadecimal UUID string'
diff --git a/tests/schema_codegen/test_federation.py b/tests/schema_codegen/test_federation.py
new file mode 100644
index 0000000000..34c553da81
--- /dev/null
+++ b/tests/schema_codegen/test_federation.py
@@ -0,0 +1,226 @@
+import textwrap
+
+from strawberry.schema_codegen import codegen
+
+
+def test_support_for_key_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
+
+ type User @key(fields: "id") {
+ id: ID!
+ username: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(keys=["id"])
+ class User:
+ id: strawberry.ID
+ username: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_support_for_shareable_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@shareable"])
+
+ type User @shareable {
+ id: ID!
+ username: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(shareable=True)
+ class User:
+ id: strawberry.ID
+ username: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_support_for_inaccessible_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
+
+ type User @inaccessible {
+ id: ID!
+ username: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(inaccessible=True)
+ class User:
+ id: strawberry.ID
+ username: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_support_for_tags_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tags"])
+
+ type User @tag(name: "user") @tag(name: "admin") {
+ id: ID!
+ username: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(tags=["user", "admin"])
+ class User:
+ id: strawberry.ID
+ username: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_uses_federation_schema():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
+
+ type Query {
+ me: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.type
+ class Query:
+ me: str
+
+ schema = strawberry.federation.Schema(query=Query, enable_federation_2=True)
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_supports_authenticated_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@authenticated"])
+
+ type User @authenticated {
+ name: String! @authenticated
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(authenticated=True)
+ class User:
+ name: str = strawberry.federation.field(authenticated=True)
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_requires_scope():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@requiresScope"])
+
+ type User @requiresScopes(scopes: [["client", "poweruser"], ["admin"], ["productowner"]]){
+ name: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(requires_scopes=[["client", "poweruser"], ["admin"], ["productowner"]])
+ class User:
+ name: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_policy_directive():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@policy"])
+
+ type User @policy(policies: ["userPolicy", [["client", "poweruser"], ["admin"], ["productowner"]]]){
+ name: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.federation.type(policy=["userPolicy", [["client", "poweruser"], ["admin"], ["productowner"]]])
+ class User:
+ name: str
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
+
+
+def test_support_for_directives_on_fields():
+ schema = """
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@requires", "@provides"])
+
+ type User {
+ a: String! @shareable
+ b: String! @inaccessible
+ c: String! @override(from: "mySubGraph")
+ c1: String! @override(from: "mySubGraph", label: "some.label")
+ d: String! @external
+ e: String! @requires(fields: "id")
+ f: String! @provides(fields: "id")
+ g: String! @tag(name: "user")
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+ from strawberry.federation.schema_directives import Override
+
+ @strawberry.type
+ class User:
+ a: str = strawberry.federation.field(shareable=True)
+ b: str = strawberry.federation.field(inaccessible=True)
+ c: str = strawberry.federation.field(override="mySubGraph")
+ c1: str = strawberry.federation.field(override=Override(override_from="mySubGraph", label="some.label"))
+ d: str = strawberry.federation.field(external=True)
+ e: str = strawberry.federation.field(requires=["id"])
+ f: str = strawberry.federation.field(provides=["id"])
+ g: str = strawberry.federation.field(tags=["user"])
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
diff --git a/tests/schema_codegen/test_order.py b/tests/schema_codegen/test_order.py
new file mode 100644
index 0000000000..3170dd58f2
--- /dev/null
+++ b/tests/schema_codegen/test_order.py
@@ -0,0 +1,49 @@
+import textwrap
+
+from strawberry.schema_codegen import codegen
+
+
+def test_generates_used_interface_before():
+ schema = """
+ type Human implements Being {
+ id: ID!
+ name: String!
+ friends: [Human]
+ }
+
+ type Cat implements Being {
+ id: ID!
+ name: String!
+ livesLeft: Int
+ }
+
+ interface Being {
+ id: ID!
+ name: String!
+ }
+ """
+
+ expected = textwrap.dedent(
+ """
+ import strawberry
+
+ @strawberry.interface
+ class Being:
+ id: strawberry.ID
+ name: str
+
+ @strawberry.type
+ class Human(Being):
+ id: strawberry.ID
+ name: str
+ friends: list[Human | None] | None
+
+ @strawberry.type
+ class Cat(Being):
+ id: strawberry.ID
+ name: str
+ lives_left: int | None
+ """
+ ).strip()
+
+ assert codegen(schema).strip() == expected
diff --git a/tests/starlite/schema.py b/tests/starlite/schema.py
index 0b671ed3f2..56b0190905 100644
--- a/tests/starlite/schema.py
+++ b/tests/starlite/schema.py
@@ -9,13 +9,14 @@
from strawberry.file_uploads import Upload
from strawberry.permission import BasePermission
from strawberry.subscriptions.protocols.graphql_transport_ws.types import PingMessage
-from strawberry.types import Info
class AlwaysFailPermission(BasePermission):
message = "You are not authorized"
- def has_permission(self, source: Any, info: Info, **kwargs: typing.Any) -> bool:
+ def has_permission(
+ self, source: Any, info: strawberry.Info, **kwargs: typing.Any
+ ) -> bool:
return False
diff --git a/tests/starlite/test_context.py b/tests/starlite/test_context.py
index 9b33127c4b..4431afa7a4 100644
--- a/tests/starlite/test_context.py
+++ b/tests/starlite/test_context.py
@@ -1,4 +1,6 @@
-from typing import Any, Dict
+from typing import Dict
+
+import pytest
import strawberry
@@ -6,7 +8,6 @@
from starlite import Provide, Starlite
from starlite.testing import TestClient
from strawberry.starlite import BaseContext, make_graphql_controller
- from strawberry.types import Info
from tests.starlite.app import create_app
except ModuleNotFoundError:
pass
@@ -21,7 +22,7 @@ def test_with_class_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.request is not None
assert info.context.strawberry == "rocks"
return "abc"
@@ -56,7 +57,7 @@ def test_with_dict_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") == "rocks"
return "abc"
@@ -86,7 +87,7 @@ def test_without_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
return "abc"
@@ -107,7 +108,7 @@ def test_with_invalid_context_getter():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info[Any, Any]) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("request") is not None
assert info.context.get("strawberry") is None
return "abc"
@@ -146,7 +147,7 @@ def test_custom_context():
@strawberry.type
class Query:
@strawberry.field
- def custom_context_value(self, info: Info[Any, Any]) -> str:
+ def custom_context_value(self, info: strawberry.Info) -> str:
return info.context["custom_value"]
schema = strawberry.Schema(query=Query)
@@ -169,7 +170,7 @@ async def task():
@strawberry.type
class Query:
@strawberry.field
- def something(self, info: Info[Any, Any]) -> str:
+ def something(self, info: strawberry.Info) -> str:
response = info.context["response"]
response.background.tasks.append(task)
return "foo"
@@ -182,3 +183,20 @@ def something(self, info: Info[Any, Any]) -> str:
assert response.json() == {"data": {"something": "foo"}}
assert task_complete
+
+
+def test_starlite_usage_triggers_deprecation_warning():
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ def abc(self, info: strawberry.Info) -> str:
+ assert info.context.get("request") is not None
+ assert info.context.get("strawberry") is None
+ return "abc"
+
+ schema = strawberry.Schema(query=Query)
+
+ with pytest.deprecated_call(
+ match="The `starlite` integration is deprecated in favor of `litestar` integration"
+ ):
+ make_graphql_controller(path="/graphql", schema=schema, context_getter=None)
diff --git a/tests/starlite/test_response_headers.py b/tests/starlite/test_response_headers.py
index 6f1ae1cb36..656f8e9ab8 100644
--- a/tests/starlite/test_response_headers.py
+++ b/tests/starlite/test_response_headers.py
@@ -1,5 +1,4 @@
import strawberry
-from strawberry.types import Info
try:
from starlite import Starlite
@@ -14,7 +13,7 @@ def test_set_response_headers():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].headers["X-Strawberry"] = "rocks"
return "abc"
@@ -36,7 +35,7 @@ def test_set_cookie_headers():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].set_cookie(
key="strawberry",
diff --git a/tests/starlite/test_response_status.py b/tests/starlite/test_response_status.py
index 38ba209378..5c9e490be5 100644
--- a/tests/starlite/test_response_status.py
+++ b/tests/starlite/test_response_status.py
@@ -1,5 +1,4 @@
import strawberry
-from strawberry.types import Info
try:
from starlite import Starlite
@@ -14,7 +13,7 @@ def test_set_custom_http_response_status():
@strawberry.type
class Query:
@strawberry.field
- def abc(self, info: Info) -> str:
+ def abc(self, info: strawberry.Info) -> str:
assert info.context.get("response") is not None
info.context["response"].status_code = 418
return "abc"
diff --git a/tests/test_aio.py b/tests/test_aio.py
index ecb6ffa281..8c55e1ef56 100644
--- a/tests/test_aio.py
+++ b/tests/test_aio.py
@@ -26,7 +26,7 @@ async def gen():
res = []
async for v in aislice(gen(), 0, 2):
- res.append(v) # noqa: PERF402
+ res.append(v) # noqa: PERF401
assert res == ["a", "b"]
@@ -39,7 +39,7 @@ async def gen():
res = []
async for v in aislice(gen(), 0, 2):
- res.append(v) # noqa: PERF402
+ res.append(v) # noqa: PERF401
assert res == []
@@ -52,7 +52,7 @@ async def gen():
res = []
async for v in aislice(gen(), 0, 0):
- res.append(v) # noqa: PERF402
+ res.append(v) # noqa: PERF401
assert res == []
@@ -68,7 +68,7 @@ async def gen():
res = []
async for v in aislice(gen(), 0, 4, 2):
- res.append(v) # noqa: PERF402
+ res.append(v) # noqa: PERF401
assert res == ["a", "c"]
diff --git a/tests/test_auto.py b/tests/test_auto.py
index f181bc78bd..ddeac975e2 100644
--- a/tests/test_auto.py
+++ b/tests/test_auto.py
@@ -3,8 +3,8 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.auto import StrawberryAuto, auto
-from strawberry.type import StrawberryList
+from strawberry.types.auto import StrawberryAuto, auto
+from strawberry.types.base import StrawberryList
@strawberry.type
@@ -20,7 +20,7 @@ def test_singleton():
def test_annotated():
assert get_args(auto) == (Any, StrawberryAuto())
some_obj = object()
- new_annotated = Annotated[auto, some_obj]
+ new_annotated = Annotated[strawberry.auto, some_obj]
assert get_args(new_annotated) == (Any, StrawberryAuto(), some_obj)
@@ -47,7 +47,7 @@ def test_isinstance_with_annotation():
def test_isinstance_with_annotated():
assert isinstance(Annotated[auto, object()], StrawberryAuto)
- assert not isinstance(Annotated[str, auto], StrawberryAuto)
+ assert not isinstance(Annotated[str, strawberry.auto], StrawberryAuto)
def test_isinstance_with_unresolvable_annotation():
diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py
index 7cf3ec8dae..33e81b949f 100644
--- a/tests/test_deprecations.py
+++ b/tests/test_deprecations.py
@@ -19,6 +19,6 @@ def test_get_warns():
def test_can_import_type_definition():
- from strawberry.types.types import TypeDefinition
+ from strawberry.types.base import TypeDefinition
assert TypeDefinition
diff --git a/tests/test_forward_references.py b/tests/test_forward_references.py
index af04206fdd..ea771ec9dd 100644
--- a/tests/test_forward_references.py
+++ b/tests/test_forward_references.py
@@ -11,8 +11,9 @@
import strawberry
from strawberry.printer import print_schema
from strawberry.scalars import JSON
-from strawberry.type import StrawberryList, StrawberryOptional
+from strawberry.types.base import StrawberryList, StrawberryOptional
from tests.a import A
+from tests.d import D
def test_forward_reference():
@@ -30,9 +31,9 @@ class MyType:
expected_representation = '''
"""
- The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
+ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
- scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+ scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type MyType {
id: ID!
@@ -74,6 +75,7 @@ async def a(self) -> A:
type B {
id: ID!
a: A!
+ aList: [A!]!
optionalA: A
optionalA2: A
}
@@ -87,6 +89,36 @@ async def a(self) -> A:
assert print_schema(schema) == textwrap.dedent(expected_representation).strip()
+@pytest.mark.skipif(
+ sys.version_info < (3, 9),
+ reason="Python 3.8 and previous can't properly resolve this.",
+)
+def test_lazy_forward_reference_schema_with_a_list_only():
+ @strawberry.type
+ class Query:
+ @strawberry.field
+ async def d(self) -> D: # pragma: no cover
+ return D(id=strawberry.ID("1"))
+
+ expected_representation = """
+ type C {
+ id: ID!
+ }
+
+ type D {
+ id: ID!
+ cList: [C!]!
+ }
+
+ type Query {
+ d: D!
+ }
+ """
+
+ schema = strawberry.Schema(query=Query)
+ assert print_schema(schema) == textwrap.dedent(expected_representation).strip()
+
+
def test_with_resolver():
global User
diff --git a/tests/test_info.py b/tests/test_info.py
new file mode 100644
index 0000000000..d1e242f58f
--- /dev/null
+++ b/tests/test_info.py
@@ -0,0 +1,25 @@
+from typing import Any
+
+import pytest
+
+import strawberry
+
+
+def test_can_use_info_with_two_arguments():
+ CustomInfo = strawberry.Info[int, str]
+
+ assert CustomInfo.__args__ == (int, str)
+
+
+def test_can_use_info_with_one_argument():
+ CustomInfo = strawberry.Info[int]
+
+ assert CustomInfo.__args__ == (int, Any)
+
+
+def test_cannot_use_info_with_more_than_two_arguments():
+ with pytest.raises(
+ TypeError,
+ match="Too many (arguments|parameters) for ; actual 3, expected 2",
+ ):
+ strawberry.Info[int, str, int] # type: ignore
diff --git a/tests/test_printer/test_basic.py b/tests/test_printer/test_basic.py
index 76da620198..e97d269268 100644
--- a/tests/test_printer/test_basic.py
+++ b/tests/test_printer/test_basic.py
@@ -6,7 +6,8 @@
from strawberry.printer import print_schema
from strawberry.scalars import JSON
from strawberry.schema.config import StrawberryConfig
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
+from tests.conftest import skip_if_gql_32
def test_simple_required_types():
@@ -183,6 +184,7 @@ def search(self, input: MyInput) -> int:
assert print_schema(schema) == textwrap.dedent(expected_type).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_input_other_inputs():
@strawberry.input
class Nested:
@@ -204,8 +206,8 @@ def search(self, input: MyInput) -> str:
expected_type = """
input MyInput {
nested: Nested!
- nested2: Nested! = {s: "a"}
- nested3: Nested! = {s: "a"}
+ nested2: Nested! = { s: "a" }
+ nested3: Nested! = { s: "a" }
nested4: Nested!
}
@@ -223,6 +225,7 @@ def search(self, input: MyInput) -> str:
assert print_schema(schema) == textwrap.dedent(expected_type).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_input_defaults_scalars():
@strawberry.input
class MyInput:
@@ -240,14 +243,14 @@ def search(self, input: MyInput) -> JSON:
expected_type = """
\"\"\"
- The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
+ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
\"\"\"
- scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+ scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
input MyInput {
- j: JSON! = {}
- j2: JSON! = {hello: "world"}
- j3: JSON! = {hello: {nice: "world"}}
+ j: JSON! = { }
+ j2: JSON! = { hello: "world" }
+ j3: JSON! = { hello: { nice: "world" } }
}
type Query {
@@ -260,6 +263,7 @@ def search(self, input: MyInput) -> JSON:
assert print_schema(schema) == textwrap.dedent(expected_type).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_arguments_scalar():
@strawberry.input
class MyInput:
@@ -285,14 +289,14 @@ def search3(self, j: JSON = {"hello": {"nice": "world"}}) -> JSON:
expected_type = """
\"\"\"
- The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
+ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
\"\"\"
- scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
+ scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Query {
- search(j: JSON! = {}): JSON!
- search2(j: JSON! = {hello: "world"}): JSON!
- search3(j: JSON! = {hello: {nice: "world"}}): JSON!
+ search(j: JSON! = { }): JSON!
+ search2(j: JSON! = { hello: "world" }): JSON!
+ search3(j: JSON! = { hello: { nice: "world" } }): JSON!
}
"""
diff --git a/tests/test_printer/test_one_of.py b/tests/test_printer/test_one_of.py
new file mode 100644
index 0000000000..faf588ed0c
--- /dev/null
+++ b/tests/test_printer/test_one_of.py
@@ -0,0 +1,50 @@
+from __future__ import annotations
+
+import textwrap
+
+import strawberry
+from strawberry.schema_directives import OneOf
+
+
+@strawberry.input(directives=[OneOf()])
+class ExampleInputTagged:
+ a: str | None
+ b: int | None
+
+
+@strawberry.type
+class ExampleResult:
+ a: str | None
+ b: int | None
+
+
+@strawberry.type
+class Query:
+ @strawberry.field
+ def test(self, input: ExampleInputTagged) -> ExampleResult: # pragma: no cover
+ return input # type: ignore
+
+
+schema = strawberry.Schema(query=Query)
+
+
+def test_prints_one_of_directive():
+ expected_type = """
+ directive @oneOf on INPUT_OBJECT
+
+ input ExampleInputTagged @oneOf {
+ a: String
+ b: Int
+ }
+
+ type ExampleResult {
+ a: String
+ b: Int
+ }
+
+ type Query {
+ test(input: ExampleInputTagged!): ExampleResult!
+ }
+ """
+
+ assert str(schema) == textwrap.dedent(expected_type).strip()
diff --git a/tests/test_printer/test_schema_directives.py b/tests/test_printer/test_schema_directives.py
index 1904bd0035..2b3be70ad3 100644
--- a/tests/test_printer/test_schema_directives.py
+++ b/tests/test_printer/test_schema_directives.py
@@ -7,7 +7,8 @@
from strawberry.printer import print_schema
from strawberry.schema.config import StrawberryConfig
from strawberry.schema_directive import Location
-from strawberry.unset import UNSET
+from strawberry.types.unset import UNSET
+from tests.conftest import skip_if_gql_32
def test_print_simple_directive():
@@ -58,6 +59,7 @@ class Query:
assert print_schema(schema) == textwrap.dedent(expected_output).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_directive_on_types():
@strawberry.input
class SensitiveValue:
@@ -132,7 +134,7 @@ def user(self, input: Input) -> User:
type User @sensitiveData(reason: "GDPR") {
firstName: String!
age: Int!
- phone: String! @sensitiveData(reason: "PRIVATE", meta: [{key: "can_share_field", value: "phone_share_accepted"}])
+ phone: String! @sensitiveData(reason: "PRIVATE", meta: [{ key: "can_share_field", value: "phone_share_accepted" }])
phoneShareAccepted: Boolean!
}
@@ -323,6 +325,7 @@ class Query:
assert print_schema(schema) == textwrap.dedent(expected_output).strip()
+@skip_if_gql_32("formatting is different in gql 3.2")
def test_prints_with_types():
@strawberry.input
class SensitiveConfiguration:
@@ -342,7 +345,7 @@ class Query:
directive @sensitive(config: SensitiveConfiguration!) on FIELD_DEFINITION
type Query {
- firstName: String! @sensitive(config: {reason: "example"})
+ firstName: String! @sensitive(config: { reason: "example" })
}
input SensitiveConfiguration {
diff --git a/tests/test_type.py b/tests/test_type.py
index 65caf93910..168cab4e21 100644
--- a/tests/test_type.py
+++ b/tests/test_type.py
@@ -5,8 +5,7 @@
import pytest
import strawberry
-from strawberry.type import get_object_definition
-from strawberry.types.types import StrawberryObjectDefinition
+from strawberry.types.base import StrawberryObjectDefinition, get_object_definition
def test_get_object_definition():
@@ -27,8 +26,7 @@ class Fruit:
assert get_object_definition(Fruit) is None
- class OtherFruit:
- ...
+ class OtherFruit: ...
assert get_object_definition(OtherFruit) is None
diff --git a/tests/tools/test_create_type.py b/tests/tools/test_create_type.py
index 6e69be5411..5f4f99518e 100644
--- a/tests/tools/test_create_type.py
+++ b/tests/tools/test_create_type.py
@@ -4,9 +4,9 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.field import StrawberryField
from strawberry.tools import create_type
-from strawberry.type import get_object_definition
+from strawberry.types.base import get_object_definition
+from strawberry.types.field import StrawberryField
def test_create_type():
diff --git a/tests/tools/test_merge_types.py b/tests/tools/test_merge_types.py
index 7631575217..d342b7ddc6 100644
--- a/tests/tools/test_merge_types.py
+++ b/tests/tools/test_merge_types.py
@@ -37,7 +37,6 @@ def bye(self, name: str = "world") -> str:
def test_custom_name():
"""The resulting type should have a custom name is one is specified"""
-
custom_name = "SuperQuery"
ComboQuery = merge_types(custom_name, (ComplexGreeter, Person))
assert ComboQuery.__name__ == custom_name
@@ -45,7 +44,6 @@ def test_custom_name():
def test_inheritance():
"""It should merge multiple types following the regular inheritance rules"""
-
ComboQuery = merge_types("SuperType", (ComplexGreeter, Person))
definition = ComboQuery.__strawberry_definition__
@@ -58,14 +56,12 @@ def test_inheritance():
def test_empty_list():
"""It should raise when the `types` argument is empty"""
-
with pytest.raises(ValueError):
merge_types("EmptyType", ())
def test_schema():
"""It should create a valid, usable schema based on a merged query"""
-
ComboQuery = merge_types("SuperSchema", (ComplexGreeter, Person))
schema = strawberry.Schema(query=ComboQuery)
@@ -90,6 +86,5 @@ def test_schema():
def test_fields_override():
"""It should warn when merging results in overriding fields"""
-
with pytest.warns(Warning):
merge_types("FieldsOverride", (ComplexGreeter, SimpleGreeter))
diff --git a/tests/typecheckers/__init__.py b/tests/typecheckers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/typecheckers/test_auto.py b/tests/typecheckers/test_auto.py
new file mode 100644
index 0000000000..f42a5c207a
--- /dev/null
+++ b/tests/typecheckers/test_auto.py
@@ -0,0 +1,60 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.type
+class SomeType:
+ foobar: strawberry.auto
+
+
+obj1 = SomeType(foobar=1)
+obj2 = SomeType(foobar="some text")
+obj3 = SomeType(foobar={"some key": "some value"})
+
+reveal_type(obj1.foobar)
+reveal_type(obj2.foobar)
+reveal_type(obj3.foobar)
+"""
+
+
+def test_auto():
+ result = typecheck(CODE)
+
+ assert result.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "obj1.foobar" is "Any"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj2.foobar" is "Any"',
+ line=15,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj3.foobar" is "Any"',
+ line=16,
+ column=13,
+ ),
+ ]
+ )
+
+ assert result.mypy == snapshot(
+ [
+ Result(type="note", message='Revealed type is "Any"', line=14, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=15, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=16, column=13),
+ ]
+ )
diff --git a/tests/typecheckers/test_directives.py b/tests/typecheckers/test_directives.py
new file mode 100644
index 0000000000..d7c47169f0
--- /dev/null
+++ b/tests/typecheckers/test_directives.py
@@ -0,0 +1,50 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+from strawberry.directive import DirectiveLocation
+
+@strawberry.directive(
+ locations=[DirectiveLocation.FRAGMENT_DEFINITION],
+ description="description.",
+)
+def make_int(value: str) -> int:
+ '''description.'''
+ try:
+ return int(value)
+ except ValueError:
+ return 0
+
+reveal_type(make_int)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "make_int" is "StrawberryDirective[int]"',
+ line=16,
+ column=13,
+ )
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.directive.StrawberryDirective[builtins.int]"',
+ line=16,
+ column=13,
+ )
+ ]
+ )
diff --git a/tests/typecheckers/test_enum.py b/tests/typecheckers/test_enum.py
new file mode 100644
index 0000000000..0f77cf3f14
--- /dev/null
+++ b/tests/typecheckers/test_enum.py
@@ -0,0 +1,270 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+CODE_WITH_DECORATOR = """
+from enum import Enum
+
+import strawberry
+
+@strawberry.enum
+class IceCreamFlavour(Enum):
+ VANILLA = "vanilla"
+ STRAWBERRY = "strawberry"
+ CHOCOLATE = "chocolate"
+
+reveal_type(IceCreamFlavour)
+reveal_type(IceCreamFlavour.VANILLA)
+"""
+
+
+def test_enum_with_decorator():
+ results = typecheck(CODE_WITH_DECORATOR)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "IceCreamFlavour" is "type[IceCreamFlavour]"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "IceCreamFlavour.VANILLA" is "Literal[IceCreamFlavour.VANILLA]"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (value: builtins.object) -> mypy_test.IceCreamFlavour"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Literal[mypy_test.IceCreamFlavour.VANILLA]?"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_WITH_DECORATOR_AND_NAME = """
+from enum import Enum
+
+import strawberry
+
+@strawberry.enum(name="IceCreamFlavour")
+class Flavour(Enum):
+ VANILLA = "vanilla"
+ STRAWBERRY = "strawberry"
+ CHOCOLATE = "chocolate"
+
+reveal_type(Flavour)
+reveal_type(Flavour.VANILLA)
+"""
+
+
+def test_enum_with_decorator_and_name():
+ results = typecheck(CODE_WITH_DECORATOR_AND_NAME)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "Flavour" is "type[Flavour]"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Flavour.VANILLA" is "Literal[Flavour.VANILLA]"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (value: builtins.object) -> mypy_test.Flavour"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Literal[mypy_test.Flavour.VANILLA]?"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_WITH_MANUAL_DECORATOR = """
+from enum import Enum
+
+import strawberry
+
+class IceCreamFlavour(Enum):
+ VANILLA = "vanilla"
+ STRAWBERRY = "strawberry"
+ CHOCOLATE = "chocolate"
+
+reveal_type(strawberry.enum(IceCreamFlavour))
+reveal_type(strawberry.enum(IceCreamFlavour).VANILLA)
+"""
+
+
+def test_enum_with_manual_decorator():
+ results = typecheck(CODE_WITH_MANUAL_DECORATOR)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "strawberry.enum(IceCreamFlavour)" is "type[IceCreamFlavour]"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "strawberry.enum(IceCreamFlavour).VANILLA" is "Literal[IceCreamFlavour.VANILLA]"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (value: builtins.object) -> mypy_test.IceCreamFlavour"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Literal[mypy_test.IceCreamFlavour.VANILLA]?"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_WITH_MANUAL_DECORATOR_AND_NAME = """
+from enum import Enum
+
+import strawberry
+
+class Flavour(Enum):
+ VANILLA = "vanilla"
+ STRAWBERRY = "strawberry"
+ CHOCOLATE = "chocolate"
+
+reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour))
+reveal_type(strawberry.enum(name="IceCreamFlavour")(Flavour).VANILLA)
+"""
+
+
+def test_enum_with_manual_decorator_and_name():
+ results = typecheck(CODE_WITH_MANUAL_DECORATOR_AND_NAME)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "strawberry.enum(name="IceCreamFlavour")(Flavour)" is "type[Flavour]"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "strawberry.enum(name="IceCreamFlavour")(Flavour).VANILLA" is "Literal[Flavour.VANILLA]"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (value: builtins.object) -> mypy_test.Flavour"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Literal[mypy_test.Flavour.VANILLA]?"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_WITH_DEPRECATION_REASON = """
+from enum import Enum
+
+import strawberry
+
+@strawberry.enum
+class IceCreamFlavour(Enum):
+ VANILLA = "vanilla"
+ STRAWBERRY = strawberry.enum_value(
+ "strawberry", deprecation_reason="We ran out"
+ )
+ CHOCOLATE = "chocolate"
+
+reveal_type(IceCreamFlavour)
+reveal_type(IceCreamFlavour.STRAWBERRY)
+"""
+
+
+def test_enum_deprecated():
+ results = typecheck(CODE_WITH_DEPRECATION_REASON)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "IceCreamFlavour" is "type[IceCreamFlavour]"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "IceCreamFlavour.STRAWBERRY" is "Literal[IceCreamFlavour.STRAWBERRY]"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (value: builtins.object) -> mypy_test.IceCreamFlavour"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Literal[mypy_test.IceCreamFlavour.STRAWBERRY]?"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_federation.py b/tests/typecheckers/test_federation.py
new file mode 100644
index 0000000000..bd63a9cd8a
--- /dev/null
+++ b/tests/typecheckers/test_federation.py
@@ -0,0 +1,212 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+def get_user_age() -> int:
+ return 0
+
+
+@strawberry.federation.type
+class User:
+ name: str
+ age: int = strawberry.field(resolver=get_user_age)
+ something_else: int = strawberry.federation.field(resolver=get_user_age)
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test_federation_type():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=16,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=16, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=16,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_INTERFACE = """
+import strawberry
+
+
+@strawberry.federation.interface
+class User:
+ name: str
+ age: int
+
+
+User(name="Patrick", age=1)
+User(n="Patrick", age=1)
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test_federation_interface():
+ results = typecheck(CODE_INTERFACE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=12,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=12, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str, age: int) -> None"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=12,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str, age: builtins.int) -> mypy_test.User"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str, age: builtins.int)"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
+
+
+CODE_INPUT = """
+import strawberry
+
+@strawberry.federation.input
+class User:
+ name: str
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test_federation_input():
+ results = typecheck(CODE_INPUT)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=10,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=10, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=10,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=13,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_federation_fields.py b/tests/typecheckers/test_federation_fields.py
new file mode 100644
index 0000000000..faa74e98fe
--- /dev/null
+++ b/tests/typecheckers/test_federation_fields.py
@@ -0,0 +1,128 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+CODE = """
+import strawberry
+
+def some_resolver(root: "User") -> str:
+ return "An address"
+
+def some_resolver_2() -> str:
+ return "Another address"
+
+@strawberry.federation.type
+class User:
+ age: int = strawberry.federation.field(description="Age")
+ name: str
+ address: str = strawberry.federation.field(resolver=some_resolver)
+ another_address: str = strawberry.federation.field(resolver=some_resolver_2)
+
+@strawberry.federation.input
+class UserInput:
+ age: int = strawberry.federation.field(description="Age")
+ name: str
+
+
+User(name="Patrick", age=1)
+User(n="Patrick", age=1)
+
+UserInput(name="Patrick", age=1)
+UserInput(n="Patrick", age=1)
+
+reveal_type(User)
+reveal_type(User.__init__)
+
+reveal_type(UserInput)
+reveal_type(UserInput.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=24,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=24, column=6),
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=27,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=27, column=11),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=29,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, age: int, name: str) -> None"',
+ line=30,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "UserInput" is "type[UserInput]"',
+ line=32,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "UserInput.__init__" is "(self: UserInput, *, age: int, name: str) -> None"',
+ line=33,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=24,
+ column=1,
+ ),
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "UserInput"',
+ line=27,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, age: builtins.int, name: builtins.str) -> mypy_test.User"',
+ line=29,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, age: builtins.int, name: builtins.str)"',
+ line=30,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, age: builtins.int, name: builtins.str) -> mypy_test.UserInput"',
+ line=32,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.UserInput, *, age: builtins.int, name: builtins.str)"',
+ line=33,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_federation_params.py b/tests/typecheckers/test_federation_params.py
new file mode 100644
index 0000000000..98eb6619d8
--- /dev/null
+++ b/tests/typecheckers/test_federation_params.py
@@ -0,0 +1,46 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.federation.type(name="User")
+class UserModel:
+ name: str
+
+
+UserModel(name="Patrick")
+UserModel(n="Patrick")
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=11,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=11, column=11),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "UserModel"',
+ line=11,
+ column=1,
+ )
+ ]
+ )
diff --git a/tests/typecheckers/test_fields.py b/tests/typecheckers/test_fields.py
new file mode 100644
index 0000000000..25e3c9d977
--- /dev/null
+++ b/tests/typecheckers/test_fields.py
@@ -0,0 +1,73 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.type
+class User:
+ name: str
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=11,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=11, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=14,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=11,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=14,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_fields_input.py b/tests/typecheckers/test_fields_input.py
new file mode 100644
index 0000000000..3858adbc3a
--- /dev/null
+++ b/tests/typecheckers/test_fields_input.py
@@ -0,0 +1,73 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.input
+class User:
+ name: str
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=11,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=11, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=14,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=11,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=14,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_fields_keyword.py b/tests/typecheckers/test_fields_keyword.py
new file mode 100644
index 0000000000..2631daefd0
--- /dev/null
+++ b/tests/typecheckers/test_fields_keyword.py
@@ -0,0 +1,58 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.type
+class User:
+ name: str
+
+
+User("Patrick")
+
+reveal_type(User.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message="Expected 0 positional arguments",
+ line=10,
+ column=6,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Too many positional arguments for "User"',
+ line=10,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=12,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_fields_resolver.py b/tests/typecheckers/test_fields_resolver.py
new file mode 100644
index 0000000000..849e39a9e9
--- /dev/null
+++ b/tests/typecheckers/test_fields_resolver.py
@@ -0,0 +1,77 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+def get_user_age() -> int:
+ return 0
+
+
+@strawberry.type
+class User:
+ name: str
+ age: int = strawberry.field(resolver=get_user_age)
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=15,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=15, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=17,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=18,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=15,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=17,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=18,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_fields_resolver_async.py b/tests/typecheckers/test_fields_resolver_async.py
new file mode 100644
index 0000000000..f8c1e28ae4
--- /dev/null
+++ b/tests/typecheckers/test_fields_resolver_async.py
@@ -0,0 +1,92 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+CODE = """
+import strawberry
+
+async def get_user_age() -> int:
+ return 0
+
+
+@strawberry.type
+class User:
+ name: str
+ age: int = strawberry.field(resolver=get_user_age)
+ something: str = strawberry.field(resolver=get_user_age)
+
+
+User(name="Patrick")
+User(n="Patrick")
+
+reveal_type(User)
+reveal_type(User.__init__)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message="""\
+Expression of type "int" is incompatible with declared type "str"
+\xa0\xa0"int" is incompatible with "str"\
+""",
+ line=12,
+ column=22,
+ ),
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=16,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=16, column=6),
+ Result(
+ type="information",
+ message='Type of "User" is "type[User]"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "User.__init__" is "(self: User, *, name: str) -> None"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Incompatible types in assignment (expression has type "int", variable has type "str")',
+ line=12,
+ column=22,
+ ),
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=16,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (*, name: builtins.str) -> mypy_test.User"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "def (self: mypy_test.User, *, name: builtins.str)"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_info.py b/tests/typecheckers/test_info.py
new file mode 100644
index 0000000000..61e604aa83
--- /dev/null
+++ b/tests/typecheckers/test_info.py
@@ -0,0 +1,111 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+def test_with_params():
+ CODE = """
+import strawberry
+
+def example(info: strawberry.Info[None, None]) -> None:
+ reveal_type(info.context)
+ reveal_type(info.root_value)
+"""
+
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "info.context" is "None"',
+ line=5,
+ column=17,
+ ),
+ Result(
+ type="information",
+ message='Type of "info.root_value" is "None"',
+ line=6,
+ column=17,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(type="note", message='Revealed type is "None"', line=5, column=17),
+ Result(type="note", message='Revealed type is "None"', line=6, column=17),
+ ]
+ )
+
+
+def test_with_one_param():
+ CODE = """
+import strawberry
+
+def example(info: strawberry.Info[None]) -> None:
+ reveal_type(info.context)
+ reveal_type(info.root_value)
+"""
+
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "info.context" is "None"',
+ line=5,
+ column=17,
+ ),
+ Result(
+ type="information",
+ message='Type of "info.root_value" is "Any"',
+ line=6,
+ column=17,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(type="note", message='Revealed type is "None"', line=5, column=17),
+ Result(type="note", message='Revealed type is "Any"', line=6, column=17),
+ ]
+ )
+
+
+def test_without_params():
+ CODE = """
+import strawberry
+
+def example(info: strawberry.Info) -> None:
+ reveal_type(info.context)
+ reveal_type(info.root_value)
+"""
+
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "info.context" is "Any"',
+ line=5,
+ column=17,
+ ),
+ Result(
+ type="information",
+ message='Type of "info.root_value" is "Any"',
+ line=6,
+ column=17,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(type="note", message='Revealed type is "Any"', line=5, column=17),
+ Result(type="note", message='Revealed type is "Any"', line=6, column=17),
+ ]
+ )
diff --git a/tests/typecheckers/test_interface.py b/tests/typecheckers/test_interface.py
new file mode 100644
index 0000000000..684a786af1
--- /dev/null
+++ b/tests/typecheckers/test_interface.py
@@ -0,0 +1,80 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.interface
+class Node:
+ id: strawberry.ID
+
+reveal_type(Node)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "Node" is "type[Node]"',
+ line=9,
+ column=13,
+ )
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (*, id: strawberry.scalars.ID) -> mypy_test.Node"',
+ line=9,
+ column=13,
+ )
+ ]
+ )
+
+
+CODE_2 = """
+import strawberry
+
+
+@strawberry.interface(name="nodeinterface")
+class Node:
+ id: strawberry.ID
+
+reveal_type(Node)
+"""
+
+
+def test_calling():
+ results = typecheck(CODE_2)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "Node" is "type[Node]"',
+ line=9,
+ column=13,
+ )
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (*, id: strawberry.scalars.ID) -> mypy_test.Node"',
+ line=9,
+ column=13,
+ )
+ ]
+ )
diff --git a/tests/typecheckers/test_params.py b/tests/typecheckers/test_params.py
new file mode 100644
index 0000000000..4c7e2905e2
--- /dev/null
+++ b/tests/typecheckers/test_params.py
@@ -0,0 +1,66 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+CODE = """
+import strawberry
+
+
+@strawberry.type(name="User")
+class UserModel:
+ name: str
+
+
+@strawberry.input(name="User")
+class UserInput:
+ name: str
+
+
+UserModel(name="Patrick")
+UserModel(n="Patrick")
+
+UserInput(name="Patrick")
+UserInput(n="Patrick")
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=16,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=16, column=11),
+ Result(
+ type="error",
+ message='Argument missing for parameter "name"',
+ line=19,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=19, column=11),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "UserModel"',
+ line=16,
+ column=1,
+ ),
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "UserInput"',
+ line=19,
+ column=1,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_private.py b/tests/typecheckers/test_private.py
new file mode 100644
index 0000000000..05579651d7
--- /dev/null
+++ b/tests/typecheckers/test_private.py
@@ -0,0 +1,74 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+
+
+@strawberry.type
+class User:
+ name: str
+ age: strawberry.Private[int]
+
+
+patrick = User(name="Patrick", age=1)
+User(n="Patrick")
+
+reveal_type(patrick.name)
+reveal_type(patrick.age)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="error",
+ message='Arguments missing for parameters "name", "age"',
+ line=12,
+ column=1,
+ ),
+ Result(type="error", message='No parameter named "n"', line=12, column=6),
+ Result(
+ type="information",
+ message='Type of "patrick.name" is "str"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "patrick.age" is "int"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="error",
+ message='Unexpected keyword argument "n" for "User"',
+ line=12,
+ column=1,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "builtins.str"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "builtins.int"',
+ line=15,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_relay.py b/tests/typecheckers/test_relay.py
new file mode 100644
index 0000000000..90545def93
--- /dev/null
+++ b/tests/typecheckers/test_relay.py
@@ -0,0 +1,358 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+from typing import (
+ Any,
+ AsyncIterator,
+ AsyncGenerator,
+ AsyncIterable,
+ Generator,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Union,
+)
+
+import strawberry
+from strawberry import relay
+from typing_extensions import Self
+
+
+@strawberry.type
+class Fruit(relay.Node):
+ id: relay.NodeID[int]
+ name: str
+ color: str
+
+
+@strawberry.type
+class FruitCustomPaginationConnection(relay.Connection[Fruit]):
+ @strawberry.field
+ def something(self) -> str:
+ return "foobar"
+
+ @classmethod
+ def resolve_connection(
+ cls,
+ nodes: Union[
+ Iterator[Fruit],
+ AsyncIterator[Fruit],
+ Iterable[Fruit],
+ AsyncIterable[Fruit],
+ ],
+ *,
+ info: Optional[strawberry.Info] = None,
+ before: Optional[str] = None,
+ after: Optional[str] = None,
+ first: Optional[int] = None,
+ last: Optional[int] = None,
+ **kwargs: Any,
+ ) -> Self:
+ ...
+
+
+class FruitAlike:
+ ...
+
+
+def fruits_resolver() -> List[Fruit]:
+ ...
+
+
+@strawberry.type
+class Query:
+ node: relay.Node
+ nodes: List[relay.Node]
+ node_optional: Optional[relay.Node]
+ nodes_optional: List[Optional[relay.Node]]
+ fruits: relay.Connection[Fruit] = strawberry.relay.connection(
+ resolver=fruits_resolver,
+ )
+ fruits_conn: relay.Connection[Fruit] = relay.connection(
+ resolver=fruits_resolver,
+ )
+ fruits_custom_pagination: FruitCustomPaginationConnection
+
+ @relay.connection(relay.Connection[Fruit])
+ def fruits_custom_resolver(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> List[Fruit]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ def fruits_custom_resolver_iterator(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> Iterator[Fruit]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ def fruits_custom_resolver_iterable(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> Iterable[Fruit]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ def fruits_custom_resolver_generator(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> Generator[Fruit, None, None]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ async def fruits_custom_resolver_async_iterator(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> AsyncIterator[Fruit]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ async def fruits_custom_resolver_async_iterable(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> AsyncIterable[Fruit]:
+ ...
+
+ @relay.connection(relay.Connection[Fruit])
+ async def fruits_custom_resolver_async_generator(
+ self,
+ info: strawberry.Info,
+ name_endswith: Optional[str] = None,
+ ) -> AsyncGenerator[Fruit, None]:
+ ...
+
+reveal_type(Query.node)
+reveal_type(Query.nodes)
+reveal_type(Query.node_optional)
+reveal_type(Query.nodes_optional)
+reveal_type(Query.fruits)
+reveal_type(Query.fruits_conn)
+reveal_type(Query.fruits_custom_pagination)
+reveal_type(Query.fruits_custom_resolver)
+reveal_type(Query.fruits_custom_resolver_iterator)
+reveal_type(Query.fruits_custom_resolver_iterable)
+reveal_type(Query.fruits_custom_resolver_generator)
+reveal_type(Query.fruits_custom_resolver_async_iterator)
+reveal_type(Query.fruits_custom_resolver_async_iterable)
+reveal_type(Query.fruits_custom_resolver_async_generator)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "Query.node" is "Node"',
+ line=131,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.nodes" is "List[Node]"',
+ line=132,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.node_optional" is "Node | None"',
+ line=133,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.nodes_optional" is "List[Node | None]"',
+ line=134,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits" is "Connection[Fruit]"',
+ line=135,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_conn" is "Connection[Fruit]"',
+ line=136,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_pagination" is "FruitCustomPaginationConnection"',
+ line=137,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver" is "Any"',
+ line=138,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_iterator" is "Any"',
+ line=139,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_iterable" is "Any"',
+ line=140,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_generator" is "Any"',
+ line=141,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_async_iterator" is "Any"',
+ line=142,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_async_iterable" is "Any"',
+ line=143,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "Query.fruits_custom_resolver_async_generator" is "Any"',
+ line=144,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(type="error", message="Missing return statement", line=34, column=5),
+ Result(type="error", message="Missing return statement", line=57, column=1),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver" untyped',
+ line=75,
+ column=6,
+ ),
+ Result(type="error", message="Missing return statement", line=76, column=5),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_iterator" untyped',
+ line=83,
+ column=6,
+ ),
+ Result(type="error", message="Missing return statement", line=84, column=5),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_iterable" untyped',
+ line=91,
+ column=6,
+ ),
+ Result(type="error", message="Missing return statement", line=92, column=5),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_generator" untyped',
+ line=99,
+ column=6,
+ ),
+ Result(
+ type="error", message="Missing return statement", line=100, column=5
+ ),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_async_iterator" untyped',
+ line=107,
+ column=6,
+ ),
+ Result(
+ type="error", message="Missing return statement", line=108, column=5
+ ),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_async_iterable" untyped',
+ line=115,
+ column=6,
+ ),
+ Result(
+ type="error", message="Missing return statement", line=116, column=5
+ ),
+ Result(
+ type="error",
+ message='Untyped decorator makes function "fruits_custom_resolver_async_generator" untyped',
+ line=123,
+ column=6,
+ ),
+ Result(
+ type="error", message="Missing return statement", line=124, column=5
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.relay.types.Node"',
+ line=131,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "builtins.list[strawberry.relay.types.Node]"',
+ line=132,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Union[strawberry.relay.types.Node, None]"',
+ line=133,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "builtins.list[Union[strawberry.relay.types.Node, None]]"',
+ line=134,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.relay.types.Connection[mypy_test.Fruit]"',
+ line=135,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.relay.types.Connection[mypy_test.Fruit]"',
+ line=136,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "mypy_test.FruitCustomPaginationConnection"',
+ line=137,
+ column=13,
+ ),
+ Result(type="note", message='Revealed type is "Any"', line=138, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=139, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=140, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=141, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=142, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=143, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=144, column=13),
+ ]
+ )
diff --git a/tests/typecheckers/test_scalars.py b/tests/typecheckers/test_scalars.py
new file mode 100644
index 0000000000..e985720629
--- /dev/null
+++ b/tests/typecheckers/test_scalars.py
@@ -0,0 +1,137 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+from strawberry.scalars import ID, JSON, Base16, Base32, Base64
+
+
+@strawberry.type
+class SomeType:
+ id: ID
+ json: JSON
+ base16: Base16
+ base32: Base32
+ base64: Base64
+
+
+obj = SomeType(
+ id=ID("123"),
+ json=JSON({"foo": "bar"}),
+ base16=Base16(b""),
+ base32=Base32(b""),
+ base64=Base64(b""),
+)
+
+reveal_type(obj.id)
+reveal_type(obj.json)
+reveal_type(obj.base16)
+reveal_type(obj.base16)
+reveal_type(obj.base64)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ # NOTE: This is also guaranteeing that those scalars could be used to annotate
+ # the attributes. Pyright 1.1.224+ doesn't allow non-types to be used there
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "obj.id" is "ID"',
+ line=23,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj.json" is "JSON"',
+ line=24,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj.base16" is "Base16"',
+ line=25,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj.base16" is "Base16"',
+ line=26,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "obj.base64" is "Base64"',
+ line=27,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.scalars.ID"',
+ line=23,
+ column=13,
+ ),
+ Result(type="note", message='Revealed type is "Any"', line=24, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=25, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=26, column=13),
+ Result(type="note", message='Revealed type is "Any"', line=27, column=13),
+ ]
+ )
+
+
+CODE_SCHEMA_OVERRIDES = """
+import strawberry
+from datetime import datetime, timezone
+
+EpochDateTime = strawberry.scalar(
+ datetime,
+)
+
+@strawberry.type
+class Query:
+ a: datetime
+
+schema = strawberry.Schema(query=Query, scalar_overrides={
+ datetime: EpochDateTime,
+})
+
+reveal_type(EpochDateTime)
+"""
+
+
+def test_schema_overrides():
+ # TODO: change strict to true when we improve type hints for scalar
+ results = typecheck(CODE_SCHEMA_OVERRIDES, strict=False)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "EpochDateTime" is "type[datetime]"',
+ line=16,
+ column=13,
+ )
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "def (year: typing.SupportsIndex, month: typing.SupportsIndex, day: typing.SupportsIndex, hour: typing.SupportsIndex =, minute: typing.SupportsIndex =, second: typing.SupportsIndex =, microsecond: typing.SupportsIndex =, tzinfo: Union[datetime.tzinfo, None] =, *, fold: builtins.int =) -> datetime.datetime"',
+ line=17,
+ column=13,
+ )
+ ]
+ )
diff --git a/tests/typecheckers/test_type.py b/tests/typecheckers/test_type.py
new file mode 100644
index 0000000000..8f855231d6
--- /dev/null
+++ b/tests/typecheckers/test_type.py
@@ -0,0 +1,136 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+CODE = """
+import strawberry
+from strawberry.types.base import StrawberryOptional, StrawberryList
+
+
+@strawberry.type
+class Fruit:
+ name: str
+
+
+reveal_type(StrawberryOptional(Fruit))
+reveal_type(StrawberryList(Fruit))
+reveal_type(StrawberryOptional(StrawberryList(Fruit)))
+reveal_type(StrawberryList(StrawberryOptional(Fruit)))
+
+reveal_type(StrawberryOptional(str))
+reveal_type(StrawberryList(str))
+reveal_type(StrawberryOptional(StrawberryList(str)))
+reveal_type(StrawberryList(StrawberryOptional(str)))
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "StrawberryOptional(Fruit)" is "StrawberryOptional"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryList(Fruit)" is "StrawberryList"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryOptional(StrawberryList(Fruit))" is "StrawberryOptional"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryList(StrawberryOptional(Fruit))" is "StrawberryList"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryOptional(str)" is "StrawberryOptional"',
+ line=16,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryList(str)" is "StrawberryList"',
+ line=17,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryOptional(StrawberryList(str))" is "StrawberryOptional"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="information",
+ message='Type of "StrawberryList(StrawberryOptional(str))" is "StrawberryList"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryOptional"',
+ line=11,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryList"',
+ line=12,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryOptional"',
+ line=13,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryList"',
+ line=14,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryOptional"',
+ line=16,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryList"',
+ line=17,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryOptional"',
+ line=18,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "strawberry.types.base.StrawberryList"',
+ line=19,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/test_union.py b/tests/typecheckers/test_union.py
new file mode 100644
index 0000000000..66df1365f7
--- /dev/null
+++ b/tests/typecheckers/test_union.py
@@ -0,0 +1,66 @@
+from inline_snapshot import snapshot
+
+from .utils.marks import requires_mypy, requires_pyright, skip_on_windows
+from .utils.typecheck import Result, typecheck
+
+pytestmark = [skip_on_windows, requires_pyright, requires_mypy]
+
+
+CODE = """
+import strawberry
+from typing_extensions import TypeAlias, Annotated
+from typing import Union
+
+@strawberry.type
+class User:
+ name: str
+
+
+@strawberry.type
+class Error:
+ message: str
+
+UserOrError: TypeAlias = Annotated[
+ Union[User, Error], strawberry.union("UserOrError")
+]
+
+reveal_type(UserOrError)
+
+x: UserOrError = User(name="Patrick")
+
+reveal_type(x)
+"""
+
+
+def test():
+ results = typecheck(CODE)
+
+ assert results.pyright == snapshot(
+ [
+ Result(
+ type="information",
+ message='Type of "UserOrError" is "type[Annotated]"',
+ line=19,
+ column=13,
+ ),
+ Result(
+ type="information", message='Type of "x" is "User"', line=23, column=13
+ ),
+ ]
+ )
+ assert results.mypy == snapshot(
+ [
+ Result(
+ type="note",
+ message='Revealed type is "typing._SpecialForm"',
+ line=19,
+ column=13,
+ ),
+ Result(
+ type="note",
+ message='Revealed type is "Union[mypy_test.User, mypy_test.Error]"',
+ line=23,
+ column=13,
+ ),
+ ]
+ )
diff --git a/tests/typecheckers/utils/__init__.py b/tests/typecheckers/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/typecheckers/utils/marks.py b/tests/typecheckers/utils/marks.py
new file mode 100644
index 0000000000..76648f7df7
--- /dev/null
+++ b/tests/typecheckers/utils/marks.py
@@ -0,0 +1,28 @@
+import shutil
+import sys
+
+import pytest
+
+
+def pyright_exist() -> bool:
+ return shutil.which("pyright") is not None
+
+
+def mypy_exists() -> bool:
+ return shutil.which("mypy") is not None
+
+
+skip_on_windows = pytest.mark.skipif(
+ sys.platform == "win32",
+ reason="Do not run pyright on windows due to path issues",
+)
+
+requires_pyright = pytest.mark.skipif(
+ not pyright_exist(),
+ reason="These tests require pyright",
+)
+
+requires_mypy = pytest.mark.skipif(
+ not mypy_exists(),
+ reason="These tests require mypy",
+)
diff --git a/tests/typecheckers/utils/mypy.py b/tests/typecheckers/utils/mypy.py
new file mode 100644
index 0000000000..d78caa3f07
--- /dev/null
+++ b/tests/typecheckers/utils/mypy.py
@@ -0,0 +1,84 @@
+from __future__ import annotations
+
+import json
+import pathlib
+import subprocess
+import tempfile
+from typing import List, TypedDict
+
+from .result import Result
+
+
+class PyrightCLIResult(TypedDict):
+ version: str
+ time: str
+ generalDiagnostics: List[GeneralDiagnostic]
+ summary: Summary
+
+
+class GeneralDiagnostic(TypedDict):
+ file: str
+ severity: str
+ message: str
+ range: Range
+
+
+class Range(TypedDict):
+ start: EndOrStart
+ end: EndOrStart
+
+
+class EndOrStart(TypedDict):
+ line: int
+ character: int
+
+
+class Summary(TypedDict):
+ filesAnalyzed: int
+ errorCount: int
+ warningCount: int
+ informationCount: int
+ timeInSec: float
+
+
+def run_mypy(code: str, strict: bool = True) -> List[Result]:
+ args = ["mypy", "--output=json"]
+
+ if strict:
+ args.append("--strict")
+
+ with tempfile.TemporaryDirectory() as directory:
+ module_path = pathlib.Path(directory) / "mypy_test.py"
+ module_path.write_text(code)
+
+ process_result = subprocess.run(
+ [*args, str(module_path)], check=False, capture_output=True
+ )
+
+ full_output = (
+ process_result.stdout.decode("utf-8")
+ + "\n"
+ + process_result.stderr.decode("utf-8")
+ )
+ full_output = full_output.strip()
+
+ results: List[Result] = []
+
+ try:
+ for line in full_output.split("\n"):
+ mypy_result = json.loads(line)
+
+ results.append(
+ Result(
+ type=mypy_result["severity"].strip(),
+ message=mypy_result["message"].strip(),
+ line=mypy_result["line"],
+ column=mypy_result["column"] + 1,
+ )
+ )
+ except json.JSONDecodeError:
+ raise Exception(f"Invalid JSON: {full_output}")
+
+ results.sort(key=lambda x: (x.line, x.column, x.message))
+
+ return results
diff --git a/tests/pyright/utils.py b/tests/typecheckers/utils/pyright.py
similarity index 74%
rename from tests/pyright/utils.py
rename to tests/typecheckers/utils/pyright.py
index 29ce41ba4e..6d03397fce 100644
--- a/tests/pyright/utils.py
+++ b/tests/typecheckers/utils/pyright.py
@@ -2,17 +2,11 @@
import json
import os
-import shutil
import subprocess
-import sys
import tempfile
-from dataclasses import dataclass
from typing import List, TypedDict, cast
-from typing_extensions import Literal
-import pytest
-
-ResultType = Literal["error", "information"]
+from .result import Result, ResultType
class PyrightCLIResult(TypedDict):
@@ -47,14 +41,6 @@ class Summary(TypedDict):
timeInSec: float
-@dataclass
-class Result:
- type: ResultType
- message: str
- line: int
- column: int
-
-
def run_pyright(code: str, strict: bool = True) -> List[Result]:
if strict:
code = "# pyright: strict\n" + code
@@ -84,18 +70,3 @@ def run_pyright(code: str, strict: bool = True) -> List[Result]:
result.sort(key=lambda x: (x.line, x.column, x.message))
return result
-
-
-def pyright_exist() -> bool:
- return shutil.which("pyright") is not None
-
-
-skip_on_windows = pytest.mark.skipif(
- sys.platform == "win32",
- reason="Do not run pyright on windows due to path issues",
-)
-
-requires_pyright = pytest.mark.skipif(
- not pyright_exist(),
- reason="These tests require pyright",
-)
diff --git a/tests/typecheckers/utils/result.py b/tests/typecheckers/utils/result.py
new file mode 100644
index 0000000000..d79ba89388
--- /dev/null
+++ b/tests/typecheckers/utils/result.py
@@ -0,0 +1,16 @@
+from dataclasses import dataclass
+from typing import Literal
+
+ResultType = Literal[
+ "error",
+ "information",
+ "note",
+]
+
+
+@dataclass
+class Result:
+ type: ResultType
+ message: str
+ line: int
+ column: int
diff --git a/tests/typecheckers/utils/typecheck.py b/tests/typecheckers/utils/typecheck.py
new file mode 100644
index 0000000000..8d34c6d301
--- /dev/null
+++ b/tests/typecheckers/utils/typecheck.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+import concurrent.futures
+from dataclasses import dataclass
+
+from .mypy import run_mypy
+from .pyright import run_pyright
+from .result import Result
+
+
+@dataclass
+class TypecheckResult:
+ pyright: list[Result]
+ mypy: list[Result]
+
+
+def typecheck(code: str, strict: bool = True) -> TypecheckResult:
+ with concurrent.futures.ThreadPoolExecutor() as executor:
+ pyright_future = executor.submit(run_pyright, code, strict=strict)
+ mypy_future = executor.submit(run_mypy, code, strict=strict)
+
+ pyright_results = pyright_future.result()
+ mypy_results = mypy_future.result()
+
+ return TypecheckResult(pyright=pyright_results, mypy=mypy_results)
diff --git a/tests/types/cross_module_resolvers/test_cross_module_resolvers.py b/tests/types/cross_module_resolvers/test_cross_module_resolvers.py
index 1a7686485b..4c218319f8 100644
--- a/tests/types/cross_module_resolvers/test_cross_module_resolvers.py
+++ b/tests/types/cross_module_resolvers/test_cross_module_resolvers.py
@@ -1,5 +1,4 @@
-"""
-The following tests ensure that the types are resolved using the correct
+"""The following tests ensure that the types are resolved using the correct
module. Concrete types should be non-problematic and are only included
here for completeness. A problematic case is when a type is a string
(forward reference) and can only be resolved at schema construction.
diff --git a/tests/types/resolving/test_generics.py b/tests/types/resolving/test_generics.py
index 2641358924..9c10f8cc73 100644
--- a/tests/types/resolving/test_generics.py
+++ b/tests/types/resolving/test_generics.py
@@ -5,17 +5,17 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.enum import EnumDefinition
-from strawberry.field import StrawberryField
-from strawberry.type import (
+from strawberry.types.base import (
StrawberryList,
+ StrawberryObjectDefinition,
StrawberryOptional,
StrawberryTypeVar,
get_object_definition,
has_object_definition,
)
-from strawberry.types.types import StrawberryObjectDefinition
-from strawberry.union import StrawberryUnion
+from strawberry.types.enum import EnumDefinition
+from strawberry.types.field import StrawberryField
+from strawberry.types.union import StrawberryUnion
def test_basic_generic():
diff --git a/tests/types/resolving/test_lists.py b/tests/types/resolving/test_lists.py
index b8014fd5ac..c3e50cba1d 100644
--- a/tests/types/resolving/test_lists.py
+++ b/tests/types/resolving/test_lists.py
@@ -6,7 +6,7 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.type import StrawberryList
+from strawberry.types.base import StrawberryList
def test_basic_list():
diff --git a/tests/types/resolving/test_optionals.py b/tests/types/resolving/test_optionals.py
index cd6ab4b370..3127d0caaf 100644
--- a/tests/types/resolving/test_optionals.py
+++ b/tests/types/resolving/test_optionals.py
@@ -2,8 +2,8 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.type import StrawberryOptional
-from strawberry.unset import UnsetType
+from strawberry.types.base import StrawberryOptional
+from strawberry.types.unset import UnsetType
def test_basic_optional():
diff --git a/tests/types/resolving/test_string_annotations.py b/tests/types/resolving/test_string_annotations.py
index 0bef01a316..321384b7e4 100644
--- a/tests/types/resolving/test_string_annotations.py
+++ b/tests/types/resolving/test_string_annotations.py
@@ -2,7 +2,11 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.type import StrawberryList, StrawberryOptional, StrawberryTypeVar
+from strawberry.types.base import (
+ StrawberryList,
+ StrawberryOptional,
+ StrawberryTypeVar,
+)
def test_basic_string():
diff --git a/tests/types/resolving/test_union_pipe.py b/tests/types/resolving/test_union_pipe.py
index 5571b4cea4..37e0253c80 100644
--- a/tests/types/resolving/test_union_pipe.py
+++ b/tests/types/resolving/test_union_pipe.py
@@ -8,8 +8,8 @@
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions.invalid_union_type import InvalidUnionTypeError
from strawberry.schema.types.base_scalars import Date, DateTime
-from strawberry.type import StrawberryOptional
-from strawberry.union import StrawberryUnion
+from strawberry.types.base import StrawberryOptional
+from strawberry.types.union import StrawberryUnion
pytestmark = pytest.mark.skipif(
sys.version_info < (3, 10),
diff --git a/tests/types/resolving/test_unions.py b/tests/types/resolving/test_unions.py
index 627c138ab1..dee6ecb39d 100644
--- a/tests/types/resolving/test_unions.py
+++ b/tests/types/resolving/test_unions.py
@@ -7,8 +7,8 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import InvalidUnionTypeError
-from strawberry.type import get_object_definition
-from strawberry.union import StrawberryUnion, union
+from strawberry.types.base import get_object_definition
+from strawberry.types.union import StrawberryUnion, union
def test_python_union():
diff --git a/tests/types/resolving/test_unions_deprecated.py b/tests/types/resolving/test_unions_deprecated.py
index 7166e57175..640bcef382 100644
--- a/tests/types/resolving/test_unions_deprecated.py
+++ b/tests/types/resolving/test_unions_deprecated.py
@@ -6,7 +6,7 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
from strawberry.exceptions import InvalidUnionTypeError
-from strawberry.union import StrawberryUnion, union
+from strawberry.types.union import StrawberryUnion, union
pytestmark = pytest.mark.filterwarnings(
"ignore:Passing types to `strawberry.union` is deprecated."
diff --git a/tests/types/test_annotation.py b/tests/types/test_annotation.py
index f242480d46..6ca9076642 100644
--- a/tests/types/test_annotation.py
+++ b/tests/types/test_annotation.py
@@ -6,7 +6,7 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.unset import UnsetType
+from strawberry.types.unset import UnsetType
class Bleh:
diff --git a/tests/types/test_argument_types.py b/tests/types/test_argument_types.py
index 2db47cf5c3..3cede4fdd3 100644
--- a/tests/types/test_argument_types.py
+++ b/tests/types/test_argument_types.py
@@ -1,6 +1,6 @@
import warnings
from enum import Enum
-from typing import Any, List, Optional, TypeVar
+from typing import List, Optional, TypeVar
import pytest
@@ -109,7 +109,7 @@ class CustomInfo(Info[ContextType, RootValueType]):
@pytest.mark.parametrize(
"annotation",
- [CustomInfo, CustomInfo[Any, Any], Info, Info[Any, Any]],
+ [CustomInfo, CustomInfo[None, None], Info, Info[None, None]],
)
def test_custom_info(annotation):
"""Test to ensure that subclassed Info does not raise warning."""
diff --git a/tests/types/test_execution.py b/tests/types/test_execution.py
index a191c63dc8..c7bce4f2bc 100644
--- a/tests/types/test_execution.py
+++ b/tests/types/test_execution.py
@@ -1,5 +1,3 @@
-import pytest
-
import strawberry
from strawberry.extensions import SchemaExtension
@@ -150,8 +148,10 @@ def on_operation(self):
schema = strawberry.Schema(Query, extensions=[MyExtension])
- with pytest.raises(RuntimeError):
- schema.execute_sync("mutation { myMutation }")
+ result = schema.execute_sync("mutation { myMutation }")
+ assert len(result.errors) == 1
+ assert isinstance(result.errors[0].original_error, RuntimeError)
+ assert result.errors[0].message == "No GraphQL document available"
def test_error_when_accessing_operation_type_with_invalid_operation_name():
@@ -165,5 +165,7 @@ def on_parse(self):
schema = strawberry.Schema(Query, extensions=[MyExtension])
- with pytest.raises(RuntimeError):
- schema.execute_sync("query { ping }", operation_name="MyQuery")
+ result = schema.execute_sync("query { ping }", operation_name="MyQuery")
+ assert len(result.errors) == 1
+ assert isinstance(result.errors[0].original_error, RuntimeError)
+ assert result.errors[0].message == "Can't get GraphQL operation type"
diff --git a/tests/types/test_field_types.py b/tests/types/test_field_types.py
index 27bcea32bb..fab4df79ef 100644
--- a/tests/types/test_field_types.py
+++ b/tests/types/test_field_types.py
@@ -3,8 +3,8 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.field import StrawberryField
-from strawberry.union import StrawberryUnion
+from strawberry.types.field import StrawberryField
+from strawberry.types.union import StrawberryUnion
def test_enum():
diff --git a/tests/types/test_lazy_types.py b/tests/types/test_lazy_types.py
index 4a2f65a7a3..429a2b7a2c 100644
--- a/tests/types/test_lazy_types.py
+++ b/tests/types/test_lazy_types.py
@@ -1,15 +1,21 @@
# type: ignore
import enum
+import sys
+import textwrap
from typing import Generic, TypeVar
-from typing_extensions import Annotated
+from typing_extensions import Annotated, TypeAlias
+
+import pytest
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.field import StrawberryField
-from strawberry.lazy_type import LazyType
-from strawberry.type import get_object_definition
+from strawberry.types.base import get_object_definition
+from strawberry.types.field import StrawberryField
from strawberry.types.fields.resolver import StrawberryResolver
-from strawberry.union import StrawberryUnion, union
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.union import StrawberryUnion, union
+
+T = TypeVar("T")
# This type is in the same file but should adequately test the logic.
@@ -18,6 +24,14 @@ class LaziestType:
something: bool
+@strawberry.type
+class LazyGenericType(Generic[T]):
+ something: T
+
+
+LazyTypeAlias: TypeAlias = LazyGenericType[int]
+
+
@strawberry.enum
class LazyEnum(enum.Enum):
BREAD = "BREAD"
@@ -38,6 +52,22 @@ def test_lazy_type():
assert resolved.resolve_type() is LaziestType
+def test_lazy_type_alias():
+ # Module path is short and relative because of the way pytest runs the file
+ LazierType = LazyType("LazyTypeAlias", "test_lazy_types")
+
+ annotation = StrawberryAnnotation(LazierType)
+ resolved = annotation.resolve()
+
+ # Currently StrawberryAnnotation(LazyType).resolve() returns the unresolved
+ # LazyType. We may want to find a way to directly return the referenced object
+ # without a second resolving step.
+ assert isinstance(resolved, LazyType)
+ resolved_type = resolved.resolve_type()
+ assert resolved_type.__origin__ is LazyGenericType
+ assert resolved_type.__args__ == (int,)
+
+
def test_lazy_type_function():
LethargicType = Annotated["LaziestType", strawberry.lazy("test_lazy_types")]
@@ -169,3 +199,56 @@ def test_lazy_function_in_union():
[type1, type2] = resolved.types
assert type1.resolve_type() is LaziestType
assert type2.resolve_type() is LazyEnum
+
+
+@pytest.mark.skipif(
+ sys.version_info < (3, 10),
+ reason="| operator without future annotations is only available on python 3.10+",
+)
+def test_optional_lazy_type_using_or_operator():
+ from tests.schema.test_lazy.type_a import TypeA
+
+ @strawberry.type
+ class SomeType:
+ foo: Annotated[TypeA, strawberry.lazy("tests.schema.test_lazy.type_a")] | None
+
+ @strawberry.type
+ class AnotherType:
+ foo: TypeA | None = None
+
+ @strawberry.type
+ class Query:
+ some_type: SomeType
+ another_type: AnotherType
+
+ schema = strawberry.Schema(query=Query)
+ expected = """\
+ type AnotherType {
+ foo: TypeA
+ }
+
+ type Query {
+ someType: SomeType!
+ anotherType: AnotherType!
+ }
+
+ type SomeType {
+ foo: TypeA
+ }
+
+ type TypeA {
+ listOfB: [TypeB!]
+ typeB: TypeB!
+ }
+
+ type TypeB {
+ typeA: TypeA!
+ typeAList: [TypeA!]!
+ typeCList: [TypeC!]!
+ }
+
+ type TypeC {
+ name: String!
+ }
+ """
+ assert str(schema).strip() == textwrap.dedent(expected).strip()
diff --git a/tests/types/test_lazy_types_future_annotations.py b/tests/types/test_lazy_types_future_annotations.py
new file mode 100644
index 0000000000..09f7f6a6d0
--- /dev/null
+++ b/tests/types/test_lazy_types_future_annotations.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+import textwrap
+from typing_extensions import Annotated
+
+import strawberry
+
+
+def test_optional_lazy_type_using_or_operator():
+ from tests.schema.test_lazy.type_a import TypeA
+
+ global SomeType, AnotherType
+
+ try:
+
+ @strawberry.type
+ class SomeType:
+ foo: (
+ Annotated[TypeA, strawberry.lazy("tests.schema.test_lazy.type_a")]
+ | None
+ )
+
+ @strawberry.type
+ class AnotherType:
+ foo: TypeA | None = None
+
+ @strawberry.type
+ class Query:
+ some_type: SomeType
+ another_type: AnotherType
+
+ schema = strawberry.Schema(query=Query)
+ expected = """\
+ type AnotherType {
+ foo: TypeA
+ }
+
+ type Query {
+ someType: SomeType!
+ anotherType: AnotherType!
+ }
+
+ type SomeType {
+ foo: TypeA
+ }
+
+ type TypeA {
+ listOfB: [TypeB!]
+ typeB: TypeB!
+ }
+
+ type TypeB {
+ typeA: TypeA!
+ typeAList: [TypeA!]!
+ typeCList: [TypeC!]!
+ }
+
+ type TypeC {
+ name: String!
+ }
+ """
+ assert str(schema).strip() == textwrap.dedent(expected).strip()
+ finally:
+ del SomeType, AnotherType
diff --git a/tests/types/test_object_types.py b/tests/types/test_object_types.py
index 7aeea9b81a..444d3160ad 100644
--- a/tests/types/test_object_types.py
+++ b/tests/types/test_object_types.py
@@ -8,8 +8,8 @@
import pytest
import strawberry
-from strawberry.field import StrawberryField
-from strawberry.type import get_object_definition
+from strawberry.types.base import get_object_definition
+from strawberry.types.field import StrawberryField
def test_enum():
@@ -179,3 +179,19 @@ class Thing:
match=re.escape("__init__() takes 1 positional argument but 2 were given"),
):
Thing("something")
+
+
+def test_object_preserves_annotations():
+ @strawberry.type
+ class Object:
+ a: bool
+ b: Annotated[str, "something"]
+ c: bool = strawberry.field(graphql_type=int)
+ d: Annotated[str, "something"] = strawberry.field(graphql_type=int)
+
+ assert Object.__annotations__ == {
+ "a": bool,
+ "b": Annotated[str, "something"],
+ "c": bool,
+ "d": Annotated[str, "something"],
+ }
diff --git a/tests/types/test_resolver_types.py b/tests/types/test_resolver_types.py
index dea1795537..9883acfd76 100644
--- a/tests/types/test_resolver_types.py
+++ b/tests/types/test_resolver_types.py
@@ -66,8 +66,7 @@ def get_2d_object() -> Polygon:
def test_optional():
- def stock_market_tool() -> Optional[str]:
- ...
+ def stock_market_tool() -> Optional[str]: ...
resolver = StrawberryResolver(stock_market_tool)
assert resolver.type == Optional[str]
@@ -76,8 +75,7 @@ def stock_market_tool() -> Optional[str]:
def test_type_var():
T = TypeVar("T")
- def caffeinated_drink() -> T:
- ...
+ def caffeinated_drink() -> T: ...
resolver = StrawberryResolver(caffeinated_drink)
assert resolver.type == T
@@ -92,8 +90,7 @@ class Venn:
class Diagram:
bar: float
- def get_overlap() -> Union[Venn, Diagram]:
- ...
+ def get_overlap() -> Union[Venn, Diagram]: ...
resolver = StrawberryResolver(get_overlap)
assert resolver.type == Union[Venn, Diagram]
diff --git a/tests/utils/test_arguments_converter.py b/tests/utils/test_arguments_converter.py
index 7d64e10a00..cb8f9e26ed 100644
--- a/tests/utils/test_arguments_converter.py
+++ b/tests/utils/test_arguments_converter.py
@@ -6,12 +6,12 @@
import strawberry
from strawberry.annotation import StrawberryAnnotation
-from strawberry.arguments import StrawberryArgument, convert_arguments
from strawberry.exceptions import UnsupportedTypeError
-from strawberry.lazy_type import LazyType
from strawberry.schema.config import StrawberryConfig
from strawberry.schema.types.scalar import DEFAULT_SCALAR_REGISTRY
-from strawberry.unset import UNSET
+from strawberry.types.arguments import StrawberryArgument, convert_arguments
+from strawberry.types.lazy_type import LazyType
+from strawberry.types.unset import UNSET
def test_simple_types():
diff --git a/tests/utils/test_inspect.py b/tests/utils/test_inspect.py
index 8704f03153..a4cf9c9048 100644
--- a/tests/utils/test_inspect.py
+++ b/tests/utils/test_inspect.py
@@ -16,92 +16,96 @@ def test_get_specialized_type_var_map_non_generic(value: type):
def test_get_specialized_type_var_map_generic_not_specialized():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
assert get_specialized_type_var_map(Foo) == {}
def test_get_specialized_type_var_map_generic():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Foo[int]):
- ...
+ class Bar(Foo[int]): ...
+
+ assert get_specialized_type_var_map(Bar) == {"_T": int}
+
+
+def test_get_specialized_type_var_map_from_alias():
+ @strawberry.type
+ class Foo(Generic[_T]): ...
+
+ SpecializedFoo = Foo[int]
+
+ assert get_specialized_type_var_map(SpecializedFoo) == {"_T": int}
+
+
+def test_get_specialized_type_var_map_from_alias_with_inheritance():
+ @strawberry.type
+ class Foo(Generic[_T]): ...
+
+ SpecializedFoo = Foo[int]
+
+ @strawberry.type
+ class Bar(SpecializedFoo): ...
assert get_specialized_type_var_map(Bar) == {"_T": int}
def test_get_specialized_type_var_map_generic_subclass():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Foo[int]):
- ...
+ class Bar(Foo[int]): ...
@strawberry.type
- class BarSubclass(Bar):
- ...
+ class BarSubclass(Bar): ...
assert get_specialized_type_var_map(BarSubclass) == {"_T": int}
def test_get_specialized_type_var_map_double_generic():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Foo[_T]):
- ...
+ class Bar(Foo[_T]): ...
@strawberry.type
- class Bin(Bar[int]):
- ...
+ class Bin(Bar[int]): ...
assert get_specialized_type_var_map(Bin) == {"_T": int}
def test_get_specialized_type_var_map_double_generic_subclass():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Foo[_T]):
- ...
+ class Bar(Foo[_T]): ...
@strawberry.type
- class Bin(Bar[int]):
- ...
+ class Bin(Bar[int]): ...
@strawberry.type
- class BinSubclass(Bin):
- ...
+ class BinSubclass(Bin): ...
assert get_specialized_type_var_map(Bin) == {"_T": int}
def test_get_specialized_type_var_map_multiple_inheritance():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Generic[_K]):
- ...
+ class Bar(Generic[_K]): ...
@strawberry.type
- class Bin(Foo[int]):
- ...
+ class Bin(Foo[int]): ...
@strawberry.type
- class Baz(Bin, Bar[str]):
- ...
+ class Baz(Bin, Bar[str]): ...
assert get_specialized_type_var_map(Baz) == {
"_T": int,
@@ -111,24 +115,19 @@ class Baz(Bin, Bar[str]):
def test_get_specialized_type_var_map_multiple_inheritance_subclass():
@strawberry.type
- class Foo(Generic[_T]):
- ...
+ class Foo(Generic[_T]): ...
@strawberry.type
- class Bar(Generic[_K]):
- ...
+ class Bar(Generic[_K]): ...
@strawberry.type
- class Bin(Foo[int]):
- ...
+ class Bin(Foo[int]): ...
@strawberry.type
- class Baz(Bin, Bar[str]):
- ...
+ class Baz(Bin, Bar[str]): ...
@strawberry.type
- class BazSubclass(Baz):
- ...
+ class BazSubclass(Baz): ...
assert get_specialized_type_var_map(BazSubclass) == {
"_T": int,
diff --git a/tests/utils/test_typing.py b/tests/utils/test_typing.py
index 91c963e328..bf8809bc3d 100644
--- a/tests/utils/test_typing.py
+++ b/tests/utils/test_typing.py
@@ -1,15 +1,17 @@
+import sys
import typing
from typing import ClassVar, ForwardRef, Optional, Union
from typing_extensions import Annotated
+import pytest
+
import strawberry
-from strawberry.lazy_type import LazyType
+from strawberry.types.lazy_type import LazyType
from strawberry.utils.typing import eval_type, get_optional_annotation, is_classvar
@strawberry.type
-class Fruit:
- ...
+class Fruit: ...
def test_get_optional_annotation():
@@ -24,8 +26,7 @@ def test_get_optional_annotation():
def test_eval_type():
- class Foo:
- ...
+ class Foo: ...
assert eval_type(ForwardRef("str")) is str
assert eval_type(str) is str
@@ -64,6 +65,79 @@ class Foo:
)
== Annotated[strawberry.auto, "foobar"]
)
+ assert (
+ eval_type(
+ ForwardRef("Annotated[datetime, strawberry.lazy('datetime')]"),
+ {"strawberry": strawberry, "Annotated": Annotated},
+ None,
+ )
+ == Annotated[
+ LazyType("datetime", "datetime"),
+ strawberry.lazy("datetime"),
+ ]
+ )
+
+
+@pytest.mark.skipif(
+ sys.version_info < (3, 9),
+ reason="python 3.8 resolves Annotated differently",
+)
+def test_eval_type_with_deferred_annotations():
+ assert (
+ eval_type(
+ ForwardRef(
+ "Annotated['Fruit', strawberry.lazy('tests.utils.test_typing')]"
+ ),
+ {"strawberry": strawberry, "Annotated": Annotated},
+ None,
+ )
+ == Annotated[
+ LazyType("Fruit", "tests.utils.test_typing"),
+ strawberry.lazy("tests.utils.test_typing"),
+ ]
+ )
+ assert (
+ eval_type(
+ ForwardRef("Annotated['datetime', strawberry.lazy('datetime')]"),
+ {"strawberry": strawberry, "Annotated": Annotated},
+ None,
+ )
+ == Annotated[
+ LazyType("datetime", "datetime"),
+ strawberry.lazy("datetime"),
+ ]
+ )
+
+
+@pytest.mark.skipif(
+ sys.version_info >= (3, 9),
+ reason="python 3.8 resolves Annotated differently",
+)
+def test_eval_type_with_deferred_annotations_3_8():
+ assert (
+ eval_type(
+ ForwardRef(
+ "Annotated['Fruit', strawberry.lazy('tests.utils.test_typing')]"
+ ),
+ {"strawberry": strawberry, "Annotated": Annotated},
+ None,
+ )
+ == Annotated[
+ ForwardRef("Fruit"),
+ strawberry.lazy("tests.utils.test_typing"),
+ ]
+ )
+ assert (
+ eval_type(
+ ForwardRef("Annotated['datetime', strawberry.lazy('datetime')]"),
+ {"strawberry": strawberry, "Annotated": Annotated},
+ None,
+ )
+ == Annotated[
+ ForwardRef("datetime"),
+ strawberry.lazy("datetime"),
+ ]
+ )
def test_is_classvar():
diff --git a/tests/utils/test_typing_forward_refs.py b/tests/utils/test_typing_forward_refs.py
index 0217852d4a..5ae315cdcc 100644
--- a/tests/utils/test_typing_forward_refs.py
+++ b/tests/utils/test_typing_forward_refs.py
@@ -11,8 +11,7 @@
def test_eval_type():
- class Foo:
- ...
+ class Foo: ...
assert eval_type(ForwardRef("Foo | None"), globals(), locals()) == Optional[Foo]
assert eval_type(ForwardRef("Foo | str"), globals(), locals()) == Union[Foo, str]
@@ -36,8 +35,7 @@ class Foo:
reason="generic type alias only available on python 3.9+",
)
def test_eval_type_generic_type_alias():
- class Foo:
- ...
+ class Foo: ...
assert eval_type(ForwardRef("Foo | None"), globals(), locals()) == Optional[Foo]
assert eval_type(ForwardRef("Foo | str"), globals(), locals()) == Union[Foo, str]
diff --git a/tests/views/schema.py b/tests/views/schema.py
index 4ce2a206c0..14a25b0c0d 100644
--- a/tests/views/schema.py
+++ b/tests/views/schema.py
@@ -10,13 +10,13 @@
from strawberry.file_uploads import Upload
from strawberry.permission import BasePermission
from strawberry.subscriptions.protocols.graphql_transport_ws.types import PingMessage
-from strawberry.types import ExecutionContext, Info
+from strawberry.types import ExecutionContext
class AlwaysFailPermission(BasePermission):
message = "You are not authorized"
- def has_permission(self, source: Any, info: Info[Any, Any], **kwargs: Any) -> bool:
+ def has_permission(self, source: Any, info: strawberry.Info, **kwargs: Any) -> bool:
return False
@@ -95,7 +95,7 @@ async def exception(self, message: str) -> str:
raise ValueError(message)
@strawberry.field
- def teapot(self, info: Info[Any, None]) -> str:
+ def teapot(self, info: strawberry.Info[Any, None]) -> str:
info.context["response"].status_code = 418
return "๐ซ"
@@ -105,11 +105,11 @@ def root_name(self) -> str:
return type(self).__name__
@strawberry.field
- def value_from_context(self, info: Info[Any, Any]) -> str:
+ def value_from_context(self, info: strawberry.Info) -> str:
return info.context["custom_value"]
@strawberry.field
- def returns_401(self, info: Info[Any, Any]) -> str:
+ def returns_401(self, info: strawberry.Info) -> str:
response = info.context["response"]
if hasattr(response, "set_status"):
response.set_status(401)
@@ -119,7 +119,7 @@ def returns_401(self, info: Info[Any, Any]) -> str:
return "hey"
@strawberry.field
- def set_header(self, info: Info[Any, Any], name: str) -> str:
+ def set_header(self, info: strawberry.Info, name: str) -> str:
response = info.context["response"]
response.headers["X-Name"] = name
@@ -162,7 +162,7 @@ async def echo(self, message: str, delay: float = 0) -> AsyncGenerator[str, None
yield message
@strawberry.subscription
- async def request_ping(self, info: Info[Any, Any]) -> AsyncGenerator[bool, None]:
+ async def request_ping(self, info: strawberry.Info) -> AsyncGenerator[bool, None]:
ws = info.context["ws"]
await ws.send_json(PingMessage().as_dict())
yield True
@@ -174,7 +174,7 @@ async def infinity(self, message: str) -> AsyncGenerator[str, None]:
await asyncio.sleep(1)
@strawberry.subscription
- async def context(self, info: Info[Any, Any]) -> AsyncGenerator[str, None]:
+ async def context(self, info: strawberry.Info) -> AsyncGenerator[str, None]:
yield info.context["custom_value"]
@strawberry.subscription
@@ -201,7 +201,7 @@ async def flavors_invalid(self) -> AsyncGenerator[Flavor, None]:
yield Flavor.CHOCOLATE
@strawberry.subscription
- async def debug(self, info: Info[Any, Any]) -> AsyncGenerator[DebugInfo, None]:
+ async def debug(self, info: strawberry.Info) -> AsyncGenerator[DebugInfo, None]:
active_result_handlers = [
task for task in info.context["get_tasks"]() if not task.done()
]
@@ -221,7 +221,7 @@ async def debug(self, info: Info[Any, Any]) -> AsyncGenerator[DebugInfo, None]:
@strawberry.subscription
async def listener(
self,
- info: Info[Any, Any],
+ info: strawberry.Info,
timeout: Optional[float] = None,
group: Optional[str] = None,
) -> AsyncGenerator[str, None]:
@@ -237,7 +237,7 @@ async def listener(
@strawberry.subscription
async def listener_with_confirmation(
self,
- info: Info[Any, Any],
+ info: strawberry.Info,
timeout: Optional[float] = None,
group: Optional[str] = None,
) -> AsyncGenerator[Union[str, None], None]:
@@ -253,13 +253,13 @@ async def listener_with_confirmation(
@strawberry.subscription
async def connection_params(
- self, info: Info[Any, Any]
+ self, info: strawberry.Info
) -> AsyncGenerator[str, None]:
yield info.context["connection_params"]["strawberry"]
@strawberry.subscription
async def long_finalizer(
- self, info: Info[Any, Any], delay: float = 0
+ self, info: strawberry.Info, delay: float = 0
) -> AsyncGenerator[str, None]:
try:
for _i in range(100):
diff --git a/tests/websockets/test_graphql_transport_ws.py b/tests/websockets/test_graphql_transport_ws.py
index 1ab738fb73..2b38108adc 100644
--- a/tests/websockets/test_graphql_transport_ws.py
+++ b/tests/websockets/test_graphql_transport_ws.py
@@ -312,9 +312,8 @@ async def test_duplicated_operation_ids(ws: WebSocketClient):
async def test_reused_operation_ids(ws: WebSocketClient):
- """
- Test that an operation id can be re-used after it has been
- previously used for a completed operation
+ """Test that an operation id can be re-used after it has been
+ previously used for a completed operation.
"""
# Use sub1 as an id for an operation
await ws.send_json(
@@ -480,9 +479,7 @@ async def test_subscription_errors(ws: WebSocketClient):
async def test_operation_error_no_complete(ws: WebSocketClient):
- """
- Test that an "error" message is not followed by "complete"
- """
+ """Test that an "error" message is not followed by "complete"."""
# get an "error" message
await ws.send_json(
SubscribeMessage(
@@ -553,10 +550,9 @@ async def test_single_result_query_operation(ws: WebSocketClient):
async def test_single_result_query_operation_async(ws: WebSocketClient):
- """
- Test a single result query operation on an
+ """Test a single result query operation on an
`async` method in the schema, including an artificial
- async delay
+ async delay.
"""
await ws.send_json(
SubscribeMessage(
@@ -580,8 +576,7 @@ async def test_single_result_query_operation_async(ws: WebSocketClient):
async def test_single_result_query_operation_overlapped(ws: WebSocketClient):
- """
- Test that two single result queries can be in flight at the same time,
+ """Test that two single result queries can be in flight at the same time,
just like regular queries. Start two queries with separate ids. The
first query has a delay, so we expect the response to the second
query to be delivered first.
@@ -705,9 +700,8 @@ async def test_single_result_operation_error(ws: WebSocketClient):
async def test_single_result_operation_exception(ws: WebSocketClient):
- """
- Test that single-result-operations which raise exceptions
- behave in the same way as streaming operations
+ """Test that single-result-operations which raise exceptions
+ behave in the same way as streaming operations.
"""
process_errors = Mock()
with patch.object(Schema, "process_errors", process_errors):
@@ -730,9 +724,8 @@ async def test_single_result_operation_exception(ws: WebSocketClient):
async def test_single_result_duplicate_ids_sub(ws: WebSocketClient):
- """
- Test that single-result-operations and streaming operations
- share the same ID namespace. Start a regular subscription,
+ """Test that single-result-operations and streaming operations
+ share the same ID namespace. Start a regular subscription,
then issue a single-result operation with same ID and expect an
error due to already existing ID
"""
@@ -762,10 +755,9 @@ async def test_single_result_duplicate_ids_sub(ws: WebSocketClient):
async def test_single_result_duplicate_ids_query(ws: WebSocketClient):
- """
- Test that single-result-operations don't allow duplicate
- IDs for two asynchronous queries. Issue one async query
- with delay, then another with same id. Expect error.
+ """Test that single-result-operations don't allow duplicate
+ IDs for two asynchronous queries. Issue one async query
+ with delay, then another with same id. Expect error.
"""
# single result subscription 1
await ws.send_json(
@@ -890,9 +882,8 @@ async def test_subsciption_cancel_finalization_delay(ws: WebSocketClient):
async def test_error_handler_for_timeout(http_client: HttpClient):
- """
- Test that the error handler is called when the timeout
- task encounters an error
+ """Test that the error handler is called when the timeout
+ task encounters an error.
"""
with contextlib.suppress(ImportError):
from tests.http.clients.channels import ChannelsHttpClient
@@ -936,9 +927,8 @@ def on_init(_handler):
async def test_subscription_errors_continue(ws: WebSocketClient):
- """
- Verify that an ExecutionResult with errors during subscription does not terminate
- the subscription
+ """Verify that an ExecutionResult with errors during subscription does not terminate
+ the subscription.
"""
process_errors = Mock()
with patch.object(Schema, "process_errors", process_errors):
diff --git a/tests/websockets/test_graphql_ws.py b/tests/websockets/test_graphql_ws.py
index 83b7e66782..1857da33d8 100644
--- a/tests/websockets/test_graphql_ws.py
+++ b/tests/websockets/test_graphql_ws.py
@@ -406,6 +406,7 @@ async def test_resolving_enums(ws: WebSocketClient):
assert response["id"] == "demo"
+@pytest.mark.xfail(reason="flaky test")
async def test_task_cancellation_separation(aiohttp_app_client: HttpClient):
# Note Python 3.7 does not support Task.get_name/get_coro so we have to use
# repr(Task) to check whether expected tasks are running.