From 16d453b7f0945f7c3e60b71167c165241725e559 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Tue, 23 Aug 2022 15:18:43 +0100 Subject: [PATCH] Update ariadne with new schema (#209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates Ariadne's schema to match the latest schema, as mentioned here #166 I've moved the schema definition to a new file to make it easier to update in future, we could split the files more if needed 😊 Closes #180 --- implementations/ariadne/Dockerfile | 2 +- implementations/ariadne/requirements.txt | 2 +- implementations/ariadne/schema.graphql | 70 ++++++ implementations/ariadne/server.py | 261 ++++++++++++++++------- 4 files changed, 252 insertions(+), 83 deletions(-) create mode 100644 implementations/ariadne/schema.graphql diff --git a/implementations/ariadne/Dockerfile b/implementations/ariadne/Dockerfile index ece4fd78b..fde1404c6 100644 --- a/implementations/ariadne/Dockerfile +++ b/implementations/ariadne/Dockerfile @@ -2,6 +2,6 @@ FROM python:3.9-alpine WORKDIR /web COPY requirements.txt ./ RUN pip install -r requirements.txt -COPY server.py ./ +COPY server.py schema.graphql ./ EXPOSE 4001 CMD python server.py diff --git a/implementations/ariadne/requirements.txt b/implementations/ariadne/requirements.txt index c83c4e94a..743a38dd9 100644 --- a/implementations/ariadne/requirements.txt +++ b/implementations/ariadne/requirements.txt @@ -1,4 +1,4 @@ -ariadne==0.15.1 +ariadne==0.16.0b1 certifi==2020.12.5 click==7.1.2 graphql-core==3.2.1 diff --git a/implementations/ariadne/schema.graphql b/implementations/ariadne/schema.graphql new file mode 100644 index 000000000..0a704a3ec --- /dev/null +++ b/implementations/ariadne/schema.graphql @@ -0,0 +1,70 @@ +extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.0", + import: [ + "@extends", + "@external", + "@key", + "@inaccessible", + "@override", + "@provides", + "@requires", + "@shareable", + "@tag" + ] + ) + +type Product + @key(fields: "id") + @key(fields: "sku package") + @key(fields: "sku variation { id }") { + id: ID! + sku: String + package: String + variation: ProductVariation + dimensions: ProductDimension + createdBy: User @provides(fields: "totalProductsCreated") + notes: String @tag(name: "internal") + research: [ProductResearch!]! +} + +type DeprecatedProduct @key(fields: "sku package") { + sku: String! + package: String! + reason: String + createdBy: User +} + +type ProductVariation { + id: ID! +} + +type ProductResearch @key(fields: "study { caseNumber }") { + study: CaseStudy! + outcome: String +} + +type CaseStudy { + caseNumber: ID! + description: String +} + +type ProductDimension @shareable { + size: String + weight: Float + unit: String @inaccessible +} + +extend type Query { + product(id: ID!): Product + deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead") +} + + +type User @key(fields: "email") @extends { + averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment") + email: ID! @external + name: String @override(from: "users") + totalProductsCreated: Int @external + yearsOfEmployment: Int! @external +} \ No newline at end of file diff --git a/implementations/ariadne/server.py b/implementations/ariadne/server.py index 8aae28674..1c079b5c0 100644 --- a/implementations/ariadne/server.py +++ b/implementations/ariadne/server.py @@ -2,42 +2,114 @@ import uvicorn -from ariadne import QueryType +from ariadne import QueryType, load_schema_from_path from ariadne.asgi import GraphQL from ariadne.contrib.federation import FederatedObjectType, make_federated_schema -type_defs = ''' - type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") { - id: ID! - sku: String - package: String - variation: ProductVariation - dimensions: ProductDimension - createdBy: User @provides(fields: "totalProductsCreated") - } +# ------- data ------- - type ProductDimension { - size: String - weight: Float - } +dimension_data = { + "size": "small", + "weight": 1, + "unit": "kg", +} - type ProductVariation { - id: ID! - } +user_data = { + "email": "support@apollographql.com", + "name": "Jane Smith", + "totalProductsCreated": 1337, + "yearsOfEmployment": 10, +} - type Query { - product(id: ID!): Product - } - type User @key(fields: "email") @extends { - email: ID! @external - totalProductsCreated: Int @external - } -''' +deprecated_product_data = { + "sku": "apollo-federation-v1", + "package": "@apollo/federation-v1", + "reason": "Migrate to Federation V2", + "createdBy": user_data["email"], +} + +products_research_data = [ + { + "study": { + "caseNumber": "1234", + "description": "Federation Study", + }, + "outcome": None, + }, + { + "study": { + "caseNumber": "1235", + "description": "Studio Study", + }, + "outcome": None, + }, +] + + +products_data = [ + { + "id": "apollo-federation", + "sku": "federation", + "package": "@apollo/federation", + "variation": {"id": "OSS"}, + "dimensions": dimension_data, + "research": [products_research_data[0]], + "createdBy": user_data["email"], + "notes": None, + }, + { + "id": "apollo-studio", + "sku": "studio", + "package": "", + "variation": {"id": "platform"}, + "dimensions": dimension_data, + "research": [products_research_data[1]], + "createdBy": user_data["email"], + "notes": None, + }, +] + + +# ------- resolvers ------- + + +def get_product_by_id(id: str) -> Optional[dict]: + return next((product for product in products_data if product["id"] == id), None) + + +def get_product_by_sku_and_package(sku: str, package: str) -> Optional[dict]: + return next( + ( + product + for product in products_data + if product["sku"] == sku and product["package"] == package + ), + None, + ) + + +def get_product_by_sku_and_variation(sku: str, variation: dict) -> Optional[dict]: + return next( + ( + product + for product in products_data + if product["sku"] == sku and product["variation"]["id"] == variation["id"] + ), + None, + ) + + +# ------- schema ------- query = QueryType() product = FederatedObjectType("Product") +deprecated_product = FederatedObjectType("DeprecatedProduct") +product_research = FederatedObjectType("ProductResearch") +user = FederatedObjectType("User") + +schema = load_schema_from_path("schema.graphql") @query.field("product") @@ -45,94 +117,121 @@ def resolve_product(*_, id): return get_product_by_id(id) -@product.field('variation') +@query.field("deprecatedProduct") +def resolve_deprecated_product(*_, sku: str, package: str): + if ( + sku == deprecated_product_data["sku"] + and package == deprecated_product_data["package"] + ): + return deprecated_product_data + + return None + + +# ------- product ------- + + +@product.field("variation") def resolve_product_variation(obj, *_): - return get_product_variation(obj) + if obj["variation"]: + return {"id": obj["variation"]["id"]} + + variation = next( + (product for product in products_data if product["id"] == obj["id"]), None + ) + + return {"id": variation} -@product.field('dimensions') +@product.field("dimensions") def resolve_product_dimensions(*_): - return {'size': 'small', 'weight': 1} + return dimension_data -@product.field('createdBy') +@product.field("createdBy") def resolve_product_created_by(*_): - return {'email': 'support@apollographql.com', - 'totalProductsCreated': 1337} + return user_data @product.reference_resolver def resolve_product_reference(_, _info, representation): if "sku" in representation and "package" in representation: - return get_product_by_sku_and_package(representation['sku'], representation['package']) + return get_product_by_sku_and_package( + representation["sku"], representation["package"] + ) if "sku" in representation and "variation" in representation: - return get_product_by_sku_and_variation(representation['sku'], representation['variation']) - return get_product_by_id(representation['id']) + return get_product_by_sku_and_variation( + representation["sku"], representation["variation"] + ) + return get_product_by_id(representation["id"]) -schema = make_federated_schema(type_defs, [query, product]) -application = GraphQL(schema) +# ------- deprecated product ------- -products = [ - { - "id": "apollo-federation", - "sku": "federation", - "package": "@apollo/federation", - "variation": "OSS", - }, - { - "id": "apollo-studio", - "sku": "studio", - "package": "", - "variation": "platform", - }, -] +@deprecated_product.reference_resolver +def resolve_deprecated_product_reference(_, _info, representation): + data = deprecated_product_data -def get_product_variation(reference): - if reference['variation']: - return {'id': reference['variation']} - variation = next((product for product in products if product['id'] - == reference['id']), None) - return {'id': variation} + if ( + representation["sku"] == data["sku"] + and representation["package"] == data["package"] + ): + return data + return None -def get_product_by_id(id): - return next((product for product in products if product['id'] - == id), None) +# ------- product research ------- -def get_product_by_sku_and_package(sku: str, package: str) -> Optional[dict]: - data = next( + +@product_research.reference_resolver +def resolve_product_research_reference(_, _info, representation): + case_number = representation["study"]["caseNumber"] + + return next( ( - product - for product in products - if product["sku"] == sku and product["package"] == package + research + for research in products_research_data + if research["study"]["caseNumber"] == case_number ), None, ) - if not data: - return None - return data +# ------- user ------- -def get_product_by_sku_and_variation(sku: str, variation: dict) -> Optional[dict]: - data = next( - ( - product - for product in products - if product["sku"] == sku and product["variation"] == variation["id"] - ), - None, - ) +@user.reference_resolver +def resolve_user_reference(_, _info, representation): + if email := representation.get("email"): + user = { + "email": email, + "name": "Jane Smith", + "totalProductsCreated": 1337, + } + + if yearsOfEmployment := representation.get("yearsOfEmployment"): + user["yearsOfEmployment"] = yearsOfEmployment + + return user - if not data: - return None + return None - return data + +@user.field("averageProductsCreatedPerYear") +def resolve_user_average_products_created_per_year(obj, info): + if obj.get("totalProductCreated") is None: + return 0 + + return obj["totalProductsCreated"] / obj["yearsOfEmployment"] + + +schema = make_federated_schema( + schema, [query, product, deprecated_product, product_research, user] +) +application = GraphQL(schema) if __name__ == "__main__": - uvicorn.run(application, host='0.0.0.0', port=4001) + uvicorn.run(application, host="0.0.0.0", port=4001)