Skip to content

Commit 9228af4

Browse files
committed
Add @specifiedBy directive
Replicates graphql/graphql-js@9d4b433
1 parent c409614 commit 9228af4

19 files changed

+269
-33
lines changed

src/graphql/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
GraphQLIncludeDirective,
7171
GraphQLSkipDirective,
7272
GraphQLDeprecatedDirective,
73+
GraphQLSpecifiedByDirective,
7374
# "Enum" of Type Kinds
7475
TypeKind,
7576
# Constant Deprecation Reason
@@ -437,6 +438,7 @@
437438
"GraphQLIncludeDirective",
438439
"GraphQLSkipDirective",
439440
"GraphQLDeprecatedDirective",
441+
"GraphQLSpecifiedByDirective",
440442
"TypeKind",
441443
"DEFAULT_DEPRECATION_REASON",
442444
"introspection_types",

src/graphql/type/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
GraphQLIncludeDirective,
109109
GraphQLSkipDirective,
110110
GraphQLDeprecatedDirective,
111+
GraphQLSpecifiedByDirective,
111112
# Constant Deprecation Reason
112113
DEFAULT_DEPRECATION_REASON,
113114
)
@@ -227,6 +228,7 @@
227228
"GraphQLIncludeDirective",
228229
"GraphQLSkipDirective",
229230
"GraphQLDeprecatedDirective",
231+
"GraphQLSpecifiedByDirective",
230232
"DEFAULT_DEPRECATION_REASON",
231233
"is_specified_scalar_type",
232234
"specified_scalar_types",

src/graphql/type/definition.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ def serialize_odd(value):
318318
319319
"""
320320

321+
specified_by_url: Optional[str]
321322
ast_node: Optional[ScalarTypeDefinitionNode]
322323
extension_ast_nodes: Optional[FrozenList[ScalarTypeExtensionNode]]
323324

@@ -328,6 +329,7 @@ def __init__(
328329
parse_value: Optional[GraphQLScalarValueParser] = None,
329330
parse_literal: Optional[GraphQLScalarLiteralParser] = None,
330331
description: Optional[str] = None,
332+
specified_by_url: Optional[str] = None,
331333
extensions: Optional[Dict[str, Any]] = None,
332334
ast_node: Optional[ScalarTypeDefinitionNode] = None,
333335
extension_ast_nodes: Optional[Collection[ScalarTypeExtensionNode]] = None,
@@ -339,6 +341,11 @@ def __init__(
339341
ast_node=ast_node,
340342
extension_ast_nodes=extension_ast_nodes,
341343
)
344+
if specified_by_url is not None and not isinstance(specified_by_url, str):
345+
raise TypeError(
346+
f"{name} must provide 'specified_by_url' as a string,"
347+
f" but got: {inspect(specified_by_url)}."
348+
)
342349
if serialize is not None and not callable(serialize):
343350
raise TypeError(
344351
f"{name} must provide 'serialize' as a function."
@@ -369,6 +376,7 @@ def __init__(
369376
self.parse_value = parse_value # type: ignore
370377
if parse_literal is not None:
371378
self.parse_literal = parse_literal # type: ignore
379+
self.specified_by_url = specified_by_url
372380

373381
def __repr__(self):
374382
return f"<{self.__class__.__name__} {self.name!r}>"
@@ -417,6 +425,7 @@ def to_kwargs(self) -> Dict[str, Any]:
417425
if getattr(self.parse_literal, "__func__", None)
418426
is GraphQLScalarType.parse_literal
419427
else self.parse_literal,
428+
specified_by_url=self.specified_by_url,
420429
)
421430

422431

src/graphql/type/directives.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"GraphQLIncludeDirective",
1515
"GraphQLSkipDirective",
1616
"GraphQLDeprecatedDirective",
17+
"GraphQLSpecifiedByDirective",
1718
"DirectiveLocation",
1819
"DEFAULT_DEPRECATION_REASON",
1920
]
@@ -196,9 +197,27 @@ def assert_directive(directive: Any) -> GraphQLDirective:
196197
description="Marks an element of a GraphQL schema as no longer supported.",
197198
)
198199

200+
# Used to provide a URL for specifying the behaviour of custom scalar definitions:
201+
GraphQLSpecifiedByDirective = GraphQLDirective(
202+
name="specifiedBy",
203+
locations=[DirectiveLocation.SCALAR],
204+
args={
205+
"url": GraphQLArgument(
206+
GraphQLNonNull(GraphQLString),
207+
description="The URL that specifies the behaviour of this scalar.",
208+
)
209+
},
210+
description="Exposes a URL that specifies the behaviour of this scalar.",
211+
)
212+
199213

200214
specified_directives: FrozenList[GraphQLDirective] = FrozenList(
201-
[GraphQLIncludeDirective, GraphQLSkipDirective, GraphQLDeprecatedDirective]
215+
[
216+
GraphQLIncludeDirective,
217+
GraphQLSkipDirective,
218+
GraphQLDeprecatedDirective,
219+
GraphQLSpecifiedByDirective,
220+
]
202221
)
203222
specified_directives.__doc__ = """The full list of specified directives."""
204223

src/graphql/type/introspection.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@
199199
" There are many kinds of types in GraphQL as represented"
200200
" by the `__TypeKind` enum.\n\nDepending on the kind of a"
201201
" type, certain fields describe information about that type."
202-
" Scalar types provide no information beyond a name and"
203-
" description, while Enum types provide their values."
202+
" Scalar types provide no information beyond a name, description"
203+
" and optional `specifiedByUrl`, while Enum types provide their values."
204204
" Object and Interface types provide the fields they describe."
205205
" Abstract types, Union and Interface, provide the Object"
206206
" types possible at runtime. List and NonNull types compose"
@@ -213,6 +213,9 @@
213213
"description": GraphQLField(
214214
GraphQLString, resolve=TypeFieldResolvers.description
215215
),
216+
"specifiedByUrl": GraphQLField(
217+
GraphQLString, resolve=TypeFieldResolvers.specified_by_url
218+
),
216219
"fields": GraphQLField(
217220
GraphQLList(GraphQLNonNull(__Field)),
218221
args={
@@ -278,6 +281,10 @@ def name(type_, _info):
278281
def description(type_, _info):
279282
return getattr(type_, "description", None)
280283

284+
@staticmethod
285+
def specified_by_url(type_, _info):
286+
return getattr(type_, "specified_by_url", None)
287+
281288
# noinspection PyPep8Naming
282289
@staticmethod
283290
def fields(type_, _info, includeDeprecated=False):

src/graphql/utilities/build_ast_schema.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
GraphQLIncludeDirective,
1111
GraphQLSchema,
1212
GraphQLSkipDirective,
13+
GraphQLSpecifiedByDirective,
1314
)
1415
from .extend_schema import extend_schema_impl
1516

@@ -71,6 +72,8 @@ def build_ast_schema(
7172
directives.append(GraphQLIncludeDirective)
7273
if not any(directive.name == "deprecated" for directive in directives):
7374
directives.append(GraphQLDeprecatedDirective)
75+
if not any(directive.name == "specifiedBy" for directive in directives):
76+
directives.append(GraphQLSpecifiedByDirective)
7477

7578
return GraphQLSchema(**schema_kwargs)
7679

src/graphql/utilities/build_client_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def build_scalar_def(scalar_introspection: Dict) -> GraphQLScalarType:
115115
return GraphQLScalarType(
116116
name=scalar_introspection["name"],
117117
description=scalar_introspection.get("description"),
118+
specified_by_url=scalar_introspection.get("specifiedByUrl"),
118119
)
119120

120121
def build_implementations_list(

src/graphql/utilities/extend_schema.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
ObjectTypeExtensionNode,
3333
OperationType,
3434
ScalarTypeDefinitionNode,
35+
ScalarTypeExtensionNode,
3536
SchemaExtensionNode,
3637
SchemaDefinitionNode,
3738
TypeDefinitionNode,
@@ -64,6 +65,7 @@
6465
GraphQLOutputType,
6566
GraphQLScalarType,
6667
GraphQLSchema,
68+
GraphQLSpecifiedByDirective,
6769
GraphQLType,
6870
GraphQLUnionType,
6971
assert_schema,
@@ -269,9 +271,14 @@ def extend_scalar_type(type_: GraphQLScalarType) -> GraphQLScalarType:
269271
kwargs = type_.to_kwargs()
270272
extensions = type_extensions_map[kwargs["name"]]
271273

274+
specified_by_url = kwargs["specified_by_url"]
275+
for extension_node in extensions:
276+
specified_by_url = get_specified_by_url(extension_node) or specified_by_url
277+
272278
return GraphQLScalarType(
273279
**{
274280
**kwargs,
281+
"specified_by_url": specified_by_url,
275282
"extension_ast_nodes": kwargs["extension_ast_nodes"] + extensions,
276283
}
277284
)
@@ -574,6 +581,7 @@ def build_scalar_type(ast_node: ScalarTypeDefinitionNode) -> GraphQLScalarType:
574581
return GraphQLScalarType(
575582
name=ast_node.name.value,
576583
description=ast_node.description.value if ast_node.description else None,
584+
specified_by_url=get_specified_by_url(ast_node),
577585
ast_node=ast_node,
578586
extension_ast_nodes=extension_nodes,
579587
)
@@ -673,6 +681,16 @@ def get_deprecation_reason(
673681
return deprecated["reason"] if deprecated else None
674682

675683

684+
def get_specified_by_url(
685+
node: Union[ScalarTypeDefinitionNode, ScalarTypeExtensionNode]
686+
) -> Optional[str]:
687+
"""Given a scalar node, return the string value for the specifiedByUrl."""
688+
from ..execution import get_directive_values
689+
690+
specified_by_url = get_directive_values(GraphQLSpecifiedByDirective, node)
691+
return specified_by_url["url"] if specified_by_url else None
692+
693+
676694
def get_description(node: Node) -> Optional[str]:
677695
"""@deprecated: Given an ast node, returns its string description."""
678696
try:

src/graphql/utilities/get_introspection_query.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44

55

66
def get_introspection_query(
7-
descriptions=True, directive_is_repeatable=False, schema_description=False
7+
descriptions=True,
8+
specified_by_url=False,
9+
directive_is_repeatable=False,
10+
schema_description=False,
811
) -> str:
912
"""Get a query for introspection.
1013
11-
Optionally, you can exclude descriptions, include repeatability of directives,
12-
and specify whether to include the schema description as well.
14+
Optionally, you can exclude descriptions, include specification URLs,
15+
include repeatability of directives, and specify whether to include
16+
the schema description as well.
1317
"""
1418
maybe_description = "description" if descriptions else ""
19+
maybe_specified_by_url = "specifiedByUrl" if specified_by_url else ""
1520
maybe_directive_is_repeatable = "isRepeatable" if directive_is_repeatable else ""
1621
maybe_schema_description = maybe_description if schema_description else ""
1722
return dedent(
@@ -41,6 +46,7 @@ def get_introspection_query(
4146
kind
4247
name
4348
{maybe_description}
49+
{maybe_specified_by_url}
4450
fields(includeDeprecated: true) {{
4551
name
4652
{maybe_description}

src/graphql/utilities/introspection_from_schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
def introspection_from_schema(
1515
schema: GraphQLSchema,
1616
descriptions: bool = True,
17+
specified_by_url: bool = False,
1718
directive_is_repeatable: bool = True,
1819
schema_description: bool = True,
1920
) -> IntrospectionSchema:
@@ -27,7 +28,7 @@ def introspection_from_schema(
2728
"""
2829
document = parse(
2930
get_introspection_query(
30-
descriptions, directive_is_repeatable, schema_description
31+
descriptions, specified_by_url, directive_is_repeatable, schema_description
3132
)
3233
)
3334

0 commit comments

Comments
 (0)