This commit is contained in:
Laurent 2022-12-27 11:15:20 +03:00 committed by GitHub
commit 9b80a1dfa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 752 additions and 21 deletions

View File

@ -1,5 +1,6 @@
# Required library
Sphinx==1.5.3
jinja2<3.1.0
sphinx-autobuild==0.7.1
# Docs template
http://graphene-python.org/sphinx_graphene_theme.zip

View File

@ -92,3 +92,153 @@ To disable this behavior, set the ``auto_camelcase`` to ``False`` upon schema in
query=MyRootQuery,
auto_camelcase=False,
)
.. _SchemaTypeNamePrefix:
Type name prefix
--------------------------
You can specify a prefix for all type names in the schema by setting the ``type_name_prefix`` argument upon schema instantiation:
.. code:: python
my_schema = Schema(
query=MyRootQuery,
mutation=MyRootMutation,
subscription=MyRootSubscription
type_name_prefix='MyPrefix',
)
This is useful in a micro-services architecture to prepend the service name to all types and avoid conflicts for example.
The prefix will be added to the name of:
* Query / Mutation / Subscription
* Scalar
* ObjectType
* InputType
* Enum
* Interface
* Union
While fields and arguments name will be left untouched.
More specifically, the following schema:
.. code::
type Query {
inner: MyType
}
type MyType {
field: String
myUnion: MyUnion
myBarType: MyBarType
myFooType: MyFooType
}
union MyUnion = MyBarType | MyFooType
type MyBarType {
field(input: MyInputObjectType): String
myInterface: MyInterface
}
input MyInputObjectType {
field: String
}
interface MyInterface {
field: String
}
type MyFooType {
field: String
myEnum: MyEnum
}
scalar MyScalar
enum MyEnum {
FOO
BAR
}
type Mutation {
createUser(name: String): CreateUser
}
type CreateUser {
name: String
}
type Subscription {
countToTen: Int
}
Will be transformed to:
.. code::
type Query {
myPrefixInner: MyPrefixMyType
}
type MyPrefixMyType {
field: String
myUnion: MyPrefixMyUnion
myBarType: MyPrefixMyBarType
myFooType: MyPrefixMyFooType
}
union MyPrefixMyUnion = MyPrefixMyBarType | MyPrefixMyFooType
type MyPrefixMyBarType {
field(input: MyPrefixMyInputObjectType): String
myInterface: MyPrefixMyInterface
}
input MyPrefixMyInputObjectType {
field: String
}
interface MyPrefixMyInterface {
field: String
}
type MyPrefixMyFooType {
field: String
myEnum: MyPrefixMyEnum
}
scalar MyPrefixMyScalar
enum MyPrefixMyEnum {
FOO
BAR
}
type Mutation {
myPrefixCreateUser(name: String): MyPrefixCreateUser
}
type MyPrefixCreateUser {
name: String
}
type Subscription {
myPrefixCountToTen: Int
}
You can override the prefix for a specific type by setting the ``type_name_prefix`` property on the ``Meta`` class:
.. code:: python
from graphene import ObjectType
class MyGraphQlType(ObjectType):
class Meta:
type_name_prefix = ''
This is useful when defining external types in a federated schema for example.

View File

@ -36,13 +36,14 @@ class BaseType(SubclassWithMeta):
@classmethod
def __init_subclass_with_meta__(
cls, name=None, description=None, _meta=None, **_kwargs
cls, name=None, description=None, type_name_prefix=None, _meta=None, **_kwargs
):
assert "_meta" not in cls.__dict__, "Can't assign meta directly"
if not _meta:
return
_meta.name = name or cls.__name__
_meta.description = description or trim_docstring(cls.__doc__)
_meta.type_name_prefix = type_name_prefix
_meta.freeze()
cls._meta = _meta
super(BaseType, cls).__init_subclass_with_meta__()

View File

@ -50,7 +50,7 @@ from .resolver import get_default_resolver
from .scalars import ID, Boolean, Float, Int, Scalar, String
from .structures import List, NonNull
from .union import Union
from .utils import get_field_as
from .utils import get_field_as, get_type_name
introspection_query = get_introspection_query()
IntrospectionSchema = introspection_types["__Schema"]
@ -92,6 +92,7 @@ class TypeMap(dict):
subscription=None,
types=None,
auto_camelcase=True,
type_name_prefix=None,
):
assert_valid_root_type(query)
assert_valid_root_type(mutation)
@ -102,9 +103,18 @@ class TypeMap(dict):
assert is_graphene_type(type_)
self.auto_camelcase = auto_camelcase
self.type_name_prefix = type_name_prefix
create_graphql_type = self.add_type
self.root_type_names = []
if query:
self.root_type_names.append(query._meta.name)
if mutation:
self.root_type_names.append(mutation._meta.name)
if subscription:
self.root_type_names.append(subscription._meta.name)
self.query = create_graphql_type(query) if query else None
self.mutation = create_graphql_type(mutation) if mutation else None
self.subscription = create_graphql_type(subscription) if subscription else None
@ -132,9 +142,9 @@ class TypeMap(dict):
elif issubclass(graphene_type, Interface):
graphql_type = self.create_interface(graphene_type)
elif issubclass(graphene_type, Scalar):
graphql_type = self.create_scalar(graphene_type)
graphql_type = self.create_scalar(graphene_type, self.type_name_prefix)
elif issubclass(graphene_type, Enum):
graphql_type = self.create_enum(graphene_type)
graphql_type = self.create_enum(graphene_type, self.type_name_prefix)
elif issubclass(graphene_type, Union):
graphql_type = self.construct_union(graphene_type)
else:
@ -143,7 +153,10 @@ class TypeMap(dict):
return graphql_type
@staticmethod
def create_scalar(graphene_type):
def create_scalar(
graphene_type,
type_name_prefix=None,
):
# We have a mapping to the original GraphQL types
# so there are no collisions.
_scalars = {
@ -158,7 +171,7 @@ class TypeMap(dict):
return GrapheneScalarType(
graphene_type=graphene_type,
name=graphene_type._meta.name,
name=get_type_name(graphene_type, type_name_prefix),
description=graphene_type._meta.description,
serialize=getattr(graphene_type, "serialize", None),
parse_value=getattr(graphene_type, "parse_value", None),
@ -166,7 +179,7 @@ class TypeMap(dict):
)
@staticmethod
def create_enum(graphene_type):
def create_enum(graphene_type, type_name_prefix=None):
values = {}
for name, value in graphene_type._meta.enum.__members__.items():
description = getattr(value, "description", None)
@ -200,7 +213,7 @@ class TypeMap(dict):
return GrapheneEnumType(
graphene_type=graphene_type,
values=values,
name=graphene_type._meta.name,
name=get_type_name(graphene_type, type_name_prefix),
description=type_description,
)
@ -222,9 +235,14 @@ class TypeMap(dict):
else:
is_type_of = graphene_type.is_type_of
if graphene_type._meta.name in self.root_type_names:
name = graphene_type._meta.name
else:
name = self.get_type_name(graphene_type)
return GrapheneObjectType(
graphene_type=graphene_type,
name=graphene_type._meta.name,
name=name,
description=graphene_type._meta.description,
fields=partial(self.create_fields_for_type, graphene_type),
is_type_of=is_type_of,
@ -250,7 +268,7 @@ class TypeMap(dict):
return GrapheneInterfaceType(
graphene_type=graphene_type,
name=graphene_type._meta.name,
name=self.get_type_name(graphene_type),
description=graphene_type._meta.description,
fields=partial(self.create_fields_for_type, graphene_type),
interfaces=interfaces,
@ -260,7 +278,7 @@ class TypeMap(dict):
def create_inputobjecttype(self, graphene_type):
return GrapheneInputObjectType(
graphene_type=graphene_type,
name=graphene_type._meta.name,
name=self.get_type_name(graphene_type),
description=graphene_type._meta.description,
out_type=graphene_type._meta.container,
fields=partial(
@ -289,7 +307,7 @@ class TypeMap(dict):
return GrapheneUnionType(
graphene_type=graphene_type,
name=graphene_type._meta.name,
name=self.get_type_name(graphene_type),
description=graphene_type._meta.description,
types=types,
resolve_type=resolve_type,
@ -366,7 +384,10 @@ class TypeMap(dict):
deprecation_reason=field.deprecation_reason,
description=field.description,
)
field_name = field.name or self.get_name(name)
if field.name:
field_name = field.name
else:
field_name = self.get_field_name(graphene_type, name)
fields[field_name] = _field
return fields
@ -400,6 +421,26 @@ class TypeMap(dict):
return_type = self[type_name]
return default_type_resolver(root, info, return_type)
def get_type_name(self, graphene_type):
return get_type_name(graphene_type, self.type_name_prefix)
def get_field_name(self, graphene_type, name):
if graphene_type._meta.name in self.root_type_names:
# We only add the prefix to the root types and types defined prefixes take precedence
# over schema defined prefix.
type_name_prefix = (
graphene_type._meta.type_name_prefix
if graphene_type._meta.type_name_prefix is not None
else self.type_name_prefix
)
if type_name_prefix:
if self.auto_camelcase:
return to_camel_case(
type_name_prefix[0].lower() + type_name_prefix[1:] + "_" + name
)
return type_name_prefix + name
return self.get_name(name)
class Schema:
"""Schema Definition.
@ -430,12 +471,18 @@ class Schema:
types=None,
directives=None,
auto_camelcase=True,
type_name_prefix=None,
):
self.query = query
self.mutation = mutation
self.subscription = subscription
type_map = TypeMap(
query, mutation, subscription, types, auto_camelcase=auto_camelcase
query,
mutation,
subscription,
types,
auto_camelcase=auto_camelcase,
type_name_prefix=type_name_prefix,
)
self.graphql_schema = GraphQLSchema(
type_map.query,

View File

@ -218,3 +218,48 @@ def test_mutation_as_subclass():
)
assert not result.errors
assert result.data == {"createUserWithPlanet": {"name": "Peter", "planet": "earth"}}
def test_type_name_prefix():
class BaseCreateUser(Mutation):
class Arguments:
name = String()
name = String()
def mutate(self, info, **args):
return args
class CreateUserWithPlanet(BaseCreateUser):
class Arguments(BaseCreateUser.Arguments):
planet = String()
planet = String()
def mutate(self, info, **args):
return CreateUserWithPlanet(**args)
class MyMutation(ObjectType):
create_user_with_planet = CreateUserWithPlanet.Field()
class Query(ObjectType):
a = String()
schema = Schema(
query=Query,
mutation=MyMutation,
type_name_prefix="MyPrefix",
)
result = schema.execute(
""" mutation mymutation {
myPrefixCreateUserWithPlanet(name:"Peter", planet: "earth") {
name
planet
}
}
"""
)
assert not result.errors
assert result.data == {
"myPrefixCreateUserWithPlanet": {"name": "Peter", "planet": "earth"}
}

View File

@ -497,3 +497,28 @@ def test_default_as_kwarg_to_NonNull():
assert not result.errors
assert result.data == expected
def test_type_name_prefix():
class Cat(ObjectType):
name = String()
class User(ObjectType):
name = String()
cat = Field(Cat)
def resolve_cat(self, *args, **kwargs):
return Cat(name="bar")
class Query(ObjectType):
user = Field(User)
def resolve_user(self, *args, **kwargs):
return User(name="foo")
schema = Schema(query=Query, type_name_prefix="MyPrefix")
expected = {"myPrefixUser": {"name": "foo", "cat": {"name": "bar"}}}
result = schema.execute("{ myPrefixUser { name cat { name } } }")
assert not result.errors
assert result.data == expected

View File

@ -5,17 +5,76 @@ from pytest import raises
from graphql.type import GraphQLObjectType, GraphQLSchema
from ..field import Field
from ..enum import Enum
from ..inputobjecttype import InputObjectType
from ..interface import Interface
from ..mutation import Mutation
from ..objecttype import ObjectType
from ..scalars import String
from ..scalars import Int, String, Scalar
from ..schema import Schema
from ..union import Union
class MyOtherType(ObjectType):
class MyInputObjectType(InputObjectType):
field = String()
class MyScalar(Scalar):
...
class MyEnum(Enum):
FOO = "foo"
BAR = "bar"
class MyInterface(Interface):
field = String()
class MyBarType(ObjectType):
field = String(input=MyInputObjectType())
my_interface = Field(MyInterface)
class MyFooType(ObjectType):
field = String()
my_scalar = MyScalar()
my_enum = MyEnum()
class MyUnion(Union):
class Meta:
types = (MyBarType, MyFooType)
class MyType(ObjectType):
field = String()
my_union = MyUnion()
my_bar_type = Field(MyBarType)
my_foo_type = Field("graphene.types.tests.test_schema.MyFooType")
class Query(ObjectType):
inner = Field(MyOtherType)
inner = Field(MyType)
class CreateUser(Mutation):
class Arguments:
name = String()
name = String()
def mutate(self, info, name):
return CreateUser(name=name)
class Mutation(ObjectType):
create_user = CreateUser.Field()
class Subscription(ObjectType):
count_to_ten = Field(Int)
def test_schema():
@ -31,7 +90,12 @@ def test_schema():
def test_schema_get_type():
schema = Schema(Query)
assert schema.Query == Query
assert schema.MyOtherType == MyOtherType
assert schema.MyType == MyType
assert schema.MyBarType == MyBarType
assert schema.MyFooType == MyFooType
assert schema.MyInputObjectType == MyInputObjectType
assert schema.MyInterface == MyInterface
assert schema.MyEnum == MyEnum
def test_schema_get_type_error():
@ -49,11 +113,42 @@ def test_schema_str():
== dedent(
"""
type Query {
inner: MyOtherType
inner: MyType
}
type MyOtherType {
type MyType {
field: String
myUnion: MyUnion
myBarType: MyBarType
myFooType: MyFooType
}
union MyUnion = MyBarType | MyFooType
type MyBarType {
field(input: MyInputObjectType): String
myInterface: MyInterface
}
input MyInputObjectType {
field: String
}
interface MyInterface {
field: String
}
type MyFooType {
field: String
myScalar: MyScalar
myEnum: MyEnum
}
scalar MyScalar
enum MyEnum {
FOO
BAR
}
"""
).strip()

View File

@ -0,0 +1,352 @@
from textwrap import dedent
from ..field import Field
from ..enum import Enum
from ..inputobjecttype import InputObjectType
from ..interface import Interface
from ..mutation import Mutation as Mutation_
from ..objecttype import ObjectType
from ..scalars import Int, String, Scalar
from ..schema import Schema
from ..union import Union
class MyInputObjectType(InputObjectType):
field = String()
class MyScalar(Scalar):
...
class MyEnum(Enum):
FOO = "foo"
BAR = "bar"
class MyInterface(Interface):
field = String()
class MyBarType(ObjectType):
field = String(input=MyInputObjectType())
my_interface = Field(MyInterface)
my_scalar = MyScalar()
my_enum = MyEnum()
class MyFooType(ObjectType):
field = String()
class MyUnion(Union):
class Meta:
types = (MyBarType, MyFooType)
class MyType(ObjectType):
field = String()
my_union = MyUnion()
my_bar_type = Field(MyBarType)
class Query(ObjectType):
inner = Field(MyType)
class CreateUser(Mutation_):
class Arguments:
name = String()
name = String()
def mutate(self, info, name):
return CreateUser(name=name)
class Mutation(ObjectType):
create_user = CreateUser.Field()
class Subscription(ObjectType):
count_to_ten = Field(Int)
def test_schema_type_name_prefix_camelcase():
schema = Schema(
Query,
Mutation,
Subscription,
auto_camelcase=True,
type_name_prefix="MyPrefix",
)
assert (
str(schema).strip()
== dedent(
"""
type Query {
myPrefixInner: MyPrefixMyType
}
type MyPrefixMyType {
field: String
myUnion: MyPrefixMyUnion
myBarType: MyPrefixMyBarType
}
union MyPrefixMyUnion = MyPrefixMyBarType | MyPrefixMyFooType
type MyPrefixMyBarType {
field(input: MyPrefixMyInputObjectType): String
myInterface: MyPrefixMyInterface
myScalar: MyPrefixMyScalar
myEnum: MyPrefixMyEnum
}
input MyPrefixMyInputObjectType {
field: String
}
interface MyPrefixMyInterface {
field: String
}
scalar MyPrefixMyScalar
enum MyPrefixMyEnum {
FOO
BAR
}
type MyPrefixMyFooType {
field: String
}
type Mutation {
myPrefixCreateUser(name: String): MyPrefixCreateUser
}
type MyPrefixCreateUser {
name: String
}
type Subscription {
myPrefixCountToTen: Int
}
"""
).strip()
)
def test_schema_type_name_prefix_camelcase_disabled():
schema = Schema(
Query,
Mutation,
Subscription,
auto_camelcase=False,
type_name_prefix="MyPrefix",
)
assert (
str(schema).strip()
== dedent(
"""
type Query {
MyPrefixinner: MyPrefixMyType
}
type MyPrefixMyType {
field: String
my_union: MyPrefixMyUnion
my_bar_type: MyPrefixMyBarType
}
union MyPrefixMyUnion = MyPrefixMyBarType | MyPrefixMyFooType
type MyPrefixMyBarType {
field(input: MyPrefixMyInputObjectType): String
my_interface: MyPrefixMyInterface
my_scalar: MyPrefixMyScalar
my_enum: MyPrefixMyEnum
}
input MyPrefixMyInputObjectType {
field: String
}
interface MyPrefixMyInterface {
field: String
}
scalar MyPrefixMyScalar
enum MyPrefixMyEnum {
FOO
BAR
}
type MyPrefixMyFooType {
field: String
}
type Mutation {
MyPrefixcreate_user(name: String): MyPrefixCreateUser
}
type MyPrefixCreateUser {
name: String
}
type Subscription {
MyPrefixcount_to_ten: Int
}
"""
).strip()
)
def test_schema_type_name_prefix_override():
class MyInputObjectType(InputObjectType):
class Meta:
type_name_prefix = "OverridePrefix"
field = String()
class MyScalar(Scalar):
class Meta:
type_name_prefix = ""
class MyEnum(Enum):
class Meta:
type_name_prefix = ""
FOO = "foo"
BAR = "bar"
class MyInterface(Interface):
class Meta:
type_name_prefix = ""
field = String()
class MyBarType(ObjectType):
class Meta:
type_name_prefix = ""
field = String(input=MyInputObjectType())
my_interface = Field(MyInterface)
my_scalar = MyScalar()
my_enum = MyEnum()
class MyFooType(ObjectType):
class Meta:
type_name_prefix = ""
field = String()
class MyUnion(Union):
class Meta:
type_name_prefix = ""
types = (MyBarType, MyFooType)
class MyType(ObjectType):
class Meta:
type_name_prefix = ""
field = String()
my_union = MyUnion()
my_bar_type = Field(MyBarType)
class Query(ObjectType):
class Meta:
type_name_prefix = "OverridePrefix"
inner = Field(MyType)
class CreateUser(Mutation_):
class Meta:
type_name_prefix = ""
class Arguments:
name = String()
name = String()
def mutate(self, info, name):
return CreateUser(name=name)
class Mutation(ObjectType):
class Meta:
type_name_prefix = ""
create_user = CreateUser.Field()
class Subscription(ObjectType):
class Meta:
type_name_prefix = ""
count_to_ten = Field(Int)
schema = Schema(
Query,
Mutation,
Subscription,
auto_camelcase=True,
type_name_prefix="MyPrefix",
)
assert (
str(schema).strip()
== dedent(
"""
type Query {
overridePrefixInner: MyType
}
type MyType {
field: String
myUnion: MyUnion
myBarType: MyBarType
}
union MyUnion = MyBarType | MyFooType
type MyBarType {
field(input: OverridePrefixMyInputObjectType): String
myInterface: MyInterface
myScalar: MyScalar
myEnum: MyEnum
}
input OverridePrefixMyInputObjectType {
field: String
}
interface MyInterface {
field: String
}
scalar MyScalar
enum MyEnum {
FOO
BAR
}
type MyFooType {
field: String
}
type Mutation {
createUser(name: String): CreateUser
}
type CreateUser {
name: String
}
type Subscription {
countToTen: Int
}
"""
).strip()
)

View File

@ -48,3 +48,18 @@ def get_underlying_type(_type):
while hasattr(_type, "of_type"):
_type = _type.of_type
return _type
def get_type_name(graphene_type, type_name_prefix):
type_name_prefix = (
graphene_type._meta.type_name_prefix
if graphene_type._meta.type_name_prefix is not None
else type_name_prefix
)
if type_name_prefix:
return (
type_name_prefix[0].upper()
+ type_name_prefix[1:]
+ graphene_type._meta.name
)
return graphene_type._meta.name

View File

@ -53,7 +53,7 @@ tests_require = [
"snapshottest>=0.6,<1",
"coveralls>=3.3,<4",
"mock>=4,<5",
"pytz==2022.1",
"pytz==2022.5",
"iso8601>=1,<2",
]