LoginSignup
38
44

More than 3 years have passed since last update.

【翻訳記事】FastAPIは実際にDjangoと非常に相性が良い

Last updated at Posted at 2020-08-21

これはなに?

FastAPIっていういい感じのpythonのAPIフレームワークがあって使っているのだが,公式ではSQLalchemyしか連携していない.ASGI関連の整備が遅れている(?)せいでDjangoとのあれこれがよくわからん状態らしい(伝聞推定)

で,どうにか先駆者はいないかと探したところ,すでに議論があった.

ここでおすすめされているやり方について,おそらく色んな人がDjangoをやってRestAPIに挑戦するもDRFに敗れるという経験をしているだろうことから,それよりもとっつきやすいであろうFastAPIでの再挑戦を促す意味で全文訳してみた.(と,いってもほぼすべてをみらい翻訳にかけただけなのだが……笑)

追記 (2020/10/13)

この記事 を参考にして自分でも作ってみた.

FastAPIの公式でやっている crud.pyadapterに相当する……という理解でいいのだろうか? from_modelfrom_qs が何をやっているのかイマイチ理解が不足している.schemaだから型をアレコレしてデータの検証をやっているだけとは思うのだが,まだ精進が足りない.

追記 (2020/10/15)

from_modelするときにForeignKeyでエラーが出た場合

Djangoで定義したモデルAが,別のモデルBを外部キー制約でもって参照していると想定する.

このとき,schema A 側で単純にfrom_modelして Django model のインスタンスを Pydantic に入れようとすると上手く行かず弾かれる.schema A 側で schema B の型をインポートしても,そのままでは型が合わないからだ.(Pydanticdjango.modelsを突っ込むことになる)

ので,この場合はschema B 側で定義されているfrom_modelを schema A 側にインポートして使えば良い


FastAPI actually plays very well with Django

You know me, I’m a Django fan. It’s my preferred way of developing web apps, mainly because of the absolutely vast ecosystem of apps and libraries it has, and the fact that it is really well-designed. I love how modular it is, and how it lets you use any of the parts you like and forget about the ones you don’t want. This is going to be emphasized rather spectacularly in this article, as I’m going to do things nobody should ever have to do.

私はDjangoのファンです。ウェブアプリの開発方法として私が気に入っているのは、主にアプリとライブラリの巨大なエコシステムと、本当によくデザインされているという事実です。モジュール化されていて、好きなパーツを使って、いらないパーツのことを忘れられるところが気に入っています。この記事では、誰もやるべきではないことを行うため、この点を非常に強調します。

My only issue with Django was that it never really had a good way of making APIs. I hate DRF with somewhat of a passion, I always found its API way too complicated and verbose, and never managed to grok it. Even the simplest things felt cumbersome, and the moment your API objects deviated from looking exactly like your DB models, you were in a world of hurt. I generally prefer writing a simple class-based view for my APIs, but then I don’t get automatic docs and other niceties.

Djangoに関する唯一の問題は、APIを作成するための優れた方法が実際には存在しなかったことです。私はDRFにいくらか情熱を持っているのですが、 APIがあまりにも複雑で冗長であることにいつも気付き、それを理解することができませんでした。最も単純なものでさえ面倒に感じられ、APIオブジェクトがDBモデルとまったく同じように見えなくなった瞬間、あなたは傷ついた世界にいました。私は通常、自分のAPIのために単純なクラスベースのビューを書くことを好みますが、自動化されたドキュメントやその他の細かい点は知りません。

It’s no surprise, then, that when I found FastAPI I was really excited, I really liked its autogenerated docs, dependency injection system, and lack of magical “request” objects or big JSON blobs. It looked very simple, well-architected and with sane defaults, and I seriously considered developing the API for my company’s next product on it, but was apprehensive about two things: It lacked Django’s ecosystem, and it didn’t have an ORM as good and well-integrated as Django’s. I would also miss Django’s admin interface a lot. Three things.

FastAPIを見つけたとき、私は本当に興奮し、その自動生成されたドキュメント、依存性注入システム、魔法のような“要求”オブジェクトや大きなJSONブロブがないことを本当に気に入りました。非常にシンプルで、よく設計されていて、適切なデフォルトがあるように見えたので、私の会社の次の製品のためにAPIを開発することを真剣に検討しましたが、2つの点について心配していました: Djangoのエコシステムが欠けていたことと、Djangoのように良くてよく統合されたORMがなかったことです。また、Djangoの管理インターフェースが恋しくなります。いや、やっぱり3つありましたねw

It would have been great if FastAPI was a Django library, but I guess the asynchronicity wouldn’t have been possible. Still, there’s no reason for DRF not to have an API as nice as FastAPI’s, but there’s no helping that. A fantastical notion caught hold of me: What if I could combine FastAPI’s view serving with Django’s ORM and apps? Verily, I say unto thee, it would be rad.

FastAPIがDjangoライブラリであればよかったのですが、非同期性は実現できなかったのではないでしょうか。とはいえ、DRFがFastAPIほど優れたAPIを持っていない理由はないが、それを助けるものはありません。FastAPIのビューをDjangoのORMやアプリと組み合わせることができたらどうでしょうか? 「それは大変なことだ!」

And that’s exactly what I did. Here’s how:

でもそれがまさに私のやったことです。その方法は次のとおりです。

General details

Each part of this unholy union needed a different integration. I will go into details on each part here, and post code in the next section.

この神聖でない結合のそれぞれの部分は、異なる統合を必要としました。ここで各部分の詳細を説明し、次のセクションでコードをお見せします。

High-level overview

The way this profane joining works is by using FastAPI as the view layer, and importing and using parts of Django separately. This means that some things like middleware will obviously not work, since Django is not handling views at all.

この月並みな結合が機能する方法は、ビュー層としてFastAPIを使用し、Djangoの一部を個別にインポートして使用することです。つまり、Djangoはビューをまったく処理しないため、ミドルウェアのようなものは明らかに機能しません。

I also didn’t try to get asynchronicity working, which might be a dealbreaker for many people. I suspect I’d have issues, as Django doesn’t support it very well yet, and the ORM would probably block all view threads. If you do get it working, or try to, please leave a comment here or contact me.

非同期性を実現しようともしませんでした。Djangoはまだ十分にサポートしておらず、ORMはおそらくすべてのビュー・スレッドをブロックするため、私には問題があるのではないかと思います。もしうまくいくようなら、ここにコメントを残すか、私に連絡してください。

ORM

The ORM works quite well, basically exactly as if you were using Django. Migrations work fine, and any app that interfaces with or enhances the ORM should also work properly.

ORMは非常によく機能し、基本的にはDjangoを使用している場合とまったく同じです。移行は正常に動作し、ORMとのインターフェイスやORMを拡張するアプリも正常に動作するはずです。

Tests

Tests are working great as well, using Django’s ./manage.py test harness/discovery. If you want to use another runner like pytest, that should also work properly, though I haven’t tested it.

テストもDjangoを使ってうまくいっています。./manage.py testで hunness/discovery を試してみてください。pytestのような別のランナーを使用したい場合も、正常に動作するはずですが、テストはしていません。

To clarify, we’ll be testing the FastAPI views using Django’s test runner and ./manage.py test. The one problem I had was that the usual TestCase class didn’t work (it gave me “table is locked” errors), but TransactionTestCase works splendidly and is probably quite a bit faster, too.

明確にするために、Djangoのテスト・ランナーとを使用してFastAPIビューをテストします。私が抱えていた1つの問題は、通常のTestCaseクラスが動作しないことでした(「テーブルはロックされています」というエラーが出ました)が、TransactionTestCaseは見事に動作し、おそらくかなり高速です。

The admin and other views

The admin is a vital part of Django, it makes dealing with your models much, much easier than having to wrangle with the database, so it was important to me to get working. And I did! It’s a pretty hacky way, but it works perfectly, since the trick is to just run Django on another port and have it serve the admin. You can have it serve any other views you want, this way you can use FastAPI to serve your API and use Django for static files and everything else (assuming you have anything else).

管理者はDjangoの重要な部分であり、データベースと口論するよりもはるかに簡単にモデルを扱うことができるため、私にとってはここが機能してくれることが重要でした。そして、成し遂げたのです!Djangoを別のポートで実行して管理者にサービスを提供するのがコツなので、これは非常に巧妙な方法ですが、完璧に機能します。他のビューにもサービスを提供することができ、FastAPIを使用してAPIにサービスを提供したり、Djangoを静的ファイルなどに使用したりすることができます(他に何かあると仮定して)。

All you need is a second stanza in your reverse proxy.

必要なのは、リバース・プロキシー内の2番目のスタンザだけです。

The code

Without further delay, the part you’ve all been waiting for, the code. This section includes both the bare essentials for getting the integration working, and some best practices I’ve found when working with FastAPI, such as URL reversing, easier DB model to API model conversion, etc. I hope you’ll find it useful.

これ以上待ちくたびれさせることがないように、皆さんが待ち望んでいた部分、つまりコードについて紹介します。このセクションには、統合を機能させるための最低限の基本事項と、FastAPIを使用する際に見つけたベストプラクティス (URLの反転、DBモデルからAPIモデルへの変換の容易さなど) の両方が含まれています。

Starting out

To begin, install FastAPI, Uvicorn and Django and create a Django project as you normally would. I recommend my “new project” template, it contains various niceties you may want to use. We’re going to replace some parts and add others. I’ve created a project called goatfish and an app called main to illustrate, you can call yours whatever you want. Make sure you add "main" to Django’s INSTALLED_APPS list in the settings so it discovers the models and migrations.

まず、FastAPI、Uvicorn、Djangoをインストールし、通常どおりDjangoプロジェクトを作成します。私の「新規プロジェクト」テンプレートをお勧めします。いくつかのパーツを交換し、他のパーツを追加します。私はgoatfishと呼ばれるプロジェクトと、イラストを描くためのmainと呼ばれるアプリを作りました。モデルとマイグレーションを検出できるように、Djangoの INSTALLED_APPS リストの設定に「main」を追加してください。

The models

We should probably start with our data model so everything else makes sense. The API is going to be a straightforward CRUD API, which will serve a model we’ll call Simulation and provide authentication.

他のすべてが意味をなすように、おそらくデータモデルから始めるべきでしょう。APIは単純なCRUD APIであり、Simulationと呼ばれるモデルを提供し、認証を提供します。

Since we have an API and a database, and the models for the two are neither semantically nor functionally identical, we’ll have two sections in our models.py, one for each model type.

APIとデータベースがあり、この2つのモデルは意味的にも機能的にも同一ではないため、このモデルには2つのセクションがあります。これは、モデル・タイプごとに1つずつです。

main/models.py:

main/models.py
from typing import Any
from typing import Dict
from typing import List

import shortuuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from pydantic import BaseModel


def generate_uuid() -> str:
    """Generate a UUID."""
    return shortuuid.ShortUUID().random(20)


##########
# Models
#


class CharIDModel(models.Model):
    """Base model that gives children random string UUIDs."""

    id = models.CharField(
        max_length=30,
        primary_key=True,
        default=generate_uuid,
        editable=False
    )

    class Meta:
        abstract = True


class User(AbstractUser):
    api_key = models.CharField(
        max_length=100,
        default=generate_uuid,
        db_index=True,
    )

    def __str__(self):
        return self.username


class Simulation(CharIDModel):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=300)
    start_date = models.DateField()
    end_date = models.DateField()

    @classmethod
    def from_api(cls, user: User, model: "DamnFastAPISimulation"):
        """
        Return a Simulation instance from an APISimulation instance.
        """
        return cls(
            user=user,
            name=model.name,
            start_date=model.start_date,
            end_date=model.end_date,
        )

    def update_from_api(self, api_model: "DamnFastAPISimulation"):
        """
        Update the Simulation Django model from an APISimulation instance.
        """
        self.name = api_model.name
        self.start_date = api_model.start_date
        self.end_date = api_model.end_date

    def __str__(self):
        return self.name


##########
# Types
#


# Over here I'm really ticked off I need to make two models just to
# exclude the id field from being required in the bodies of POSTs.
#
# Please comment on this bug if you feel as strongly:
# https://github.com/tiangolo/fastapi/issues/1357
class DamnFastAPISimulation(BaseModel):
    name: str
    start_date: date
    end_date: date

    @classmethod
    def from_model(cls, instance: Simulation):
        """
        Convert a Django Simulation model instance to an APISimulation instance.
        """
        return cls(
            id=instance.id,
            name=instance.name,
            start_date=instance.start_date,
            end_date=instance.end_date,
        )


class APISimulation(DamnFastAPISimulation):
    id: str


class APISimulations(BaseModel):
    items: List[APISimulation]

    @classmethod
    def from_qs(cls, qs):
        """
        Convert a Django Simulation queryset to APISimulation instances.
        """
        return cls(items=[APISimulation.from_model(i) for i in qs])

A few notes: Each class instance has helper methods, like from_api, from_model, from_qs, etc to facilitate converting between API-level and DB-level objects easily.

注意:APIレベルとDBレベルのオブジェクト間の変換を容易にするために、各クラスインスタンスにはfrom_api、from_model、from_qsなどのヘルパーメソッドがあります。

Also, unfortunately we have to make two separate type classes just to avoid having the id field show up in the POST request, as the user of the API should not be able to send/set the id when creating a new object. It’s annoying, and I have opened an issue on FastAPI’s GitHub.

また、残念ながら、POSTリクエストでidフィールドが表示されないようにするために、2つの別々の型クラスを作成しなければなりません。これは煩わしいし、ついでにFastAPIのGitHubでissueを開けておきました。

The entry point

To serve requests, we need a place for our wsgi app to live. The best place is, naturally, wsgi.py:

リクエストを処理するには、wsgiアプリケーションが動作する場所が必要です。最高の場所は当然 wsgi.pyでしょう.

goatfish/wsgi.py:

goatfish/wsgi.py
import os

from django.core.wsgi import get_wsgi_application
from fastapi import FastAPI

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "goatfish.settings")

application = get_wsgi_application()

from main.urls import router as main_router

app = FastAPI(
    title="Goatfish",
    description="A demo project. Also, an actual fish with a weird name.",
    version="We aren't doing versions yet. Point oh.",
)

app.include_router(main_router, prefix="/api")

This will allow Uvicorn to load and serve our app. Keep in mind that app is FastAPI’s entry point and application is Django’s. You’ll need to remember that if you deploy them to something that needs to load the WSGI entry points.

これにより、Uvicornはアプリケーションをロードして提供できるようになります。アプリケーションはFastAPIのエントリー・ポイントであり、アプリケーションはDjangoのものであることに注意してください。WSGIエントリー・ポイントをロードする必要があるものにデプロイする場合は、この点を覚えておく必要があります。

You can start the server by running uvicorn goatfish.wsgi:app --reload, but it’ll probably crash at this point because there are no URL routes yet. Let’s add them.

`uvicorn goatfish.wsgi:app --reload'を実行すると、サーバを起動できます。しかし、URLルートがまだないため、この時点でクラッシュする可能性があります。追加しましょう。

The routes

I prefer the Django convention of having each app’s route in a urls.py in the app directory, so let’s put them there:

私は、各アプリのルートをURLにするというDjangoの規約を好んでいます。appディレクトリーに.pyがあるので、ここに配置しましょう。

main/urls.py:

main/urls.py
from fastapi import APIRouter

from main import views

# The API model for one object.
from main.models import APISimulation

# The API model for a collection of objects.
from main.models import APISimulations

router = APIRouter()

router.get(
    "/simulation/",
    summary="Retrieve a list of all the simulations.",
    tags=["simulations"],
    response_model=APISimulations,
    name="simulations-get",
)(views.simulations_get)
router.post(
    "/simulation/",
    summary="Create a new simulation.",
    tags=["simulations"],
    response_model=APISimulation,
    name="simulations-post",
)(views.simulation_post)

router.get(
    "/simulation/{simulation_id}/",
    summary="Retrieve a specific simulation.",
    tags=["simulations"],
    response_model=APISimulation,
    name="simulation-get",
)(views.simulation_get)
router.put(
    "/simulation/{simulation_id}/",
    summary="Update a simulation.",
    tags=["simulations"],
    response_model=APISimulation,
    name="simulation-put",
)(views.simulation_put)
router.delete(
    "/simulation/{simulation_id}/",
    summary="Delete a simulation.",
    tags=["simulations"],
    response_model=APISimulation,
    name="simulation-delete",
)(views.simulation_delete)

This should be pretty straightforward. summary and tags are used purely for the documentation site, they have no other functional purpose. name is used for getting a route’s URL by name, and response_model is used for validation of the response and documentation.

これは非常に簡単なはずです。summaryとタグは純粋にドキュメントサイトのために使用され、他の機能的な目的はありません。nameはルートのURLを名前で取得するために使用され、response_modelはレスポンスとドキュメントの検証に使用されます。

Onto the views!

それではいよいよビューを見ていきましょう!

The views

The views are more straightforward than you’d expect. We have the GET/POST on the collection and GET/POST/PUT/DELETE on the object, but the heavy lifting is done by the from_model/from_api methods.

ビューは予想以上に単純です。コレクションにはGET/POSTがあり、オブジェクトにはGET/POST/PUT/DELETEがありますが、面倒な作業はfrom_model/from_apiメソッドによって行われます。

Also, notice how authentication is done with the Depends(get_user) dependency, making it mandatory for each endpoint, and the simulation parameter is an actual Simulation model instance, not an ID. The model instance also gets validated to make sure it actually belongs to the user. We’ll see exactly how in a later section.

また、認証がDepends (get_user) 依存関係でどのように行われるかに注目してください。これにより、各エンドポイントで認証が必須になり、シミュレーション・パラメータはIDではなく実際のシミュレーション・モデル・インスタンスになります。モデル・インスタンスも検証され、実際にユーザーに属していることが確認されます。詳細については、後のセクションで説明します。

main/views.py:

main/views.py
from fastapi import Body
from fastapi import Depends

from .models import APISimulation
from .models import APISimulations
from .models import APIUser
from .models import DamnFastAPISimulation
from .models import Simulation
from .models import User
from .utils import get_simulation
from .utils import get_user


def simulations_get(user: User = Depends(get_user)) -> APISimulations:
    """
    Return a list of available simulations.
    """
    simulations = user.simulation_set.all()
    api_simulations = [APISimulation.from_model(s) for s in simulations]
    return APISimulations(items=api_simulations)


def simulation_post(
    simulation: DamnFastAPISimulation,
    user: User = Depends(get_user)
) -> APISimulation:
    """
    Create a new simulation.
    """
    s = Simulation.from_api(user, simulation)
    s.save()
    return APISimulation.from_model(s)


def simulation_get(
    simulation: Simulation = Depends(get_simulation),
    user: User = Depends(get_user)
) -> APISimulation:
    """
    Return a specific simulation object.
    """
    return APISimulation.from_model(simulation)


def simulation_put(
    simulation: Simulation = Depends(get_simulation),
    sim_body: DamnFastAPISimulation = Body(...),
    user: User = Depends(get_user),
) -> APISimulation:
    """
    Update a simulation.
    """
    simulation.update_from_api(sim_body)
    simulation.save()
    return APISimulation.from_model(simulation)


def simulation_delete(
    simulation: Simulation = Depends(get_simulation),
    user: User = Depends(get_user)
) -> APISimulation:
    """
    Delete a simulation.
    """
    d = APISimulation.from_model(simulation)
    simulation.delete()
    return d

Utils

In this file we define methods to authenticate users and retrieve objects from the database by their ID while still making sure they belong to the authenticating user. get_object is a generic function to avoid repeating ourselves for every one of our models.

このファイルでは、ユーザーを認証し、認証するユーザーに属していることを確認しながら、IDによってデータベースからオブジェクトを取得するメソッドを定義します。get_objectは、モデルごとに自分自身を繰り返すことを避けるための汎用関数です。

main/utils.py:

main/utils.py
from typing import Type

from django.db import models
from fastapi import Header
from fastapi import HTTPException
from fastapi import Path

from main.models import Simulation
from main.models import User

# This is to avoid typing it once every object.
API_KEY_HEADER = Header(..., description="The user's API key.")


def get_user(x_api_key: str = API_KEY_HEADER) -> User:
    """
    Retrieve the user by the given API key.
    """
    u = User.objects.filter(api_key=x_api_key).first()
    if not u:
        raise HTTPException(status_code=400, detail="X-API-Key header invalid.")
    return u


def get_object(
    model_class: Type[models.Model],
    id: str,
    x_api_key: str
) -> models.Model:
    """
    Retrieve an object for the given user by id.

    This is a generic helper method that will retrieve any object by ID,
    ensuring that the user owns it.
    """
    user = get_user(x_api_key)
    instance = model_class.objects.filter(user=user, pk=id).first()
    if not instance:
        raise HTTPException(status_code=404, detail="Object not found.")
    return instance


def get_simulation(
    simulation_id: str = Path(..., description="The ID of the simulation."),
    x_api_key: str = Header(...),
):
    """
    Retrieve the user's simulation from the given simulation ID.
    """
    return get_object(Simulation, simulation_id, x_api_key)

Tests

The tests are very close to what you’re already used to from Django. We’ll be using Django’s testing harness/runner, but we can test FastAPI’s views by using FastAPI’s client. We’ll also be using Django’s/unittest’s assert functions, as I find them more convenient, but you can use anything you’d use with Django for those.

テストは、Djangoで既に慣れ親しんでいるものに非常に近いといえます。Djangoのテスト用harness/runnerを使用しますが、FastAPIのクライアントを使用してFastAPIのビューをテストすることもできます。また、Djangoの/unittestのassert関数を使用する方が便利だと思いますが、Djangoで使用するものは何でも使用できます。

As I mentioned earlier, the plain TestCase didn’t work for me, so I had to use the TransactionTestCase.

先ほど述べたように、単純なTestCaseではうまくいかなかったので、TransactionTestCaseを使わなければなりませんでした。

main/tests.py:

main/tests.py
# We use this as TestCase doesn't work.
from django.test import TransactionTestCase
from django.test.runner import DiscoverRunner
from fastapi.testclient import TestClient

from .models import User
from goatfish.wsgi import app


# A convenient helper for getting URL paths by name.
reverse = app.router.url_path_for


class TestRunner(DiscoverRunner):
    def teardown_databases(self, old_config, **kwargs):
        # This is necessary because either FastAPI/Starlette or Django's
        # ORM isn't cleaning up the connections after it's done with
        # them.
        # The query below kills all database connections before
        # dropping the database.
        with connection.cursor() as cursor:
            cursor.execute(
                f"""SELECT
                pg_terminate_backend(pid) FROM pg_stat_activity WHERE
                pid <> pg_backend_pid() AND
                pg_stat_activity.datname =
                  '{settings.DATABASES["default"]["NAME"]}';"""
            )
            print(f"Killed {len(cursor.fetchall())} stale connections.")
        super().teardown_databases(old_config, **kwargs)


class SmokeTests(TransactionTestCase):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Warning: Naming this `self.client` leads Django to overwrite it
        # with its own test client.
        self.c = TestClient(app)

    def setUp(self):
        self.user = User.objects.create(username="user", api_key="mykey")
        self.headers = {"X-API-Key": self.user.api_key}

    def test_read_main(self):
        response = self.c.get(reverse("simulations-get"), headers=self.headers)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"username": "user"})

Keep in mind that, as the comment says, you need to use this custom runner, otherwise PostgreSQL connections don’t get cleaned up, for some reason. I’d love to figure out why, but after hours of debugging I didn’t manage to get anywhere, so I added this workaround.

コメントにあるように、このカスタムランナーを使用する必要があります。使用しないと、何らかの理由でPostgreSQL接続がクリーンアップされません。理由を知りたいのですが、何時間もデバッグを続けてもうまくいかなかったので、この回避策を追加しました。

You need to add TEST_RUNNER = "main.TestRunner" to your settings.py to use that.

設定にTEST_RUNNER=「メイン。テストランナー」を追加する必要があります。これを使うには settings.pyに追加してください。

That’s it!

やったぜ!!

Epilogue

I hope that was clear enough, there is a bit of confusion when trying to figure out which part is served by which library, but I’m confident that you’ll be able to figure it all out without much difficulty. Just keep in mind that FastAPI does everything view-specific and Django does everything else.

どの部分がどのライブラリによって提供されているのかを理解しようとすると、多少混乱があるかもしれません。しかし私は、あなたがそれほど困難なくすべてを理解できると確信しています。FastAPIはビュー固有のすべての処理を行い、Djangoはそれ以外のすべての処理を行うことに注意してください。

I was legitimately surprised at how well the two libraries worked together, and how minimal the amounts of hackery involved were. I don’t really see myself using anything else for APIs in the future, as the convenience of using both libraries is hard to beat. I hope that asynchronicity will work when Django releases async support, which would complete this integration and give us the best of both worlds.

2つのライブラリがうまく連携していて、ハッキングの量が非常に少なかったことには、本当に驚きました。両方のライブラリを使う便利さに打ち勝つのは難しいので、将来APIに他のものを使うつもりはありません。Djangoが非同期サポートをリリースしたときに非同期性が機能することを期待しています。

Tweet or toot at me, or email me directly.

この記事についてツイートしてもいいしTootしてもいい、あるいは直接メールしても構いません。

reference

38
44
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
44