Slide 1

Slide 1 text

GraphQL in Python Marcin Gębala - PyCon APAC 2024

Slide 2

Slide 2 text

About me » Principal Developer at Saleor Commerce (saleor.io) » Specialize in web development using Python, Django and GraphQL » Based in Wrocław, Poland ! Marcin Gębala - PyCon APAC 2024

Slide 3

Slide 3 text

What is GraphQL? Marcin Gębala - PyCon APAC 2024

Slide 4

Slide 4 text

GraphQL GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. » Alternative to REST » Fetching only data that is needed » Combining resources in a single request » Strong typing » Backend agnostic » Open-source Marcin Gębala - PyCon APAC 2024

Slide 5

Slide 5 text

Use-cases » Building APIs for web and mobile apps » Works great with single-page apps! » Apollo Client + React + Typescript » Code generation » Federations » Combining multiple services under one gateway » Communication between microservices - possible but not that common and practical Marcin Gębala - PyCon APAC 2024

Slide 6

Slide 6 text

Components of a GraphQL API » Schema - defines the structure of the API » Resolvers - functions that return data for queries and mutations def resolve_user(obj, info, **kwargs): user_id = kwargs["id"] return db.get_user_by_id(user_id) schema { query: Query mutation: Mutation } type Query { users: [User!]! user(id: ID!): User posts: [Post!]! } type Mutation { createPost(input: PostData!): CreatePostResponse! } type User { id: ID! email: String! name: String } type Post { id: ID! user: User! content: String! createdAt: String! } input PostData { user: ID! content: String! } type CreatePostResponse { error: String post: Post } Marcin Gębala - PyCon APAC 2024

Slide 7

Slide 7 text

Schema-first vs code-first Two approaches to building GraphQL APIs: - Schema-first - explicitly define the schema, then implement the resolvers to comply with the schema - Code-first - define the schema by writing classes/functions, then generate the schema from the code Marcin Gębala - PyCon APAC 2024

Slide 8

Slide 8 text

Python ecosystem Marcin Gębala - PyCon APAC 2024

Slide 9

Slide 9 text

Ariadne Library for building GraphQL servers in Python. » Schema-first » Lightweight and simple » Asynchronous » Supports GraphQL modules and generating Python API clients github.com/mirumee/ariadne Marcin Gębala - PyCon APAC 2024

Slide 10

Slide 10 text

Schema definition from ariadne import make_executable_schema, MutationType, ObjectType, QueryType type_defs = """ type Query { user(id: ID!): User } type Mutation { createPost(userId: ID!, content: String!): Post! } type User { id: ID! email: String! name: String posts: [Post!]! } type Post { id: ID! user: User! content: String! createdAt: String! } """ mutation = MutationType() query = QueryType() post = ObjectType("Post") user = ObjectType("User") schema = make_executable_schema(type_defs, mutation, query, user, post) Marcin Gębala - PyCon APAC 2024

Slide 11

Slide 11 text

Resolvers @query.field("user") def resolve_user(*_, **kwargs): user_id = kwargs["id"] return db.get_user_by_id(user_id) @mutation.field("createPost") def resolve_create_post(*_, userId, content): error = None post = None try: post = db.create_post(userId, content) except Exception as e: error = str(e) return post Marcin Gębala - PyCon APAC 2024

Slide 12

Slide 12 text

Running the app Ariadne provides both ASGI and WSGI application that can be mounted in any ASGI-compatible (WSGI-compatible) framework. from ariadne.asgi import GraphQL from fastapi import FastAPI from .schema import schema app = FastAPI() app.mount("/graphql/", GraphQL(schema=schema)) Marcin Gębala - PyCon APAC 2024

Slide 13

Slide 13 text

Graphene High-level framework for building GraphQL APIs in Python. » Code-first approach - generating GraphQL schema from Python classes » Rich ecosystem of libraries and integrations (Django, Flask, SQLAlchemy, Mongo) graphene-python.org github.com/graphql-python/graphene Marcin Gębala - PyCon APAC 2024

Slide 14

Slide 14 text

Queries from graphene import ObjectType, Schema class Query(ObjectType): user = graphene.Field(User, id=graphene.ID(required=True)) users = graphene.List(types.User) @staticmethod def resolve_user(root, info, id): return models.User.objects.filter(id=id).first() @staticmethod def resolve_users(root, info): return models.User.objects.all() schema = Schema(query=Query) Marcin Gębala - PyCon APAC 2024

Slide 15

Slide 15 text

Types from graphene import DjangoObjectType class User(DjangoObjectType): credit_card_number = graphene.Field(String, description="User's credit card number.") class Meta: description = "Represents a user." model = models.User only_fields = [ "created_at", "email", "profile_picture_url", ] @staticmethod def resolve_credit_card_number(root: models.User, *_): # Custom resolver to anonymize the credit card number. return anonymize_credit_card(root.credit_card) Marcin Gębala - PyCon APAC 2024

Slide 16

Slide 16 text

Mutations class UserCreate(graphene.Mutation): user = graphene.Field(types.User) errors = graphene.List(Error, required=True, default_value=[]) class Arguments: input = UserCreateInput(required=True) @classmethod def mutate(cls, root, info, input): user = models.User(**input) try: user.full_clean() except ValidationError as e: errors = validation_error_to_error_type(e) return UserCreate(errors=errors) user.save() send_activation_email(user) return UserCreate(user=user) Marcin Gębala - PyCon APAC 2024

Slide 17

Slide 17 text

Serving the schema Expose the API using Django views: from django.urls import path from graphene_django.views import GraphQLView urlpatterns = [ path("graphql", GraphQLView.as_view()), ] Marcin Gębala - PyCon APAC 2024

Slide 18

Slide 18 text

Common patterns Marcin Gębala - PyCon APAC 2024

Slide 19

Slide 19 text

Authentication JSON Web Token authentication is provided by django-graphql-jwt package: mutation { tokenCreate(email: "[email protected]", password: "secret") { token refreshToken user { id email } } } Authenticating requests with the Authorization header: { "Authorization": "JWT jwt-token-value" } Marcin Gębala - PyCon APAC 2024

Slide 20

Slide 20 text

Permissions django-graphql-jwt also provides decorators to restrict access to particular fields in the schema. # types.py from graphql_jwt.decorators import permission_required class User(DjangoObjectType): credit_card_number = graphene.Field(String, description="User's credit card number.") @permission_required("user.manage_users") def resolve_credit_card_number(root, info): return anonymize_credit_card(root.credit_card) Marcin Gębala - PyCon APAC 2024

Slide 21

Slide 21 text

N+1 problem Without optimization, the following query could result in duplicated database queries: { posts { name user { email } } } SELECT * FROM "blog_post"; SELECT * FROM "user" WHERE "blog_post"."id" = 1; SELECT * FROM "user" WHERE "blog_post"."id" = 2; SELECT * FROM "user" WHERE "blog_post"."id" = 3; ... SELECT * FROM "user" WHERE "blog_post"."id" = N; Marcin Gębala - PyCon APAC 2024

Slide 22

Slide 22 text

Data loaders Lazy loading of related objects using data loaders. class Post(DjangoObjectType): user = graphene.Field(User, description="Author of the post.") @staticmethod def resolve_user(root: models.Product, info, *_): return UserByIdLoader(info.context).load(root.user_id) from promise import Promise from promise.dataloader import DataLoader class UserByIdLoader(DataLoader): def batch_load_fn(self, user_ids): # in_bulk creates a dictionary of {id: , ...} users = models.User.objects.in_bulk(keys) results = [users.get(user_id) for user_id in user_ids] return Promise.resolve(results) Marcin Gębala - PyCon APAC 2024

Slide 23

Slide 23 text

Versioning » By design GraphQL APIs doesn't use versioning. » Evolve the schema by adding new fields, types and queries. » Use deprecation mechanism for backward-compatibility type Query { user(id: ID!): User @deprecated(reason: "Use `userById` instead.") userById(id: ID!): User } Marcin Gębala - PyCon APAC 2024

Slide 24

Slide 24 text

Resources » Books: » "Learning GraphQL" by Eve Porcello, Alex Banks » "Production Ready GraphQL" by Marc-Andre Giroux » Example implementations: » Saleor GraphQL API: github.com/saleor/saleor » Ariadne demo: github.com/maarcingebala/ariadne-demo » Libraries: » Graphene: graphene-python.org » Ariadne: ariadnegraphql.org Marcin Gębala - PyCon APAC 2024

Slide 25

Slide 25 text

Thank you! Contact: github.com/maarcingebala Scan the QR code to access the slides. Marcin Gębala - PyCon APAC 2024