Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"sphinx.ext.coverage",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinx_autodoc_typehints",
]
if not on_rtd:
extensions += ["sphinx.ext.githubpages"]
Expand Down
43 changes: 43 additions & 0 deletions docs/execution/queryvalidation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <queryvalidation>` is registered.


Implementing custom validators
------------------------------
All custom query validators should extend the `ValidationRule <https://github.com/graphql-python/graphql-core/blob/v3.0.5/src/graphql/validation/rules/__init__.py#L37>`_
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions docs/types/list-and-nonnull.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------

Expand All @@ -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``.

57 changes: 30 additions & 27 deletions graphene/types/field.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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<ResolverParamGraphQLArguments>`).
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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
18 changes: 10 additions & 8 deletions graphene/types/structures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from .unmountedtype import UnmountedType
from .utils import get_type

Expand All @@ -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__
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions graphene/types/utils.py
Original file line number Diff line number Diff line change
@@ -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
"""
Expand All @@ -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
Expand All @@ -35,15 +40,15 @@ 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):
return _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
Expand Down