Skip to content

Releases: strollby/graphene-federation

[3.1.4-beta.14] @sharable fixes

22 Apr 17:40
Compare
Choose a tag to compare
  • Fixed @sharable is applied multiple times on PageInfo in multithreaded environments
  • Fixed: incorrect @sharable definition in federation v2.2

[3.1.4-beta.13] - Fix type conversion when using @requires

22 Mar 09:43
Compare
Choose a tag to compare

Added type conversion when using @requires

For example

class XYZ:
	
	a = external(graphene.Enum.from_enum(TestEnum)
	b = external(JSONString)
	c = external(graphene.Field(ABCObjectType))
	d = external(graphene.Date())
	e = external(graphene.DateTime())
	f = external(graphene.Decimal())
	g = external(graphene.UUID())

	new_field = requires(graphene.Int(), fields =a b c d e f g”)

	def resolve_new_field(self, info) -> int:
		self.a # Old: str, New: TestEnum.
		
		self.b # Old: str, New: dict
    		self.c # Old: dict, New: ABCObjectType
    		self.d # Old: str, New: datetime.datetime
		self.e # Old: str, New: datetime.date
		self.f  # Old: str, New: Decimal
		self.g # Old: str, New: UUID
		
		return 10	
  • All the types are build using official methods of graphene_library

[3.1.4-beta.12] - Support for federation v2.7

08 Mar 19:44
Compare
Choose a tag to compare
  • Added support for federation v2.7 changes
  • @override added an optional parameter label
directive @override(from: String!, label: String) on FIELD_DEFINITION

v3.1.4-beta.11 - Graphene Federation v2.6

16 Jan 15:44
Compare
Choose a tag to compare

Supported versions, v1.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6

All directives are purely based on apollo-specs.

Some non existent directive like@extend which acted like @key was deprecated.

Migration Guide

For V1 @extend can be replaced by @extends and @key combination
For V2 just @key is enough.

Apollo Spec Supported

  • v1.0
  • v2.0
  • v2.1
  • v2.2
  • v2.3
  • v2.4
  • v2.5
  • v2.6

All directives could be easily integrated with the help of graphene-directives.
Now every directive's values are validated at run time itself by graphene-directives.

Directives (v2.6)

directive @composeDirective(name: String!) repeatable on SCHEMA
directive @extends on OBJECT | INTERFACE
directive @external on OBJECT | FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @inaccessible on
  | FIELD_DEFINITION
  | OBJECT
  | INTERFACE
  | UNION
  | ENUM
  | ENUM_VALUE
  | SCALAR
  | INPUT_OBJECT
  | INPUT_FIELD_DEFINITION
  | ARGUMENT_DEFINITION
directive @interfaceObject on OBJECT
directive @override(from: String!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @shareable repeatable on FIELD_DEFINITION | OBJECT
directive @tag(name: String!) repeatable on
  | FIELD_DEFINITION
  | INTERFACE
  | OBJECT
  | UNION
  | ARGUMENT_DEFINITION
  | SCALAR
  | ENUM
  | ENUM_VALUE
  | INPUT_OBJECT
  | INPUT_FIELD_DEFINITION
directive @authenticated on
    FIELD_DEFINITION
  | OBJECT
  | INTERFACE
  | SCALAR
  | ENUM
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on
    FIELD_DEFINITION
  | OBJECT
  | INTERFACE
  | SCALAR
  | ENUM
directive @policy(policies: [[federation__Policy!]!]!) on
  | FIELD_DEFINITION
  | OBJECT
  | INTERFACE
  | SCALAR
  | ENUM
scalar federation__Policy
scalar federation__Scope
scalar FieldSet

Read about directives in official documentation

Each type which is decorated with @key or @extends is added to the _Entity union.
The __resolve_reference method can be defined for each type that is an entity.
Note that since the notation with double underscores can be problematic in Python for model inheritance this resolver method can also be named _resolve_reference (the __resolve_reference method will take precedence if both are declared).

This method is called whenever an entity is requested as part of the fulfilling a query plan.
If not explicitly defined, the default resolver is used.
The default resolver just creates instance of type with passed fieldset as kwargs, see entity.get_entity_query for more details

  • You should define __resolve_reference, if you need to extract object before passing it to fields resolvers (example: FileNode)
  • You should not define __resolve_reference, if fields resolvers need only data passed in fieldset (example: FunnyText)
    Read more in official documentation.

Example

Here is an example of implementation based on the Apollo Federation introduction example.
It implements a federation schema for a basic e-commerce application over three services: accounts, products, reviews.

Accounts

First add an account service that expose a User type that can then be referenced in other services by its id field:

from graphene import Field, Int, ObjectType, String

from graphene_federation import build_schema, key


@key("id")
class User(ObjectType):
    id = Int(required=True)
    username = String(required=True)

    def __resolve_reference(self, info, **kwargs):
        """
        Here we resolve the reference of the user entity referenced by its `id` field.
        """
        return User(id=self.id, email=f"user_{self.id}@mail.com")


class Query(ObjectType):
    me = Field(User)


schema = build_schema(query=Query, enable_federation_2=True)

Product

The product service exposes a Product type that can be used by other services via the upc field:

from graphene import Argument, Int, List, ObjectType, String

from graphene_federation import build_schema, key


@key("upc")
class Product(ObjectType):
    upc = String(required=True)
    name = String(required=True)
    price = Int()

    def __resolve_reference(self, info, **kwargs):
        """
        Here we resolve the reference of the product entity referenced by its `upc` field.
        """
        return Product(upc=self.upc, name=f"product {self.upc}")


class Query(ObjectType):
    topProducts = List(Product, first=Argument(Int, default_value=5))


schema = build_schema(query=Query, enable_federation_2=True)

Reviews

The reviews service exposes a Review type which has a link to both the User and Product types.
It also has the ability to provide the username of the User.
On top of that it adds to the User/Product types (that are both defined in other services) the ability to get their reviews.

from graphene import Field, Int, List, ObjectType, String

from graphene_federation import build_schema, external, key, provides


@key("id")
class User(ObjectType):
    id = external(Int(required=True))
    reviews = List(lambda: Review)

    def resolve_reviews(self, info, *args, **kwargs):
        """
        Get all the reviews of a given user. (not implemented here)
        """
        return []


@key("upc")
class Product(ObjectType):
    upc = external(String(required=True))
    reviews = List(lambda: Review)


class Review(ObjectType):
    body = String()
    author = provides(Field(User), fields="username")
    product = Field(Product)


class Query(ObjectType):
    review = Field(Review)


schema = build_schema(query=Query, enable_federation_2=True)

Federation

Note that each schema declaration for the services is a valid graphql schema (it only adds the _Entity and _Service types).
The best way to check that the decorator are set correctly is to request the service sdl:

from graphql import graphql

query = """
query {
    _service {
        sdl
    }
}
"""

result = graphql(schema, query)
print(result.data["_service"]["sdl"])

Those can then be used in a federated schema.

You can find more examples in the unit / integration tests and examples folder.

There is also a cool example of integration with Mongoengine.


Other Notes

build_schema new arguments

  • schema_directives (Collection[SchemaDirective]): Directives that can be defined at DIRECTIVE_LOCATION.SCHEMA with their argument values.
  • include_graphql_spec_directives (bool): Includes directives defined by GraphQL spec (@include, @skip, @deprecated, @specifiedBy)
  • enable_federation_2 (bool): Whether to enable federation 2 directives (default False)
  • federation_version (FederationVersion): Specify the version explicit (default LATEST_VERSION)

In case both enable_federation_2 and federation_version are specified, federation_version is given higher priority

Directives Additional arguments

  • federation_version: (FederationVersion = LATEST_VERSION) : You can use this to take a directive from a particular federation version

Note: The federation_version in build_schema is given higher priority. If the directive you have chosen is not compatible, it will raise an error

Custom Directives

You can define custom directives as follows

from graphene import Field, ObjectType, String
from graphql import GraphQLArgument, GraphQLInt, GraphQLNonNull

from graphene_federation import DirectiveLocation, FederationDirective
from graphene_federation import build_schema

CacheDirective = FederationDirective(
    name="cache",
    locations=[DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT],
    args={
        "maxAge": GraphQLArgument(
            GraphQLNonNull(GraphQLInt), description="Specifies the maximum age for cache in seconds."
        ),
    },
    description="Caching directive to control cache behavior.",
    spec_url="https://specs.example.dev/directives/v1.0",
)

cache = CacheDirective.decorator()


@cache(max_age=20)
class Review(ObjectType):
    body = cache(field=String(),max_age=100)


class Query(ObjectType):
    review = Field(Review)


schema = build_schema(
    query=Query,
    directives=(CacheDirective,),
    enable_federation_2=True,
)

This will automatically add @link and @composeDirective to schema

extend schema
	@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@composeDirective"])
	@link(url: "https://specs.example.dev/directives/v1.0", import: ["@cache"])
	@composeDirective(name: "@cache")

"""Caching directive to control cache behavior."""
directive @cache(
  """Specifies the maximum age for cache in seconds."""
  maxAge: Int!
) on FIELD_DEFINITION | OBJECT

type Query {
  review: Review
  _service: _Service!
}

type Review  @cache(maxAge: 20) {
  body: String @cache(maxAge: 100)
}

...

Read more

[v3.1.4-beta.10] Support @external directive on OBJECT

10 Jan 08:05
Compare
Choose a tag to compare

v3.1.4-beta.9 - Fix @key enum not supported



08 Jan 20:16
Compare
Choose a tag to compare
  • fix: enum type not allowed in @key

Full Changelog: 3.1.4-beta.8...3.1.4-beta.9

v3.1.4-beta.8 - Fix @requires bugs

08 Jan 18:50
Compare
Choose a tag to compare
  • fix: when @requires is used, resolvers receive dict of fields instead of a graphql type
  • fix: graphene-federation does not work without directives in federation version >= 2.1
  • fix: schema white spacing issues
    Full Changelog: 3.1.4-beta.7...3.1.4-beta.8

v3.1.4-beta.7 - Fix recursion error when resolving nested entities

03 Jan 13:52
666e5ce
Compare
Choose a tag to compare
fix(Schema): Entities recursion error fixed

v3.1.4-beta.6 - Support for ComposeDirective, Federation versions

01 Jan 19:34
f3ba390
Compare
Choose a tag to compare

v3.1.4-beta.5 - Support for usage of Pre-built schema

29 Dec 10:33
859ce3b
Compare
Choose a tag to compare