graphql-api 1.4.11__tar.gz → 1.4.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphql_api-1.4.11/graphql_api.egg-info → graphql_api-1.4.13}/PKG-INFO +2 -2
- graphql_api-1.4.13/VERSION +1 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/__init__.py +1 -1
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/mapper.py +13 -14
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/reduce.py +57 -15
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/types.py +26 -3
- {graphql_api-1.4.11 → graphql_api-1.4.13/graphql_api.egg-info}/PKG-INFO +2 -2
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_dataclass.py +2 -2
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_filtering.py +60 -20
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_graphql.py +328 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_schema.py +23 -21
- graphql_api-1.4.11/VERSION +0 -1
- {graphql_api-1.4.11 → graphql_api-1.4.13}/LICENSE +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/MANIFEST.in +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/README.md +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/api.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/context.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/dataclass_mapping.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/decorators.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/directives.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/error.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/exception.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/executor.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/federation/__init__.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/federation/directives.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/federation/federation.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/federation/types.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/middleware.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/pydantic.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/relay.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/remote.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/schema.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api/utils.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api.egg-info/SOURCES.txt +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api.egg-info/dependency_links.txt +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api.egg-info/requires.txt +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/graphql_api.egg-info/top_level.txt +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/setup.cfg +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/setup.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/__init__.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_async.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_calculator.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_custom_types.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_directives.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_docstrings.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_error.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_federation.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_federation_example.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_middleware.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_pydantic.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_relay.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_remote.py +0 -0
- {graphql_api-1.4.11 → graphql_api-1.4.13}/tests/test_subscriptions.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphql_api
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.13
|
|
4
4
|
Summary: A framework for building Python GraphQL APIs.
|
|
5
5
|
Home-page: https://gitlab.com/parob/graphql-api
|
|
6
|
-
Download-URL: https://gitlab.com/parob/graphql/-/archive/v1.4.
|
|
6
|
+
Download-URL: https://gitlab.com/parob/graphql/-/archive/v1.4.13/graphql_api-v1.4.13.tar.gz
|
|
7
7
|
Author: Robert Parker
|
|
8
8
|
Author-email: rob@parob.com
|
|
9
9
|
License: MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.4.13
|
|
@@ -5,5 +5,5 @@ from graphql_api.api import GraphQLAPI, GraphQLRootTypeDelegate
|
|
|
5
5
|
from graphql_api.decorators import field, type
|
|
6
6
|
from graphql_api.error import GraphQLError
|
|
7
7
|
from graphql_api.executor import GraphQLExecutor
|
|
8
|
-
from graphql_api.reduce import GraphQLFilter, TagFilter
|
|
8
|
+
from graphql_api.reduce import GraphQLFilter, TagFilter, FilterResponse
|
|
9
9
|
from graphql_api.schema import AppliedDirective
|
|
@@ -60,7 +60,6 @@ from graphql_api.utils import (
|
|
|
60
60
|
has_single_type_union_return,
|
|
61
61
|
to_camel_case,
|
|
62
62
|
to_camel_case_text,
|
|
63
|
-
to_input_value,
|
|
64
63
|
to_snake_case,
|
|
65
64
|
)
|
|
66
65
|
|
|
@@ -406,11 +405,15 @@ class GraphQLTypeMapper:
|
|
|
406
405
|
|
|
407
406
|
def serialize(_self, value) -> Union[str, None, UndefinedType]:
|
|
408
407
|
if value and isinstance(value, collections.abc.Hashable):
|
|
408
|
+
# For both string and regular enums, we need to extract the .value attribute
|
|
409
409
|
if isinstance(value, enum.Enum):
|
|
410
|
-
|
|
410
|
+
lookup_key = value.value
|
|
411
|
+
else:
|
|
412
|
+
# If it's not an enum instance, use the value directly
|
|
413
|
+
lookup_key = value
|
|
411
414
|
|
|
412
415
|
# noinspection PyProtectedMember
|
|
413
|
-
lookup_value = _self._value_lookup.get(
|
|
416
|
+
lookup_value = _self._value_lookup.get(lookup_key)
|
|
414
417
|
if lookup_value:
|
|
415
418
|
return lookup_value
|
|
416
419
|
else:
|
|
@@ -566,16 +569,10 @@ class GraphQLTypeMapper:
|
|
|
566
569
|
# noinspection PyTypeChecker
|
|
567
570
|
input_arg_type = GraphQLNonNull(input_arg_type) # type: ignore
|
|
568
571
|
|
|
572
|
+
# Use the raw Python default for input coercion.
|
|
573
|
+
# Do not convert to GraphQL query literals here.
|
|
569
574
|
default_value = local_default_args.get(key, None)
|
|
570
575
|
|
|
571
|
-
if default_value is not None:
|
|
572
|
-
try:
|
|
573
|
-
default_value = to_input_value(default_value)
|
|
574
|
-
except ValueError as _err:
|
|
575
|
-
raise ValueError(
|
|
576
|
-
f"Unable to map {local_name}.{key}, {_err}."
|
|
577
|
-
)
|
|
578
|
-
|
|
579
576
|
arguments[to_camel_case(key)] = GraphQLInputField(
|
|
580
577
|
type_=input_arg_type, default_value=default_value # type: ignore
|
|
581
578
|
)
|
|
@@ -751,12 +748,14 @@ class GraphQLTypeMapper:
|
|
|
751
748
|
if issubclass(type__, GraphQLType):
|
|
752
749
|
return type__()
|
|
753
750
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
751
|
+
# Check for enum.Enum before scalar types to handle string enums correctly
|
|
752
|
+
# String enums inherit from both str and Enum, so we need to check Enum first
|
|
757
753
|
if issubclass(type__, enum.Enum):
|
|
758
754
|
return self.map_to_enum(type__)
|
|
759
755
|
|
|
756
|
+
if issubclass(type__, tuple(self.scalar_classes())):
|
|
757
|
+
return self.map_to_scalar(type__)
|
|
758
|
+
|
|
760
759
|
if is_interface(type__, self.schema):
|
|
761
760
|
return self.map_to_interface(type__)
|
|
762
761
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
2
|
from enum import Enum
|
|
3
3
|
|
|
4
4
|
from graphql import GraphQLList, GraphQLNonNull, GraphQLObjectType
|
|
@@ -12,14 +12,15 @@ class FilterResponse(Enum):
|
|
|
12
12
|
"""
|
|
13
13
|
Response from a GraphQL filter indicating how to handle a field and transitive types.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
REMOVE - Remove the field but preserve types referenced by unfiltered fields
|
|
18
|
-
REMOVE_STRICT -
|
|
15
|
+
KEEP - Keep the field, don't preserve transitive object types
|
|
16
|
+
KEEP_TRANSITIVE - Keep the field and preserve transitive object types
|
|
17
|
+
REMOVE - Remove the field but preserve types referenced by unfiltered fields
|
|
18
|
+
REMOVE_STRICT - Always remove the field even if it is referenced by an unfiltered field that preserves transitive types.
|
|
19
|
+
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
KEEP = "keep"
|
|
23
|
+
KEEP_TRANSITIVE = "allow_transitive"
|
|
23
24
|
REMOVE = "remove"
|
|
24
25
|
REMOVE_STRICT = "remove_strict"
|
|
25
26
|
|
|
@@ -31,18 +32,32 @@ class FilterResponse(Enum):
|
|
|
31
32
|
@property
|
|
32
33
|
def preserve_transitive(self) -> bool:
|
|
33
34
|
"""True if types referenced by unfiltered fields should be preserved"""
|
|
34
|
-
return self in (FilterResponse.
|
|
35
|
+
return self in (FilterResponse.KEEP_TRANSITIVE, FilterResponse.REMOVE)
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class GraphQLFilter:
|
|
38
|
-
|
|
39
|
+
|
|
40
|
+
def filter_field(self, name: str, meta: dict) -> Union[bool, FilterResponse]:
|
|
39
41
|
"""
|
|
40
|
-
Return
|
|
42
|
+
Return either FilterReponse or a bool value indicating how to handle the field.
|
|
43
|
+
|
|
44
|
+
There are two options for what is returned;
|
|
45
|
+
|
|
46
|
+
Basic usage:
|
|
47
|
+
True: This field should be removed.
|
|
48
|
+
False: This field should be kept.
|
|
49
|
+
|
|
50
|
+
Advanced usage:
|
|
51
|
+
FilterResponse.KEEP_TRANSITIVE: This field should be kept and any transitive types should be kept.
|
|
52
|
+
FilterResponse.KEEP: This field should be kept but any transitive types set to REMOVE or REMOVE_STRICT will be removed.
|
|
53
|
+
FilterResponse.REMOVE: This field should generally be removed, but will be kept if it is referenced by an unfiltered field that is set to KEEP_TRANSITIVE.
|
|
54
|
+
FilterResponse.REMOVE_STRICT: This field should always be removed, even if it is referenced by an unfiltered field that is set to KEEP_TRANSITIVE.
|
|
41
55
|
"""
|
|
42
56
|
raise NotImplementedError()
|
|
43
57
|
|
|
44
58
|
|
|
45
59
|
class TagFilter(GraphQLFilter):
|
|
60
|
+
|
|
46
61
|
def __init__(
|
|
47
62
|
self, tags: Optional[List[str]] = None, preserve_transitive: bool = True
|
|
48
63
|
):
|
|
@@ -62,9 +77,9 @@ class TagFilter(GraphQLFilter):
|
|
|
62
77
|
if not should_filter:
|
|
63
78
|
# Field is allowed - use ALLOW_TRANSITIVE only if preserve_transitive is enabled
|
|
64
79
|
if self.preserve_transitive:
|
|
65
|
-
return FilterResponse.
|
|
80
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
66
81
|
else:
|
|
67
|
-
return FilterResponse.
|
|
82
|
+
return FilterResponse.KEEP
|
|
68
83
|
elif self.preserve_transitive:
|
|
69
84
|
# Field is filtered but preserve transitive dependencies
|
|
70
85
|
return FilterResponse.REMOVE
|
|
@@ -448,8 +463,20 @@ class GraphQLSchemaReducer:
|
|
|
448
463
|
filter_response = field_filter.filter_field(
|
|
449
464
|
field_name, field_meta
|
|
450
465
|
)
|
|
466
|
+
|
|
467
|
+
# Handle backwards compatibility with boolean returns
|
|
468
|
+
if isinstance(filter_response, bool):
|
|
469
|
+
# Boolean True means filter the field (old API)
|
|
470
|
+
# Convert to FilterResponse for consistent handling
|
|
471
|
+
if filter_response:
|
|
472
|
+
# Field should be filtered - use REMOVE_STRICT as default
|
|
473
|
+
filter_response = FilterResponse.REMOVE_STRICT
|
|
474
|
+
else:
|
|
475
|
+
# Field should be kept - use KEEP as default
|
|
476
|
+
filter_response = FilterResponse.KEEP
|
|
477
|
+
|
|
451
478
|
# If this field uses ALLOW_TRANSITIVE, preserve its referenced type
|
|
452
|
-
if isinstance(filter_response, FilterResponse) and filter_response == FilterResponse.
|
|
479
|
+
if isinstance(filter_response, FilterResponse) and filter_response == FilterResponse.KEEP_TRANSITIVE:
|
|
453
480
|
if isinstance(type_, (GraphQLInterfaceType, GraphQLObjectType)):
|
|
454
481
|
allow_transitive_types.add(type_)
|
|
455
482
|
# If any filter response wants to preserve transitive,
|
|
@@ -559,7 +586,13 @@ class GraphQLSchemaReducer:
|
|
|
559
586
|
filter_response = field_filter.filter_field(
|
|
560
587
|
field_name, field_meta
|
|
561
588
|
)
|
|
562
|
-
|
|
589
|
+
|
|
590
|
+
# Handle backwards compatibility with boolean returns
|
|
591
|
+
if isinstance(filter_response, bool):
|
|
592
|
+
# Boolean True means filter the field (old API)
|
|
593
|
+
if filter_response:
|
|
594
|
+
invalid_fields.add((root_type, key))
|
|
595
|
+
elif isinstance(filter_response, FilterResponse) and filter_response.should_filter:
|
|
563
596
|
invalid_fields.add((root_type, key))
|
|
564
597
|
|
|
565
598
|
if isinstance(type_, (GraphQLInterfaceType, GraphQLObjectType)):
|
|
@@ -667,7 +700,16 @@ class GraphQLSchemaReducer:
|
|
|
667
700
|
filter_response = field_filter.filter_field(
|
|
668
701
|
field_name, field_meta
|
|
669
702
|
)
|
|
670
|
-
|
|
703
|
+
|
|
704
|
+
# Handle backwards compatibility with boolean returns
|
|
705
|
+
should_filter_field = False
|
|
706
|
+
if isinstance(filter_response, bool):
|
|
707
|
+
# Boolean True means filter the field (old API)
|
|
708
|
+
should_filter_field = filter_response
|
|
709
|
+
elif isinstance(filter_response, FilterResponse):
|
|
710
|
+
should_filter_field = filter_response.should_filter
|
|
711
|
+
|
|
712
|
+
if should_filter_field:
|
|
671
713
|
invalid_fields.add((current_type, key))
|
|
672
714
|
field_is_filtered = True
|
|
673
715
|
break
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import uuid
|
|
5
5
|
import enum
|
|
6
6
|
|
|
7
|
-
from typing import Any, Dict, List, Optional, Type, Union
|
|
7
|
+
from typing import Any, Dict, List, Optional, Type, Union, cast
|
|
8
8
|
|
|
9
9
|
from graphql import (
|
|
10
10
|
GraphQLEnumType,
|
|
@@ -24,8 +24,31 @@ class GraphQLMappedEnumType(GraphQLEnumType):
|
|
|
24
24
|
|
|
25
25
|
def parse_literal(self, *args, **kwargs):
|
|
26
26
|
result = super().parse_literal(*args, **kwargs)
|
|
27
|
-
if result and
|
|
28
|
-
|
|
27
|
+
if result is not Undefined and result is not None and getattr(self, "enum_type", None):
|
|
28
|
+
try:
|
|
29
|
+
enum_type: Optional[Type[enum.Enum]] = self.enum_type
|
|
30
|
+
if enum_type is None:
|
|
31
|
+
return result
|
|
32
|
+
return cast(Type[enum.Enum], enum_type)(result)
|
|
33
|
+
except Exception:
|
|
34
|
+
return Undefined
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
def parse_value(self, value):
|
|
38
|
+
"""Coerce incoming variable values to the mapped Python Enum instance.
|
|
39
|
+
|
|
40
|
+
GraphQL variables use `parse_value`, while inline literals use
|
|
41
|
+
`parse_literal`. Ensure both paths produce the Python Enum.
|
|
42
|
+
"""
|
|
43
|
+
result = super().parse_value(value)
|
|
44
|
+
if result is not Undefined and result is not None and getattr(self, "enum_type", None):
|
|
45
|
+
try:
|
|
46
|
+
enum_type: Optional[Type[enum.Enum]] = self.enum_type
|
|
47
|
+
if enum_type is None:
|
|
48
|
+
return result
|
|
49
|
+
return cast(Type[enum.Enum], enum_type)(result)
|
|
50
|
+
except Exception:
|
|
51
|
+
return Undefined
|
|
29
52
|
return result
|
|
30
53
|
|
|
31
54
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphql_api
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.13
|
|
4
4
|
Summary: A framework for building Python GraphQL APIs.
|
|
5
5
|
Home-page: https://gitlab.com/parob/graphql-api
|
|
6
|
-
Download-URL: https://gitlab.com/parob/graphql/-/archive/v1.4.
|
|
6
|
+
Download-URL: https://gitlab.com/parob/graphql/-/archive/v1.4.13/graphql_api-v1.4.13.tar.gz
|
|
7
7
|
Author: Robert Parker
|
|
8
8
|
Author-email: rob@parob.com
|
|
9
9
|
License: MIT
|
|
@@ -111,11 +111,11 @@ class TestDataclass:
|
|
|
111
111
|
if "admin" in tags:
|
|
112
112
|
if name == "get_stats":
|
|
113
113
|
# Use ALLOW_TRANSITIVE to preserve the Stats type
|
|
114
|
-
return FilterResponse.
|
|
114
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
115
115
|
else:
|
|
116
116
|
# Other admin fields should be removed
|
|
117
117
|
return FilterResponse.REMOVE_STRICT
|
|
118
|
-
return FilterResponse.
|
|
118
|
+
return FilterResponse.KEEP
|
|
119
119
|
|
|
120
120
|
# Build the API with the custom filter
|
|
121
121
|
api = GraphQLAPI(root_type=Root, filters=[TestFilter()])
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from graphql_api.api import GraphQLAPI
|
|
2
2
|
from graphql_api.mapper import GraphQLMetaKey
|
|
3
|
+
from graphql_api.directives import print_schema
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class TestSchemaFiltering:
|
|
@@ -515,12 +516,12 @@ class TestSchemaFiltering:
|
|
|
515
516
|
from graphql_api.reduce import FilterResponse
|
|
516
517
|
|
|
517
518
|
# Test ALLOW - keep field, don't preserve transitive
|
|
518
|
-
assert not FilterResponse.
|
|
519
|
-
assert not FilterResponse.
|
|
519
|
+
assert not FilterResponse.KEEP.should_filter
|
|
520
|
+
assert not FilterResponse.KEEP.preserve_transitive
|
|
520
521
|
|
|
521
522
|
# Test ALLOW_TRANSITIVE - keep field, preserve transitive
|
|
522
|
-
assert not FilterResponse.
|
|
523
|
-
assert FilterResponse.
|
|
523
|
+
assert not FilterResponse.KEEP_TRANSITIVE.should_filter
|
|
524
|
+
assert FilterResponse.KEEP_TRANSITIVE.preserve_transitive
|
|
524
525
|
|
|
525
526
|
# Test REMOVE - remove field, preserve transitive
|
|
526
527
|
assert FilterResponse.REMOVE.should_filter
|
|
@@ -545,10 +546,10 @@ class TestSchemaFiltering:
|
|
|
545
546
|
tags = meta.get("tags", [])
|
|
546
547
|
|
|
547
548
|
if "allow" in tags:
|
|
548
|
-
return FilterResponse.
|
|
549
|
+
return FilterResponse.KEEP # Keep field, don't preserve transitive
|
|
549
550
|
elif "allow_transitive" in tags:
|
|
550
551
|
return (
|
|
551
|
-
FilterResponse.
|
|
552
|
+
FilterResponse.KEEP_TRANSITIVE
|
|
552
553
|
) # Keep field, preserve transitive
|
|
553
554
|
elif "remove" in tags:
|
|
554
555
|
return FilterResponse.REMOVE # Remove field, preserve transitive
|
|
@@ -557,7 +558,7 @@ class TestSchemaFiltering:
|
|
|
557
558
|
FilterResponse.REMOVE_STRICT
|
|
558
559
|
) # Remove field, don't preserve transitive
|
|
559
560
|
else:
|
|
560
|
-
return FilterResponse.
|
|
561
|
+
return FilterResponse.KEEP_TRANSITIVE # Default behavior
|
|
561
562
|
|
|
562
563
|
class ReferencedType:
|
|
563
564
|
@field({"tags": ["allow"]})
|
|
@@ -673,7 +674,7 @@ class TestSchemaFiltering:
|
|
|
673
674
|
elif "admin" in tags:
|
|
674
675
|
return FilterResponse.REMOVE # Remove with preservation
|
|
675
676
|
else:
|
|
676
|
-
return FilterResponse.
|
|
677
|
+
return FilterResponse.KEEP_TRANSITIVE # Keep with preservation
|
|
677
678
|
|
|
678
679
|
class DataType:
|
|
679
680
|
@field
|
|
@@ -975,11 +976,11 @@ class TestSchemaFiltering:
|
|
|
975
976
|
if "admin" in tags:
|
|
976
977
|
if name == "admin_user":
|
|
977
978
|
# Use ALLOW_TRANSITIVE to preserve the AdminUser type
|
|
978
|
-
return FilterResponse.
|
|
979
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
979
980
|
else:
|
|
980
981
|
# Other admin fields should be removed
|
|
981
982
|
return FilterResponse.REMOVE_STRICT
|
|
982
|
-
return FilterResponse.
|
|
983
|
+
return FilterResponse.KEEP
|
|
983
984
|
|
|
984
985
|
# Build the API with the custom filter
|
|
985
986
|
api = GraphQLAPI(root_type=Root, filters=[TestFilter()])
|
|
@@ -1087,10 +1088,10 @@ class TestSchemaFiltering:
|
|
|
1087
1088
|
tags = meta.get("tags", [])
|
|
1088
1089
|
if "admin" in tags:
|
|
1089
1090
|
if name == "admin_user":
|
|
1090
|
-
return FilterResponse.
|
|
1091
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
1091
1092
|
else:
|
|
1092
1093
|
return FilterResponse.REMOVE_STRICT
|
|
1093
|
-
return FilterResponse.
|
|
1094
|
+
return FilterResponse.KEEP
|
|
1094
1095
|
|
|
1095
1096
|
api_strict = GraphQLAPI(root_type=Root, filters=[TestFilterStrict()])
|
|
1096
1097
|
schema_strict, _ = api_strict.build_schema()
|
|
@@ -1101,10 +1102,10 @@ class TestSchemaFiltering:
|
|
|
1101
1102
|
tags = meta.get("tags", [])
|
|
1102
1103
|
if "admin" in tags:
|
|
1103
1104
|
if name == "admin_user":
|
|
1104
|
-
return FilterResponse.
|
|
1105
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
1105
1106
|
else:
|
|
1106
1107
|
return FilterResponse.REMOVE # Use REMOVE instead of REMOVE_STRICT
|
|
1107
|
-
return FilterResponse.
|
|
1108
|
+
return FilterResponse.KEEP
|
|
1108
1109
|
|
|
1109
1110
|
api_remove = GraphQLAPI(root_type=Root, filters=[TestFilterRemove()])
|
|
1110
1111
|
schema_remove, _ = api_remove.build_schema()
|
|
@@ -1198,7 +1199,7 @@ class TestSchemaFiltering:
|
|
|
1198
1199
|
|
|
1199
1200
|
if name == "secret_data" and "admin" in tags:
|
|
1200
1201
|
# Use ALLOW_TRANSITIVE to preserve SecretData type
|
|
1201
|
-
return FilterResponse.
|
|
1202
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
1202
1203
|
elif "secret" in tags or "classified" in tags:
|
|
1203
1204
|
# These fields should ALWAYS be removed, even if type is preserved
|
|
1204
1205
|
return FilterResponse.REMOVE_STRICT
|
|
@@ -1207,7 +1208,7 @@ class TestSchemaFiltering:
|
|
|
1207
1208
|
return FilterResponse.REMOVE
|
|
1208
1209
|
else:
|
|
1209
1210
|
# Allow all other fields
|
|
1210
|
-
return FilterResponse.
|
|
1211
|
+
return FilterResponse.KEEP
|
|
1211
1212
|
|
|
1212
1213
|
# Build the API with the test filter
|
|
1213
1214
|
api = GraphQLAPI(root_type=Root, filters=[TestFilter()])
|
|
@@ -1524,11 +1525,11 @@ class TestSchemaFiltering:
|
|
|
1524
1525
|
class Animal:
|
|
1525
1526
|
@field
|
|
1526
1527
|
def name(self) -> str:
|
|
1527
|
-
|
|
1528
|
+
...
|
|
1528
1529
|
|
|
1529
1530
|
@field({"tags": ["vet"]})
|
|
1530
1531
|
def medical_history(self) -> str:
|
|
1531
|
-
|
|
1532
|
+
...
|
|
1532
1533
|
|
|
1533
1534
|
class Dog(Animal):
|
|
1534
1535
|
def __init__(self):
|
|
@@ -1913,7 +1914,7 @@ class TestSchemaFiltering:
|
|
|
1913
1914
|
else FilterResponse.REMOVE_STRICT
|
|
1914
1915
|
)
|
|
1915
1916
|
else:
|
|
1916
|
-
return FilterResponse.
|
|
1917
|
+
return FilterResponse.KEEP_TRANSITIVE
|
|
1917
1918
|
|
|
1918
1919
|
class Data:
|
|
1919
1920
|
@field
|
|
@@ -2058,6 +2059,45 @@ class TestSchemaFiltering:
|
|
|
2058
2059
|
assert result.errors
|
|
2059
2060
|
# Should fail because updateName field is filtered out
|
|
2060
2061
|
|
|
2062
|
+
def test_custom_public_filter(self):
|
|
2063
|
+
"""
|
|
2064
|
+
Test that we can use a custom filter to keep public fields and remove non-public ones
|
|
2065
|
+
"""
|
|
2066
|
+
from graphql_api.reduce import GraphQLFilter
|
|
2067
|
+
|
|
2068
|
+
class PublicFilter(GraphQLFilter):
|
|
2069
|
+
|
|
2070
|
+
def filter_field(self, name: str, meta: dict) -> bool:
|
|
2071
|
+
# Return True to remove the field, False to keep it
|
|
2072
|
+
# We want to keep public fields and remove non-public ones
|
|
2073
|
+
|
|
2074
|
+
is_public = meta.get("public")
|
|
2075
|
+
should_filter = not is_public
|
|
2076
|
+
if should_filter:
|
|
2077
|
+
print(f"Filtering field {name} with meta {meta}: {should_filter}")
|
|
2078
|
+
else:
|
|
2079
|
+
print(f"Keeping field {name} with meta {meta}: {should_filter}")
|
|
2080
|
+
|
|
2081
|
+
return should_filter
|
|
2082
|
+
|
|
2083
|
+
api = GraphQLAPI(filters=[PublicFilter()])
|
|
2084
|
+
|
|
2085
|
+
@api.type(is_root_type=True)
|
|
2086
|
+
class Root:
|
|
2087
|
+
|
|
2088
|
+
@api.field({"public": True})
|
|
2089
|
+
def public_field(self) -> str:
|
|
2090
|
+
return "public"
|
|
2091
|
+
|
|
2092
|
+
@api.field
|
|
2093
|
+
def non_public_field(self) -> str:
|
|
2094
|
+
return "non-public"
|
|
2095
|
+
|
|
2096
|
+
schema, _ = api.build_schema()
|
|
2097
|
+
printed_schema = print_schema(schema)
|
|
2098
|
+
assert "publicField" in printed_schema
|
|
2099
|
+
assert "nonPublicField" not in printed_schema
|
|
2100
|
+
|
|
2061
2101
|
def test_recursive_object_type_preservation(self):
|
|
2062
2102
|
"""
|
|
2063
2103
|
Test that object types on fields of unfiltered objects (recursive)
|
|
@@ -2195,7 +2235,7 @@ class TestSchemaFiltering:
|
|
|
2195
2235
|
elif self.return_type == "int":
|
|
2196
2236
|
return 42 # Should be FilterResponse
|
|
2197
2237
|
else:
|
|
2198
|
-
return FilterResponse.
|
|
2238
|
+
return FilterResponse.KEEP # Valid response
|
|
2199
2239
|
|
|
2200
2240
|
api = GraphQLAPI()
|
|
2201
2241
|
|
|
@@ -900,6 +900,334 @@ class TestGraphQL:
|
|
|
900
900
|
|
|
901
901
|
assert result.data == expected
|
|
902
902
|
|
|
903
|
+
def test_string_enum(self):
|
|
904
|
+
"""Test string enum functionality"""
|
|
905
|
+
api = GraphQLAPI()
|
|
906
|
+
|
|
907
|
+
class Timeframe(str, enum.Enum):
|
|
908
|
+
"""A timeframe."""
|
|
909
|
+
LAST_30_DAYS = "last_30_days"
|
|
910
|
+
ALL_TIME = "all_time"
|
|
911
|
+
|
|
912
|
+
@api.type(is_root_type=True)
|
|
913
|
+
class Root:
|
|
914
|
+
@api.field
|
|
915
|
+
def get_timeframe(self) -> Timeframe:
|
|
916
|
+
return Timeframe.LAST_30_DAYS
|
|
917
|
+
|
|
918
|
+
@api.field
|
|
919
|
+
def echo_timeframe(self, timeframe: Timeframe) -> Timeframe:
|
|
920
|
+
return timeframe
|
|
921
|
+
|
|
922
|
+
executor = api.executor()
|
|
923
|
+
|
|
924
|
+
# Test return value
|
|
925
|
+
query1 = """
|
|
926
|
+
query {
|
|
927
|
+
getTimeframe
|
|
928
|
+
}
|
|
929
|
+
"""
|
|
930
|
+
result1 = executor.execute(query1)
|
|
931
|
+
assert result1.data == {'getTimeframe': 'LAST_30_DAYS'}
|
|
932
|
+
|
|
933
|
+
# Test input argument
|
|
934
|
+
query2 = """
|
|
935
|
+
query {
|
|
936
|
+
echoTimeframe(timeframe: ALL_TIME)
|
|
937
|
+
}
|
|
938
|
+
"""
|
|
939
|
+
result2 = executor.execute(query2)
|
|
940
|
+
assert result2.data == {'echoTimeframe': 'ALL_TIME'}
|
|
941
|
+
|
|
942
|
+
def test_string_enum_with_regular_enum(self):
|
|
943
|
+
"""Test that both string enums and regular enums work together"""
|
|
944
|
+
api = GraphQLAPI()
|
|
945
|
+
|
|
946
|
+
class StringStatus(str, enum.Enum):
|
|
947
|
+
ACTIVE = "active"
|
|
948
|
+
INACTIVE = "inactive"
|
|
949
|
+
|
|
950
|
+
class RegularPriority(enum.Enum):
|
|
951
|
+
LOW = 1
|
|
952
|
+
MEDIUM = 2
|
|
953
|
+
HIGH = 3
|
|
954
|
+
|
|
955
|
+
@api.type(is_root_type=True)
|
|
956
|
+
class Root:
|
|
957
|
+
@api.field
|
|
958
|
+
def get_status(self) -> StringStatus:
|
|
959
|
+
return StringStatus.ACTIVE
|
|
960
|
+
|
|
961
|
+
@api.field
|
|
962
|
+
def get_priority(self) -> RegularPriority:
|
|
963
|
+
return RegularPriority.HIGH
|
|
964
|
+
|
|
965
|
+
@api.field
|
|
966
|
+
def combine(self, status: StringStatus, priority: RegularPriority) -> str:
|
|
967
|
+
return f"{status.value}:{priority.value}"
|
|
968
|
+
|
|
969
|
+
executor = api.executor()
|
|
970
|
+
|
|
971
|
+
query = """
|
|
972
|
+
query {
|
|
973
|
+
getStatus
|
|
974
|
+
getPriority
|
|
975
|
+
combine(status: ACTIVE, priority: HIGH)
|
|
976
|
+
}
|
|
977
|
+
"""
|
|
978
|
+
|
|
979
|
+
result = executor.execute(query)
|
|
980
|
+
assert result.data == {
|
|
981
|
+
'getStatus': 'ACTIVE',
|
|
982
|
+
'getPriority': 'HIGH',
|
|
983
|
+
'combine': 'active:3'
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
def test_string_enum_optional(self):
|
|
987
|
+
"""Test optional string enum"""
|
|
988
|
+
api = GraphQLAPI()
|
|
989
|
+
|
|
990
|
+
class Color(str, enum.Enum):
|
|
991
|
+
RED = "red"
|
|
992
|
+
GREEN = "green"
|
|
993
|
+
BLUE = "blue"
|
|
994
|
+
|
|
995
|
+
@api.type(is_root_type=True)
|
|
996
|
+
class Root:
|
|
997
|
+
@api.field
|
|
998
|
+
def get_color(self, color: Optional[Color] = None) -> str:
|
|
999
|
+
if color:
|
|
1000
|
+
return f"Color is {color.value}"
|
|
1001
|
+
return "No color specified"
|
|
1002
|
+
|
|
1003
|
+
executor = api.executor()
|
|
1004
|
+
|
|
1005
|
+
# Test with value
|
|
1006
|
+
query1 = """
|
|
1007
|
+
query {
|
|
1008
|
+
getColor(color: RED)
|
|
1009
|
+
}
|
|
1010
|
+
"""
|
|
1011
|
+
result1 = executor.execute(query1)
|
|
1012
|
+
assert result1.data == {'getColor': 'Color is red'}
|
|
1013
|
+
|
|
1014
|
+
# Test without value
|
|
1015
|
+
query2 = """
|
|
1016
|
+
query {
|
|
1017
|
+
getColor
|
|
1018
|
+
}
|
|
1019
|
+
"""
|
|
1020
|
+
result2 = executor.execute(query2)
|
|
1021
|
+
assert result2.data == {'getColor': 'No color specified'}
|
|
1022
|
+
|
|
1023
|
+
def test_string_enum_list(self):
|
|
1024
|
+
"""Test list of string enums"""
|
|
1025
|
+
api = GraphQLAPI()
|
|
1026
|
+
|
|
1027
|
+
class Tag(str, enum.Enum):
|
|
1028
|
+
PYTHON = "python"
|
|
1029
|
+
JAVASCRIPT = "javascript"
|
|
1030
|
+
RUST = "rust"
|
|
1031
|
+
GO = "go"
|
|
1032
|
+
|
|
1033
|
+
@api.type(is_root_type=True)
|
|
1034
|
+
class Root:
|
|
1035
|
+
@api.field
|
|
1036
|
+
def get_tags(self) -> List[Tag]:
|
|
1037
|
+
return [Tag.PYTHON, Tag.RUST]
|
|
1038
|
+
|
|
1039
|
+
@api.field
|
|
1040
|
+
def filter_tags(self, tags: List[Tag]) -> List[Tag]:
|
|
1041
|
+
# Return only tags that start with 'P' or 'R'
|
|
1042
|
+
return [t for t in tags if t.value[0] in ('p', 'r')]
|
|
1043
|
+
|
|
1044
|
+
executor = api.executor()
|
|
1045
|
+
|
|
1046
|
+
# Test returning list
|
|
1047
|
+
query1 = """
|
|
1048
|
+
query {
|
|
1049
|
+
getTags
|
|
1050
|
+
}
|
|
1051
|
+
"""
|
|
1052
|
+
result1 = executor.execute(query1)
|
|
1053
|
+
assert result1.data == {'getTags': ['PYTHON', 'RUST']}
|
|
1054
|
+
|
|
1055
|
+
# Test list as input
|
|
1056
|
+
query2 = """
|
|
1057
|
+
query {
|
|
1058
|
+
filterTags(tags: [PYTHON, JAVASCRIPT, RUST, GO])
|
|
1059
|
+
}
|
|
1060
|
+
"""
|
|
1061
|
+
result2 = executor.execute(query2)
|
|
1062
|
+
assert result2.data == {'filterTags': ['PYTHON', 'RUST']}
|
|
1063
|
+
|
|
1064
|
+
def test_string_enum_argument(self):
|
|
1065
|
+
api = GraphQLAPI()
|
|
1066
|
+
|
|
1067
|
+
class Tag(str, enum.Enum):
|
|
1068
|
+
PYTHON = "python"
|
|
1069
|
+
JAVASCRIPT = "javascript"
|
|
1070
|
+
RUST = "rust"
|
|
1071
|
+
|
|
1072
|
+
@api.type(is_root_type=True)
|
|
1073
|
+
class Root:
|
|
1074
|
+
@api.field
|
|
1075
|
+
def check_tag(self, tag: Tag) -> bool:
|
|
1076
|
+
if isinstance(tag, Tag):
|
|
1077
|
+
return True
|
|
1078
|
+
return False
|
|
1079
|
+
|
|
1080
|
+
executor = api.executor()
|
|
1081
|
+
|
|
1082
|
+
query = """
|
|
1083
|
+
query CheckTag($tag: TagEnum!) {
|
|
1084
|
+
checkTag(tag: $tag)
|
|
1085
|
+
}
|
|
1086
|
+
"""
|
|
1087
|
+
result = executor.execute(query, variables={'tag': Tag.PYTHON.name})
|
|
1088
|
+
assert result.data == {'checkTag': True}
|
|
1089
|
+
|
|
1090
|
+
def test_string_enum_variable_rejects_value_literal(self):
|
|
1091
|
+
api = GraphQLAPI()
|
|
1092
|
+
|
|
1093
|
+
class Tag(str, enum.Enum):
|
|
1094
|
+
PYTHON = "python"
|
|
1095
|
+
|
|
1096
|
+
@api.type(is_root_type=True)
|
|
1097
|
+
class Root:
|
|
1098
|
+
@api.field
|
|
1099
|
+
def check(self, tag: Tag) -> bool:
|
|
1100
|
+
return isinstance(tag, Tag)
|
|
1101
|
+
|
|
1102
|
+
executor = api.executor()
|
|
1103
|
+
query = """
|
|
1104
|
+
query($tag: TagEnum!){
|
|
1105
|
+
check(tag: $tag)
|
|
1106
|
+
}
|
|
1107
|
+
"""
|
|
1108
|
+
# Variable must be the NAME ("PYTHON"), not the underlying value ("python")
|
|
1109
|
+
result = executor.execute(query, variables={"tag": "python"})
|
|
1110
|
+
assert result.errors and "does not exist in 'TagEnum'" in result.errors[0].message
|
|
1111
|
+
|
|
1112
|
+
def test_regular_enum_variable(self):
|
|
1113
|
+
api = GraphQLAPI()
|
|
1114
|
+
|
|
1115
|
+
class Priority(enum.Enum):
|
|
1116
|
+
LOW = 1
|
|
1117
|
+
HIGH = 3
|
|
1118
|
+
|
|
1119
|
+
@api.type(is_root_type=True)
|
|
1120
|
+
class Root:
|
|
1121
|
+
@api.field
|
|
1122
|
+
def is_high(self, p: Priority) -> bool:
|
|
1123
|
+
return p is Priority.HIGH
|
|
1124
|
+
|
|
1125
|
+
executor = api.executor()
|
|
1126
|
+
q = "query($p: PriorityEnum!){ isHigh(p: $p) }"
|
|
1127
|
+
result = executor.execute(q, variables={"p": "HIGH"})
|
|
1128
|
+
assert result.data == {"isHigh": True}
|
|
1129
|
+
|
|
1130
|
+
def test_input_object_enum_default_and_override(self):
|
|
1131
|
+
api = GraphQLAPI()
|
|
1132
|
+
|
|
1133
|
+
class Status(str, enum.Enum):
|
|
1134
|
+
ACTIVE = "active"
|
|
1135
|
+
INACTIVE = "inactive"
|
|
1136
|
+
|
|
1137
|
+
class Filter:
|
|
1138
|
+
def __init__(self, status: Status = Status.ACTIVE):
|
|
1139
|
+
self.status = status
|
|
1140
|
+
|
|
1141
|
+
@api.type(is_root_type=True)
|
|
1142
|
+
class Root:
|
|
1143
|
+
@api.field
|
|
1144
|
+
def get_status(self, flt: Filter) -> Status:
|
|
1145
|
+
return flt.status
|
|
1146
|
+
|
|
1147
|
+
executor = api.executor()
|
|
1148
|
+
# Uses default from InputObject field
|
|
1149
|
+
r1 = executor.execute("query{ getStatus(flt: {}) }")
|
|
1150
|
+
assert not r1.errors and r1.data == {"getStatus": "ACTIVE"}
|
|
1151
|
+
|
|
1152
|
+
# Override via variable with enum NAME
|
|
1153
|
+
q = "query($s: StatusEnum!){ getStatus(flt: {status: $s}) }"
|
|
1154
|
+
r2 = executor.execute(q, variables={"s": "INACTIVE"})
|
|
1155
|
+
assert r2.data == {"getStatus": "INACTIVE"}
|
|
1156
|
+
|
|
1157
|
+
def test_string_enum_list_variable(self):
|
|
1158
|
+
api = GraphQLAPI()
|
|
1159
|
+
|
|
1160
|
+
class Tag(str, enum.Enum):
|
|
1161
|
+
PYTHON = "python"
|
|
1162
|
+
RUST = "rust"
|
|
1163
|
+
|
|
1164
|
+
@api.type(is_root_type=True)
|
|
1165
|
+
class Root:
|
|
1166
|
+
@api.field
|
|
1167
|
+
def echo(self, tags: List[Tag]) -> List[Tag]:
|
|
1168
|
+
assert all(isinstance(t, Tag) for t in tags)
|
|
1169
|
+
return tags
|
|
1170
|
+
|
|
1171
|
+
executor = api.executor()
|
|
1172
|
+
q = "query($tags: [TagEnum!]!){ echo(tags: $tags) }"
|
|
1173
|
+
r = executor.execute(q, variables={"tags": ["PYTHON", "RUST"]})
|
|
1174
|
+
assert r.data == {"echo": ["PYTHON", "RUST"]}
|
|
1175
|
+
|
|
1176
|
+
def test_optional_enum_undefined_vs_null(self):
|
|
1177
|
+
api = GraphQLAPI()
|
|
1178
|
+
|
|
1179
|
+
class Color(str, enum.Enum):
|
|
1180
|
+
RED = "red"
|
|
1181
|
+
|
|
1182
|
+
@api.type(is_root_type=True)
|
|
1183
|
+
class Root:
|
|
1184
|
+
@api.field
|
|
1185
|
+
def show(self, color: Optional[Color] = None) -> str:
|
|
1186
|
+
return "none" if color is None else color.value
|
|
1187
|
+
|
|
1188
|
+
executor = api.executor()
|
|
1189
|
+
q = "query($c: ColorEnum){ show(color: $c) }"
|
|
1190
|
+
# Undefined variable
|
|
1191
|
+
r1 = executor.execute(q, variables={})
|
|
1192
|
+
assert r1.data == {"show": "none"}
|
|
1193
|
+
# Explicit null
|
|
1194
|
+
r2 = executor.execute(q, variables={"c": None})
|
|
1195
|
+
assert r2.data == {"show": "none"}
|
|
1196
|
+
|
|
1197
|
+
def test_returning_underlying_value_maps_to_enum_name(self):
|
|
1198
|
+
api = GraphQLAPI()
|
|
1199
|
+
|
|
1200
|
+
class Color(str, enum.Enum):
|
|
1201
|
+
RED = "red"
|
|
1202
|
+
|
|
1203
|
+
@api.type(is_root_type=True)
|
|
1204
|
+
class Root:
|
|
1205
|
+
@api.field
|
|
1206
|
+
def color(self) -> Color: # type: ignore[override]
|
|
1207
|
+
# Returning the underlying value should still serialize to NAME
|
|
1208
|
+
return "red" # type: ignore[return-value]
|
|
1209
|
+
|
|
1210
|
+
executor = api.executor()
|
|
1211
|
+
r = executor.execute("query{ color }")
|
|
1212
|
+
assert r.data == {"color": "RED"}
|
|
1213
|
+
|
|
1214
|
+
def test_enum_variable_mixed_case_invalid(self):
|
|
1215
|
+
api = GraphQLAPI()
|
|
1216
|
+
|
|
1217
|
+
class Tag(str, enum.Enum):
|
|
1218
|
+
PYTHON = "python"
|
|
1219
|
+
|
|
1220
|
+
@api.type(is_root_type=True)
|
|
1221
|
+
class Root:
|
|
1222
|
+
@api.field
|
|
1223
|
+
def ok(self, tag: Tag) -> bool:
|
|
1224
|
+
return True
|
|
1225
|
+
|
|
1226
|
+
executor = api.executor()
|
|
1227
|
+
q = "query($t: TagEnum!){ ok(tag: $t) }"
|
|
1228
|
+
res = executor.execute(q, variables={"t": "Python"})
|
|
1229
|
+
assert res.errors and "does not exist in 'TagEnum'" in res.errors[0].message
|
|
1230
|
+
|
|
903
1231
|
# noinspection PyUnusedLocal
|
|
904
1232
|
def test_literal(self):
|
|
905
1233
|
api = GraphQLAPI()
|
|
@@ -10,44 +10,44 @@ class TestGraphQLSchema:
|
|
|
10
10
|
class ObjectNoSchema:
|
|
11
11
|
@field
|
|
12
12
|
def test_query_no_schema(self, a: int) -> int:
|
|
13
|
-
|
|
13
|
+
...
|
|
14
14
|
|
|
15
15
|
@field(mutable=True)
|
|
16
16
|
def test_mutation_no_schema(self, a: int) -> int:
|
|
17
|
-
|
|
17
|
+
...
|
|
18
18
|
|
|
19
19
|
@type(abstract=True)
|
|
20
20
|
class AbstractNoSchema:
|
|
21
21
|
@field
|
|
22
22
|
def test_abstract_query_no_schema(self, a: int) -> int:
|
|
23
|
-
|
|
23
|
+
...
|
|
24
24
|
|
|
25
25
|
@field(mutable=True)
|
|
26
26
|
def test_abstract_mutation_no_schema(self, a: int) -> int:
|
|
27
|
-
|
|
27
|
+
...
|
|
28
28
|
|
|
29
29
|
@type(interface=True)
|
|
30
30
|
class InterfaceNoSchema:
|
|
31
31
|
@field
|
|
32
32
|
def test_interface_query_no_schema(self, a: int) -> int:
|
|
33
|
-
|
|
33
|
+
...
|
|
34
34
|
|
|
35
35
|
@field(mutable=True)
|
|
36
36
|
def test_interface_mutation_no_schema(self, a: int) -> int:
|
|
37
|
-
|
|
37
|
+
...
|
|
38
38
|
|
|
39
39
|
# noinspection PyUnresolvedReferences
|
|
40
|
-
assert ObjectNoSchema._graphql
|
|
40
|
+
assert ObjectNoSchema._graphql # type: ignore[reportAttributeAccess]
|
|
41
41
|
assert ObjectNoSchema.test_query_no_schema._graphql
|
|
42
42
|
assert ObjectNoSchema.test_mutation_no_schema._graphql
|
|
43
43
|
|
|
44
44
|
# noinspection PyUnresolvedReferences
|
|
45
|
-
assert AbstractNoSchema._graphql
|
|
45
|
+
assert AbstractNoSchema._graphql # type: ignore[reportAttributeAccess]
|
|
46
46
|
assert AbstractNoSchema.test_abstract_query_no_schema._graphql
|
|
47
47
|
assert AbstractNoSchema.test_abstract_mutation_no_schema._graphql
|
|
48
48
|
|
|
49
49
|
# noinspection PyUnresolvedReferences
|
|
50
|
-
assert InterfaceNoSchema._graphql
|
|
50
|
+
assert InterfaceNoSchema._graphql # type: ignore[reportAttributeAccess]
|
|
51
51
|
assert InterfaceNoSchema.test_interface_query_no_schema._graphql
|
|
52
52
|
assert InterfaceNoSchema.test_interface_mutation_no_schema._graphql
|
|
53
53
|
|
|
@@ -58,14 +58,14 @@ class TestGraphQLSchema:
|
|
|
58
58
|
class ObjectSchema:
|
|
59
59
|
@api_1.field
|
|
60
60
|
def test_query_schema(self, a: int) -> int:
|
|
61
|
-
|
|
61
|
+
...
|
|
62
62
|
|
|
63
63
|
@api_1.field(mutable=True)
|
|
64
64
|
def test_mutation_schema(self, a: int) -> int:
|
|
65
|
-
|
|
65
|
+
...
|
|
66
66
|
|
|
67
67
|
# noinspection PyUnresolvedReferences
|
|
68
|
-
assert ObjectSchema._graphql
|
|
68
|
+
assert ObjectSchema._graphql # type: ignore[reportAttributeAccess]
|
|
69
69
|
assert ObjectSchema.test_query_schema._graphql
|
|
70
70
|
assert ObjectSchema.test_mutation_schema._graphql
|
|
71
71
|
|
|
@@ -74,14 +74,14 @@ class TestGraphQLSchema:
|
|
|
74
74
|
class ObjectNoSchemaMeta:
|
|
75
75
|
@field(meta={"test": "test"})
|
|
76
76
|
def test_query_no_schema_meta(self, a: int) -> int:
|
|
77
|
-
|
|
77
|
+
...
|
|
78
78
|
|
|
79
79
|
@field(meta={"test": "test"}, mutable=True)
|
|
80
80
|
def test_mutation_no_schema_meta(self, a: int) -> int:
|
|
81
|
-
|
|
81
|
+
...
|
|
82
82
|
|
|
83
83
|
# noinspection PyUnresolvedReferences
|
|
84
|
-
assert ObjectNoSchemaMeta._graphql
|
|
84
|
+
assert ObjectNoSchemaMeta._graphql # type: ignore[reportAttributeAccess]
|
|
85
85
|
assert ObjectNoSchemaMeta.test_query_no_schema_meta._graphql
|
|
86
86
|
assert ObjectNoSchemaMeta.test_mutation_no_schema_meta._graphql
|
|
87
87
|
|
|
@@ -92,20 +92,21 @@ class TestGraphQLSchema:
|
|
|
92
92
|
class ObjectSchemaMeta:
|
|
93
93
|
@api_1.field(meta={"test3": "test4"})
|
|
94
94
|
def test_query_schema_meta(self, a: int) -> int:
|
|
95
|
-
|
|
95
|
+
...
|
|
96
96
|
|
|
97
97
|
@api_1.field(meta={"test5": "test6"}, mutable=True)
|
|
98
98
|
def test_mutation_schema_meta(self, a: int) -> int:
|
|
99
|
-
|
|
99
|
+
...
|
|
100
100
|
|
|
101
101
|
# noinspection PyUnresolvedReferences
|
|
102
|
-
assert ObjectSchemaMeta._graphql
|
|
102
|
+
assert ObjectSchemaMeta._graphql # type: ignore[reportAttributeAccess]
|
|
103
103
|
assert ObjectSchemaMeta.test_query_schema_meta._graphql
|
|
104
104
|
assert ObjectSchemaMeta.test_mutation_schema_meta._graphql
|
|
105
105
|
|
|
106
|
-
schema = api_1.build_schema()
|
|
106
|
+
schema, _ = api_1.build_schema()
|
|
107
107
|
|
|
108
|
-
assert schema
|
|
108
|
+
assert schema is not None
|
|
109
|
+
assert schema.query_type is not None
|
|
109
110
|
|
|
110
111
|
def test_schema_with_no_root_type(self):
|
|
111
112
|
"""
|
|
@@ -115,7 +116,8 @@ class TestGraphQLSchema:
|
|
|
115
116
|
api = GraphQLAPI()
|
|
116
117
|
schema, _ = api.build_schema()
|
|
117
118
|
|
|
118
|
-
assert schema
|
|
119
|
+
assert schema is not None
|
|
120
|
+
assert schema.query_type is not None
|
|
119
121
|
assert schema.query_type.name == "PlaceholderQuery"
|
|
120
122
|
assert "placeholder" in schema.query_type.fields
|
|
121
123
|
assert schema.mutation_type is None
|
graphql_api-1.4.11/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.4.11
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|