From a8dd9bb367d3a86d48300cea36564097c891406a Mon Sep 17 00:00:00 2001 From: faresrafat3 Date: Thu, 1 Oct 2026 15:12:25 +0300 Subject: [PATCH 1/5] Add type hints to graphene/types/field.py Part of #1454. Annotates the module and removes the now-duplicated type information from the Field docstring, so the code is the single source of truth for typing. sphinx-autodoc-typehints is added to the docs so the hints are rendered back into the API reference. The name and source parameters accept an Argument or UnmountedType as well as a string (the constructor moves such values into extra_args), so their annotations include those types. mypy graphene: Success: no issues found in 120 source files ruff: All checks passed pytest: 465 passed --- docs/conf.py | 1 + docs/requirements.txt | 1 + graphene/types/field.py | 57 ++++++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 873531ae..c5d3bb3b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,6 +42,7 @@ "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", ] if not on_rtd: extensions += ["sphinx.ext.githubpages"] diff --git a/docs/requirements.txt b/docs/requirements.txt index dee009c7..5a310a69 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,6 @@ # Required library Sphinx==6.1.3 sphinx-autobuild==2021.3.14 +sphinx-autodoc-typehints==2.2.0 # Docs template http://graphene-python.org/sphinx_graphene_theme.zip diff --git a/graphene/types/field.py b/graphene/types/field.py index 06ba8487..11e457b7 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -1,6 +1,7 @@ import inspect from collections.abc import Mapping from functools import partial +from typing import Any, Callable, Optional, Union from .argument import Argument, to_arguments from .mountedtype import MountedType @@ -13,7 +14,7 @@ base_type = type -def source_resolver(source, root, info, **args): +def source_resolver(source: str, root: Any, info: Any, **args: Any) -> Any: resolved = default_resolver(source, None, root, info, **args) if inspect.isfunction(resolved) or inspect.ismethod(resolved): return resolved() @@ -41,43 +42,43 @@ class Person(ObjectType): last_name = graphene.Field(String, description='Surname') # explicitly mounted as Field args: - type (class for a graphene.UnmountedType): Must be a class (not an instance) of an + type_: Must be a class (not an instance) of an unmounted graphene type (ex. scalar or object) which is used for the type of this field in the GraphQL schema. You can provide a dotted module import path (string) to the class instead of the class itself (e.g. to avoid circular import issues). - args (optional, Dict[str, graphene.Argument]): Arguments that can be input to the field. + args: Arguments that can be input to the field. Prefer to use ``**extra_args``, unless you use an argument name that clashes with one of the Field arguments presented here (see :ref:`example`). - resolver (optional, Callable): A function to get the value for a Field from the parent + resolver: A function to get the value for a Field from the parent value object. If not set, the default resolver method for the schema is used. - source (optional, str): attribute name to resolve for this field from the parent value + source: attribute name to resolve for this field from the parent value object. Alternative to resolver (cannot set both source and resolver). - deprecation_reason (optional, str): Setting this value indicates that the field is + deprecation_reason: Setting this value indicates that the field is depreciated and may provide instruction or reason on how for clients to proceed. - required (optional, bool): indicates this field as not null in the graphql schema. Same behavior as + required: indicates this field as not null in the graphql schema. Same behavior as graphene.NonNull. Default False. - name (optional, str): the name of the GraphQL field (must be unique in a type). Defaults to attribute + name: the name of the GraphQL field (must be unique in a type). Defaults to attribute name. - description (optional, str): the description of the GraphQL field in the schema. - default_value (optional, Any): Default value to resolve if none set from schema. - **extra_args (optional, Dict[str, Union[graphene.Argument, graphene.UnmountedType]): any + description: the description of the GraphQL field in the schema. + default_value: Default value to resolve if none set from schema. + **extra_args: any additional arguments to mount on the field. """ def __init__( self, - type_, - args=None, - resolver=None, - source=None, - deprecation_reason=None, - name=None, - description=None, - required=False, - _creation_counter=None, - default_value=None, - **extra_args, - ): + type_: Any, + args: Optional[Mapping[str, Any]] = None, + resolver: Optional[Callable[..., Any]] = None, + source: Optional[Union[str, Argument, UnmountedType]] = None, + deprecation_reason: Optional[str] = None, + name: Optional[Union[str, Argument, UnmountedType]] = None, + description: Optional[str] = None, + required: bool = False, + _creation_counter: Optional[int] = None, + default_value: Any = None, + **extra_args: Any, + ) -> None: super(Field, self).__init__(_creation_counter=_creation_counter) assert not args or isinstance( args, Mapping @@ -113,12 +114,12 @@ def __init__( self.default_value = default_value @property - def type(self): + def type(self) -> Any: return get_type(self._type) - get_resolver = None + get_resolver: Optional[Callable[..., Any]] = None - def wrap_resolve(self, parent_resolver): + def wrap_resolve(self, parent_resolver: Callable[..., Any]) -> Callable[..., Any]: """ Wraps a function resolver, using the ObjectType resolve_{FIELD_NAME} (parent_resolver) if the Field definition has no resolver. @@ -131,7 +132,9 @@ def wrap_resolve(self, parent_resolver): return self.resolver or parent_resolver - def wrap_subscribe(self, parent_subscribe): + def wrap_subscribe( + self, parent_subscribe: Callable[..., Any] + ) -> Callable[..., Any]: """ Wraps a function subscribe, using the ObjectType subscribe_{FIELD_NAME} (parent_subscribe) if the Field definition has no subscribe. From 0384fd64085732c5acb5a27700f6029f65feda0a Mon Sep 17 00:00:00 2001 From: faresrafat3 Date: Thu, 1 Oct 2026 15:14:04 +0300 Subject: [PATCH 2/5] Add type hints to graphene/types/structures.py Part of #1454. Annotates the Structure, List and NonNull classes. mypy graphene: Success: no issues found in 120 source files ruff: All checks passed pytest: 465 passed --- graphene/types/structures.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/graphene/types/structures.py b/graphene/types/structures.py index a6763978..c7c1d305 100644 --- a/graphene/types/structures.py +++ b/graphene/types/structures.py @@ -1,3 +1,5 @@ +from typing import Any + from .unmountedtype import UnmountedType from .utils import get_type @@ -8,7 +10,7 @@ class Structure(UnmountedType): wraps a main type with certain structure. """ - def __init__(self, of_type, *args, **kwargs): + def __init__(self, of_type: Any, *args: Any, **kwargs: Any) -> None: super(Structure, self).__init__(*args, **kwargs) if not isinstance(of_type, Structure) and isinstance(of_type, UnmountedType): cls_name = type(self).__name__ @@ -20,10 +22,10 @@ def __init__(self, of_type, *args, **kwargs): self._of_type = of_type @property - def of_type(self): + def of_type(self) -> Any: return get_type(self._of_type) - def get_type(self): + def get_type(self) -> "Structure": """ This function is called when the unmounted type (List or NonNull instance) is mounted (as a Field, InputField or Argument) @@ -48,10 +50,10 @@ class List(Structure): field_name = List(String, description="There will be many values") """ - def __str__(self): + def __str__(self) -> str: return f"[{self.of_type}]" - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return isinstance(other, List) and ( self.of_type == other.of_type and self.args == other.args @@ -82,16 +84,16 @@ class NonNull(Structure): """ - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(NonNull, self).__init__(*args, **kwargs) assert not isinstance( self._of_type, NonNull ), f"Can only create NonNull of a Nullable GraphQLType but got: {self._of_type}." - def __str__(self): + def __str__(self) -> str: return f"{self.of_type}!" - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return isinstance(other, NonNull) and ( self.of_type == other.of_type and self.args == other.args From e0c91b933df60adcd14b7175d7f5913d9f37de69 Mon Sep 17 00:00:00 2001 From: faresrafat3 Date: Thu, 1 Oct 2026 15:15:16 +0300 Subject: [PATCH 3/5] Add type hints to graphene/types/utils.py Part of #1454. Annotates get_field_as, yank_fields_from_attrs, get_type and get_underlying_type. mypy graphene: Success: no issues found in 120 source files ruff: All checks passed pytest: 465 passed --- graphene/types/utils.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/graphene/types/utils.py b/graphene/types/utils.py index 1976448a..0070cb9f 100644 --- a/graphene/types/utils.py +++ b/graphene/types/utils.py @@ -1,12 +1,13 @@ import inspect from functools import partial +from typing import Any, Dict, Optional, Type from ..utils.module_loading import import_string from .mountedtype import MountedType from .unmountedtype import UnmountedType -def get_field_as(value, _as=None): +def get_field_as(value: Any, _as: Optional[Type[MountedType]] = None) -> Any: """ Get type mounted """ @@ -18,7 +19,11 @@ def get_field_as(value, _as=None): return _as.mounted(value) -def yank_fields_from_attrs(attrs, _as=None, sort=True): +def yank_fields_from_attrs( + attrs: Dict[str, Any], + _as: Optional[Type[MountedType]] = None, + sort: bool = True, +) -> Dict[str, Any]: """ Extract all the fields in given attributes (dict) and return them ordered @@ -35,7 +40,7 @@ def yank_fields_from_attrs(attrs, _as=None, sort=True): return dict(fields_with_names) -def get_type(_type): +def get_type(_type: Any) -> Any: if isinstance(_type, str): return import_string(_type) if inspect.isfunction(_type) or isinstance(_type, partial): @@ -43,7 +48,7 @@ def get_type(_type): return _type -def get_underlying_type(_type): +def get_underlying_type(_type: Any) -> Any: """Get the underlying type even if it is wrapped in structures like NonNull""" while hasattr(_type, "of_type"): _type = _type.of_type From 277ffd2e215b446df18ca15c64a6c85369fe48cc Mon Sep 17 00:00:00 2001 From: faresrafat3 Date: Thu, 1 Oct 2026 15:21:05 +0300 Subject: [PATCH 4/5] Document how to enforce DisableIntrospection on every query The existing example only validates a single already-parsed query via graphql.validate(), which left it unclear how to actually apply the rule to incoming requests (see #1552). Add an 'Enforcing validation on every query' section showing the pattern: parse, run validate() with the rules tuple, return the errors if any, and otherwise execute. The example output was produced by running the code against graphene, not written from reading it. Part of #1552. --- docs/execution/queryvalidation.rst | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/execution/queryvalidation.rst b/docs/execution/queryvalidation.rst index 02e29a35..dad71cc1 100644 --- a/docs/execution/queryvalidation.rst +++ b/docs/execution/queryvalidation.rst @@ -82,6 +82,49 @@ Here is how you would disable introspection for your schema. ) +Enforcing validation on every query +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The example above validates a single already-parsed query. To apply a rule to +*every* request, run :func:`graphql.validate` before executing and return the +errors instead of the result when validation fails: + +.. code:: python + + from graphql import graphql_sync, parse, validate + from graphene import ObjectType, Schema, String + from graphene.validation import DisableIntrospection + + + class MyQuery(ObjectType): + name = String(required=True) + + + schema = Schema(query=MyQuery) + + validation_rules = (DisableIntrospection,) + + + def execute(query_string): + document = parse(query_string) + + # Validation runs against the same schema that will execute the query. + errors = validate(schema.graphql_schema, document, rules=validation_rules) + if errors: + return {"errors": [error.formatted for error in errors]} + + return graphql_sync(schema.graphql_schema, query_string).formatted + +An introspection query is now rejected:: + + >>> execute("{ __schema { types { name } } }") + {'errors': [{'message': "Cannot query '__schema': introspection is disabled.", + 'locations': [{'line': 1, 'column': 3}]}]} + +The same ``validation_rules`` tuple is where any other +:doc:`custom validator ` is registered. + + Implementing custom validators ------------------------------ All custom query validators should extend the `ValidationRule `_ From 030889c4cc112e77c2e2778eed51cfa06283e811 Mon Sep 17 00:00:00 2001 From: faresrafat3 Date: Thu, 1 Oct 2026 15:22:16 +0300 Subject: [PATCH 5/5] Document returning lists of objects and self-referencing lists The List section only showed a list of strings and moved on (see #1171). Adds answers to the three questions raised: - returning a list of other objects, and how little the parent needs to know about the child - self-referencing lists (and circular references) via lambda - combining NonNull items with required=True for [String!]! Every claim was checked by running the code: (1) list of objects -> {'data': {'people': [{'name': 'Ada'}, ...]}} (2) self-referencing -> {'data': {'me': {'friends': [{'name': ...}]}}} (3) required list -> [String!]! and the two negative claims were confirmed to actually fail without lambda: graphene.List(Later) -> NameError: name 'Later' is not defined graphene.List(U) -> NameError: name 'U' is not defined Part of #1171. --- docs/types/list-and-nonnull.rst | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/types/list-and-nonnull.rst b/docs/types/list-and-nonnull.rst index a127a9d2..e50a5724 100644 --- a/docs/types/list-and-nonnull.rst +++ b/docs/types/list-and-nonnull.rst @@ -49,6 +49,50 @@ Lists work in a similar way: We can use a type modifier to mark a type as a It works the same for arguments, where the validation step will expect a list for that value. +A list can hold any type, including other ``ObjectType`` classes. The resolver +returns a plain Python iterable, and Graphene maps each item through the +declared type: + +.. code:: python + + import graphene + + class Person(graphene.ObjectType): + name = graphene.String() + + class Query(graphene.ObjectType): + people = graphene.List(Person) + + def resolve_people(root, info): + return [Person(name="Ada"), Person(name="Grace")] + +This produces the type ``[Person]``, and the resolver's return value is +serialized item by item:: + + {"data": {"people": [{"name": "Ada"}, {"name": "Grace"}]}} + +The parent object needs to know nothing about the child type beyond the class +itself; ``Person`` only has to be defined before it is referenced. + +Self-referencing lists +---------------------- + +A type that returns a list of itself would otherwise need to be defined before +it exists. Wrap the class in ``lambda`` so the reference is resolved lazily: + +.. code:: python + + import graphene + + class User(graphene.ObjectType): + name = graphene.String() + friends = graphene.List(lambda: User) + + def resolve_friends(root, info): + return [User(name=f"{root.name}'s friend")] + +The same lazy form is what makes circular references between two types work. + NonNull Lists ------------- @@ -69,3 +113,14 @@ The above results in the type definition: type Character { appearsIn: [String!] } + +Combining ``NonNull`` with ``required=True`` makes the list itself non-null as +well as its items: + +.. code:: python + + tags = graphene.List(graphene.NonNull(graphene.String), required=True) + +which gives ``[String!]!`` — the list is always present and never contains +``null``. +