Skip to content

Commit 176eabc

Browse files
committed
Add strip_ignored_characters utility function
Replicates graphql/graphql-js@081db43
1 parent cb7aafd commit 176eabc

File tree

7 files changed

+577
-29
lines changed

7 files changed

+577
-29
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ a query language for APIs created by Facebook.
1313
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
1414

1515
The current version 1.0.3 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.2.1. All parts of the API are covered by an extensive test suite of currently 1758
16+
14.2.1. All parts of the API are covered by an extensive test suite of currently 1776
1717
unit tests.
1818

1919

docs/modules/pyutils.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PyUtils
99
.. autofunction:: dedent
1010
.. autoclass:: EventEmitter
1111
.. autoclass:: EventEmitterAsyncIterator
12+
.. autofunction:: inspect
1213
.. autofunction:: is_finite
1314
.. autofunction:: is_integer
1415
.. autofunction:: is_invalid
@@ -17,3 +18,5 @@ PyUtils
1718
.. autofunction:: or_list
1819
.. autofunction:: quoted_or_list
1920
.. autofunction:: suggestion_list
21+
.. autofunction:: ReadOnlyError
22+
.. autofunction:: ReadOnlyList

docs/modules/utilities.rst

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ The GraphQL query recommended for a full schema introspection:
77

88
.. autofunction:: get_introspection_query
99

10-
Gets the target Operation from a Document:
10+
Get the target Operation from a Document:
1111

1212
.. autofunction:: get_operation_ast
1313

14-
Gets the Type for the target Operation AST:
14+
Get the Type for the target Operation AST:
1515

1616
.. autofunction:: get_operation_root_type
1717

@@ -29,7 +29,7 @@ Build a GraphQLSchema from GraphQL Schema language:
2929
.. autofunction:: build_schema
3030
.. autofunction:: get_description
3131

32-
Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST:
32+
Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST:
3333

3434
.. autofunction:: extend_schema
3535

@@ -64,30 +64,35 @@ type system:
6464

6565
.. autoclass:: TypeInfo
6666

67-
Coerces a Python value to a GraphQL type, or produces errors:
67+
Coerce a Python value to a GraphQL type, or produce errors:
6868

6969
.. autofunction:: coerce_value
7070

71-
Concatenates multiple AST together:
71+
Concatenate multiple ASTs together:
7272

7373
.. autofunction:: concat_ast
7474

75-
Separates an AST into an AST per Operation:
75+
Separate an AST into an AST per Operation:
7676

7777
.. autofunction:: separate_operations
7878

79+
Strip characters that are not significant to the validity or execution
80+
of a GraphQL document:
81+
82+
.. autofunction:: strip_ignored_characters
83+
7984
Comparators for types:
8085

8186
.. autofunction:: is_equal_type
8287
.. autofunction:: is_type_sub_type_of
8388
.. autofunction:: do_types_overlap
8489

85-
Asserts that a string is a valid GraphQL name:
90+
Assert that a string is a valid GraphQL name:
8691

8792
.. autofunction:: assert_valid_name
8893
.. autofunction:: is_valid_name_error
8994

90-
Compares two GraphQLSchemas and detects breaking changes:
95+
Compare two GraphQLSchemas and detect breaking changes:
9196

9297
.. autofunction:: find_breaking_changes
9398
.. autofunction:: find_dangerous_changes
@@ -97,6 +102,6 @@ Compares two GraphQLSchemas and detects breaking changes:
97102
.. autoclass:: DangerousChange
98103
.. autoclass:: DangerousChangeType
99104

100-
Report all deprecated usage within a GraphQL document:
105+
Report all deprecated usages within a GraphQL document:
101106

102107
.. autofunction:: find_deprecated_usages

graphql/__init__.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,11 @@
313313
# Produce the GraphQL query recommended for a full schema introspection.
314314
# Accepts optional IntrospectionOptions.
315315
get_introspection_query,
316-
# Gets the target Operation from a Document
316+
# Get the target Operation from a Document.
317317
get_operation_ast,
318-
# Gets the Type for the target Operation AST.
318+
# Get the Type for the target Operation AST.
319319
get_operation_root_type,
320-
# Convert a GraphQLSchema to an IntrospectionQuery
320+
# Convert a GraphQLSchema to an IntrospectionQuery.
321321
introspection_from_schema,
322322
# Build a GraphQLSchema from an introspection result.
323323
build_client_schema,
@@ -327,7 +327,7 @@
327327
build_schema,
328328
# @deprecated: Get the description from a schema AST node.
329329
get_description,
330-
# Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST.
330+
# Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST.
331331
extend_schema,
332332
# Sort a GraphQLSchema.
333333
lexicographic_sort_schema,
@@ -348,21 +348,24 @@
348348
# A helper to use within recursive-descent visitors which need to be aware of the
349349
# GraphQL type system.
350350
TypeInfo,
351-
# Coerces a Python value to a GraphQL type, or produces errors.
351+
# Coerce a Python value to a GraphQL type, or produce errors.
352352
coerce_value,
353-
# Concatenates multiple AST together.
353+
# Concatenates multiple ASTs together.
354354
concat_ast,
355-
# Separates an AST into an AST per Operation.
355+
# Separate an AST into an AST per Operation.
356356
separate_operations,
357+
# Strip characters that are not significant to the validity or execution
358+
# of a GraphQL document.
359+
strip_ignored_characters,
357360
# Comparators for types
358361
is_equal_type,
359362
is_type_sub_type_of,
360363
do_types_overlap,
361-
# Asserts a string is a valid GraphQL name.
364+
# Assert a string is a valid GraphQL name.
362365
assert_valid_name,
363366
# Determine if a string is a valid GraphQL name.
364367
is_valid_name_error,
365-
# Compares two GraphQLSchemas and detects breaking changes.
368+
# Compare two GraphQLSchemas and detect breaking changes.
366369
find_breaking_changes,
367370
find_dangerous_changes,
368371
BreakingChange,
@@ -622,6 +625,7 @@
622625
"coerce_value",
623626
"concat_ast",
624627
"separate_operations",
628+
"strip_ignored_characters",
625629
"is_equal_type",
626630
"is_type_sub_type_of",
627631
"do_types_overlap",

graphql/utilities/__init__.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
# The GraphQL query recommended for a full schema introspection.
88
from .introspection_query import get_introspection_query
99

10-
# Gets the target Operation from a Document
10+
# Get the target Operation from a Document.
1111
from .get_operation_ast import get_operation_ast
1212

13-
# Gets the Type for the target Operation AST.
13+
# Get the Type for the target Operation AST.
1414
from .get_operation_root_type import get_operation_root_type
1515

16-
# Convert a GraphQLSchema to an IntrospectionQuery
16+
# Convert a GraphQLSchema to an IntrospectionQuery.
1717
from .introspection_from_schema import introspection_from_schema
1818

1919
# Build a GraphQLSchema from an introspection result.
@@ -22,7 +22,7 @@
2222
# Build a GraphQLSchema from GraphQL Schema language.
2323
from .build_ast_schema import build_ast_schema, build_schema, get_description
2424

25-
# Extends an existing GraphQLSchema from a parsed GraphQL Schema language AST.
25+
# Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST.
2626
from .extend_schema import extend_schema
2727

2828
# Sort a GraphQLSchema.
@@ -52,22 +52,26 @@
5252
# the GraphQL type system
5353
from .type_info import TypeInfo
5454

55-
# Coerces a Python value to a GraphQL type, or produces errors.
55+
# Coerce a Python value to a GraphQL type, or produce errors.
5656
from .coerce_value import coerce_value
5757

58-
# Concatenates multiple AST together.
58+
# Concatenate multiple ASTs together.
5959
from .concat_ast import concat_ast
6060

61-
# Separates an AST into an AST per Operation.
61+
# Separate an AST into an AST per Operation.
6262
from .separate_operations import separate_operations
6363

64+
# Strip characters that are not significant to the validity or execution
65+
# of a GraphQL document.
66+
from .strip_ignored_characters import strip_ignored_characters
67+
6468
# Comparators for types
6569
from .type_comparators import is_equal_type, is_type_sub_type_of, do_types_overlap
6670

67-
# Asserts that a string is a valid GraphQL name
71+
# Assert that a string is a valid GraphQL name.
6872
from .assert_valid_name import assert_valid_name, is_valid_name_error
6973

70-
# Compares two GraphQLSchemas and detects breaking changes.
74+
# Compare two GraphQLSchemas and detect breaking changes.
7175
from .find_breaking_changes import (
7276
BreakingChange,
7377
BreakingChangeType,
@@ -77,7 +81,7 @@
7781
find_dangerous_changes,
7882
)
7983

80-
# Report all deprecated usage within a GraphQL document.
84+
# Report all deprecated usages within a GraphQL document.
8185
from .find_deprecated_usages import find_deprecated_usages
8286

8387
__all__ = [
@@ -112,6 +116,7 @@
112116
"print_type",
113117
"print_value",
114118
"separate_operations",
119+
"strip_ignored_characters",
115120
"type_from_ast",
116121
"value_from_ast",
117122
"value_from_ast_untyped",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from typing import Union
2+
3+
from ..language import Lexer, Source, TokenKind
4+
from ..language.block_string import (
5+
dedent_block_string_value,
6+
get_block_string_indentation,
7+
)
8+
from ..language.lexer import is_punctuator_token
9+
from ..pyutils import inspect
10+
11+
12+
def strip_ignored_characters(source: Union[str, Source]) -> str:
13+
"""Strip characters that are ignored anyway.
14+
15+
Strips characters that are not significant to the validity or execution
16+
of a GraphQL document:
17+
- UnicodeBOM
18+
- WhiteSpace
19+
- LineTerminator
20+
- Comment
21+
- Comma
22+
- BlockString indentation
23+
24+
Note: It is required to have a delimiter character between neighboring
25+
non-punctuator tokes and this function always uses single space as delimiter.
26+
27+
It is guaranteed that both input and output documents if parsed would result
28+
in the exact same AST except for nodes location.
29+
30+
Warning: It is guaranteed that this function will always produce stable results.
31+
However, it's not guaranteed that it will stay the same between different
32+
releases due to bugfixes or changes in the GraphQL specification.
33+
""" '''
34+
35+
Query example::
36+
37+
query SomeQuery($foo: String!, $bar: String) {
38+
someField(foo: $foo, bar: $bar) {
39+
a
40+
b {
41+
c
42+
d
43+
}
44+
}
45+
}
46+
47+
Becomes::
48+
49+
query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{c d}}}
50+
51+
SDL example:
52+
53+
"""
54+
Type description
55+
"""
56+
type Foo {
57+
"""
58+
Field description
59+
"""
60+
bar: String
61+
}
62+
63+
Becomes::
64+
65+
"""Type description""" type Foo{"""Field description""" bar:String}
66+
'''
67+
source_obj = Source(source) if isinstance(source, str) else source
68+
if not isinstance(source_obj, Source):
69+
raise TypeError(
70+
f"Must provide string or Source. Received: {inspect(source_obj)}"
71+
)
72+
73+
body = source_obj.body
74+
lexer = Lexer(source_obj)
75+
stripped_body = ""
76+
was_last_added_token_non_punctuator = False
77+
while lexer.advance().kind != TokenKind.EOF:
78+
current_token = lexer.token
79+
token_kind = current_token.kind
80+
81+
# Every two non-punctuator tokens should have space between them.
82+
# Also prevent case of non-punctuator token following by spread resulting
83+
# in invalid token (e.g.`1...` is invalid Float token).
84+
is_non_punctuator = not is_punctuator_token(current_token)
85+
if was_last_added_token_non_punctuator:
86+
if is_non_punctuator or current_token.kind == TokenKind.SPREAD:
87+
stripped_body += " "
88+
89+
token_body = body[current_token.start : current_token.end]
90+
if token_kind == TokenKind.BLOCK_STRING:
91+
stripped_body += dedent_block_string(token_body)
92+
else:
93+
stripped_body += token_body
94+
95+
was_last_added_token_non_punctuator = is_non_punctuator
96+
97+
return stripped_body
98+
99+
100+
def dedent_block_string(block_str: str) -> str:
101+
"""Skip leading and trailing triple quotations"""
102+
raw_str = block_str[3:-3]
103+
body = dedent_block_string_value(raw_str)
104+
105+
lines = body.splitlines()
106+
if get_block_string_indentation(lines) > 0:
107+
body = "\n" + body
108+
109+
last_char = body[-1:]
110+
has_trailing_quote = last_char == '"' and body[-4:] != '\\"""'
111+
if has_trailing_quote or last_char == "\\":
112+
body += "\n"
113+
114+
return '"""' + body + '"""'

0 commit comments

Comments
 (0)