Jamie  Graham

Jamie Graham

1640327377

Fastapi Strawberry Graphql with Quick and Dirty

fastapi-strawberry-graphql

Quick and dirty 🍓

python

python --version
Python 3.10

pip

pip install sqlalchemy
pip install sqlmodel
pip install fastapi
pip install 'strawberry-graphql[fastapi]'
pip install "uvicorn[standard]"

create gambiarra db

python models.py
# You can pre-populate it with gambiarra.sql if you want.

start rest

uvicorn rest:app --reload

start graphql

uvicorn app:app --reload

Play around graphQL

query {
allDatasets {
  id
  name
  schemas{
    attr
  }
  files{
    path
  }
}
}

Download Details:
Author: rodrigoney
Source Code: https://github.com/rodrigoney/fastapi-strawberry-graphql
License: 

#graphql #python #fastapi 

What is GEEK

Buddha Community

Fastapi Strawberry Graphql with Quick and Dirty
Jamie  Graham

Jamie Graham

1640327377

Fastapi Strawberry Graphql with Quick and Dirty

fastapi-strawberry-graphql

Quick and dirty 🍓

python

python --version
Python 3.10

pip

pip install sqlalchemy
pip install sqlmodel
pip install fastapi
pip install 'strawberry-graphql[fastapi]'
pip install "uvicorn[standard]"

create gambiarra db

python models.py
# You can pre-populate it with gambiarra.sql if you want.

start rest

uvicorn rest:app --reload

start graphql

uvicorn app:app --reload

Play around graphQL

query {
allDatasets {
  id
  name
  schemas{
    attr
  }
  files{
    path
  }
}
}

Download Details:
Author: rodrigoney
Source Code: https://github.com/rodrigoney/fastapi-strawberry-graphql
License: 

#graphql #python #fastapi 

Using GraphQL with Strawberry, FastAPI, and Next.js

Hi everyone! 👋

You’ve probably heard of FastAPI, Strawberry, GraphQL, and the like. Now, we’ll be showing you how to put them together in a Next.js app. We will be focusing on getting a good developer experience (DX) with typed code. Plenty of articles can teach you how to use each individually, but there aren’t many resources out there on putting them together, particularly with Strawberry.

There are multiple Python-based GraphQL libraries and they all vary slightly from each other. For the longest time, Graphene was a natural choice as it was the oldest and was used in production at different companies, but now other newer libraries have also started gaining some traction.

We will be focusing on one such library called Strawberry. It is relatively new and requires Python 3.7+ because it makes use of Python features that weren’t available in earlier versions of the language. It makes heavy use of dataclasses and is fully typed using mypy.

N .B., you can find the complete code from this article on GitHub.

The final product: A book database

We will have a basic project structure in place that will demonstrate how you can successfully start writing SQLAlchemy + Strawberry + FastAPI applications while making use of types and automatically generating typed React Hooks to make use of your GraphQL queries and mutations in your Typescript code. The React Hooks will make use of urql, but you can easily switch it out for Apollo.

I will create the DB schema based on the idea of a bookstore. We will store information about authors and their books. We will not create a full application using React/Next.js but will have all the necessary pieces in place to do so if required.

The goal is to have a better developer experience by using types everywhere and automating as much of the code generation as possible. This will help catch a lot more bugs in development.

This post is inspired by this GitHub repo.

Getting started

We first need to install the following libraries/packages before we can start working:

  • Strawberry — This is our GraphQL library that will provide GraphQL support on the Python side
  • FastAPI — This is our web framework for serving our Strawberry-based GraphQL API
  • Uvicorn — This is an ASGI web server that will serve our FastAPI application in production
  • Aiosqlite — This provides async support for SQLite
  • SQLAlchemy — This is our ORM for working with SQLite DB

Let’s create a new folder and install these libraries using pip. Instead of creating the new folder manually, I will use the create-next-app command to make it. We will treat the folder created by this command as the root folder for our whole project. This just makes the explanation easier. I will discuss the required JS/TS libraries later on. For now, we will only focus on the Python side.

Make sure you have create-next-app available as a valid command on your system. Once you do, run the following command in the terminal:

$ npx create-next-app@latest --typescript strawberry_nextjs

The above command should create a strawberry_nextjs folder. Now go into that folder and install the required Python-based dependencies:

$ cd strawberry_nextjs

$ python -m venv virtualenv
$ source virtualenv/bin/activate

$ pip install 'strawberry-graphql[fastapi]' fastapi 'uvicorn[standard]' aiosqlite sqlalchemy

Strawberry + FastAPI: Hello, world!

Let’s start with a “Hello, world!” example and it will show us the bits and pieces that make up a Strawberry application. Create a new file named app.py and add the following code to it:

import strawberry

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

authors: list[str] = []

@strawberry.type
class Query:
@strawberry.field
def all_authors(self) -\> list[str]:
return authors

@strawberry.type
class Mutation:
@strawberry.field
def add_author(self, name: str) -\> str:
authors.append(name)
return name

schema = strawberry.Schema(query=Query, mutation=Mutation)

graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")

Let’s look at this code in chunks. We start by importing the required libraries and packages. We create an authors list that acts as our temporary database and holds the author names (we will create an actual database briefly).

We then create the Query class and decorate it with the strawberry.type decorator. This converts it into a GraphQL type. Within this class, we define an all_authors resolver that returns all the authors from the list. A resolver needs to state its return type as well. We will look at defining slightly complex types in the next section, but for now, a list of strings would suffice.

Next, we create a new Mutation class that contains all the GraphQL mutations. For now, we only have a simple add_author mutation that takes in a name and adds it to the authors list.

Then we pass the query and mutation classes to strawberry.Schema to create a GraphQL schema and then pass that on to GraphQLRouter. Lastly, we plug in the GraphQLRouter to FastAPI and let GraphQLRouter handle all incoming requests to /graphql endpoint.

If you don’t know what these terms mean, then let me give you a quick refresher:

  • Queries — A type of request sent to the server to retrieve data/records
  • Mutations — A type of request sent to the server to create/update/delete data/record
  • Types — The objects we interact with in GraphQL. These represent the data/records/errors and everything in between
  • Resolver — A function that populates the data for a single field in our schema

You can read more about the schema basics in Strawberry on the official docs page.

To run this code, hop on over to the terminal and execute the following command:

$ uvicorn app:app --reload --host '::'

This should print something like the following as output:

INFO: Will watch for changes in these directories: [‘/Users/yasoob/Desktop/strawberry_nextjs’]
INFO: Uvicorn running on http://[::]:8000 (Press CTRL+C to quit)
INFO: Started reloader process [56427] using watchgod
INFO: Started server process [56429]
INFO: Waiting for application startup.
INFO: Application startup complete.

Now go to https://127.0.0.1:8000/graphql and you should be greeted by the interactive GraphiQL playground:

Interactive GraphiQL Playground

Try executing this query:

query MyQuery {
allAuthors
}

This should output an empty list. This is expected because we don’t have any authors in our list. However, we can fix this by running a mutation first and then running the above query.

To create a new author, run the addAuthor mutation:

mutation MyMutation {
addAuthor(name: "Yasoob")
}

And now if you run the allAuthors query, you should see Yasoob in the output list:

{
"data": {
"allAuthors": [
"Yasoob"
]
}
}

You might have already realized this by now, but Strawberry automatically converts our camel_case fields into PascalCase fields internally so that we can follow the convention of using PascalCase in our GraphQL API calls and camel_case in our Python code.

With the basics down, let’s go ahead and start working on our bookstore-type application.

Defining the schema

The very first thing we need to figure out is what our schema is going to be. What queries, mutations, and types do we need to define for our application.

I will not be focusing on GraphQL basics but rather only on the Strawberry-specific parts in this article. As I already mentioned, we will be following the idea of a bookstore. We will store the data for authors and their books. This is what our database will look like at the end:

Defining Schema

Defining SQLAlchemy models

We will be working with SQLAlchemy, so let’s define both of our models as classes. We will be using async SQLAlchemy. Create a new models.py file in the strawberry_nextjs folder and add the following imports to it:

import asyncio
from contextlib import asynccontextmanager
from typing import AsyncGenerator, Optional

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

These imports will make sense in just a bit. We will be defining our models declaratively using classes that will inherit from the declarative base model from SQLAlchemy. SQLAlchemy provides us with declarative_base() function to get the declarative base model. Let’s use that and define our models:

Base = declarative_base()

class Author(Base):
__tablename__ ="authors"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False, unique=True)

books: list["Book"] = relationship("Book", lazy="joined", back_populates="author")

class Book(Base):
__tablename__ ="books"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False)
author_id: Optional[int] = Column(Integer, ForeignKey(Author.id), nullable=True)

author: Optional[Author] = relationship(Author, lazy="joined", back_populates="books")

Our Author class defines two columns: id and name. books is just a relationship attribute that helps us navigate the relationships between models but is not stored in the authors table as a separate column. We back populate the books attribute as author. This means that we can access book.author to access the linked author for a book.

The Book class is very similar to the Author class. We define an additional author_idcolumn that links authors and books. This is stored in the book table, unlike the relationships. And we also back populate the author attribute as books. This way we can access the books of a particular author like this: author.books.

Now we need to tell SQLAlchemy which DB to use and where to find it:

engine = create_async_engine(
"sqlite+aiosqlite:///./database.db", connect_args={"check_same_thread": False}
)

We use aiosqlite as part of the connection string as aiosqlite allows SQLAlchemy to use theSQLiteDBin an async manner. And we pass the check_same_thread argument to make sure we can use the same connection across multiple threads.

It is not safe to use SQLite in a multithreaded fashion without taking extra care to make sure data doesn’t get corrupted on concurrent write operations, so it is recommended to use PostgreSQL or a similar high-performance DB in production.

Next, we need to create a session:

async_session = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)

And to make sure we properly close the session on each interaction, we will create a new context manager:

@asynccontextmanager
async def get_session() -\> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
async with session.begin():
try :
yield session
finally :
await session.close()

We can use the session without the context manager too, but it will mean that we will have to close the session manually after each session usage.

Lastly, we need to make sure we have the new DB created. We can add some code to the models.py file that will create a new DB file using our declared models if we try to execute the models.py file directly:

async def _async_main():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
await engine.dispose()

if __name__=="__main__":
print("Dropping and re/creating tables")
asyncio.run(_async_main())
print("Done.")

This will drop all the existing tables in our DB and recreate them based on the models defined in this file. We can add safeguards and make sure we don’t delete our data accidentally, but that is beyond the scope of this article. I am just trying to show you how everything ties together.

Now our models.py file is complete and we are ready to define our Strawberry Author and Book types that will map onto the SQLAlchemy models.

Defining Strawberry types

By the time you read this article, Strawberry might have stable inbuilt support for directly using SQLAlchemy models, but for now, we have to define custom Strawberry types that will map on to SQLAlchemy models. Let’s define those first and then understand how they work. Put this code in the app.py file:

import models

@strawberry.type
class Author:
id: strawberry.ID
name: str

@classmethod
def marshal(cls, model: models.Author) -\>”Author”:
return cls(id=strawberry.ID(str(model.id)), name=model.name)

@strawberry.type
class Book:
id: strawberry.ID
name: str
author: Optional[Author] =None

@classmethod
def marshal(cls, model: models.Book) -\>”Book”:
return cls(
id=strawberry.ID(str(model.id)),
name=model.name,
author=Author.marshal(model.author) if model.author else None,
)

To define a new type, we simply create a class and decorate it with the strawberry.type decorator. This is very similar to how we defined the Mutation and Query types. The only difference is that this time, we will not pass these types directly to strawberry.Schema so Strawberry won’t treat them as Mutation or Query types.

Each class has a marshal method. This method is what allows us to take in an SQLAlchemy model and create a Strawberry type class instance from it. Strawberry uses strawberry.ID to represent a unique identifier to an object. Strawberry provides a few scalar types by default that work just like strawberry.ID. It is up to us how we use those to map our SQLAlchemy data to this custom type class attribute. We generally try to find the best and closely resembling alternative to the SQLAlchemy column type and use that.

In the Book class, I also show you how you can mark a type attribute as optional and provide a default value. We mark the author as optional. This is just to show you how it is done and later on; I will mark this as required.

Another thing to note is that we can also define a list of return types for our mutation and query calls. This makes sure our GraphQL API consumer can process the output appropriately based on the return type it receives. If you know about GraphQL, then this is how we define fragments. Let’s first define the types and then I will show you how to use them once we start defining our new mutation and query classes:

@strawberry.type
class AuthorExists:
message: str ="Author with this name already exists"

@strawberry.type
class AuthorNotFound:
message: str ="Couldn't find an author with the supplied name"

@strawberry.type
class AuthorNameMissing:
message: str ="Please supply an author name"

AddBookResponse = strawberry.union("AddBookResponse", (Book, AuthorNotFound, AuthorNameMissing))
AddAuthorResponse = strawberry.union("AddAuthorResponse", (Author, AuthorExists))

We are basically saying that our AddBookResponse and AddAuthorResponse types are union types and can be either of the three (or two) types listed in the tuple.

Defining queries and mutations

Let’s define our queries now. We will have only two queries. One to list all the books and one to list all the authors:

from sqlalchemy import select

# ...

@strawberry.type
class Query:
@strawberry.field
async* def* books(self) -\> list[Book]:
async* with* models.get_session() as s:
sql = select(models.Book).order_by(models.Book.name)
db_books = ( await s.execute(sql)).scalars().unique().all()
return [Book.marshal(book) for book in db_books]

@strawberry.field
async* def* authors(self) -\> list[Author]:
async* with* models.get_session() as s:
sql = select(models.Author).order_by(models.Author.name)
db_authors = ( await s.execute(sql)).scalars().unique().all()
return [Author.marshal(loc) for loc in db_authors]

There seems to be a lot happening here, so let’s break it down.

Firstly, look at the books resolver. We use the get_session context manager to create a new session. Then we create a new SQL statement that selects Book models and orders them based on the book name. Afterward, we execute the SQL statement using the session we created earlier and put the results in the db_books variable. Finally, we marshal each book into a Strawberry Book type and return that as an output. We also mark the return type of books resolver as a list of Books.

The authors resolver is very similar to the books resolver, so I don’t need to explain that.

Let’s write our mutations now:

@strawberry.type
class Mutation:
@strawberry.mutation
async* def* add_book(self, name: str, author_name: Optional[str]) -\> AddBookResponse:
async* with* models.get_session() as s:
db_author =None
if author_name:
sql = select(models.Author).where(models.Author.name == author_name)
db_author = ( await s.execute(sql)).scalars().first()
if* not* db_author:
return AuthorNotFound()
else :
return AuthorNameMissing()
db_book = models.Book(name=name, author=db_author)
s.add(db_book)
await s.commit()
return Book.marshal(db_book)

@strawberry.mutation
async* def* add_author(self, name: str) -\> AddAuthorResponse:
async* with* models.get_session() as s:
sql = select(models.Author).where(models.Author.name == name)
existing_db_author = ( await s.execute(sql)).first()
if existing_db_author is* notNone:
*return AuthorExists()
db_author = models.Author(name=name)
s.add(db_author)
await s.commit()
return Author.marshal(db_author)

Mutations are fairly straightforward. Let’s start with the add_book mutation.

add_book takes in the name of the book and the name of the author as inputs. I am defining the author_name as optional just to show you how you can define optional arguments, but in the method body, I enforce the presence of author_name by returning AuthorNameMissing if the author_name is not passed in.

I filter Authors in db based on the passed in author_name and make sure that an author with the specified name exists. Otherwise, I return AuthorNotFound. If both of these checks pass, I create a new models.Book instance, add it to the db via the session, and commit it. Finally, I return a marshaled book as the return value.

add_author is almost the same as add_book, so no reason to go over the code again.

We are almost done on the Strawberry side, but I have one bonus thing to share, and that is data loaders.

Another (not always) fun feature of GraphQL is recursive resolvers. You saw above that in the marshal method of Book I also define author. This way we can run a GraphQL query like this:

query {
book {
author {
name
}
}
}

But what if we want to run a query like this:

query {
author {
books {
name
}
}
}

This will not work because we haven’t defined a books attribute on our Strawberry type. Let’s rewrite our Author class and add a DataLoader to the default context Strawberry provides us in our class methods:

from strawberry.dataloader import DataLoader

# ...

@strawberry.type
class Author:
id: strawberry.ID
name: str

@strawberry.field
async* def* books(self, info: Info) -\> list["Book"]:
books = await info.context["books_by_author"].load(self.id)
return [Book.marshal(book) for book in books]

@classmethod
def marshal(cls, model: models.Author) -\>"Author":
return cls(id=strawberry.ID(str(model.id)), name=model.name)

# ...

async* def* load_books_by_author(keys: list) -\> list[Book]:
async* with* models.get_session() as s:
all_queries = [select(models.Book).where(models.Book.author_id == key) for key in keys]
data = [( await s.execute(sql)).scalars().unique().all() for sql in all_queries]
print(keys, data)
return data

async* def* get_context() -\> dict:
return {
"books_by_author": DataLoader(load_fn=load_books_by_author),
}

# ...

graphql_app = GraphQLRouter(schema, context_getter=get_context)

Let’s understand this from the bottom up. Strawberry allows us to pass custom functions to our class (those wrapped with @strawberry.type) methods via a context. This context is shared across a single request.

DataLoader allows us to batch multiple requests so that we can reduce back and forth calls to the db. We create a DataLoader instance and inform it how to load books from the db for the passed-in author. We put this DataLoader in a dictionary and pass that as the context_getter argument to GraphQLRouter. This makes the dictionary available to our class methods via info.context. We use that to load the books for each author.

In this example, DataLoader isn’t super useful. Its main benefits shine through when we call the DataLoader with a list of arguments. That reduces the database calls considerably. And DataLoaders also cache output and they are shared in a single request. Therefore, if you were to pass the same arguments to the data loader in a single request multiple times, it will not result in additional database hits. Super powerful!

Testing out Strawberry

The uvicorn instance should automatically reload once you make these code changes and save them. Go over to http://127.0.0.1:8000/graphql and test out the latest code.

Try executing the following mutation twice:

mutation Author {
addAuthor(name: "Yasoob") {
... on Author {
id
name
}
... on AuthorExists{
message
}
}
}

The first time it should output this:

{
"data": {
"addAuthor": {
"id": "1",
"name": "Yasoob"
}
}
}

And the second time it should output this:

{
"data": {
"addAuthor": {
"message": "Author with this name already exist"
}
}
}

Strawberry Output

Now let’s try adding new books:

mutation Book {
addBook(name: "Practical Python Projects", authorName: "Yasoob") {
... on Book {
id
name
}
}
}

Python/Strawberry Side

Sweet! Our Python/Strawberry side is working perfectly fine. But now we need to tie this up on the Node/Next.js side.

Setting up Node dependencies

We will be using graphql-codegen to automatically create typed hooks for us. So the basic workflow will be that before we can use a GraphQL query, mutation, or fragment in our Typescript code, we will define that in a GraphQL file. Then graphql-codegen will introspect our Strawberry GraphQL API and create types and use our custom defined GraphQL Query/Mutations/Fragments to create custom urql hooks.

urql is a fairly full-featured GraphQL library for React that makes interacting with GraphQL APIs a lot simpler. By doing all this, we will reduce a lot of effort in coding typed hooks ourselves before we can use our GraphQL API in our Next.js/React app.

Before we can move on, we need to install a few dependencies:

$ npm install graphql
$ npm install @graphql-codegen/cli
$ npm install @graphql-codegen/typescript
$ npm install @graphql-codegen/typescript-operations
$ npm install @graphql-codegen/typescript-urql
$ npm install urql

Here we are installing urql and a few plugins for @graphql-codegen.

Setting up graphql-codegen

Now we will create a codegen.yml file in the root of our project that will tell graphql-codegen what to do:

overwrite: true schema: "http://127.0.0.1:8000/graphql" documents: './graphql/**/*.graphql' generates: graphql/graphql.ts: plugins:

  • “typescript”
  • “typescript-operations”
  • “typescript-urql”

We are informing graphql-codegen that it can find the schema for our GraphQL API at http://127.0.0.1:8000/graphql. We also tell it (via the documents key) that we have defined our custom fragments, queries, and mutations in graphql files located in the graphql folder. Then we instruct it to generate graphql/graphql.ts file by running the schema and documents through three plugins.

Now make a graphql folder in our project directory and create a new operations.graphql file within it. We will define all the fragments, queries, and mutations we plan on using in our app. We can create separate files for all three and graphql-codegen will automatically merge them while processing, but we will keep it simple and put everything in one file for now. Let’s add the following GraphQL to operations.graphql:

query Books {
books {
...BookFields
}
}

query Authors {
authors {
...AuthorFields
}
}

fragment BookFields on Book {
id
name
author {
name
}
}

fragment AuthorFields on Author {
id
name
}

mutation AddBook($name: String!, $authorName: String!) {
addBook(name: $name, authorName: $authorName) {
__typename
... on Book {
__typename
...BookFields
}
}
}

mutation AddAuthor($name: String!) {
addAuthor(name: $name) {
__typename
... on AuthorExists {
__typename
message
}
... on Author {
__typename
...AuthorFields
}
}
}

This is very similar to the code we were executing in the GraphiQL online interface. This GraphQL code will tell graphql-codegen which urql mutation and query hooks it needs to produce for us.

There has been discussion to make graphql-codegen generate all mutations and queries by introspecting our online GraphQL API, but so far it is not possible to do that using only graphql-codegen. There do exist tools that allow you to do that, but I am not going to use them in this article. You can explore them on your own.

Let’s edit package.json file next and add a command to run graphql-codegen via npm. Add this code in the scripts section:

"codegen": "graphql-codegen --config codegen.yml"

Now we can go to the terminal and run graphql-codegen:

$ npm run codegen

If the command succeeds, you should have a graphql.ts file in graphql folder. We can go ahead and use the generated urql hooks in our Next code like so:

import {
useAuthorsQuery,
} from "../graphql/graphql";

// ....

const [result] = useAuthorsQuery(...);

You can read more about the graphql-codegen urql plugin here.

Resolve CORS issue

In a production environment, you can serve the GraphQL API and the Next.js/React app from the same domain+PORT and that will make sure you don’t encounter CORS issues. For the development environment, we can add some proxy code to next.config.js file to instruct NextJS to proxy all calls to /graphql to uvicorn that is running on a different port:

/** _ @type _ {import('next').NextConfig} */
module.exports= {
reactStrictMode: true ,
async rewrites() {
return {
beforeFiles: [
{
source:"/graphql",
destination:"http://localhost:8000/graphql",
},
],
};
},
};

This will make sure you don’t encounter any CORS issues on local development either.

Conclusion

I hope you learned a thing or two from this article. I deliberately did not go into too much detail on any single topic as such articles already exist online, but it is very hard to find an article that shows you how everything connects together.

You can find all the code for this article on my GitHub. In the future, I might create a full project to show you amore concrete example of how you can make use of the generated code in your apps. In the meantime, you can take a look at this repo, which was inspiration for this article. Jokull was probably the first person to publicly host a project combining all of these different tools. Thanks, Jokull!

Also, if you have any Python or web development projects in mind, reach out to me at hi@yasoob.me and share your ideas. I do quite a variety of projects so almost nothing is out of the ordinary. Let’s create something awesome together.  See you later! 👋 ❤️

 This story was originally published at https://blog.logrocket.com/using-graphql-strawberry-fastapi-next-js/

#graphql #strawberry #fastapi #nextjs 

Uso de GraphQL con Strawberry, FastAPI y Next.js

¡Hola a todos!👋

Probablemente haya oído hablar de FastAPI, Strawberry, GraphQL y similares. Ahora, le mostraremos cómo juntarlos en una aplicación Next.js. Nos centraremos en obtener una buena experiencia de desarrollador (DX) con código escrito. Muchos artículos pueden enseñarle cómo usar cada uno individualmente, pero no hay muchos recursos disponibles para combinarlos, particularmente con Strawberry.

Hay varias bibliotecas de GraphQL basadas en Python y todas varían ligeramente entre sí. Durante mucho tiempo, el grafeno fue una elección natural, ya que era el más antiguo y se usaba en la producción en diferentes empresas, pero ahora otras bibliotecas más nuevas también han comenzado a ganar algo de tracción.

Nos centraremos en una de esas bibliotecas llamada Strawberry . Es relativamente nuevo y requiere Python 3.7+ porque utiliza funciones de Python que no estaban disponibles en versiones anteriores del lenguaje. Hace un uso intensivo de las clases de datos y se escribe completamente con mypy.

N .B., puede encontrar el código completo de este artículo en GitHub .

El producto final: una base de datos de libros

Tendremos una estructura de proyecto básica que demostrará cómo puede comenzar a escribir con éxito aplicaciones SQLAlchemy + Strawberry + FastAPI mientras utiliza tipos y genera automáticamente React Hooks tipeados para hacer uso de sus consultas GraphQL y mutaciones en su código Typescript. Los React Hooks harán uso de urql, pero puedes cambiarlo fácilmente por Apollo.

Crearé el esquema DB basado en la idea de una librería. Almacenaremos información sobre los autores y sus libros. No crearemos una aplicación completa usando React/Next.js, pero tendremos todas las piezas necesarias para hacerlo si es necesario.

El objetivo es tener una mejor experiencia de desarrollador mediante el uso de tipos en todas partes y la automatización de la generación de código tanto como sea posible. Esto ayudará a detectar muchos más errores en el desarrollo.

Esta publicación está inspirada en este repositorio de GitHub .

Empezando

Primero debemos instalar las siguientes bibliotecas/paquetes antes de que podamos comenzar a trabajar:

  • Strawberry : esta es nuestra biblioteca GraphQL que proporcionará compatibilidad con GraphQL en el lado de Python
  • FastAPI : este es nuestro marco web para servir nuestra API GraphQL basada en Strawberry
  • Uvicorn : este es un servidor web ASGI que servirá a nuestra aplicación FastAPI en producción
  • Aiosqlite — Esto proporciona soporte asincrónico para SQLite
  • SQLAlchemy : este es nuestro ORM para trabajar con SQLite DB

Creemos una nueva carpeta e instalemos estas bibliotecas usando pip. En lugar de crear la nueva carpeta manualmente, usaré el create-next-appcomando para crearla. Trataremos la carpeta creada por este comando como la carpeta raíz de todo nuestro proyecto. Esto solo hace que la explicación sea más fácil. Discutiré las bibliotecas JS/TS requeridas más adelante. Por ahora, solo nos centraremos en el lado de Python.

Asegúrese create-next-appde tener disponible un comando válido en su sistema. Una vez que lo hagas, ejecuta el siguiente comando en la terminal:

$ npx create-next-app@latest --typescript strawberry_nextjs

El comando anterior debería crear una strawberry_nextjscarpeta. Ahora ve a esa carpeta e instala las dependencias requeridas basadas en Python:

$ cd strawberry_nextjs

$ python -m venv virtualenv
$ source virtualenv/bin/activate

$ pip install 'strawberry-graphql[fastapi]' fastapi 'uvicorn[standard]' aiosqlite sqlalchemy

Strawberry + FastAPI: ¡Hola, mundo!

Comencemos con un "¡Hola, mundo!" ejemplo y nos mostrará los fragmentos que componen una aplicación Strawberry. Cree un nuevo archivo llamado app.py y agréguele el siguiente código:

import strawberry

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

authors: list[str] = []

@strawberry.type
class Query:
@strawberry.field
def all_authors(self) -\> list[str]:
return authors

@strawberry.type
class Mutation:
@strawberry.field
def add_author(self, name: str) -\> str:
authors.append(name)
return name

schema = strawberry.Schema(query=Query, mutation=Mutation)

graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")

Veamos este código en fragmentos. Comenzamos importando las bibliotecas y paquetes requeridos. Creamos una lista de autores que actúa como nuestra base de datos temporal y contiene los nombres de los autores (crearemos una base de datos real brevemente).

Luego creamos la clase Consulta y la decoramos con el decorador de tipo fresa. Esto lo convierte en un tipo GraphQL. Dentro de esta clase, definimos un all_authorsresolver que devuelve todos los autores de la lista. Un resolutor también debe indicar su tipo de retorno. Veremos la definición de tipos ligeramente complejos en la siguiente sección, pero por ahora, una lista de cadenas sería suficiente.

A continuación, creamos una nueva Mutationclase que contiene todas las mutaciones de GraphQL. Por ahora, solo tenemos una simple add_authormutación que toma un nombre y lo agrega a la lista de autores.

Luego pasamos las clases de consulta y mutación a strawberry.Schemapara crear un esquema GraphQL y luego lo pasamos a GraphQLRouter. Por último, conectamos GraphQLRouterFastAPI y dejamos que GraphQLRouter maneje todas las solicitudes entrantes al punto final /graphql.

Si no sabe lo que significan estos términos, permítame refrescarlo rápidamente:

  • Consultas: un tipo de solicitud enviada al servidor para recuperar datos/registros
  • Mutaciones : un tipo de solicitud enviada al servidor para crear/actualizar/eliminar datos/registrar
  • Tipos: los objetos con los que interactuamos en GraphQL. Estos representan los datos/registros/errores y todo lo demás
  • Resolver : una función que completa los datos de un solo campo en nuestro esquema

Puede leer más sobre los conceptos básicos del esquema en Strawberry en la página oficial de documentos .

Para ejecutar este código, acceda a la terminal y ejecute el siguiente comando:

$ uvicorn app:app --reload --host '::'

Esto debería imprimir algo como lo siguiente como salida:

INFO: Will watch for changes in these directories: [‘/Users/yasoob/Desktop/strawberry_nextjs’]
INFO: Uvicorn running on http://[::]:8000 (Press CTRL+C to quit)
INFO: Started reloader process [56427] using watchgod
INFO: Started server process [56429]
INFO: Waiting for application startup.
INFO: Application startup complete.

Ahora vaya a https://127.0.0.1:8000/graphql y debería ser recibido por el área de juegos interactiva de GraphiQL:

Intenta ejecutar esta consulta:

query MyQuery {
allAuthors
}

Esto debería generar una lista vacía. Esto es de esperar porque no tenemos ningún autor en nuestra lista. Sin embargo, podemos solucionar esto ejecutando primero una mutación y luego ejecutando la consulta anterior.

Para crear un nuevo autor, ejecute la addAuthormutación:

mutation MyMutation {
addAuthor(name: "Yasoob")
}

Y ahora, si ejecuta la consulta allAuthors, debería ver a Yasoob en la lista de salida:

{
"data": {
"allAuthors": [
"Yasoob"
]
}
}

Es posible que ya se haya dado cuenta de esto, pero Strawberry convierte automáticamente nuestros campos camel_case en campos PascalCase internamente para que podamos seguir la convención de usar PascalCase en nuestras llamadas API GraphQL y camel_case en nuestro código Python.

Con lo básico abajo, sigamos adelante y comencemos a trabajar en nuestra aplicación tipo librería.

Definición del esquema

Lo primero que tenemos que averiguar es cuál va a ser nuestro esquema. Qué consultas, mutaciones y tipos necesitamos definir para nuestra aplicación.

No me centraré en los conceptos básicos de GraphQL, sino solo en las partes específicas de Strawberry en este artículo. Como ya mencioné, seguiremos la idea de una librería. Almacenaremos los datos de los autores y sus libros. Así se verá nuestra base de datos al final:

Definición de modelos de SQLAlchemy

Trabajaremos con SQLAlchemy, así que definamos nuestros dos modelos como clases. Usaremos async SQLAlchemy. Cree un nuevo models.pyarchivo en la strawberry_nextjscarpeta y agréguele las siguientes importaciones:

import asyncio
from contextlib import asynccontextmanager
from typing import AsyncGenerator, Optional

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

Estas importaciones tendrán sentido en un momento. Definiremos nuestros modelos declarativamente usando clases que heredarán del modelo base declarativo de SQLAlchemy. SQLAlchemy nos proporciona una declarative_base()función para obtener el modelo base declarativo. Usemos eso y definamos nuestros modelos:

Base = declarative_base()

class Author(Base):
__tablename__ ="authors"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False, unique=True)

books: list["Book"] = relationship("Book", lazy="joined", back_populates="author")

class Book(Base):
__tablename__ ="books"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False)
author_id: Optional[int] = Column(Integer, ForeignKey(Author.id), nullable=True)

author: Optional[Author] = relationship(Author, lazy="joined", back_populates="books")

Our Authorclass define dos columnas: idy name. bookses solo un atributo de relación que nos ayuda a navegar por las relaciones entre modelos, pero no se almacena en la tabla de autores como una columna separada. Volvemos a llenar el atributo de los libros como autor. Esto significa que podemos acceder book.authorpara acceder al autor enlazado de un libro.

The BookLa clase es muy similar a la clase Autor. Definimos un adicional author_idcolumnque vincula autores y libros. Esto se almacena en la tabla de libros, a diferencia de las relaciones. Y también volvemos a llenar el authoratributo como books. De esta manera podemos acceder a los libros de un autor en particular así: author.books.

Ahora necesitamos decirle a SQLAlchemy qué base de datos usar y dónde encontrarla:

engine = create_async_engine(
"sqlite+aiosqlite:///./database.db", connect_args={"check_same_thread": False}
)

Lo usamos aiosqlitecomo parte de la cadena de conexión, ya que aiosqlitepermite que SQLAlchemy use SQLiteDB de forma asíncrona . Y pasamos el check_same_threadargumento para asegurarnos de que podemos usar la misma conexión en varios subprocesos.

No es seguro usar SQLite de manera multiproceso sin tener cuidado adicional para asegurarse de que los datos no se corrompan en las operaciones de escritura simultáneas, por lo que se recomienda usar PostgreSQL o una base de datos similar de alto rendimiento en producción.

A continuación, necesitamos crear una sesión:

async_session = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)

Y para asegurarnos de cerrar correctamente la sesión en cada interacción, crearemos un nuevo administrador de contexto:

@asynccontextmanager
async def get_session() -\> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
async with session.begin():
try :
yield session
finally :
await session.close()

También podemos usar la sesión sin el administrador de contexto, pero eso significará que tendremos que cerrar la sesión manualmente después de cada uso de la sesión.

Por último, debemos asegurarnos de tener la nueva base de datos creada. Podemos agregar algo de código al models.pyarchivo que creará un nuevo archivo DB utilizando nuestros modelos declarados si intentamos ejecutar el models.pyarchivo directamente:

async def _async_main():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
await engine.dispose()

if __name__=="__main__":
print("Dropping and re/creating tables")
asyncio.run(_async_main())
print("Done.")

Esto eliminará todas las tablas existentes en nuestra base de datos y las recreará en función de los modelos definidos en este archivo. Podemos agregar medidas de seguridad y asegurarnos de que no eliminemos nuestros datos accidentalmente, pero eso está más allá del alcance de este artículo. Solo estoy tratando de mostrarte cómo todo se une.

Ahora nuestro models.pyarchivo está completo y estamos listos para definir nuestra Strawberry Authory los Booktipos que se asignarán a los modelos de SQLAlchemy.

Definición de tipos de fresa

Para cuando lea este artículo, Strawberry podría tener un soporte incorporado estable para usar directamente los modelos de SQLAlchemy, pero por ahora, tenemos que definir tipos personalizados de Strawberry que se asignarán a los modelos de SQLAlchemy. Primero definámoslos y luego entendamos cómo funcionan. Pon este código en el app.pyarchivo:

importar modelos

@fresa.tipo
clase Autor:
id: fresa.ID
nombre: str

@classmethod
def marshal(cls, modelo: modelos.Autor) -\>”Autor”:
return cls(id=fresa.ID(str(modelo.id)), nombre=modelo.nombre)

@fresa.tipo
clase Libro:
id: fresa.ID
nombre: str
autor: Opcional[Autor] =Ninguno

@classmethod
def marshal(cls, modelo: modelos.Libro) -\>”Libro”:
return cls(
id=fresa.ID(str(modelo.id)),
nombre=modelo.nombre,
autor=Autor.marshal(modelo .autor) si modelo.autor si no Ninguno,
)

Para definir un nuevo tipo, simplemente creamos una clase y la decoramos con el decorador strawberry.type. Esto es muy similar a cómo definimos los tipos Mutationy . QueryLa única diferencia es que esta vez, no pasaremos estos tipos directamente a strawberry.SchemaStrawberry, por lo que no los tratará como tipos Mutationo .Query

Cada clase tiene un método marshal. Este método es lo que nos permite tomar un modelo SQLAlchemy y crear una instancia de clase de tipo Strawberry a partir de él. Strawberry usa strawberry.IDpara representar un identificador único para un objeto. Strawberry proporciona algunos tipos escalares por defecto que funcionan igual que strawberry.ID. Depende de nosotros cómo los usamos para asignar nuestros datos de SQLAlchemy a este atributo de clase de tipo personalizado. Por lo general, tratamos de encontrar la mejor y más parecida alternativa al tipo de columna SQLAlchemy y la usamos.

En la Bookclase, también le muestro cómo puede marcar un atributo de tipo como opcional y proporcionar un valor predeterminado. Marcamos el autor como opcional. Esto es solo para mostrarle cómo se hace y más adelante; Marcaré esto como requerido.

Otra cosa a tener en cuenta es que también podemos definir una lista de tipos de devolución para nuestras llamadas de mutación y consulta. Esto asegura que nuestro consumidor de la API de GraphQL pueda procesar la salida de manera adecuada en función del tipo de retorno que recibe. Si conoce GraphQL, así es como definimos los fragmentos. Primero definamos los tipos y luego le mostraré cómo usarlos una vez que comencemos a definir nuestras nuevas clases de mutación y consulta:

@strawberry.type
class AuthorExists:
message: str ="Author with this name already exists"

@strawberry.type
class AuthorNotFound:
message: str ="Couldn't find an author with the supplied name"

@strawberry.type
class AuthorNameMissing:
message: str ="Please supply an author name"

AddBookResponse = strawberry.union("AddBookResponse", (Book, AuthorNotFound, AuthorNameMissing))
AddAuthorResponse = strawberry.union("AddAuthorResponse", (Author, AuthorExists))

Básicamente, estamos diciendo que nuestros tipos AddBookResponsey AddAuthorResponseson tipos de unión y pueden ser cualquiera de los tres (o dos) tipos enumerados en la tupla.

Definición de consultas y mutaciones

Definamos nuestras consultas ahora. Solo tendremos dos consultas. Uno para listar todos los libros y otro para listar todos los autores:

from sqlalchemy import select

# ...

@strawberry.type
class Query:
@strawberry.field
async* def* books(self) -\> list[Book]:
async* with* models.get_session() as s:
sql = select(models.Book).order_by(models.Book.name)
db_books = ( await s.execute(sql)).scalars().unique().all()
return [Book.marshal(book) for book in db_books]

@strawberry.field
async* def* authors(self) -\> list[Author]:
async* with* models.get_session() as s:
sql = select(models.Author).order_by(models.Author.name)
db_authors = ( await s.execute(sql)).scalars().unique().all()
return [Author.marshal(loc) for loc in db_authors]

Parece que están sucediendo muchas cosas aquí, así que vamos a desglosarlo.

En primer lugar, mire la resolución de libros. Usamos el get_sessionadministrador de contexto para crear una nueva sesión. Luego creamos una nueva instrucción SQL que selecciona los modelos de libros y los ordena según el nombre del libro. Luego, ejecutamos la instrucción SQL utilizando la sesión que creamos anteriormente y colocamos los resultados en la db_booksvariable. Finalmente, clasificamos cada libro en un tipo Strawberry Book y lo devolvemos como salida. También marcamos el tipo de retorno de la resolución de libros como listde Books.

El authorsresolutor es muy similar al booksresolutor, así que no necesito explicar eso.

Escribamos nuestras mutaciones ahora:

@strawberry.type
class Mutation:
@strawberry.mutation
async* def* add_book(self, name: str, author_name: Optional[str]) -\> AddBookResponse:
async* with* models.get_session() as s:
db_author =None
if author_name:
sql = select(models.Author).where(models.Author.name == author_name)
db_author = ( await s.execute(sql)).scalars().first()
if* not* db_author:
return AuthorNotFound()
else :
return AuthorNameMissing()
db_book = models.Book(name=name, author=db_author)
s.add(db_book)
await s.commit()
return Book.marshal(db_book)

@strawberry.mutation
async* def* add_author(self, name: str) -\> AddAuthorResponse:
async* with* models.get_session() as s:
sql = select(models.Author).where(models.Author.name == name)
existing_db_author = ( await s.execute(sql)).first()
if existing_db_author is* notNone:
*return AuthorExists()
db_author = models.Author(name=name)
s.add(db_author)
await s.commit()
return Author.marshal(db_author)

Las mutaciones son bastante sencillas. Comencemos con la add_bookmutación.

add_booktoma el nombre del libro y el nombre del autor como entradas. Estoy definiendo el author_namecomo opcional solo para mostrarle cómo puede definir argumentos opcionales, pero en el cuerpo del método, impongo la presencia de author_nameregresando AuthorNameMissingsi author_nameno se pasa.

Filtro Authorsen dbfunción de lo que se transmite author_namey me aseguro de que exista un autor con el nombre especificado. De lo contrario, vuelvo AuthorNotFound. Si se aprueban ambas comprobaciones, creo una nueva models.Bookinstancia, la agrego a dbtravés de la sesión y la confirmo. Finalmente, devuelvo un libro clasificado como valor de retorno.

add_authores casi lo mismo que add_book, por lo que no hay razón para repasar el código nuevamente.

Casi hemos terminado en el lado de Strawberry, pero tengo una cosa adicional para compartir, y son los cargadores de datos.

Otra característica divertida (no siempre) de GraphQL son los resolutores recursivos. Viste arriba que en el marshalmétodo del Libro también defino author. De esta manera podemos ejecutar una consulta GraphQL como esta:

query {
book {
author {
name
}
}
}

Pero, ¿y si queremos ejecutar una consulta como esta?

query {
author {
books {
name
}
}
}

Esto no funcionará porque no hemos definido un atributo de libros en nuestro tipo Strawberry. Reescribamos nuestra Authorclase y agreguemos un DataLoaderal contexto predeterminado que Strawberry nos proporciona en nuestros métodos de clase:

from strawberry.dataloader import DataLoader

# ...

@strawberry.type
class Author:
id: strawberry.ID
name: str

@strawberry.field
async* def* books(self, info: Info) -\> list["Book"]:
books = await info.context["books_by_author"].load(self.id)
return [Book.marshal(book) for book in books]

@classmethod
def marshal(cls, model: models.Author) -\>"Author":
return cls(id=strawberry.ID(str(model.id)), name=model.name)

# ...

async* def* load_books_by_author(keys: list) -\> list[Book]:
async* with* models.get_session() as s:
all_queries = [select(models.Book).where(models.Book.author_id == key) for key in keys]
data = [( await s.execute(sql)).scalars().unique().all() for sql in all_queries]
print(keys, data)
return data

async* def* get_context() -\> dict:
return {
"books_by_author": DataLoader(load_fn=load_books_by_author),
}

# ...

graphql_app = GraphQLRouter(schema, context_getter=get_context)

Entendamos esto de abajo hacia arriba. Strawberry nos permite pasar funciones personalizadas a nuestros @strawberry.typemétodos de clase (aquellos envueltos con ) a través de un contexto. Este contexto se comparte en una sola solicitud.

DataLoader nos permite procesar por lotes múltiples solicitudes para que podamos reducir las llamadas de ida y vuelta al db. Creamos una DataLoaderinstancia y le informamos cómo cargar libros desde el dbpara el autor pasado. Ponemos esto DataLoaderen un diccionario y lo pasamos como context_getterargumento a GraphQLRouter. Esto hace que el diccionario esté disponible para nuestros métodos de clase a través de info.context. Lo usamos para cargar los libros de cada autor.

En este ejemplo, DataLoader no es muy útil. Sus principales beneficios brillan cuando llamamos a the DataLoadercon una lista de argumentos. Eso reduce considerablemente las llamadas a la base de datos. Y DataLoaderstambién la salida de caché y se comparten en una sola solicitud. Por lo tanto, si tuviera que pasar los mismos argumentos al cargador de datos en una sola solicitud varias veces, no se producirán visitas adicionales a la base de datos. ¡Súper poderoso!

Probando Fresa

La instancia de uvicorn  debería recargarse automáticamente una vez que realice estos cambios en el código y los guarde. Vaya a http://127.0.0.1:8000/graphql y pruebe el código más reciente.

Intenta ejecutar la siguiente mutación dos veces:

mutation Author {
addAuthor(name: "Yasoob") {
... on Author {
id
name
}
... on AuthorExists{
message
}
}
}

La primera vez debería salir esto:

{
"data": {
"addAuthor": {
"id": "1",
"name": "Yasoob"
}
}
}

Y la segunda vez debería generar esto:

{
"data": {
"addAuthor": {
"message": "Author with this name already exist"
}
}
}

Ahora intentemos agregar nuevos libros:

mutation Book {
addBook(name: "Practical Python Projects", authorName: "Yasoob") {
... on Book {
id
name
}
}
}

¡Dulce! Nuestro lado Python/Strawberry está funcionando perfectamente bien. Pero ahora necesitamos unir esto en el lado de Node/Next.js.

Configuración de dependencias de nodo

Usaremos graphql-codegenpara crear automáticamente ganchos escritos para nosotros. Entonces, el flujo de trabajo básico será que antes de que podamos usar una consulta, mutación o fragmento de GraphQL en nuestro código Typescript, lo definiremos en un archivo GraphQL. Luego graphql-codegen, analizará nuestra API Strawberry GraphQL y creará tipos y utilizará nuestra Consulta/Mutaciones/Fragmentos GraphQL personalizados para crear urqlganchos personalizados.

urqles una biblioteca de GraphQL bastante completa para React que hace que la interacción con las API de GraphQL sea mucho más simple. Al hacer todo esto, reduciremos mucho el esfuerzo de codificar ganchos escritos nosotros mismos antes de poder usar nuestra API GraphQL en nuestra aplicación Next.js/React.

Antes de que podamos continuar, necesitamos instalar algunas dependencias:

$ npm install graphql
$ npm install @graphql-codegen/cli
$ npm install @graphql-codegen/typescript
$ npm install @graphql-codegen/typescript-operations
$ npm install @graphql-codegen/typescript-urql
$ npm install urql

Aquí estamos instalando urqly algunos complementos para @graphql-codegen.

Configurando graphql-codegen

Ahora crearemos un codegen.ymlarchivo en la raíz de nuestro proyecto que dirá graphql-codegenqué hacer:

sobrescribir: verdadero esquema: "http://127.0.0.1:8000/graphql" documentos: './graphql/**/*.graphql' genera: graphql/graphql.ts: complementos:

  • "mecanografiado"
  • "operaciones mecanografiadas"
  • "mecanografiado-urql"

Informamos a graphql-codegen que puede encontrar el esquema para nuestra API GraphQL en http://127.0.0.1:8000/graphql . También le decimos (a través de la documentsclave) que hemos definido nuestros fragmentos personalizados, consultas y mutaciones en archivos graphql ubicados en la graphqlcarpeta. Luego le indicamos que genere graphql/graphql.tsun archivo ejecutando el esquema y los documentos a través de tres complementos.

Ahora cree una graphqlcarpeta en nuestro directorio de proyectos y cree un nuevo operations.graphqlarchivo dentro de ella. Definiremos todos los fragmentos, consultas y mutaciones que planeamos usar en nuestra aplicación. Podemos crear archivos separados para los tres y graphql-codegenlos fusionaremos automáticamente durante el procesamiento, pero lo mantendremos simple y pondremos todo en un solo archivo por ahora. Agreguemos el siguiente GraphQL a operations.graphql:

query Books {
books {
...BookFields
}
}

query Authors {
authors {
...AuthorFields
}
}

fragment BookFields on Book {
id
name
author {
name
}
}

fragment AuthorFields on Author {
id
name
}

mutation AddBook($name: String!, $authorName: String!) {
addBook(name: $name, authorName: $authorName) {
__typename
... on Book {
__typename
...BookFields
}
}
}

mutation AddAuthor($name: String!) {
addAuthor(name: $name) {
__typename
... on AuthorExists {
__typename
message
}
... on Author {
__typename
...AuthorFields
}
}
}

Esto es muy similar al código que estábamos ejecutando en la interfaz en línea de GraphiQL. Este código de GraphQL indicará graphql-codegenqué urqlmutación y enlaces de consulta necesita producir para nosotros.

Se ha debatido generar todas las graphql-codegenmutaciones y consultas mediante la introspección de nuestra API GraphQL en línea, pero hasta ahora no es posible hacerlo usando solo graphql-codegen. Existen herramientas que te permiten hacer eso, pero no las voy a usar en este artículo. Puedes explorarlos por tu cuenta.

Editemos el archivo package.json a continuación y agreguemos un comando para ejecutar a graphql-codegentravés de npm. Agrega este código en la scriptssección:

"codegen": "graphql-codegen --config codegen.yml"

Ahora podemos ir a la terminal y ejecutar graphql-codegen:

$ npm run codegen

Si el comando tiene éxito, debería tener un graphql.tsarchivo en la graphqlcarpeta. Podemos continuar y usar los urqlganchos generados en nuestro código Siguiente de la siguiente manera:

import {
useAuthorsQuery,
} from "../graphql/graphql";

// ....

const [result] = useAuthorsQuery(...);

Puede leer más sobre el graphql-codegen urqlcomplemento aquí .

Resolver problema de CORS

En un entorno de producción, puede servir la API de GraphQL y la aplicación Next.js/React desde el mismo dominio+PORT y eso asegurará que no encuentre problemas de CORS. Para el entorno de desarrollo, podemos agregar un código de proxy al next.config.jsarchivo para indicar a NextJS que envíe todas las llamadas a las /graphqlque uvicornse ejecutan en un puerto diferente:

/** _ @type _ {import('next').NextConfig} */
module.exports= {
reactStrictMode: true ,
async rewrites() {
return {
beforeFiles: [
{
source:"/graphql",
destination:"http://localhost:8000/graphql",
},
],
};
},
};

Esto asegurará que tampoco encuentre ningún problema de CORS en el desarrollo local.

Conclusión

Espero que hayas aprendido una cosa o dos de este artículo. Deliberadamente, no entré en demasiados detalles sobre ningún tema en particular, ya que dichos artículos ya existen en línea, pero es muy difícil encontrar un artículo que muestre cómo se conecta todo.

Puede encontrar todo el código de este artículo en mi GitHub . En el futuro, podría crear un proyecto completo para mostrarle un ejemplo más concreto de cómo puede utilizar el código generado en sus aplicaciones. Mientras tanto, puede echar un vistazo a este repositorio , que fue la inspiración para este artículo. Jokull fue probablemente la primera persona en presentar públicamente un proyecto que combinaba todas estas herramientas diferentes. ¡Gracias, Jokull!

Además, si tiene en mente algún proyecto de Python o de desarrollo web, comuníquese conmigo en hi@yasoob.me y comparta sus ideas. Hago una gran variedad de proyectos por lo que casi nada está fuera de lo común. Vamos a crear algo increíble juntos. ¡Te veo luego!👋❤️

 Esta historia se publicó originalmente en https://blog.logrocket.com/using-graphql-strawberry-fastapi-next-js/

#graphql #strawberry #fastapi #nextjs 

Jenny Jabde

1621251999

Quick Flow Male Enhancement Reviews, Benefits Price & Buy Quick Flow?

364bb242-ab45-4601-b9cc-e444f2270076

On the off chance that you fall in the subsequent class, Quick Flow Male Enhancement is the thing that your body is needing right now. The recently discovered male arrangement is the difficult solver for numerous types and types of erectile pressure causing brokenness and causes those issues to be rectified and henceforth blessings you with the more youthful sexual variant.

What is Quick Flow Male Enhancement?
With the new pill, you can supplant all extraordinary and numerous allopathic drugs you had been taking for each issue in an unexpected way. Quick Flow Male Enhancement is the one in all treating instrument pill and causes those explicitly hurtful issues to get right. Regardless of everything, those obstacles are restored and unquestionably, you can feel that the sexual peaks are better. This item builds body imperativeness and the measure of discharge that is required is likewise directed by it.

**How can it really function? **

Different results of this class have numerous regular Ingredients in them, yet the ones here in Quick Flow Male Enhancement are truly uncommon and furthermore natural in their reap and produce. This allows you to get the experience of the truth of more profound sex intercourse which you generally thought was a fantasy for you. Positively, this is a demonstrated natural thing, and relying upon it is no place off-base according to specialists. It is time that your body is given valuable minerals as requested by age.

download-1
**How to Buy? **

It is fundamental that you visit the site and see by your own eyes you willing we are to help you in each progression. Start from the terms and furthermore know inconspicuously the states of procurement. Any question must be addressed as of now or, more than likely later things probably won’t go as you might suspect. Purchase Quick Flow Male Enhancement utilizing any method of online installment and you may likewise go for the simple EMI choice out there.

https://www.benzinga.com/press-releases/21/03/wr20313473/quick-flow-male-enhancement-reviews-fast-flow-male-enhancement-most-effective-and-natural-formul

https://www.facebook.com/Quick-Flow-Male-Enhancement-111452187779423

#quick flow male enhancement #quick flow male enhancement reviews #quick flow male enhancement male health #quick flow male enhancement review #quick flow male enhancement offer #quick flow male enhancement trial

坂本  篤司

坂本 篤司

1654924740

Strawberry、FastAPI、およびNext.jsでのGraphQLの使用

皆さんこんにちは!👋

FastAPI、Strawberry、GraphQLなどについて聞いたことがあると思います。次に、Next.jsアプリでそれらを組み合わせる方法を紹介します。型付きコードで優れた開発者エクスペリエンス(DX)を取得することに焦点を当てます。たくさんの記事でそれぞれを個別に使用する方法を学ぶことができますが、特にStrawberryを使用して、それらを組み合わせるためのリソースはそれほど多くありません。

PythonベースのGraphQLライブラリは複数あり、それらはすべて互いにわずかに異なります。グラフェンは最も古く、さまざまな企業で本番環境で使用されていたため、長い間自然な選択でしたが、今では他の新しいライブラリもある程度の注目を集め始めています。

そのようなライブラリの1つであるStrawberryに焦点を当てます。これは比較的新しく、以前のバージョンの言語では利用できなかったPython機能を利用するため、Python3.7以降が必要です。データクラスを多用し、mypyを使用して完全に型指定されます。

N .B。、 GitHubのこの記事から完全なコードを見つけることができます。

最終製品:本のデ​​ータベース

タイプを利用し、TypescriptコードのGraphQLクエリとミューテーションを利用するために型付きReactフックを自動的に生成しながら、SQLAlchemy + Strawberry+FastAPIアプリケーションの作成を正常に開始する方法を示す基本的なプロジェクト構造を用意します。React Hooksはを利用しurqlますが、Apolloに簡単に切り替えることができます。

書店のアイデアに基づいてDBスキーマを作成します。著者とその本に関する情報を保存します。React / Next.jsを使用して完全なアプリケーションを作成することはしませんが、必要に応じて、必要なすべての要素を用意します。

目標は、あらゆる場所で型を使用し、可能な限り多くのコード生成を自動化することで、開発者のエクスペリエンスを向上させることです。これは、開発中のより多くのバグをキャッチするのに役立ちます。

この投稿は、このGitHubリポジトリに触発されています。

入門

作業を開始する前に、まず次のライブラリ/パッケージをインストールする必要があります。

  • Strawberry —これはPython側でGraphQLサポートを提供するGraphQLライブラリです
  • FastAPI —これはStrawberryベースのGraphQLAPIを提供するためのWebフレームワークです
  • Uvicorn —これは本番環境でFastAPIアプリケーションを提供するASGIWebサーバーです
  • Aiosqlite —これはSQLiteの非同期サポートを提供します
  • SQLAlchemy —これはSQLiteDBを操作するためのORMです

新しいフォルダを作成し、pipを使用してこれらのライブラリをインストールしましょう。新しいフォルダを手動で作成する代わりに、create-next-appコマンドを使用して作成します。このコマンドで作成されたフォルダーを、プロジェクト全体のルートフォルダーとして扱います。これにより、説明が簡単になります。必要なJS/TSライブラリについては後で説明します。今のところ、Python側にのみ焦点を当てます。

create-next-appシステムで有効なコマンドとして使用できることを確認してください。実行したら、ターミナルで次のコマンドを実行します。

$ npx create-next-app@latest --typescript strawberry_nextjs

上記のコマンドでstrawberry_nextjsフォルダを作成する必要があります。次に、そのフォルダーに移動して、必要なPythonベースの依存関係をインストールします。

$ cd strawberry_nextjs

$ python -m venv virtualenv
$ source virtualenv/bin/activate

$ pip install 'strawberry-graphql[fastapi]' fastapi 'uvicorn[standard]' aiosqlite sqlalchemy

Strawberry + FastAPI:Hello、world!

「Hello、world!」から始めましょう。例を挙げれば、Strawberryアプリケーションを構成する断片が表示されます。app.pyという名前の新しいファイルを作成し、それに次のコードを追加します。

import strawberry

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

authors: list[str] = []

@strawberry.type
class Query:
@strawberry.field
def all_authors(self) -\> list[str]:
return authors

@strawberry.type
class Mutation:
@strawberry.field
def add_author(self, name: str) -\> str:
authors.append(name)
return name

schema = strawberry.Schema(query=Query, mutation=Mutation)

graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")

このコードをまとめて見てみましょう。まず、必要なライブラリとパッケージをインポートします。一時データベースとして機能し、著者名を保持する著者リストを作成します(実際のデータベースを簡単に作成します)。

次に、Queryクラスを作成し、それをイチゴ.typeデコレータでデコレートします。これにより、GraphQLタイプに変換されます。このクラス内でall_authors、リストからすべての作成者を返すリゾルバーを定義します。リゾルバーは、その戻りタイプも指定する必要があります。次のセクションで少し複雑な型の定義を見ていきますが、今のところ、文字列のリストで十分です。

Mutation次に、すべてのGraphQLミューテーションを含む新しいクラスを作成します。add_author今のところ、名前を取得して作成者リストに追加する単純なミューテーションしかありません。

次に、クエリクラスとミューテーションクラスをstrawberry.Schemaに渡してGraphQLスキーマを作成し、それをに渡しますGraphQLRouter。最後に、をFastAPIにプラグインし、GraphQLRouterGraphQLRouterに/graphqlエンドポイントへのすべての着信要求を処理させます。

これらの用語の意味がわからない場合は、簡単に復習させてください。

  • クエリ—データ/レコードを取得するためにサーバーに送信されるリクエストの一種
  • ミューテーション—データ/レコードを作成/更新/削除するためにサーバーに送信されるリクエストの一種
  • タイプ—GraphQLで操作するオブジェクト。これらは、データ/レコード/エラーとその間のすべてを表します
  • リゾルバー—スキーマの単一フィールドのデータを入力する関数

Strawberryのスキーマの基本について詳しくは、公式ドキュメントページをご覧ください。

このコードを実行するには、ターミナルに移動して次のコマンドを実行します。

$ uvicorn app:app --reload --host '::'

これにより、次のような出力が出力されます。

INFO: Will watch for changes in these directories: [‘/Users/yasoob/Desktop/strawberry_nextjs’]
INFO: Uvicorn running on http://[::]:8000 (Press CTRL+C to quit)
INFO: Started reloader process [56427] using watchgod
INFO: Started server process [56429]
INFO: Waiting for application startup.
INFO: Application startup complete.

ここでhttps://127.0.0.1:8000/graphqlにアクセスすると、インタラクティブなGraphiQLプレイグラウンドが表示されます。

このクエリを実行してみてください:

query MyQuery {
allAuthors
}

これにより、空のリストが出力されます。リストに著者がいないため、これは予想されることです。ただし、最初にミューテーションを実行してから上記のクエリを実行することで、これを修正できます。

新しい作成者を作成するには、addAuthorミューテーションを実行します。

mutation MyMutation {
addAuthor(name: "Yasoob")
}

そして、allAuthorsクエリを実行すると、出力リストにYasoobが表示されます。

{
"data": {
"allAuthors": [
"Yasoob"
]
}
}

すでにこれに気付いているかもしれませんが、Strawberryは内部でcamel_caseフィールドをPascalCaseフィールドに自動的に変換するため、GraphQL API呼び出しでPascalCaseを使用し、Pythonコードでcamel_caseを使用する規則に従うことができます。

基本を理解したところで、先に進んで、書店タイプのアプリケーションの作業を始めましょう。

スキーマの定義

最初に理解する必要があるのは、スキーマがどうなるかです。アプリケーションに対してどのクエリ、ミューテーション、およびタイプを定義する必要がありますか。

この記事では、GraphQLの基本に焦点を当てるのではなく、Strawberry固有の部分にのみ焦点を当てます。すでに述べたように、私たちは書店のアイデアに従います。著者とその本のデータを保存します。これが私たちのデータベースが最後にどのように見えるかです:

SQLAlchemyモデルの定義

SQLAlchemyを使用するので、両方のモデルをクラスとして定義しましょう。非同期SQLAlchemyを使用します。フォルダに新しいmodels.pyファイルを作成しstrawberry_nextjs、それに次のインポートを追加します。

import asyncio
from contextlib import asynccontextmanager
from typing import AsyncGenerator, Optional

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

これらのインポートは、少しだけ意味があります。SQLAlchemyの宣言型ベースモデルから継承するクラスを使用して、モデルを宣言的に定義します。SQLAlchemyはdeclarative_base()、宣言型ベースモデルを取得するための関数を提供します。それを使用して、モデルを定義しましょう。

Base = declarative_base()

class Author(Base):
__tablename__ ="authors"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False, unique=True)

books: list["Book"] = relationship("Book", lazy="joined", back_populates="author")

class Book(Base):
__tablename__ ="books"
id: int = Column(Integer, primary_key=True, index=True)
name: str = Column(String, nullable=False)
author_id: Optional[int] = Column(Integer, ForeignKey(Author.id), nullable=True)

author: Optional[Author] = relationship(Author, lazy="joined", back_populates="books")

Our Authorクラスは2つの列を定義します。idこれname. booksは、モデル間の関係をナビゲートするのに役立つ単なる関係属性ですが、別の列として作成者テーブルに格納されません。Books属性を作成者としてバックポピュレートします。これはbook.author、本のリンクされた著者にアクセスするためにアクセスできることを意味します。

The BookclassはAuthorクラスと非常によく似ています。author_idcolumn著者と本をリンクする追加を定義します。リレーションシップとは異なり、これはブックテーブルに保存されます。また、author属性をとしてバックポピュレートしますbooks。このようにして、次のような特定の著者の本にアクセスできますauthor.books

次に、SQLAlchemyに使用するDBとその場所を指定する必要があります。

engine = create_async_engine(
"sqlite+aiosqlite:///./database.db", connect_args={"check_same_thread": False}
)

SQLAlchemyが非同期でSQLiteDBを使用できるようaiosqliteに、接続文字列の一部として使用します。そして、引数を渡して、複数のスレッド間で同じ接続を使用できることを確認します。aiosqlitecheck_same_thread

同時書き込み操作でデータが破損しないように特に注意せずにマルチスレッド方式でSQLiteを使用することは安全ではないため、本番環境ではPostgreSQLまたは同様の高性能DBを使用することをお勧めします。

次に、セッションを作成する必要があります。

async_session = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)

また、各インタラクションでセッションを適切に閉じるために、新しいコンテキストマネージャーを作成します。

@asynccontextmanager
async def get_session() -\> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
async with session.begin():
try :
yield session
finally :
await session.close()

コンテキストマネージャーなしでもセッションを使用できますが、セッションを使用するたびに手動でセッションを閉じる必要があることを意味します。

最後に、新しいDBが作成されていることを確認する必要があります。ファイルを直接models.py実行しようとすると、宣言されたモデルを使用して新しいDBファイルを作成するコードをファイルに追加できます。models.py

async def _async_main():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
await engine.dispose()

if __name__=="__main__":
print("Dropping and re/creating tables")
asyncio.run(_async_main())
print("Done.")

これにより、DB内の既存のテーブルがすべて削除され、このファイルで定義されているモデルに基づいて再作成されます。セーフガードを追加して、データを誤って削除しないようにすることはできますが、それはこの記事の範囲を超えています。すべてがどのように結びついているかをお見せしようとしています。

これでファイルが完成し、SQLAlchemyモデルにマップするStrawberryとタイプmodels.pyを定義する準備が整いました。AuthorBook

イチゴの種類の定義

この記事を読むまでに、StrawberryはSQLAlchemyモデルを直接使用するための安定した組み込みサポートを備えている可能性がありますが、今のところ、SQLAlchemyモデルにマップするカスタムStrawberryタイプを定義する必要があります。最初にそれらを定義してから、それらがどのように機能するかを理解しましょう。このコードをapp.pyファイルに入れます:

モデルのインポート

@ skeleton.type
クラス作成者:
id:イチゴ.ID
名:str

@classmethod
def marshal(cls、model:models.Author)-\>” Author”:
return cls(id = skeleton.ID(str(model.id))、name = model.name)

@ 新たなタイプ
クラス本:
id:新たにいちご.ID
名:str
作者:オプション[作者]=なし

@classmethod
def marshal(cls、model:models.Book)-\>” Book”:
return cls(
id = skeleton.ID(str(model.id))、
name = model.name、
author = Author.marshal(model .author)if model.author else None、

新しいタイプを定義するには、クラスを作成し、それをイチゴ.typeデコレータでデコレートするだけです。Mutationこれは、とタイプを定義した方法と非常によく似ていQueryます。唯一の違いは、今回はこれらの型を直接に渡さないため、Strawberryはそれらをまたは型strawberry.Schemaとして扱わないことです。MutationQuery

各クラスにはマーシャルメソッドがあります。このメソッドにより、SQLAlchemyモデルを取り込んで、そこからStrawberry型クラスインスタンスを作成できます。Strawberryはstrawberry.ID、オブジェクトの一意の識別子を表すために使用します。Strawberryは、デフォルトで、と同じように機能するいくつかのスカラータイプを提供しますstrawberry.ID。これらをどのように使用してSQLAlchemyデータをこのカスタム型クラス属性にマップするかは私たち次第です。私たちは通常、SQLAlchemyの列タイプに最もよく似た代替案を見つけて、それを使用しようとします。

このBookクラスでは、type属性をオプションとしてマークし、デフォルト値を指定する方法も示します。著者をオプションとしてマークします。これは、それがどのように行われ、後で行われるかを示すためだけのものです。これを必要に応じてマークします。

もう1つの注意点は、ミューテーションとクエリ呼び出しのリターンタイプのリストを定義することもできるということです。これにより、GraphQL APIコンシューマーは、受け取ったリターンタイプに基づいて出力を適切に処理できるようになります。GraphQLについてご存知の場合は、これがフラグメントの定義方法です。最初に型を定義しましょう。次に、新しいミューテーションとクエリクラスの定義を開始したら、それらの使用方法を示します。

@strawberry.type
class AuthorExists:
message: str ="Author with this name already exists"

@strawberry.type
class AuthorNotFound:
message: str ="Couldn't find an author with the supplied name"

@strawberry.type
class AuthorNameMissing:
message: str ="Please supply an author name"

AddBookResponse = strawberry.union("AddBookResponse", (Book, AuthorNotFound, AuthorNameMissing))
AddAuthorResponse = strawberry.union("AddAuthorResponse", (Author, AuthorExists))

基本的に、私たちAddBookResponseAddAuthorResponseタイプは共用体タイプであり、タプルにリストされている3つ(または2つ)のタイプのいずれかである可能性があると言っています。

クエリとミューテーションの定義

それでは、クエリを定義しましょう。クエリは2つだけです。1つはすべての本をリストし、もう1つはすべての著者をリストします。

from sqlalchemy import select

# ...

@strawberry.type
class Query:
@strawberry.field
async* def* books(self) -\> list[Book]:
async* with* models.get_session() as s:
sql = select(models.Book).order_by(models.Book.name)
db_books = ( await s.execute(sql)).scalars().unique().all()
return [Book.marshal(book) for book in db_books]

@strawberry.field
async* def* authors(self) -\> list[Author]:
async* with* models.get_session() as s:
sql = select(models.Author).order_by(models.Author.name)
db_authors = ( await s.execute(sql)).scalars().unique().all()
return [Author.marshal(loc) for loc in db_authors]

ここではたくさんのことが起こっているようですので、分解してみましょう。

まず、ブックリゾルバーを見てください。コンテキストマネージャーを使用get_sessionして、新しいセッションを作成します。次に、ブックモデルを選択し、ブック名に基づいてそれらを並べ替える新しいSQLステートメントを作成します。その後、前に作成したセッションを使用してSQLステートメントを実行し、結果をdb_books変数に入れます。最後に、各本をStrawberry Bookタイプにマーシャリングし、それを出力として返します。また、ブックリゾルバーの返品タイプをのとしてマークしlistますBooks

リゾルバーはauthorsリゾルバーと非常に似ているbooksので、説明する必要はありません。

今、私たちの突然変異を書きましょう:

@strawberry.type
class Mutation:
@strawberry.mutation
async* def* add_book(self, name: str, author_name: Optional[str]) -\> AddBookResponse:
async* with* models.get_session() as s:
db_author =None
if author_name:
sql = select(models.Author).where(models.Author.name == author_name)
db_author = ( await s.execute(sql)).scalars().first()
if* not* db_author:
return AuthorNotFound()
else :
return AuthorNameMissing()
db_book = models.Book(name=name, author=db_author)
s.add(db_book)
await s.commit()
return Book.marshal(db_book)

@strawberry.mutation
async* def* add_author(self, name: str) -\> AddAuthorResponse:
async* with* models.get_session() as s:
sql = select(models.Author).where(models.Author.name == name)
existing_db_author = ( await s.execute(sql)).first()
if existing_db_author is* notNone:
*return AuthorExists()
db_author = models.Author(name=name)
s.add(db_author)
await s.commit()
return Author.marshal(db_author)

突然変異はかなり簡単です。add_book突然変異から始めましょう。

add_book本の名前と著者の名前を入力として受け取ります。author_nameオプションの引数を定義する方法を示すために、をオプションとして定義していますが、メソッド本体では、が渡されない場合に戻ることでの存在を強制しますauthor_nameAuthorNameMissingauthor_name

Authors渡されたものにdb基づいてフィルターをauthor_nameかけ、指定された名前の作成者が存在することを確認します。それ以外の場合は、を返しAuthorNotFoundます。これらのチェックの両方に合格した場合は、新しいmodels.Bookインスタンスを作成dbし、セッションを介してそれをviaに追加し、コミットします。最後に、マーシャリングされた本を戻り値として返します。

add_authorはとほぼ同じなadd_bookので、コードをもう一度確認する理由はありません。

ストロベリー側でほぼ完了しましたが、共有できるボーナスが1つあります。それは、データローダーです。

GraphQLのもう1つの(常にではない)楽しい機能は、再帰的リゾルバーです。あなたはその上でmarshal私が定義する本の方法でそれを見ましたauthor。このようにして、次のようなGraphQLクエリを実行できます。

query {
book {
author {
name
}
}
}

しかし、次のようなクエリを実行したい場合はどうなりますか?

query {
author {
books {
name
}
}
}

Strawberryタイプにbooks属性を定義していないため、これは機能しません。クラスを書き直して、Strawberryがクラスメソッドで提供するデフォルトのコンテキストにAuthorを追加しましょう。DataLoader

from strawberry.dataloader import DataLoader

# ...

@strawberry.type
class Author:
id: strawberry.ID
name: str

@strawberry.field
async* def* books(self, info: Info) -\> list["Book"]:
books = await info.context["books_by_author"].load(self.id)
return [Book.marshal(book) for book in books]

@classmethod
def marshal(cls, model: models.Author) -\>"Author":
return cls(id=strawberry.ID(str(model.id)), name=model.name)

# ...

async* def* load_books_by_author(keys: list) -\> list[Book]:
async* with* models.get_session() as s:
all_queries = [select(models.Book).where(models.Book.author_id == key) for key in keys]
data = [( await s.execute(sql)).scalars().unique().all() for sql in all_queries]
print(keys, data)
return data

async* def* get_context() -\> dict:
return {
"books_by_author": DataLoader(load_fn=load_books_by_author),
}

# ...

graphql_app = GraphQLRouter(schema, context_getter=get_context)

これを下から上に理解しましょう。Strawberryを使用すると、コンテキストを介してカスタム関数をクラス(ラップされたもの@strawberry.type)メソッドに渡すことができます。このコンテキストは、単一のリクエスト間で共有されます。

DataLoaderを使用すると、複数のリクエストをバッチ処理できるため、への呼び出しを減らすことができますdb。インスタンスを作成し、渡された作成者のDataLoaderからブックをロードする方法を通知します。これを辞書にdb入れて、引数としてに渡します。これにより、。を介してクラスメソッドで辞書を使用できるようになります。これを使用して、各著者の本をロードします。DataLoadercontext_getterGraphQLRouterinfo.context

この例では、DataLoaderはあまり役に立ちません。DataLoaderその主な利点は、引数のリストを使用してを呼び出すときに明らかになります。これにより、データベース呼び出しが大幅に削減されます。またDataLoaders、出力をキャッシュし、それらは単一のリクエストで共有されます。したがって、1回のリクエストで同じ引数をデータローダーに複数回渡す場合、追加のデータベースヒットは発生しません。超パワフル!

ストロベリーのテスト

これらのコードを変更して保存すると、 uvicorn インスタンスは自動的にリロードされます。http://127.0.0.1:8000/graphqlにアクセスして、最新のコードをテストします。

次のミューテーションを2回実行してみてください。

mutation Author {
addAuthor(name: "Yasoob") {
... on Author {
id
name
}
... on AuthorExists{
message
}
}
}

初めてこれを出力する必要があります:

{
"data": {
"addAuthor": {
"id": "1",
"name": "Yasoob"
}
}
}

そして2回目はこれを出力する必要があります:

{
"data": {
"addAuthor": {
"message": "Author with this name already exist"
}
}
}

それでは、新しい本を追加してみましょう。

mutation Book {
addBook(name: "Practical Python Projects", authorName: "Yasoob") {
... on Book {
id
name
}
}
}

甘い!Python/Strawberry側は完全に正常に機能しています。しかし今、これをNode/Next.js側で結び付ける必要があります。

ノードの依存関係の設定

を使用graphql-codegenして、型付きフックを自動的に作成します。したがって、基本的なワークフローは、TypescriptコードでGraphQLクエリ、ミューテーション、またはフラグメントを使用する前に、GraphQLファイルでそれを定義することです。次にgraphql-codegen、Strawberry GraphQL APIをイントロスペクトして型を作成し、カスタム定義のGraphQLクエリ/ミューテーション/フラグメントを使用してカスタムurqlフックを作成します。

urqlはReact用のかなりフル機能のGraphQLライブラリであり、GraphQLAPIとの対話を非常に簡単にします。これらすべてを行うことで、Next.js /ReactアプリでGraphQLAPIを使用する前に、型付きフックを自分でコーディングする手間を大幅に削減できます。

先に進む前に、いくつかの依存関係をインストールする必要があります。

$ npm install graphql
$ npm install @graphql-codegen/cli
$ npm install @graphql-codegen/typescript
$ npm install @graphql-codegen/typescript-operations
$ npm install @graphql-codegen/typescript-urql
$ npm install urql

ここではurql、のプラグインといくつかのプラグインをインストールしています@graphql-codegen

graphql-codegenの設定

codegen.yml次に、プロジェクトのルートに、graphql-codegen何をすべきかを示すファイルを作成します。

上書き:trueスキーマ: "http://127.0.0.1:8000/graphql"ドキュメント:'./graphql/**/*.graphql'生成:graphql /graphql.ts:プラグイン:

  • 「typescript」
  • 「typescript-operations」
  • 「typescript-urql」

GraphQL APIのスキーマがhttp://127.0.0.1:8000/graphqlにあることをgraphql-codegenに通知しています。documentsまた、フォルダーにあるgraphqlファイルでカスタムフラグメント、クエリ、およびミューテーションを定義したことも(キーを介して)通知しgraphqlます。graphql/graphql.ts次に、3つのプラグインを介してスキーマとドキュメントを実行することにより、ファイルを生成するように指示します。

次にgraphql、プロジェクトディレクトリにフォルダを作成し、その中に新しいoperations.graphqlファイルを作成します。アプリで使用する予定のすべてのフラグメント、クエリ、ミューテーションを定義します。3つすべてに個別のファイルを作成graphql-codegenし、処理中にそれらを自動的にマージしますが、ここでは単純に保ち、すべてを1つのファイルにまとめます。次のGraphQLを次のように追加しましょうoperations.graphql

query Books {
books {
...BookFields
}
}

query Authors {
authors {
...AuthorFields
}
}

fragment BookFields on Book {
id
name
author {
name
}
}

fragment AuthorFields on Author {
id
name
}

mutation AddBook($name: String!, $authorName: String!) {
addBook(name: $name, authorName: $authorName) {
__typename
... on Book {
__typename
...BookFields
}
}
}

mutation AddAuthor($name: String!) {
addAuthor(name: $name) {
__typename
... on AuthorExists {
__typename
message
}
... on Author {
__typename
...AuthorFields
}
}
}

これは、GraphiQLオンラインインターフェイスで実行していたコードと非常によく似ています。このGraphQLコードは、graphql-codegenどのurqlミューテーションとクエリフックを生成する必要があるかを示します。

オンラインのGraphQLAPIをイントロスペクトして、すべてのミューテーションとクエリを生成するようにすることが議論されてましたが、これまでのところ、を使用してそれを行うことはできません。それを可能にするツールは存在しますが、この記事ではそれらを使用しません。あなたは自分でそれらを探索することができます。graphql-codegengraphql-codegen

graphql-codegen次にpackage.jsonファイルを編集し、npmを介して実行するコマンドを追加しましょう。scripts次のセクションに次のコードを追加します。

"codegen": "graphql-codegen --config codegen.yml"

これで、ターミナルに移動して、graphql-codegenを実行できます。

$ npm run codegen

コマンドが成功した場合は、フォルダ内にgraphql.tsファイルがあるはずです。次のように、次のコードでgraphql生成されたフックを使用できます。urql

import {
useAuthorsQuery,
} from "../graphql/graphql";

// ....

const [result] = useAuthorsQuery(...);

graphql-codegen urqlプラグインの詳細については、こちらをご覧ください。

CORSの問題を解決する

実稼働環境では、同じドメイン+PORTからGraphQLAPIとNext.js/ Reactアプリを提供できます。これにより、CORSの問題が発生しないようになります。開発環境では、ファイルにプロキシコードを追加して、別のポートで実行されているすべてnext.config.jsの呼び出しをプロキシするようにNextJSに指示できます。/graphqluvicorn

/** _ @type _ {import('next').NextConfig} */
module.exports= {
reactStrictMode: true ,
async rewrites() {
return {
beforeFiles: [
{
source:"/graphql",
destination:"http://localhost:8000/graphql",
},
],
};
},
};

これにより、ローカル開発でもCORSの問題が発生しないようになります。

結論

この記事から1つか2つのことを学んだことを願っています。そのような記事はすでにオンラインに存在するため、私は意図的に単一のトピックについてあまり詳しく説明しませんでしたが、すべてがどのように相互に関連しているかを示す記事を見つけるのは非常に困難です。

この記事のすべてのコードは、私のGitHubにあります。将来的には、生成されたコードをアプリで利用する方法のより具体的な例を示すために、完全なプロジェクトを作成する可能性があります。それまでの間、この記事のインスピレーションとなったこのリポジトリをご覧ください。Jokullは、おそらく、これらのさまざまなツールをすべて組み合わせたプロジェクトを公にホストした最初の人物でした。ありがとう、ジョクル!

また、PythonまたはWeb開発プロジェクトを念頭に置いている場合は、hi @ yasoob.meに連絡して、アイデアを共有してください。私はさまざまなプロジェクトを行っているので、異常なことはほとんどありません。一緒に素晴らしいものを作りましょう。また後で!👋❤️

 このストーリーは、もともとhttps://blog.logrocket.com/using-graphql-新たに公開されました-fastapi-next-js /

#graphql #strawberry #fastapi #nextjs