Skip to content
This repository has been archived by the owner on Oct 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #24 from preply/provides-support
Browse files Browse the repository at this point in the history
Provides support
  • Loading branch information
erebus1 authored Apr 19, 2020
2 parents 583c097 + daea0c6 commit 32413d7
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 4 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@ 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:
* @provides
* provides # to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway.
* **Base class should be decorated with `@provides`** as well as field on a base type that provides. Check example bellow:
```python
import graphene
from graphene_federation import provides

@provides
class ArticleThatProvideAuthorAge(graphene.ObjectType):
id = Int(required=True)
text = String(required=True)
author = provides(Field(User), fields='age')
```


```python
Expand Down
1 change: 1 addition & 0 deletions graphene_federation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .main import build_schema
from .entity import key
from .extend import extend, external, requires
from .provides import provides
19 changes: 19 additions & 0 deletions graphene_federation/provides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from graphene import Field

provides_parent_types = set()


def provides(field, fields: str = None):
"""
:param field: base type (when used as decorator) or field of base type
:param fields:
:return:
"""
if fields is None: # used as decorator on base type
if isinstance(field, Field):
raise RuntimeError("Please specify fields")
provides_parent_types.add(field)
else: # used as wrapper over field
field._provides = fields
return field
12 changes: 12 additions & 0 deletions graphene_federation/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from graphene.utils.str_converters import to_camel_case

from graphene_federation.extend import extended_types
from graphene_federation.provides import provides_parent_types
from .entity import custom_entities


Expand Down Expand Up @@ -37,6 +38,13 @@ def _mark_requires(entity_name, entity, schema, auto_camelcase):
)


def _mark_provides(entity_name, entity, schema, auto_camelcase):
return _mark_field(
entity_name, entity, schema, '_provides', lambda fields: f'@provides(fields: "{fields}")',
auto_camelcase
)


def get_sdl(schema, custom_entities):
string_schema = str(schema)
string_schema = string_schema.replace("\n", " ")
Expand All @@ -51,6 +59,10 @@ def get_sdl(schema, custom_entities):
pattern = re.compile(type_def_re)
string_schema = pattern.sub(repl_str, string_schema)

for entity in provides_parent_types:
string_schema = _mark_provides(
entity.__name__, entity, string_schema, schema.auto_camelcase)

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)
Expand Down
4 changes: 4 additions & 0 deletions integration_tests/service_b/src/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def __resolve_reference(self, info, **kwargs):
class User(ObjectType):
id = Int(required=True)
primary_email = String()
age = Int()

def resolve_age(self, info):
return 17

def __resolve_reference(self, info, **kwargs):
if self.id is not None:
Expand Down
25 changes: 24 additions & 1 deletion integration_tests/service_c/src/schema.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from graphene import ObjectType, String, Int, List, NonNull, Field
from graphene_federation import build_schema, extend, external, requires, key
from graphene_federation import build_schema, extend, external, requires, key, provides


@extend(fields='id')
class User(ObjectType):
id = external(Int(required=True))
primary_email = external(String())
uppercase_email = requires(String(), fields='primaryEmail')
age = external(Int())

def resolve_uppercase_email(self, info):
return self.primary_email.upper() if self.primary_email else self.primary_email

def resolve_age(self, info):
return 18


@key(fields='id')
class Article(ObjectType):
Expand All @@ -22,13 +26,32 @@ def __resolve_reference(self, info, **kwargs):
return Article(id=self.id, text=f'text_{self.id}')


@provides
class ArticleThatProvideAuthorAge(ObjectType):
"""
should not contain other graphene-federation decorators to proper test test-case
"""
id = Int(required=True)
text = String(required=True)
author = provides(Field(User), fields='age')

def __resolve_reference(self, info, **kwargs):
return Article(id=self.id, text=f'text_{self.id}')


class Query(ObjectType):
articles = List(NonNull(lambda: Article))
articles_with_author_age_provide = List(NonNull(lambda: ArticleThatProvideAuthorAge))

def resolve_articles(self, info):
return [
Article(id=1, text='some text', author=User(id=5))
]

def resolve_articles_with_author_age_provide(self, info):
return [
ArticleThatProvideAuthorAge(id=1, text='some text', author=User(id=5))
]


schema = build_schema(Query)
44 changes: 44 additions & 0 deletions integration_tests/tests/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,47 @@ def test_requires():

assert articles == [
{'id': 1, 'text': 'some text', 'author': {'uppercaseEmail': '[email protected]'}}]


def test_provides():
"""
articles -> w/o provide (get age value from service b)
articlesWithAuthorAgeProvide -> w/ provide (get age value from service c)
:return:
"""
query = {
'query': """
query {
articles {
id
text
author {
age
}
}
articlesWithAuthorAgeProvide {
id
text
author {
age
}
}
}
""",
'variables': {}
}
response = requests.post(
'http://federation:3000/graphql/',
json=query,
)
assert response.status_code == 200
data = json.loads(response.content)['data']
articles = data['articles']
articles_with_age_provide = data['articlesWithAuthorAgeProvide']

assert articles == [
{'id': 1, 'text': 'some text', 'author': {'age': 17}}]

assert articles_with_age_provide == [
{'id': 1, 'text': 'some text', 'author': {'age': 18}}]

0 comments on commit 32413d7

Please sign in to comment.