From a04b15ed17bf0afac46463a68cb68cb3fe3eeba4 Mon Sep 17 00:00:00 2001 From: Igor Kasianov Date: Thu, 21 Nov 2019 02:06:33 +0200 Subject: [PATCH 1/2] add @requires support --- graphene_federation/__init__.py | 2 +- graphene_federation/extend.py | 5 ++++ graphene_federation/service.py | 21 ++++++++++++++--- integration_tests/service_c/src/schema.py | 7 +++++- integration_tests/tests/tests/test_main.py | 27 ++++++++++++++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/graphene_federation/__init__.py b/graphene_federation/__init__.py index bdf46dd..04cb080 100644 --- a/graphene_federation/__init__.py +++ b/graphene_federation/__init__.py @@ -1,2 +1,2 @@ from .main import key, build_schema -from .extend import extend, external +from .extend import extend, external, requires diff --git a/graphene_federation/extend.py b/graphene_federation/extend.py index eb460ef..c60ddf1 100644 --- a/graphene_federation/extend.py +++ b/graphene_federation/extend.py @@ -20,3 +20,8 @@ def decorator(Type): def external(field): field._external = True return field + + +def requires(field, fields: str): + field._requires = fields + return field diff --git a/graphene_federation/service.py b/graphene_federation/service.py index d74ef6c..d4f5217 100644 --- a/graphene_federation/service.py +++ b/graphene_federation/service.py @@ -7,20 +7,34 @@ from .entity import custom_entities -def _mark_external(entity_name, entity, schema, auto_camelcase): +def _mark_field( + entity_name, entity, schema: str, mark_name: str, decorator: callable, auto_camelcase: bool +): for field_name in dir(entity): field = getattr(entity, field_name, None) - if field is not None and getattr(field, '_external', False): + if field is not None and getattr(field, mark_name, None): # todo write tests on regexp schema_field_name = to_camel_case(field_name) if auto_camelcase else field_name pattern = re.compile( r"(\s%s\s[^\{]*\{[^\}]*\s%s[\s]*:[\s]*[^\s]+)(\s)" % ( entity_name, schema_field_name)) - schema = pattern.sub(r'\g<1> @external ', schema) + schema = pattern.sub(rf'\g<1> {decorator(getattr(field, mark_name))} ', schema) return schema +def _mark_external(entity_name, entity, schema, auto_camelcase): + return _mark_field( + entity_name, entity, schema, '_external', lambda _: '@external', auto_camelcase) + + +def _mark_requires(entity_name, entity, schema, auto_camelcase): + return _mark_field( + entity_name, entity, schema, '_requires', lambda fields: f'@requires(fields: "{fields}")', + auto_camelcase + ) + + def get_sdl(schema, custom_entities): string_schema = str(schema) string_schema = string_schema.replace("\n", " ") @@ -37,6 +51,7 @@ def get_sdl(schema, custom_entities): for entity_name, entity in extended_types.items(): string_schema = _mark_external(entity_name, entity, string_schema, schema.auto_camelcase) + string_schema = _mark_requires(entity_name, entity, string_schema, schema.auto_camelcase) type_def_re = r"type %s ([^\{]*)" % entity_name type_def = r"type %s " % entity_name diff --git a/integration_tests/service_c/src/schema.py b/integration_tests/service_c/src/schema.py index e0d3625..34a9638 100644 --- a/integration_tests/service_c/src/schema.py +++ b/integration_tests/service_c/src/schema.py @@ -1,10 +1,15 @@ from graphene import ObjectType, String, Int, List, NonNull, Field -from graphene_federation import build_schema, extend, external +from graphene_federation import build_schema, extend, external, requires @extend(fields='id') class User(ObjectType): id = external(Int(required=True)) + primary_email = external(String()) + uppercase_email = requires(String(), fields='primaryEmail') + + def resolve_uppercase_email(self, info): + return self.primary_email.upper() if self.primary_email else self.primary_email class Article(ObjectType): diff --git a/integration_tests/tests/tests/test_main.py b/integration_tests/tests/tests/test_main.py index dae0ee4..540ca30 100644 --- a/integration_tests/tests/tests/test_main.py +++ b/integration_tests/tests/tests/test_main.py @@ -118,3 +118,30 @@ def test_multiple_key_decorators_apply_multiple_key_annotations(): def test_avoid_duplication_of_key_decorator(): sdl = fetch_sdl('service_a') assert 'extend type FileNode @key(fields: \"id\") {' in sdl + + +def test_requires(): + query = { + 'query': """ + query { + articles { + id + text + author { + uppercaseEmail + } + } + } + """, + 'variables': {} + } + response = requests.post( + 'http://federation:3000/graphql/', + json=query, + ) + assert response.status_code == 200 + data = json.loads(response.content)['data'] + articles = data['articles'] + + assert articles == [ + {'id': 1, 'text': 'some text', 'author': {'uppercaseEmail': 'NAME_5@GMAIL.COM'}}] From f7be6dc5c34ab3f280f5c56c06081a20747838fb Mon Sep 17 00:00:00 2001 From: Igor Kasianov Date: Thu, 21 Nov 2019 02:09:21 +0200 Subject: [PATCH 2/2] refactor --- README.md | 2 +- graphene_federation/service.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d94008d..a16648b 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ Supports now: ``` * extend # extend remote types * external # mark field as external +* requires # mark that field resolver requires other fields to be pre-fetched Todo implement: -* @requires * @provides diff --git a/graphene_federation/service.py b/graphene_federation/service.py index d4f5217..824d7f2 100644 --- a/graphene_federation/service.py +++ b/graphene_federation/service.py @@ -8,17 +8,19 @@ def _mark_field( - entity_name, entity, schema: str, mark_name: str, decorator: callable, auto_camelcase: bool + entity_name, entity, schema: str, mark_attr_name: str, + decorator_resolver: callable, auto_camelcase: bool ): for field_name in dir(entity): field = getattr(entity, field_name, None) - if field is not None and getattr(field, mark_name, None): + if field is not None and getattr(field, mark_attr_name, None): # todo write tests on regexp schema_field_name = to_camel_case(field_name) if auto_camelcase else field_name pattern = re.compile( r"(\s%s\s[^\{]*\{[^\}]*\s%s[\s]*:[\s]*[^\s]+)(\s)" % ( entity_name, schema_field_name)) - schema = pattern.sub(rf'\g<1> {decorator(getattr(field, mark_name))} ', schema) + schema = pattern.sub( + rf'\g<1> {decorator_resolver(getattr(field, mark_attr_name))} ', schema) return schema