prisma-client-py icon indicating copy to clipboard operation
prisma-client-py copied to clipboard

Add Support for async requests or http3 Async Client

Open Ali-Parandeh opened this issue 3 years ago • 4 comments

Bug description

Prisma internals do not support async requests client or http3 protocol for making async requests to external APIs and other fastapi endpoints.

How to reproduce

from fastapi import FastAPI
from pydantic import BaseModel
import http3

class HealthResponse(BaseModel):
    status: str

app = FastAPI()

@router.get("/users")
async def list_all_users():
    async with Prisma() as db:
        users = await db.user.find_many(take=1)
    return users

@app.get("/health", response_model=HealthResponse, tags=["Health"])
async def check_health():
    client = http3.AsyncClient()
    response = await client.get("http://localhost:8000/users")
    if response.status_code == 200:
        return {"status": "healthy"}
    return {"status": "unhealthy"}

Expected behavior

The engine connection should work with async requests.

Reinstalling h11 to lower version that prisma supports fix the errors for now.

Prisma information

generator client {
  provider  = "prisma-client-py"
  interface = "asyncio"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum Role {
  USER
  ADMIN
}

model User {
id       String         @id             @default(dbgenerated("gen_random_uuid()")) @db.Uuid
email    String         @unique
password Bytes
disabled Boolean        @default(false)
role     Role           @default(USER)
}

Environment & setup

[tool.poetry]
name = "backend"
version = "1.0.0"
description = ""
authors = ["Ali Parandeh <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
fastapi = "^0.79.0"
prisma = "^0.6.6"
SQLAlchemy = "^1.4.40"
uvicorn = "^0.18.2"
black = "^22.6.0"
python-multipart = "^0.0.5"
bcrypt = "^3.2.2"
aiohttp = "^3.8.1"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

  • OS: MacOS Monterey
  • Database: PostgreSQL
  • Python version: 3.9.10
  • Prisma version: ^0.6.6

ERROR

      Because httpx (0.23.0) depends on httpcore (>=0.15.0,<0.16.0)
   and httpx (0.22.0) depends on httpcore (>=0.14.5,<0.15.0), httpx (0.22.0 || 0.23.0) requires httpcore (>=0.14.5,<0.16.0).
      And because httpx (0.21.3) depends on httpcore (>=0.14.0,<0.15.0), httpx (0.21.3 || 0.22.0 || 0.23.0) requires httpcore (>=0.14.0,<0.16.0).
      And because httpx (0.21.2) depends on httpcore (>=0.14.0,<0.15.0)
   and httpx (0.21.1) depends on httpcore (>=0.14.0,<0.15.0), httpx (0.21.1 || 0.21.2 || 0.21.3 || 0.22.0 || 0.23.0) requires httpcore (>=0.14.0,<0.16.0).
      And because httpx (0.21.0) depends on httpcore (>=0.14.0,<0.15.0)
   and httpx (0.20.0) depends on httpcore (>=0.13.3,<0.14.0), httpx (0.20.0 || 0.21.0 || 0.21.1 || 0.21.2 || 0.21.3 || 0.22.0 || 0.23.0) requires httpcore (>=0.13.3,<0.16.0).
      Because httpx (0.19.0) depends on httpcore (>=0.13.3,<0.14.0)
   and no versions of httpx match >0.19.0,<0.20.0 || >0.20.0,<0.21.0 || >0.21.0,<0.21.1 || >0.21.1,<0.21.2 || >0.21.2,<0.21.3 || >0.21.3,<0.22.0 || >0.22.0,<0.23.0 || >0.23.0, httpx (>=0.19.0,<0.20.0 || >0.20.0,<0.21.0 || >0.21.0,<0.21.1 || >0.21.1,<0.21.2 || >0.21.2,<0.21.3 || >0.21.3,<0.22.0 || >0.22.0,<0.23.0 || >0.23.0) requires httpcore (>=0.13.3,<0.14.0).
      Thus, httpx (>=0.19.0) requires httpcore (>=0.13.3,<0.16.0).
      Because httpcore (0.15.0) depends on h11 (>=0.11,<0.13)
   and no versions of httpcore match >0.15.0,<0.16.0, httpcore (>=0.15.0,<0.16.0) requires h11 (>=0.11,<0.13).
  (1) Thus, httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.15.0).
  
      Because no versions of httpcore match >0.14.5,<0.14.6 || >0.14.6,<0.14.7 || >0.14.7,<0.15.0
   and httpcore (0.14.5) depends on h11 (>=0.11,<0.13), httpcore (>=0.14.5,<0.14.6 || >0.14.6,<0.14.7 || >0.14.7,<0.15.0) requires h11 (>=0.11,<0.13).
      And because httpcore (0.14.6) depends on h11 (>=0.11,<0.13)
   and httpcore (0.14.7) depends on h11 (>=0.11,<0.13), httpcore (>=0.14.5,<0.15.0) requires h11 (>=0.11,<0.13).
  (2) So, because httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.15.0) (1), httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.14.5)
  
      Because no versions of httpcore match >0.14.0,<0.14.1 || >0.14.1,<0.14.2 || >0.14.2,<0.14.3 || >0.14.3,<0.14.4 || >0.14.4,<0.14.5
   and httpcore (0.14.0) depends on h11 (>=0.11,<0.13), httpcore (>=0.14.0,<0.14.1 || >0.14.1,<0.14.2 || >0.14.2,<0.14.3 || >0.14.3,<0.14.4 || >0.14.4,<0.14.5) requires h11 (>=0.11,<0.13).
      And because httpcore (0.14.1) depends on h11 (>=0.11,<0.13)
   and httpcore (0.14.2) depends on h11 (>=0.11,<0.13), httpcore (>=0.14.0,<0.14.3 || >0.14.3,<0.14.4 || >0.14.4,<0.14.5) requires h11 (>=0.11,<0.13).
      And because httpcore (0.14.3) depends on h11 (>=0.11,<0.13)
   and httpcore (0.14.4) depends on h11 (>=0.11,<0.13), httpcore (>=0.14.0,<0.14.5) requires h11 (>=0.11,<0.13).
  (3) So, because httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.14.5) (2), httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.14.0)
  
      Because no versions of httpcore match >0.13.3,<0.13.4 || >0.13.4,<0.13.5 || >0.13.5,<0.13.6 || >0.13.6,<0.13.7 || >0.13.7,<0.14.0
   and httpcore (0.13.3) depends on h11 (>=0.11,<0.13), httpcore (>=0.13.3,<0.13.4 || >0.13.4,<0.13.5 || >0.13.5,<0.13.6 || >0.13.6,<0.13.7 || >0.13.7,<0.14.0) requires h11 (>=0.11,<0.13).
      And because httpcore (0.13.4) depends on h11 (>=0.11,<0.13)
   and httpcore (0.13.5) depends on h11 (>=0.11,<0.13), httpcore (>=0.13.3,<0.13.6 || >0.13.6,<0.13.7 || >0.13.7,<0.14.0) requires h11 (>=0.11,<0.13).
      And because httpcore (0.13.6) depends on h11 (>=0.11,<0.13)
   and httpcore (0.13.7) depends on h11 (>=0.11,<0.13), httpcore (>=0.13.3,<0.14.0) requires h11 (>=0.11,<0.13).
      And because httpx (>=0.19.0) requires h11 (>=0.11,<0.13) or httpcore (>=0.13.3,<0.14.0) (3), httpx (>=0.19.0) requires h11 (>=0.11,<0.13)
      Because no versions of http3 match >0.6.7,<0.7.0
   and http3 (0.6.7) depends on h11 (>=0.8.0,<0.9.0), http3 (>=0.6.7,<0.7.0) requires h11 (>=0.8.0,<0.9.0).
      Thus, http3 (>=0.6.7,<0.7.0) is incompatible with httpx (>=0.19.0).
      And because prisma (0.6.6) depends on httpx (>=0.19.0)
   and no versions of prisma match >0.6.6,<0.7.0, http3 (>=0.6.7,<0.7.0) is incompatible with prisma (>=0.6.6,<0.7.0).
      So, because backend depends on both prisma (^0.6.6) and http3 (^0.6.7), version solving failed.

INFO:     127.0.0.1:62948 - "GET /users/ HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/engine/query.py", line 161, in spawn
    data = await self.request('GET', '/status')
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/engine/http.py", line 96, in request
    resp = await self.session.request(method, url, **kwargs)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/_async_http.py", line 28, in request
    return Response(await self.session.request(method, url, **kwargs))
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_client.py", line 1527, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_client.py", line 1614, in send
    response = await self._send_handling_auth(
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_client.py", line 1642, in _send_handling_auth
    response = await self._send_handling_redirects(
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_client.py", line 1679, in _send_handling_redirects
    response = await self._send_single_request(request)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_client.py", line 1716, in _send_single_request
    response = await transport.handle_async_request(request)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpx/_transports/default.py", line 353, in handle_async_request
    resp = await self._pool.handle_async_request(req)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/connection_pool.py", line 253, in handle_async_request
    raise exc
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/connection_pool.py", line 237, in handle_async_request
    response = await connection.handle_async_request(request)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/connection.py", line 90, in handle_async_request
    return await self._connection.handle_async_request(request)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/http11.py", line 105, in handle_async_request
    raise exc
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/http11.py", line 84, in handle_async_request
    ) = await self._receive_response_headers(**kwargs)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/httpcore/_async/http11.py", line 156, in _receive_response_headers
    headers = event.headers.raw_items()
AttributeError: 'list' object has no attribute 'raw_items'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/fastapi/applications.py", line 269, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 93, in __call__
    raise exc
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    await self.app(scope, receive, sender)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 670, in __call__
    await route.handle(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 266, in handle
    await self.app(scope, receive, send)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 65, in app
    response = await func(request)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/fastapi/routing.py", line 227, in app
    raw_response = await run_endpoint_function(
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/fastapi/routing.py", line 160, in run_endpoint_function
    return await dependant.call(**values)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/./routes/users.py", line 16, in list_all_users
    async with Prisma() as db:
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/client.py", line 212, in __aenter__
    await self.connect()
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/client.py", line 248, in connect
    await self.__engine.connect(
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/engine/query.py", line 109, in connect
    await self.spawn(file, timeout=timeout, datasources=datasources)
  File "/Users/aliparandeh/Documents/Github/BYAI/backend/.venv/lib/python3.9/site-packages/prisma/engine/query.py", line 180, in spawn
    raise errors.EngineConnectionError(
prisma.engine.errors.EngineConnectionError: Could not connect to the query engine

Resolved Package List that allows Prisma to work with HTTP3

aiohttp==3.8.1; python_version >= "3.6"
aiosignal==1.2.0; python_version >= "3.6"
anyio==3.6.1; python_version >= "3.7" and python_full_version >= "3.7.0"
async-timeout==4.0.2; python_version >= "3.6"
attrs==22.1.0; python_version >= "3.6"
bcrypt==3.2.2; python_version >= "3.6"
black==22.6.0; python_full_version >= "3.6.2"
certifi==2022.6.15; python_version >= "3.7" and python_full_version >= "3.7.0"
cffi==1.15.1; python_version >= "3.6"
charset-normalizer==2.1.0; python_full_version >= "3.6.0" and python_version >= "3.6"
click==8.1.3; python_version >= "3.7" and python_full_version >= "3.7.0"
colorama==0.4.5; python_version >= "3.7" and python_full_version >= "3.7.0" and platform_system == "Windows"
fastapi==0.79.0; python_full_version >= "3.6.1"
frozenlist==1.3.1; python_version >= "3.7"
greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") or python_version >= "3" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and python_full_version >= "3.5.0"
h11==0.12.0; python_version >= "3.7" and python_full_version >= "3.7.0"
httpcore==0.15.0; python_version >= "3.7" and python_full_version >= "3.7.0"
httpx==0.23.0; python_version >= "3.7" and python_full_version >= "3.7.0"
idna==3.3; python_version >= "3.7" and python_full_version >= "3.7.0"
jinja2==3.1.2; python_version >= "3.7" and python_full_version >= "3.7.0"
markupsafe==2.1.1; python_version >= "3.7" and python_full_version >= "3.7.0"
multidict==6.0.2; python_version >= "3.7"
mypy-extensions==0.4.3; python_full_version >= "3.6.2"
pathspec==0.9.0; python_full_version >= "3.6.2"
platformdirs==2.5.2; python_version >= "3.7" and python_full_version >= "3.6.2"
prisma==0.6.6; python_full_version >= "3.7.0"
pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
pydantic==1.9.2; python_full_version >= "3.7.0"
python-dotenv==0.20.0; python_version >= "3.5" and python_full_version >= "3.7.0"
python-multipart==0.0.5
rfc3986==1.5.0; python_version >= "3.7" and python_full_version >= "3.7.0"
six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0"
sniffio==1.2.0; python_version >= "3.7" and python_full_version >= "3.7.0"
sqlalchemy==1.4.40; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
starlette==0.19.1; python_version >= "3.6" and python_full_version >= "3.6.1"
tomli==2.0.1; python_full_version < "3.11.0a7" and python_full_version >= "3.6.2" and python_version >= "3.7"
typing-extensions==4.3.0; python_version >= "3.7" and python_full_version >= "3.7.0" and python_version < "3.10"
uvicorn==0.18.2; python_version >= "3.7"
yarl==1.8.1; python_version >= "3.7"

Ali-Parandeh avatar Aug 13 '22 23:08 Ali-Parandeh

@Ali-Parandeh Sorry you're running into this, dependency resolution in Python can be hell sometimes.

Is there a reason you need to use aiohttp? Removing that dependency works for me.

RobertCraigie avatar Sep 03 '22 08:09 RobertCraigie

@RobertCraigie I was trying to implement a /health endpoint where I send an async GET request to /users to get some data in FastAPI from the DB using prisma-python to check that everything is working.

Ali-Parandeh avatar Sep 04 '22 12:09 Ali-Parandeh

If the request is not async then FastAPI just hangs.

Ali-Parandeh avatar Sep 04 '22 13:09 Ali-Parandeh

@Ali-Parandeh Ah okay, in that case you could switch to using httpx instead as that is what Prisma Client Python uses internally and they have async support: https://www.python-httpx.org/async/.

Actually I've just realised that http3 is actually just an old version of httpx, you should definitely upgrade to use httpx.

RobertCraigie avatar Sep 04 '22 13:09 RobertCraigie

@Ali-Parandeh can this be closed now?

jacobdr avatar Oct 24 '22 18:10 jacobdr

Ahhh I see. Thank you I will use that instead. Yes please you can close this issue

Ali-Parandeh avatar Oct 24 '22 19:10 Ali-Parandeh