diff --git a/tutorials/backend/backend-stack/tutorial-site/content/languages/java.md b/tutorials/backend/backend-stack/tutorial-site/content/languages/java.md index 4c902914f..65f30f908 100644 --- a/tutorials/backend/backend-stack/tutorial-site/content/languages/java.md +++ b/tutorials/backend/backend-stack/tutorial-site/content/languages/java.md @@ -1,6 +1,6 @@ --- -title: "GraphQL APIs with Java: Tutorial with server examples" -metaTitle: "GraphQL APIs with Java: Tutorial with server examples" +title: "GraphQL with Java: Tutorial with server and API examples" +metaTitle: "GraphQL with Java: Tutorial with server and API examples" metaDescription: "In this tutorial, learn how to integrate Java with GraphQL using various frameworks like Springboot and Netflix DGS along with performance and security considerations." --- diff --git a/tutorials/backend/backend-stack/tutorial-site/content/languages/python.md b/tutorials/backend/backend-stack/tutorial-site/content/languages/python.md index 547125f5a..a20b9b62a 100644 --- a/tutorials/backend/backend-stack/tutorial-site/content/languages/python.md +++ b/tutorials/backend/backend-stack/tutorial-site/content/languages/python.md @@ -1,32 +1,111 @@ --- -title: "Python" -metaTitle: "GraphQL Server with Python | Backend Tutorial" -metaDescription: "In this tutorial, learn how to integrate Python in a GraphQL backend server stack with Hasura" +title: "GraphQL with Python: Tutorial with server and API examples" +metaTitle: "GraphQL with Python: Tutorial with server and API examples" +metaDescription: "In this tutorial, learn how to integrate Python with GraphQL using various frameworks like Graphene and Strawberry along with performance and security considerations." --- -## GraphQL server with Python +In recent years, GraphQL has gained significant traction among Python developers seeking more efficient ways to build and consume APIs. This query language for APIs offers a fresh approach to data fetching and manipulation, particularly appealing in Python's versatile ecosystem. -Python is a programming language that lets you work quickly and integrate systems more effectively. Learn more at [the official website](https://www.python.org/). +> New to GraphQL? Check out the [Introduction to GraphQL](https://hasura.io/learn/graphql/intro-graphql/introduction/) tutorial to learn the core concepts quickly. -The following guide covers common backend application tasks, such as creating REST endpoints using [FastAPI](https://fastapi.tiangolo.com/). We also go over how to integrate your Python app with Hasura. +## Understanding GraphQL in the Python Ecosystem -> New to GraphQL? Check out the [Introduction to GraphQL](https://hasura.io/learn/graphql/intro-graphql/introduction/) tutorial to learn the core concepts quickly. +Python's ecosystem provides an interesting playground for GraphQL implementation: -- You will learn how to create a GraphQL server with Python and Strawberry FastAPI. -- If you have an existing GraphQL API with Python, you can integrate it with Hasura as a [Remote Schema](https://hasura.io/docs/latest/remote-schemas/index/) to get a unified GraphQL API. -- If you have an existing REST API with Python, you can transform that declaratively to GraphQL without writing any code using [Hasura REST Connectors](https://hasura.io/docs/latest/actions/rest-connectors/). -- You can also re-use or custom write REST endpoints with Python and map the endpoint to a GraphQL schema in Hasura. +- Extensive Library Support: Libraries like Graphene and Ariadne facilitate smooth GraphQL integration. +- Scalability: Python's scalability complements GraphQL's efficient data loading. -> New to Hasura? The Hasura GraphQL Engine makes your data instantly accessible over a real-time GraphQL API so that you can build and ship modern, performant apps and APIs 10x faster. Hasura connects to your databases, REST and GraphQL endpoints, and third-party APIs to provide a unified, connected, real-time, secured GraphQL API for all your data. Check out the [Hasura documentation](https://hasura.io/docs/latest/index/). +Ready to explore GraphQL with Python? The next section will guide you through setting up your first Python GraphQL server, demonstrating how they can work together effectively. + +## Setting Up a Python GraphQL Server + +Implementing a GraphQL server in Python is a fairly standard process, thanks to the ecosystem of libraries and tools available. This section will guide you through the essentials of setting up a Python GraphQL server, highlighting various approaches to get your API up and running quickly. + +### Choosing Your GraphQL Framework in Python + +Fundamentally, you can build a GraphQL server with schema first, code first or a domain driven approach. Several Python frameworks support GraphQL implementation. Here are some popular options: + +- **Graphene**: A Python library for building GraphQL schemas/types. +- **Strawberry**: A new library for creating GraphQL APIs using Python type hints. +- **Ariadne**: A schema-first GraphQL library for Python. +- **graphql-core**: Python reference implementation of the GraphQL spec. + +Graphene is one of the most widely used schema first GraphQL libraries for Python. + +- Object-oriented schema definition +- Integration with popular web frameworks (Django, Flask) +- Support for relay specification + +Strawberry is a library that leverages Python's type hints for GraphQL schema definition. + +- Type-first approach using Python 3.7+ type hints +- Code-first schema definition +- Built-in support for dataclasses + +There are some parameters to look at while choosing the right library/framework for building a GraphQL server in Python. + +Choosing the right Python GraphQL library/framework: + +- For Django/Flask Integration: Consider Graphene +- For Schema-First Approach: Look at Ariadne +- For Type Hints and Modern Python: Try Strawberry +- For Low-Level Control: Use graphql-core +- For domain-driven: Use Hasura for bootstrapping the API and integrate Python for business logic + +Each has its pros and cons, but for this guide, we'll focus on Graphene and Strawberry due to its widespread adoption and extensive documentation. + +### Creating a Python GraphQL Server with Graphene + +1. First, set up a virtual environment and install the necessary packages: + +```bash +python -m venv graphql_env +source graphql_env/bin/activate # On Windows, use `graphql_env\Scripts\activate` +pip install graphene Flask graphene-flask +``` + +2. Define your GraphQL schema using Graphene: + +```python +import graphene + +class Query(graphene.ObjectType): + hello = graphene.String(name=graphene.String(default_value="World")) + + def resolve_hello(self, info, name): + return f'Hello {name}' + +schema = graphene.Schema(query=Query) +``` + +3. Integrate your GraphQL schema with Flask: +```python +from flask import Flask +from flask_graphql import GraphQLView + +app = Flask(__name__) +app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) + +if __name__ == '__main__': + app.run(debug=True) +``` -## Create a Python GraphQL Server with Strawberry +4. Run your GraphQL server: -We can make a custom GraphQL server in Python using [Strawberry](https://strawberry.rocks/) and connect it to Hasura using a [remote schema](https://hasura.io/docs/latest/graphql/core/remote-schemas/index/). +```bash +python app.py +``` + +Visit http://localhost:5000/graphql to access the GraphiQL interface. + +### Create a Python GraphQL Server with Strawberry + +We can make a custom GraphQL server in Python using [Strawberry](https://strawberry.rocks/). 1. Run the [Strawberry FastAPI quickstart](https://strawberry.rocks/docs/integrations/fastapi) -2. In `remoteSchema/remoteSchema.py` add the Strawberry code +2. In `remoteSchema.py` add the Strawberry code ```python import strawberry @@ -46,241 +125,320 @@ We can make a custom GraphQL server in Python using [Strawberry](https://strawbe 3. Add the generated GraphQL handler to `main.py` ```python - from remoteSchema.remoteSchema import graphql_app + from remoteSchema import graphql_app app.include_router(graphql_app, prefix="/graphql") ``` -### Python GraphQL API Federation using Hasura Remote Schema +While the above setup works for a simple Hello World API, it's worth considering more efficient approaches: -We can connect our custom GraphQL server to Hasura using [remote schemas](https://hasura.io/docs/latest/graphql/core/remote-schemas/index/) to unify the GraphQL endpoint as a single endpoint. +**Schema Generation**: Instead of manually defining resolvers, consider tools that can generate schemas from your database models. +**Batched Resolvers**: Implement batching to reduce the number of database queries. +**Caching**: Implement a caching layer to improve performance for frequently accessed data. -1. In the Hasura Console remote schema tab, add your Python server `/graphql` +- How do I handle authentication in my GraphQL server? +Implement authentication middleware in your Flask app and access it within your resolvers. -2. In the API Explorer tab, try querying the sample todos. +- Can I use async/await with Python GraphQL servers? +Yes, libraries like Ariadne support asynchronous resolvers for improved performance. - ```graphql - { - hello - } - ``` +- How do I connect my GraphQL server to a database? +You can use ORMs like SQLAlchemy or integrate directly using database-specific Python libraries. -Hasura Remote Schema with Python backend +The key to a successful GraphQL implementation lies in understanding your data relationships and designing your API thoughtfully. -## Convert a Python REST API endpoint to GraphQL +### Implementing mutations with Python -In this section, we will write a REST Endpoint in Python using FastAPI and see how to transform that to GraphQL. We will create a login POST endpoint that takes a username and password and returns an access code. +In this section, we will explore how to implement and use mutations in a Python GraphQL environment, emphasizing best practices for data modification. -In our `main.py`, we use FastAPI to create an HTTP server: +A typical GraphQL mutation will look like this: + +```graphql +mutation createUser($name: String!, $email: String!) { + createUser(name: $name, email: $email) { + id + name + email + } +} +``` + +The simplest prototype implementation of this mutation in Graphene will look like this: ```python -from fastapi import FastAPI -from typing import Generic, TypeVar -from pydantic import BaseModel -from pydantic.generics import GenericModel -from action.loginTypes import LoginResponse, loginArgs -from event.event import Payload -from remoteSchema.remoteSchema import graphql_app -from qlient.aiohttp import AIOHTTPClient, GraphQLResponse +import graphene -ActionInput = TypeVar("ActionInput", bound=BaseModel | None) +class CreateUser(graphene.Mutation): + class Arguments: + name = graphene.String(required=True) + email = graphene.String(required=True) + user = graphene.Field(lambda: User) -class ActionName(BaseModel): - name: str + def mutate(self, info, name, email): + user = User(name=name, email=email) + # Add logic to save user to database + return CreateUser(user=user) +class Mutation(graphene.ObjectType): + create_user = CreateUser.Field() -class ActionPayload(GenericModel, Generic[ActionInput]): - action: ActionName - input: ActionInput - request_query: str - session_variables: dict[str, str] +schema = graphene.Schema(mutation=Mutation) +``` +The logic to save user to the database will include ORM library usage and client SDKs to various databases. -app = FastAPI() +Some of the Best Practices for Mutations: +- **Input Validation**: Always validate input data before processing. +- **Error Handling**: Provide clear error messages for failed mutations. +- **Atomicity**: Ensure that mutations are atomic - they should either complete fully or not at all. +- **Return Updated Data**: Return the modified object in the mutation response. -@app.post("/action") -async def actionHandler(action: ActionPayload[loginArgs]) -> LoginResponse: - action.input - return LoginResponse(AccessToken="") -``` +One of the way to optimize for performance is to batch the mutations: Group related mutations to reduce network requests. -In `action/action.py`, we create the handler: +What are the different ways to optimize mutation execution? -```python -from enum import Enum, auto -from pydantic import BaseModel +While manually implementing mutations works well for many cases, consider leveraging tools that can: +- Automatically generate CRUD mutations based on your data model +- Handle authentication and authorization checks +- Provide real-time updates to subscribed clients -class LoginResponse(BaseModel): - AccessToken: str +For instance, some advanced GraphQL engines can automatically create efficient mutations from your database schema, reducing boilerplate code and ensuring consistency between your data layer and API. Hasura does this across any data source, for example. +## Building a Python GraphQL Application: A Tutorial -class Mutation(BaseModel): - login: LoginResponse | None +In this section, we'll walk through creating a simple yet functional GraphQL application using Python. This tutorial will demonstrate how to set up a GraphQL server, define a schema, and implement queries and mutations. +Let's setup the project environment: -class loginArgs(BaseModel): - username: str - password: str +```bash +mkdir python-graphql-tutorial +cd python-graphql-tutorial +python -m venv venv +source venv/bin/activate +pip install flask graphene flask-graphql ``` -Install the dependencies and run the app +We'll create a simple book library application. Here's the structure: ```bash -pip install "fastapi[all]" +python-graphql-tutorial/ +├── app.py +└── models.py +``` + +We will start by defining data models. In models.py, we'll define our data models: -uvicorn main:app --reload +```python +class Author: + def __init__(self, id, name): + self.id = id + self.name = name + +class Book: + def __init__(self, id, title, author_id): + self.id = id + self.title = title + self.author_id = author_id + +# Sample data +authors = [ + Author(1, "J.K. Rowling"), + Author(2, "J.R.R. Tolkien") +] + +books = [ + Book(1, "Harry Potter and the Philosopher's Stone", 1), + Book(2, "The Hobbit", 2) +] ``` -### Add Python REST Endpoint to GraphQL schema using Hasura Actions +In app.py, let's create the GraphQL schema: -When writing a backend we usually have to write around 80% of our code doing boilerplate CRUD operations. Hasura helps us by autogenerating this part. +```python +from flask import Flask +from flask_graphql import GraphQLView +import graphene +from models import Author, Book, authors, books -When we need to write custom business logic we can integrate our Python REST endpoint using [Hasura Actions](https://hasura.io/docs/latest/actions/index/), giving us the best of both worlds. +class AuthorType(graphene.ObjectType): + id = graphene.ID() + name = graphene.String() -In the Actions tab on the Hasura Console we will set up a custom login function that calls the REST endpoint we created: +class BookType(graphene.ObjectType): + id = graphene.ID() + title = graphene.String() + author = graphene.Field(AuthorType) -```graphql -type Mutation { - login(username: String!, password: String!): LoginResponse -} -``` + def resolve_author(self, info): + return next(author for author in authors if author.id == self.author_id) -New types definition: +class Query(graphene.ObjectType): + books = graphene.List(BookType) + authors = graphene.List(AuthorType) -```graphql -type LoginResponse { - AccessToken: String! -} -``` + def resolve_books(self, info): + return books -Create the action, click the `Codegen` tab, and select `python-fast-api`. + def resolve_authors(self, info): + return authors -Copy `login.py` to `main.py` and `loginTypes.py` in your project. +schema = graphene.Schema(query=Query) -In the Hasura API explorer tab you should now be able to test it +app = Flask(__name__) +app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) -```graphql -mutation { - login(password: "password", username: "username") { - AccessToken - } -} +if __name__ == '__main__': + app.run(debug=True) ``` -Result: +Now, we can run the app `python app.py`. Visit `http://localhost:5000/graphiql` to start exploring the queries. You can try out the following query: -```json -{ - "data": { - "login": { - "AccessToken": "" +```graphql +query { + books { + id + title + author { + name } } } ``` -Hasura Actions with Python backend +Let's add the ability to create new books. Update `app.py`: -### Run async scheduled events using a Python REST API and Hasura GraphQL +```python +class CreateBook(graphene.Mutation): + class Arguments: + title = graphene.String(required=True) + author_id = graphene.Int(required=True) -Databases like Postgres can run triggers when data changes, with [Hasura event triggers](https://hasura.io/docs/latest/event-triggers/index/) we can easily call an HTTP endpoint whenever we have one of these events. + book = graphene.Field(lambda: BookType) -Let's send a webhook when a new user is created and print out their name. + def mutate(self, info, title, author_id): + book = Book(id=len(books) + 1, title=title, author_id=author_id) + books.append(book) + return CreateBook(book=book) -1. In the Hasura Console add a `user` table with a `Text` column `name` and the frequently used `UUID` column id. +class Mutation(graphene.ObjectType): + create_book = CreateBook.Field() -2. In the event trigger tab, on the `user` table, check the insert and via console trigger operations. +schema = graphene.Schema(query=Query, mutation=Mutation) +``` -3. The event trigger payload schema can be found [in the docs](https://hasura.io/docs/latest/graphql/core/event-triggers/payload/#json-payload). We make pydantic classes in Python to represent this +You can try out the following mutation to create a new book: - ```python - from pydantic import BaseModel, Field - from pydantic.generics import GenericModel - from typing import Generic, Literal, TypeVar +```graphql +mutation { + createBook(title: "The Hitchhikers Guide", authorId: 2) { + book { + id + title + author { + name + } + } + } +} +``` + +For the above queries to be performant, you might have to implement DataLoader to batch and cache database queries for better performance. + +- How can I connect this to a real database? +Replace the in-memory lists with database queries using an ORM like SQLAlchemy. + +- Can I add authentication to this API? +Yes, you can implement authentication middleware in Flask and access it in your resolvers. - New = TypeVar("New", bound=BaseModel | None) - Old = TypeVar("Old", bound=BaseModel | None) +- How do I handle file uploads? +Use libraries like graphene-file-upload to handle file uploads in mutations. - class DeliveryInfo(BaseModel): - current_retry: int - max_retries: int +## GraphQL Queries with Python - class Data(GenericModel, Generic[New, Old]): - new: New - old: Old +A GraphQL query in Python (or any language/framework) typically consists of three main components: - class TraceContext(BaseModel): - span_id: str - trace_id: str +- The query string +- Variables (optional) +- The execution method - class Event(GenericModel, Generic[New, Old]): - data: Data[New, Old] - op: Literal["INSERT", "UPDATE", "DELETE", "MANUAL"] - session_variables: dict[str, str] - trace_context: TraceContext +Let's break these down: - class Table(BaseModel): - name: str - schema_: str = Field("", alias="schema") +Here's how a query string would look like: - class Trigger(BaseModel): - name: str +```graphql +query getUser($id: String!) { + user(id: $id) { + name + email + posts { + title + } + } +} +``` - class Payload(GenericModel, Generic[New, Old]): - created_at: str - delivery_info: DeliveryInfo - event: Event[New, Old] - id: str - table: Table - trigger: Trigger +The variable for the above query can be defined in Python as: - ``` +```python +variables = { + "id": "123" +} +``` -4. Now we make an HTTP handler that handles the event +Once we have these defined, the execution depends on how the GraphQL server is exposing the API. Typically the API is exposed as a HTTP endpoint or Websocket (in case of a realtime app). In this example, let's look at HTTP request execution with Python. - ```python - from event import Payload - class UserTable(BaseModel): - id: str - name: str +### Executing GraphQL queries using Python requests library - @app.post("/event") - async def actionHandler(action: Payload[UserTable, None]): - return - ``` +The execution in Python for the GraphQL query will be as follows: -When you add a user in Hasura your Python server should receive the event. +```python +from gql import gql, Client +from gql.transport.requests import RequestsHTTPTransport + +transport = RequestsHTTPTransport(url='http://your-graphql-endpoint') +client = Client(transport=transport, fetch_schema_from_transport=True) + +query = gql(''' + query getUser($id: String!) { + user(id: $id) { + name + email + posts { + title + } + } + } +''') -Hasura Event Triggers with Python backend +result = client.execute(query, variable_values=variables) +print(result) +``` -## Example: Querying GraphQL with Python Client qlient +- How do I handle errors in GraphQL queries? +GraphQL returns errors in a standardized format. Check the `errors` key in the response. -To query a GraphQL endpoint from Python we use the async version of [qlient](https://github.com/qlient-org/python-qlient). +- Can I use GraphQL queries with asynchronous Python code? +Yes, libraries like `gql` support async operations with asyncio. -1. Install qlient +## FAQs - ```bash - pip install qlient.aiohttp - ``` +- Is GraphQL suitable for all Python projects? +While not universal, GraphQL can benefit projects of various sizes, especially those requiring flexible data fetching. -2. Query all users in the event trigger handler we created earlier, +- How does GraphQL handle database operations in Python? +GraphQL itself is database-agnostic. Tools like Hasura can auto-generate GraphQL APIs from your database schema, simplifying the process. - ```python - @app.post("/event") - async def actionHandler(action: Payload [UserTable, None]): - async with AIOHTTPClient("http://localhost:8080/v1/graphql") as client: - result: GraphQLResponse = await client.query.user(["id", "name"]) - print(result.request.query) - print(result.data) - return - ``` +- Can GraphQL improve API performance in Python applications? +Yes, by reducing over-fetching and allowing batched queries, GraphQL can significantly enhance API efficiency. ## Summary -When developing backend applications, we may need to write custom business logic. When we use Hasura, it autogenerates most of our API but gives us escape hatches for this custom logic. We've gone over a few ways you can use the power of Python. +As we've explored throughout this post, GraphQL offers significant advantages for building flexible and efficient APIs in Python ecosystems. However, the traditional resolver-based approach, while intuitive, often leads to performance challenges and increased complexity as applications scale. This is where domain-driven compiler-style GraphQL APIs shine, providing a superior solution for Python developers looking to harness the full power of GraphQL. + +> New to Hasura? The Hasura GraphQL Engine makes your data instantly accessible over a real-time GraphQL API so that you can build and ship modern, performant apps and APIs 10x faster. Hasura connects to your databases, REST and GraphQL endpoints, and third-party APIs to provide a unified, connected, real-time, secured GraphQL API for all your data. Check out the [Hasura documentation](https://hasura.io/docs/latest/index/). See the [server source code on Github](https://github.com/hasura/learn-graphql/tree/master/tutorials/backend/backend-stack/source-code/python).