From 0f491aa90b62ed358810254c44d14c76aa7a3518 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:55:52 +0530 Subject: [PATCH] docs: update readme --- README.md | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 217 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e68dde3..9c61e00 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Federation support for ![Graphene Logo](http://graphene-python.org/favicon.png) This repository is heavily based on the repo it was forked from... Huge thanks to [Preply for setting up the foundations](https://medium.com/preply-engineering/apollo-federation-support-in-graphene-761a0512456d). + WARNING: This version is not compatible with `graphene` version below v3. If you need to use a version compatible with `graphene` v2 I recommend using the version 1.0.0 of `graphene_federation`. @@ -26,14 +27,82 @@ If you need to use a version compatible with `graphene` v2 I recommend using the ## Supported Features -At the moment it supports: - * `sdl` (`_service` on field): enable to add schema in federation (as is) -* `@key` decorator (entity support): enable to perform queries across service boundaries (you can have more than one key per type) -* `@extends`: extend remote types -* `external()`: mark a field as external -* `requires()`: mark that field resolver requires other fields to be pre-fetched -* `provides()`/`@provides`: annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. + +## Apollo Spec Supported + +- [x] v1.0 +- [x] v2.0 +- [x] v2.1 +- [x] v2.2 +- [x] v2.3 +- [x] v2.4 +- [x] v2.5 +- [x] v2.6 + +All directives could be easily integrated with the help of [graphene-directives](https://github.com/strollby/graphene-directives). +Now every directive's values are validated at run time itself by [graphene-directives](https://github.com/strollby/graphene-directives). + +### Directives (v2.6) + +```graphql +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: [[Scope!]!]!) on + FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM +directive @policy(policies: [[federation__Policy!]!]!) on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM +scalar federation__Policy +scalar Scope +scalar FieldSet + +``` + +Read about directives in [official documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives) + Each type which is decorated with `@key` or `@extends` is added to the `_Entity` union. The [`__resolve_reference` method](https://www.apollographql.com/docs/federation/api/apollo-federation/#__resolvereference) can be defined for each type that is an entity. @@ -58,8 +127,10 @@ First add an account service that expose a `User` type that can then be referenc ```python from graphene import Field, Int, ObjectType, String + from graphene_federation import build_schema, key + @key("id") class User(ObjectType): id = Int(required=True) @@ -71,10 +142,12 @@ class User(ObjectType): """ return User(id=self.id, email=f"user_{self.id}@mail.com") + class Query(ObjectType): me = Field(User) -schema = build_schema(query=Query) + +schema = build_schema(query=Query, enable_federation_2=True) ``` ### Product @@ -82,8 +155,10 @@ The product service exposes a `Product` type that can be used by other services ```python from graphene import Argument, Int, List, ObjectType, String + from graphene_federation import build_schema, key + @key("upc") class Product(ObjectType): upc = String(required=True) @@ -96,10 +171,12 @@ class Product(ObjectType): """ 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) + +schema = build_schema(query=Query, enable_federation_2=True) ``` ### Reviews @@ -110,7 +187,7 @@ On top of that it adds to the `User`/`Product` types (that are both defined in o ```python from graphene import Field, Int, List, ObjectType, String -from graphene_federation import build_schema, extends, external, key, provides +from graphene_federation import build_schema, external, key, provides @key("id") @@ -141,7 +218,7 @@ class Query(ObjectType): review = Field(Review) -schema = build_schema(query=Query) +schema = build_schema(query=Query, enable_federation_2=True) ``` ### Federation @@ -171,8 +248,136 @@ You can find more examples in the unit / integration tests and [examples folder] There is also a cool [example](https://github.com/preply/graphene-federation/issues/1) 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 + +```python +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 + + +```graphql +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) +} +``` + +If you wish to add the schema_directives `@link` `@composeDirective` manually. +You can pass the `add_to_schema_directives` as `False` + +```python +from graphene import Field, ObjectType, String +from graphql import GraphQLArgument, GraphQLInt, GraphQLNonNull + +from graphene_federation import DirectiveLocation, FederationDirective, build_schema, compose_directive, link_directive + +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.", + add_to_schema_directives=False +) + +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,), + schema_directives=( + link_directive(url="https://specs.example.dev/directives/v1.0", import_=['@cache']), + compose_directive(name='@cache'), + ), + enable_federation_2=True, +) +``` -## Custom field name +### Custom field name When using decorator on a field with custom name