Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-graphene for openSUSE:Factory checked in at 2023-12-17 21:33:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-graphene (Old) and /work/SRC/openSUSE:Factory/.python-graphene.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-graphene" Sun Dec 17 21:33:10 2023 rev:11 rq:1133682 version:3.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-graphene/python-graphene.changes 2023-07-10 16:40:48.450867136 +0200 +++ /work/SRC/openSUSE:Factory/.python-graphene.new.25432/python-graphene.changes 2023-12-17 21:35:20.284153316 +0100 @@ -1,0 +2,19 @@ +Sun Dec 17 01:58:11 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 3.3.0: + * Default value for InputObjectTypes + * Default enum description to "An enumeration." + * Allow the user to change InputObjectType's default value on + non-specified inputs to a sentinel value + * 881: Corrected enum metaclass to fix pickle.dumps() + * chore: Use `typing.TYPE_CHECKING` instead of MYPY + * test: print schema with InputObjectType with DateTime field + with default_value (#1293) + * docs: add get_human function + * CI: drop python 3.6 + * types: add option for strict connection types +- update to 3.2.2: + * This release provides some internal refactoring to the relay + types to improve support for adding custom fields to them. + +------------------------------------------------------------------- Old: ---- graphene-3.2.1.tar.gz New: ---- graphene-3.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-graphene.spec ++++++ --- /var/tmp/diff_new_pack.05ARxi/_old 2023-12-17 21:35:20.708168723 +0100 +++ /var/tmp/diff_new_pack.05ARxi/_new 2023-12-17 21:35:20.708168723 +0100 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-graphene -Version: 3.2.1 +Version: 3.3.0 Release: 0 Summary: GraphQL Framework for Python License: MIT ++++++ graphene-3.2.1.tar.gz -> graphene-3.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/.github/workflows/tests.yml new/graphene-3.3.0/.github/workflows/tests.yml --- old/graphene-3.2.1/.github/workflows/tests.yml 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/.github/workflows/tests.yml 2023-07-26 08:26:30.000000000 +0200 @@ -25,12 +25,11 @@ fail-fast: false matrix: include: - - {name: '3.11', python: '3.11-dev', os: ubuntu-latest, tox: py311} + - {name: '3.11', python: '3.11', os: ubuntu-latest, tox: py311} - {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310} - {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39} - {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38} - {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37} - - {name: '3.6', python: '3.6', os: ubuntu-20.04, tox: py36} steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/README.md new/graphene-3.3.0/README.md --- old/graphene-3.2.1/README.md 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/README.md 2023-07-26 08:26:30.000000000 +0200 @@ -1,6 +1,6 @@ -# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master) +# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master) [![](https://dcbadge.vercel.app/api/server/T6Gp6NFYHe?style=flat)](https://discord.gg/T6Gp6NFYHe) -[ð¬ Join the community on Slack](https://join.slack.com/t/graphenetools/shared_invite/enQtOTE2MDQ1NTg4MDM1LTA4Nzk0MGU0NGEwNzUxZGNjNDQ4ZjAwNDJjMjY0OGE1ZDgxZTg4YjM2ZTc4MjE2ZTAzZjE2ZThhZTQzZTkyMmM) +[ð¬ Join the community on Discord](https://discord.gg/T6Gp6NFYHe) **We are looking for contributors**! Please check the current issues to see how you can help â¤ï¸ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/README.rst new/graphene-3.3.0/README.rst --- old/graphene-3.2.1/README.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/README.rst 2023-07-26 08:26:30.000000000 +0200 @@ -36,9 +36,6 @@ | SQLAlchemy | `graphene-sqlalchemy <https://git | | | hub.com/graphql-python/graphene-sqlalchemy/>`__ | +-------------------+-------------------------------------------------+ -| Google App Engine | `graphene-gae <http | -| | s://github.com/graphql-python/graphene-gae/>`__ | -+-------------------+-------------------------------------------------+ Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/api/index.rst new/graphene-3.3.0/docs/api/index.rst --- old/graphene-3.2.1/docs/api/index.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/api/index.rst 2023-07-26 08:26:30.000000000 +0200 @@ -92,7 +92,7 @@ .. autoclass:: graphene.Context -.. autoclass:: graphql.execution.base.ExecutionResult +.. autoclass:: graphql.ExecutionResult .. Relay .. ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/conf.py new/graphene-3.3.0/docs/conf.py --- old/graphene-3.2.1/docs/conf.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/conf.py 2023-07-26 08:26:30.000000000 +0200 @@ -82,7 +82,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -456,5 +456,4 @@ "http://docs.graphene-python.org/projects/sqlalchemy/en/latest/", None, ), - "graphene_gae": ("http://docs.graphene-python.org/projects/gae/en/latest/", None), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/execution/dataloader.rst new/graphene-3.3.0/docs/execution/dataloader.rst --- old/graphene-3.2.1/docs/execution/dataloader.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/execution/dataloader.rst 2023-07-26 08:26:30.000000000 +0200 @@ -36,10 +36,10 @@ user_loader = UserLoader() user1 = await user_loader.load(1) - user1_best_friend = await user_loader.load(user1.best_friend_id)) + user1_best_friend = await user_loader.load(user1.best_friend_id) user2 = await user_loader.load(2) - user2_best_friend = await user_loader.load(user2.best_friend_id)) + user2_best_friend = await user_loader.load(user2.best_friend_id) A naive application may have issued *four* round-trips to a backend for the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/execution/queryvalidation.rst new/graphene-3.3.0/docs/execution/queryvalidation.rst --- old/graphene-3.2.1/docs/execution/queryvalidation.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/execution/queryvalidation.rst 2023-07-26 08:26:30.000000000 +0200 @@ -1,5 +1,5 @@ Query Validation -========== +================ GraphQL uses query validators to check if Query AST is valid and can be executed. Every GraphQL server implements standard query validators. For example, there is an validator that tests if queried field exists on queried type, that makes query fail with "Cannot query field on type" error if it doesn't. @@ -8,7 +8,7 @@ Depth limit Validator ------------------ +--------------------- The depth limit validator helps to prevent execution of malicious queries. It takes in the following arguments. @@ -17,7 +17,7 @@ - ``callback`` Called each time validation runs. Receives an Object which is a map of the depths for each operation. Usage -------- +----- Here is how you would implement depth-limiting on your schema. @@ -54,7 +54,7 @@ This is a useful security measure in production environments. Usage -------- +----- Here is how you would disable introspection for your schema. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/index.rst new/graphene-3.3.0/docs/index.rst --- old/graphene-3.2.1/docs/index.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/index.rst 2023-07-26 08:26:30.000000000 +0200 @@ -1,12 +1,6 @@ Graphene ======== ------------- - -The documentation below is for the ``dev`` (prerelease) version of Graphene. To view the documentation for the latest stable Graphene version go to the `v2 docs <https://docs.graphene-python.org/en/stable/>`_. - ------------- - Contents: .. toctree:: @@ -27,7 +21,6 @@ * `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_) * Flask-Graphql (`source <https://github.com/graphql-python/flask-graphql>`_) * `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_) -* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_) * `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_) * `Starlette <https://www.starlette.io/graphql/>`_ (`source <https://github.com/encode/starlette>`_) * `FastAPI <https://fastapi.tiangolo.com/advanced/graphql/>`_ (`source <https://github.com/tiangolo/fastapi>`_) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/requirements.txt new/graphene-3.3.0/docs/requirements.txt --- old/graphene-3.2.1/docs/requirements.txt 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/requirements.txt 2023-07-26 08:26:30.000000000 +0200 @@ -1,5 +1,5 @@ # Required library -Sphinx==1.5.3 -sphinx-autobuild==0.7.1 +Sphinx==6.1.3 +sphinx-autobuild==2021.3.14 # Docs template http://graphene-python.org/sphinx_graphene_theme.zip diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/types/objecttypes.rst new/graphene-3.3.0/docs/types/objecttypes.rst --- old/graphene-3.2.1/docs/types/objecttypes.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/types/objecttypes.rst 2023-07-26 08:26:30.000000000 +0200 @@ -80,6 +80,10 @@ from graphene import ObjectType, String, Field + def get_human(name): + first_name, last_name = name.split() + return Person(first_name, last_name) + class Person(ObjectType): full_name = String() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/docs/types/scalars.rst new/graphene-3.3.0/docs/types/scalars.rst --- old/graphene-3.2.1/docs/types/scalars.rst 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/docs/types/scalars.rst 2023-07-26 08:26:30.000000000 +0200 @@ -271,7 +271,7 @@ @staticmethod def parse_literal(node, _variables=None): - if isinstance(node, ast.StringValue): + if isinstance(node, ast.StringValueNode): return datetime.datetime.strptime( node.value, "%Y-%m-%dT%H:%M:%S.%f") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/__init__.py new/graphene-3.3.0/graphene/__init__.py --- old/graphene-3.2.1/graphene/__init__.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/__init__.py 2023-07-26 08:26:30.000000000 +0200 @@ -46,7 +46,7 @@ from .utils.module_loading import lazy_import from .utils.resolve_only_args import resolve_only_args -VERSION = (3, 2, 1, "final", 0) +VERSION = (3, 3, 0, "final", 0) __version__ = get_version(VERSION) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/relay/connection.py new/graphene-3.3.0/graphene/relay/connection.py --- old/graphene-3.2.1/graphene/relay/connection.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/relay/connection.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,6 +1,7 @@ import re from collections.abc import Iterable from functools import partial +from typing import Type from graphql_relay import connection_from_array @@ -8,7 +9,34 @@ from ..types.field import Field from ..types.objecttype import ObjectType, ObjectTypeOptions from ..utils.thenables import maybe_thenable -from .node import is_node +from .node import is_node, AbstractNode + + +def get_edge_class( + connection_class: Type["Connection"], + _node: Type[AbstractNode], + base_name: str, + strict_types: bool = False, +): + edge_class = getattr(connection_class, "Edge", None) + + class EdgeBase: + node = Field( + NonNull(_node) if strict_types else _node, + description="The item at the end of the edge", + ) + cursor = String(required=True, description="A cursor for use in pagination") + + class EdgeMeta: + description = f"A Relay edge containing a `{base_name}` and its cursor." + + edge_name = f"{base_name}Edge" + + edge_bases = [edge_class, EdgeBase] if edge_class else [EdgeBase] + if not isinstance(edge_class, ObjectType): + edge_bases = [*edge_bases, ObjectType] + + return type(edge_name, tuple(edge_bases), {"Meta": EdgeMeta}) class PageInfo(ObjectType): @@ -61,8 +89,11 @@ abstract = True @classmethod - def __init_subclass_with_meta__(cls, node=None, name=None, **options): - _meta = ConnectionOptions(cls) + def __init_subclass_with_meta__( + cls, node=None, name=None, strict_types=False, _meta=None, **options + ): + if not _meta: + _meta = ConnectionOptions(cls) assert node, f"You have to provide a node in {cls.__name__}.Meta" assert isinstance(node, NonNull) or issubclass( node, (Scalar, Enum, ObjectType, Interface, Union, NonNull) @@ -72,39 +103,29 @@ if not name: name = f"{base_name}Connection" - edge_class = getattr(cls, "Edge", None) - _node = node + options["name"] = name - class EdgeBase: - node = Field(_node, description="The item at the end of the edge") - cursor = String(required=True, description="A cursor for use in pagination") - - class EdgeMeta: - description = f"A Relay edge containing a `{base_name}` and its cursor." - - edge_name = f"{base_name}Edge" - if edge_class: - edge_bases = (edge_class, EdgeBase, ObjectType) - else: - edge_bases = (EdgeBase, ObjectType) + _meta.node = node - edge = type(edge_name, edge_bases, {"Meta": EdgeMeta}) - cls.Edge = edge + if not _meta.fields: + _meta.fields = {} - options["name"] = name - _meta.node = node - _meta.fields = { - "page_info": Field( + if "page_info" not in _meta.fields: + _meta.fields["page_info"] = Field( PageInfo, name="pageInfo", required=True, description="Pagination data for this connection.", - ), - "edges": Field( - NonNull(List(edge)), + ) + + if "edges" not in _meta.fields: + edge_class = get_edge_class(cls, node, base_name, strict_types) # type: ignore + cls.Edge = edge_class + _meta.fields["edges"] = Field( + NonNull(List(NonNull(edge_class) if strict_types else edge_class)), description="Contains the nodes in this connection.", - ), - } + ) + return super(Connection, cls).__init_subclass_with_meta__( _meta=_meta, **options ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/relay/tests/test_connection.py new/graphene-3.3.0/graphene/relay/tests/test_connection.py --- old/graphene-3.2.1/graphene/relay/tests/test_connection.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/relay/tests/test_connection.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,7 +1,15 @@ +import re + from pytest import raises from ...types import Argument, Field, Int, List, NonNull, ObjectType, Schema, String -from ..connection import Connection, ConnectionField, PageInfo +from ..connection import ( + Connection, + ConnectionField, + PageInfo, + ConnectionOptions, + get_edge_class, +) from ..node import Node @@ -51,6 +59,111 @@ assert list(fields) == ["page_info", "edges", "extra"] +def test_connection_extra_abstract_fields(): + class ConnectionWithNodes(Connection): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, node=None, name=None, **options): + _meta = ConnectionOptions(cls) + + _meta.fields = { + "nodes": Field( + NonNull(List(node)), + description="Contains all the nodes in this connection.", + ), + } + + return super(ConnectionWithNodes, cls).__init_subclass_with_meta__( + node=node, name=name, _meta=_meta, **options + ) + + class MyObjectConnection(ConnectionWithNodes): + class Meta: + node = MyObject + + class Edge: + other = String() + + assert MyObjectConnection._meta.name == "MyObjectConnection" + fields = MyObjectConnection._meta.fields + assert list(fields) == ["nodes", "page_info", "edges"] + edge_field = fields["edges"] + pageinfo_field = fields["page_info"] + nodes_field = fields["nodes"] + + assert isinstance(edge_field, Field) + assert isinstance(edge_field.type, NonNull) + assert isinstance(edge_field.type.of_type, List) + assert edge_field.type.of_type.of_type == MyObjectConnection.Edge + + assert isinstance(pageinfo_field, Field) + assert isinstance(pageinfo_field.type, NonNull) + assert pageinfo_field.type.of_type == PageInfo + + assert isinstance(nodes_field, Field) + assert isinstance(nodes_field.type, NonNull) + assert isinstance(nodes_field.type.of_type, List) + assert nodes_field.type.of_type.of_type == MyObject + + +def test_connection_override_fields(): + class ConnectionWithNodes(Connection): + class Meta: + abstract = True + + @classmethod + def __init_subclass_with_meta__(cls, node=None, name=None, **options): + _meta = ConnectionOptions(cls) + base_name = ( + re.sub("Connection$", "", name or cls.__name__) or node._meta.name + ) + + edge_class = get_edge_class(cls, node, base_name) + + _meta.fields = { + "page_info": Field( + NonNull( + PageInfo, + name="pageInfo", + required=True, + description="Pagination data for this connection.", + ) + ), + "edges": Field( + NonNull(List(NonNull(edge_class))), + description="Contains the nodes in this connection.", + ), + } + + return super(ConnectionWithNodes, cls).__init_subclass_with_meta__( + node=node, name=name, _meta=_meta, **options + ) + + class MyObjectConnection(ConnectionWithNodes): + class Meta: + node = MyObject + + assert MyObjectConnection._meta.name == "MyObjectConnection" + fields = MyObjectConnection._meta.fields + assert list(fields) == ["page_info", "edges"] + edge_field = fields["edges"] + pageinfo_field = fields["page_info"] + + assert isinstance(edge_field, Field) + assert isinstance(edge_field.type, NonNull) + assert isinstance(edge_field.type.of_type, List) + assert isinstance(edge_field.type.of_type.of_type, NonNull) + + assert edge_field.type.of_type.of_type.of_type.__name__ == "MyObjectEdge" + + # This page info is NonNull + assert isinstance(pageinfo_field, Field) + assert isinstance(edge_field.type, NonNull) + assert pageinfo_field.type.of_type == PageInfo + + def test_connection_name(): custom_name = "MyObjectCustomNameConnection" @@ -186,3 +299,20 @@ executed = schema.execute("{ testConnection { edges { cursor } } }") assert not executed.errors assert executed.data == {"testConnection": {"edges": []}} + + +def test_connectionfield_strict_types(): + class MyObjectConnection(Connection): + class Meta: + node = MyObject + strict_types = True + + connection_field = ConnectionField(MyObjectConnection) + edges_field_type = connection_field.type._meta.fields["edges"].type + assert isinstance(edges_field_type, NonNull) + + edges_list_element_type = edges_field_type.of_type.of_type + assert isinstance(edges_list_element_type, NonNull) + + node_field = edges_list_element_type.of_type._meta.fields["node"] + assert isinstance(node_field.type, NonNull) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/tests/issues/test_1293.py new/graphene-3.3.0/graphene/tests/issues/test_1293.py --- old/graphene-3.2.1/graphene/tests/issues/test_1293.py 1970-01-01 01:00:00.000000000 +0100 +++ new/graphene-3.3.0/graphene/tests/issues/test_1293.py 2023-07-26 08:26:30.000000000 +0200 @@ -0,0 +1,41 @@ +# https://github.com/graphql-python/graphene/issues/1293 + +import datetime + +import graphene +from graphql.utilities import print_schema + + +class Filters(graphene.InputObjectType): + datetime_after = graphene.DateTime( + required=False, + default_value=datetime.datetime.utcfromtimestamp(1434549820776 / 1000), + ) + datetime_before = graphene.DateTime( + required=False, + default_value=datetime.datetime.utcfromtimestamp(1444549820776 / 1000), + ) + + +class SetDatetime(graphene.Mutation): + class Arguments: + filters = Filters(required=True) + + ok = graphene.Boolean() + + def mutate(root, info, filters): + return SetDatetime(ok=True) + + +class Query(graphene.ObjectType): + goodbye = graphene.String() + + +class Mutations(graphene.ObjectType): + set_datetime = SetDatetime.Field() + + +def test_schema_printable_with_default_datetime_value(): + schema = graphene.Schema(query=Query, mutation=Mutations) + schema_str = print_schema(schema.graphql_schema) + assert schema_str, "empty schema printed" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/tests/issues/test_881.py new/graphene-3.3.0/graphene/tests/issues/test_881.py --- old/graphene-3.2.1/graphene/tests/issues/test_881.py 1970-01-01 01:00:00.000000000 +0100 +++ new/graphene-3.3.0/graphene/tests/issues/test_881.py 2023-07-26 08:26:30.000000000 +0200 @@ -0,0 +1,27 @@ +import pickle + +from ...types.enum import Enum + + +class PickleEnum(Enum): + # is defined outside of test because pickle unable to dump class inside ot pytest function + A = "a" + B = 1 + + +def test_enums_pickling(): + a = PickleEnum.A + pickled = pickle.dumps(a) + restored = pickle.loads(pickled) + assert type(a) is type(restored) + assert a == restored + assert a.value == restored.value + assert a.name == restored.name + + b = PickleEnum.B + pickled = pickle.dumps(b) + restored = pickle.loads(pickled) + assert type(a) is type(restored) + assert b == restored + assert b.value == restored.value + assert b.name == restored.name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/enum.py new/graphene-3.3.0/graphene/types/enum.py --- old/graphene-3.2.1/graphene/types/enum.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/enum.py 2023-07-26 08:26:30.000000000 +0200 @@ -31,9 +31,11 @@ # with the enum values. enum_members.pop("Meta", None) enum = PyEnum(cls.__name__, enum_members) - return SubclassWithMeta_Meta.__new__( + obj = SubclassWithMeta_Meta.__new__( cls, name_, bases, dict(classdict, __enum__=enum), **options ) + globals()[name_] = obj.__enum__ + return obj def get(cls, value): return cls._meta.enum(value) @@ -63,7 +65,7 @@ cls, enum, name=None, description=None, deprecation_reason=None ): # noqa: N805 name = name or enum.__name__ - description = description or enum.__doc__ + description = description or enum.__doc__ or "An enumeration." meta_dict = { "enum": enum, "description": description, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/inputobjecttype.py new/graphene-3.3.0/graphene/types/inputobjecttype.py --- old/graphene-3.2.1/graphene/types/inputobjecttype.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/inputobjecttype.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,11 +1,12 @@ +from typing import TYPE_CHECKING + from .base import BaseOptions, BaseType from .inputfield import InputField from .unmountedtype import UnmountedType from .utils import yank_fields_from_attrs -# For static type checking with Mypy -MYPY = False -if MYPY: +# For static type checking with type checker +if TYPE_CHECKING: from typing import Dict, Callable # NOQA @@ -14,6 +15,31 @@ container = None # type: InputObjectTypeContainer +# Currently in Graphene, we get a `None` whenever we access an (optional) field that was not set in an InputObjectType +# using the InputObjectType.<attribute> dot access syntax. This is ambiguous, because in this current (Graphene +# historical) arrangement, we cannot distinguish between a field not being set and a field being set to None. +# At the same time, we shouldn't break existing code that expects a `None` when accessing a field that was not set. +_INPUT_OBJECT_TYPE_DEFAULT_VALUE = None + +# To mitigate this, we provide the function `set_input_object_type_default_value` to allow users to change the default +# value returned in non-specified fields in InputObjectType to another meaningful sentinel value (e.g. Undefined) +# if they want to. This way, we can keep code that expects a `None` working while we figure out a better solution (or +# a well-documented breaking change) for this issue. + + +def set_input_object_type_default_value(default_value): + """ + Change the sentinel value returned by non-specified fields in an InputObjectType + Useful to differentiate between a field not being set and a field being set to None by using a sentinel value + (e.g. Undefined is a good sentinel value for this purpose) + + This function should be called at the beginning of the app or in some other place where it is guaranteed to + be called before any InputObjectType is defined. + """ + global _INPUT_OBJECT_TYPE_DEFAULT_VALUE + _INPUT_OBJECT_TYPE_DEFAULT_VALUE = default_value + + class InputObjectTypeContainer(dict, BaseType): # type: ignore class Meta: abstract = True @@ -21,7 +47,7 @@ def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) for key in self._meta.fields: - setattr(self, key, self.get(key, None)) + setattr(self, key, self.get(key, _INPUT_OBJECT_TYPE_DEFAULT_VALUE)) def __init_subclass__(cls, *args, **kwargs): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/interface.py new/graphene-3.3.0/graphene/types/interface.py --- old/graphene-3.2.1/graphene/types/interface.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/interface.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,10 +1,11 @@ +from typing import TYPE_CHECKING + from .base import BaseOptions, BaseType from .field import Field from .utils import yank_fields_from_attrs -# For static type checking with Mypy -MYPY = False -if MYPY: +# For static type checking with type checker +if TYPE_CHECKING: from typing import Dict, Iterable, Type # NOQA diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/mutation.py new/graphene-3.3.0/graphene/types/mutation.py --- old/graphene-3.2.1/graphene/types/mutation.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/mutation.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING + from ..utils.deprecated import warn_deprecation from ..utils.get_unbound_function import get_unbound_function from ..utils.props import props @@ -6,9 +8,8 @@ from .utils import yank_fields_from_attrs from .interface import Interface -# For static type checking with Mypy -MYPY = False -if MYPY: +# For static type checking with type checker +if TYPE_CHECKING: from .argument import Argument # NOQA from typing import Dict, Type, Callable, Iterable # NOQA diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/objecttype.py new/graphene-3.3.0/graphene/types/objecttype.py --- old/graphene-3.2.1/graphene/types/objecttype.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/objecttype.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING + from .base import BaseOptions, BaseType, BaseTypeMeta from .field import Field from .interface import Interface @@ -7,9 +9,8 @@ from dataclasses import make_dataclass, field except ImportError: from ..pyutils.dataclasses import make_dataclass, field # type: ignore -# For static type checking with Mypy -MYPY = False -if MYPY: +# For static type checking with type checker +if TYPE_CHECKING: from typing import Dict, Iterable, Type # NOQA diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/tests/conftest.py new/graphene-3.3.0/graphene/types/tests/conftest.py --- old/graphene-3.2.1/graphene/types/tests/conftest.py 1970-01-01 01:00:00.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/tests/conftest.py 2023-07-26 08:26:30.000000000 +0200 @@ -0,0 +1,12 @@ +import pytest +from graphql import Undefined + +from graphene.types.inputobjecttype import set_input_object_type_default_value + + +@pytest.fixture() +def set_default_input_object_type_to_undefined(): + """This fixture is used to change the default value of optional inputs in InputObjectTypes for specific tests""" + set_input_object_type_default_value(Undefined) + yield + set_input_object_type_default_value(None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/tests/test_enum.py new/graphene-3.3.0/graphene/types/tests/test_enum.py --- old/graphene-3.2.1/graphene/types/tests/test_enum.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/tests/test_enum.py 2023-07-26 08:26:30.000000000 +0200 @@ -65,6 +65,21 @@ assert RGB.BLUE +def test_enum_custom_description_in_constructor(): + description = "An enumeration, but with a custom description" + RGB = Enum( + "RGB", + "RED,GREEN,BLUE", + description=description, + ) + assert RGB._meta.description == description + + +def test_enum_from_python3_enum_uses_default_builtin_doc(): + RGB = Enum("RGB", "RED,GREEN,BLUE") + assert RGB._meta.description == "An enumeration." + + def test_enum_from_builtin_enum_accepts_lambda_description(): def custom_description(value): if not value: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/tests/test_inputobjecttype.py new/graphene-3.3.0/graphene/types/tests/test_inputobjecttype.py --- old/graphene-3.2.1/graphene/types/tests/test_inputobjecttype.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/tests/test_inputobjecttype.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,3 +1,5 @@ +from graphql import Undefined + from ..argument import Argument from ..field import Field from ..inputfield import InputField @@ -6,6 +8,7 @@ from ..scalars import Boolean, String from ..schema import Schema from ..unmountedtype import UnmountedType +from ... import NonNull class MyType: @@ -136,3 +139,31 @@ assert not result.errors assert result.data == {"isChild": True} + + +def test_inputobjecttype_default_input_as_undefined( + set_default_input_object_type_to_undefined, +): + class TestUndefinedInput(InputObjectType): + required_field = String(required=True) + optional_field = String() + + class Query(ObjectType): + undefined_optionals_work = Field(NonNull(Boolean), input=TestUndefinedInput()) + + def resolve_undefined_optionals_work(self, info, input: TestUndefinedInput): + # Confirm that optional_field comes as Undefined + return ( + input.required_field == "required" and input.optional_field is Undefined + ) + + schema = Schema(query=Query) + result = schema.execute( + """query basequery { + undefinedOptionalsWork(input: {requiredField: "required"}) + } + """ + ) + + assert not result.errors + assert result.data == {"undefinedOptionalsWork": True} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/tests/test_type_map.py new/graphene-3.3.0/graphene/types/tests/test_type_map.py --- old/graphene-3.2.1/graphene/types/tests/test_type_map.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/tests/test_type_map.py 2023-07-26 08:26:30.000000000 +0200 @@ -20,8 +20,8 @@ from ..interface import Interface from ..objecttype import ObjectType from ..scalars import Int, String -from ..structures import List, NonNull from ..schema import Schema +from ..structures import List, NonNull def create_type_map(types, auto_camelcase=True): @@ -227,6 +227,18 @@ assert foo_field.description == "Field description" +def test_inputobject_undefined(set_default_input_object_type_to_undefined): + class OtherObjectType(InputObjectType): + optional_field = String() + + type_map = create_type_map([OtherObjectType]) + assert "OtherObjectType" in type_map + graphql_type = type_map["OtherObjectType"] + + container = graphql_type.out_type({}) + assert container.optional_field is Undefined + + def test_objecttype_camelcase(): class MyObjectType(ObjectType): """Description""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/types/union.py new/graphene-3.3.0/graphene/types/union.py --- old/graphene-3.2.1/graphene/types/union.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/types/union.py 2023-07-26 08:26:30.000000000 +0200 @@ -1,9 +1,10 @@ +from typing import TYPE_CHECKING + from .base import BaseOptions, BaseType from .unmountedtype import UnmountedType -# For static type checking with Mypy -MYPY = False -if MYPY: +# For static type checking with type checker +if TYPE_CHECKING: from .objecttype import ObjectType # NOQA from typing import Iterable, Type # NOQA diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/graphene/validation/depth_limit.py new/graphene-3.3.0/graphene/validation/depth_limit.py --- old/graphene-3.2.1/graphene/validation/depth_limit.py 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/graphene/validation/depth_limit.py 2023-07-26 08:26:30.000000000 +0200 @@ -30,7 +30,7 @@ except ImportError: # backwards compatibility for v3.6 from typing import Pattern -from typing import Callable, Dict, List, Optional, Union +from typing import Callable, Dict, List, Optional, Union, Tuple from graphql import GraphQLError from graphql.validation import ValidationContext, ValidationRule @@ -82,7 +82,7 @@ def get_fragments( - definitions: List[DefinitionNode], + definitions: Tuple[DefinitionNode, ...], ) -> Dict[str, FragmentDefinitionNode]: fragments = {} for definition in definitions: @@ -94,7 +94,7 @@ # This will actually get both queries and mutations. # We can basically treat those the same def get_queries_and_mutations( - definitions: List[DefinitionNode], + definitions: Tuple[DefinitionNode, ...], ) -> Dict[str, OperationDefinitionNode]: operations = {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/graphene-3.2.1/tox.ini new/graphene-3.3.0/tox.ini --- old/graphene-3.2.1/tox.ini 2022-12-11 21:05:25.000000000 +0100 +++ new/graphene-3.3.0/tox.ini 2023-07-26 08:26:30.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist = py3{6,7,8,9,10}, mypy, pre-commit +envlist = py3{7,8,9,10,11}, mypy, pre-commit skipsdist = true [testenv] @@ -8,7 +8,7 @@ setenv = PYTHONPATH = .:{envdir} commands = - py{36,37,38,39,310}: pytest --cov=graphene graphene --cov-report=term --cov-report=xml examples {posargs} + py{37,38,39,310,311}: pytest --cov=graphene graphene --cov-report=term --cov-report=xml examples {posargs} [testenv:pre-commit] basepython = python3.10