# Step 1: Setup your Puff project Follow the instructions on how to setup a new poetry project and install Puff: * [Using the CLI](https://github.com/hansonkd/puff/blob/master/book/CLI.md) # Step 2: Add Django and Create a Project ```bash poetry add django poetry run django-admin startproject hello_world_django_app . ``` Update `puff.toml` with the following lines ```toml wsgi = "hello_world_django_app.wsgi.application" redis = true postgres = true django = true ``` Create a `.env` file to help manage your environment: ```python DJANGO_SETTINGS_MODULE=hello_world_django_app.settings ``` Change `hello_world_django_app/wsgi.py` to serve static files: ```python ... from django.core.wsgi import get_wsgi_application from django.contrib.staticfiles.handlers import StaticFilesHandler application = StaticFilesHandler(get_wsgi_application()) ``` Update `hello_world_django_app/settings.py` so that Django uses the Puff Database and Redis pools: ```python DATABASES = { 'default': { 'ENGINE': 'puff.contrib.django.postgres', 'NAME': 'global', } } CACHES = { 'default': { 'BACKEND': 'puff.contrib.django.redis.PuffRedisCache', 'LOCATION': 'redis://global', } } ``` Run the server: ```bash poetry run puff django migrate poetry run puff serve ``` To use Puff in development mode, use `puff-watch` which is a wrapper around puff that will restart the process when files change in the directory. ```bash poetry run puff-watch serve # Spawn multiple processes on the same Port to simulate gunicorn workers PUFF_REUSE_PORT=true poetry run puff-watch --num 2 serve ``` # Step 3: Add GraphQL Create a new file called `my_puff_project/graphql.py` ```python from typing import Optional, Iterable, List, Any, Tuple from dataclasses import dataclass from puff import graphql from puff.pubsub import global_pubsub as pubsub from puff.contrib.django import query_and_params import django from django.contrib.auth import get_user_model # Setup Django since Puff will import GraphQL first. django.setup() User = get_user_model() @dataclass class UserType: id: int username: str email: str @dataclass class Query: @classmethod @graphql.borrow_db_context def lookup_user(cls, context, /, user_id: int) -> Optional[UserType]: # Lookup a user by their user_id # borrow_db_context lets Django have access to Puff Connection to use for query return User.objects.filter(id=user_id).first() @classmethod def list_users(cls, context, /) -> Tuple[List[UserType], str, List[Any]]: # List all Users. Offload the query into the puff runtime. # The ellipsis is a placeholder allowing the Python type system to know which field type it should transform into. query, params = query_and_params(User.objects.order_by('id').all()) return ..., query, params @classmethod def auth_token(cls, context, /) -> Optional[str]: # All GraphQL queries have access to the Bearer token if set. return context.auth_token() @classmethod def new_connection_id(cls, context, /) -> str: # Get a new connection identifier for pubsub. return pubsub.new_connection_id() @dataclass class Mutation: @classmethod def send_message_to_channel(cls, context, /, connection_id: str, channel: str, message: str) -> bool: return pubsub.publish_as(connection_id, channel, message) @dataclass class MessageObject: message_text: str from_connection_id: str num_processed: int @dataclass class Subscription: @classmethod def read_messages_from_channel(cls, context, /, channel: str, connection_id: Optional[str] = None) -> Iterable[MessageObject]: if connection_id is not None: conn = pubsub.connection_with_id(connection_id) else: conn = pubsub.connection() conn.subscribe(channel) num_processed = 0 while msg := conn.receive(): from_connection_id = msg.from_connection_id # Filter out messages from yourself. if connection_id != from_connection_id: yield MessageObject(message_text=msg.text, from_connection_id=from_connection_id, num_processed=num_processed) num_processed += 1 @dataclass class Schema: query: Query mutation: Mutation subscription: Subscription ``` Update `puff.toml` with the following lines ```toml pubsub = true graphql_schema = "my_puff_project.graphql.Schema" graphql_url = "/graphql/" graphql_subscription_url = "/subscriptions/" graphql_playground_url = "/playground/" ``` Run the server and go to `http://localhost:7777/playground/` # Step 4: Add Distributed Tasks Update `puff.toml` with the following lines ```toml task_queue = true ``` Create a new file called `my_puff_project/tasks.py` ```python def my_task(payload): print(f"Inside `my_task` with payload: {payload}") return {"from_task": payload} ``` Change your `Mutation` class in `graphql.py` to call the function through the queue: ```python from puff.task_queue import global_task_queue as tq from my_puff_project.tasks import my_task ... @dataclass class Mutation: @classmethod def send_message_to_channel(cls, context, /, connection_id: str, channel: str, message: str) -> bool: tq.add_task(my_task, {"auth_token": context.auth_token(), "connection_id": connection_id, "channel": channel, "message": message}) return pubsub.publish_as(connection_id, channel, message) ``` Run a worker server by using wait_forever in another tab: ```bash poetry run puff wait_forever ``` or use `puff-watch` to spawn multiple workers: ```bash poetry run puff-watch --num 2 wait_forever ``` Now run the server. Going to `http://localhost:7777/graphql/` calling the mutation will trigger tasks. ```bash poetry run puff serve ```