ApeWorX / ethpm-types

Implementation of EIP-2678
Apache License 2.0
14 stars 8 forks source link

PackageManifest doesn't work as a FastAPI model-parameter #95

Closed Ninjagod1251 closed 10 months ago

Ninjagod1251 commented 10 months ago

Environment information

Here is the draft PR that is working with the issue: https://github.com/ApeWorX/hosted-compiler/pull/24 You can checkout and recreate the issue

What went wrong?

When i run uvicorn main:app --reload and try to use the post request in fastapi. it freezes command in the browser.

  1. There is not network data in or out.
  2. There are no error codes or statments when it freezes

the issue is within how PackageManifest is loaded into parameter.

The section of bode that bugs is here:

@app.post("/compile/")
async def create_compilation_task(
    background_tasks: BackgroundTasks,
    manifest: PackageManifest,

):
    """
    Creates the task with the list of vyper contracts to compile
    and sets each file with a task.
    """
    project_root = Path(tempfile.mkdtemp(""))

    tasks[project_root.name] = TaskStatus.PENDING
    # Run the compilation task in the background using TaskIQ
    background_tasks.add_task(compile_project, project_root, manifest)

    return project_root.name

when you comment manifest: PackageManifest, it runs fine. But when you include it. It breaks.

How can it be fixed?

Fill this in if you have ideas on how the bug could be fixed.

antazoey commented 10 months ago

What do you mean by "freezes command in browser"? Do you have any error output?

fubuloubu commented 10 months ago

What do you mean by "freezes command in browser"? Do you have any error output?

We think it is related to how FastAPI attempts to generate the OpenAPI spec for the /docs subsite automatically when you add PackageManifest as the type of a query param argument for a POST method, or return type of a GET method

antazoey commented 10 months ago

Gotcha. That is good to know! It is must be related to the schema then, and not the request itself, as it seems to work fine as a FastAPI POST body model.

I will investigate the docs generation.

antazoey commented 10 months ago

OK I was able to reproduce by going to /docs and seeing it happen

dtdang commented 10 months ago

The bug seems to be coming from here when trying to isolate the issue. I tried to launch the local server and seeing this issue in /docs

@app.get("/compiled_artifact/{task_id}")
async def get_compiled_artifact(task_id: str) -> PackageManifest:
    """
    Fetch the compiled artifact data in ethPM v3 format for a particular task
    """
    if task_id not in tasks:
        raise HTTPException(status_code=404, detail="task id not found")
    if tasks[task_id] is not TaskStatus.SUCCESS:
        raise HTTPException(
            status_code=404, detail="Task is not completed with Success status"
        )
    # TODO Debug why it is producing serialize_response raise ResponseValidationError( fastapi.exceptions.ResponseValidationError ) when you use return results[task_id] as a PackageManifest
    return results[task_id]
(test) ~/apeworx/demos/hosted-compiler (feat/eip2678)$ uvicorn main:app --reload
INFO:     Will watch for changes in these directories: ['/home/kitsu/apeworx/demos/hosted-compiler']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [18015] using WatchFiles
INFO:     Started server process [18017]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:58272 - "GET / HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:58272 - "GET /docs HTTP/1.1" 200 OK
INFO:     127.0.0.1:58272 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
    return await self.app(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/applications.py", line 1106, in __call__
    await super().__call__(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/middleware/cors.py", line 83, in __call__
    await self.app(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/applications.py", line 1061, in openapi
    return JSONResponse(self.openapi())
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/applications.py", line 1033, in openapi
    self.openapi_schema = get_openapi(
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/openapi/utils.py", line 475, in get_openapi
    field_mapping, definitions = get_definitions(
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/fastapi/_compat.py", line 227, in get_definitions
    field_mapping, definitions = schema_generator.generate_definitions(
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 374, in generate_definitions
    self.generate_inner(schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 1140, in chain_schema
    return self.generate_inner(schema['steps'][step_index])
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 953, in function_plain_schema
    return self._function_schema(schema)
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 918, in _function_schema
    return self.handle_invalid_for_json_schema(
  File "/home/kitsu/virtualenvs/test/lib/python3.10/site-packages/pydantic/json_schema.py", line 2069, in handle_invalid_for_json_schema
    raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}')
pydantic.errors.PydanticInvalidForJsonSchema: Cannot generate a JsonSchema for core_schema.PlainValidatorFunctionSchema ({'type': 'with-info', 'function': <bound method BaseModel.validate of <class 'ethpm_types.manifest.PackageManifest'>>})

For further information visit https://errors.pydantic.dev/2.4/u/invalid-for-json-schema
antazoey commented 10 months ago

Thanks @dtdang ! That confirms there is a problem with schema(). I am going to investigate calling .schema() in a test with more data and seeing if anything stands out.

antazoey commented 10 months ago

@dtdang your error is likely different, though something we will want to look at once Chris moves to Pydantic v2. But we are using a v1 version and I have isolated the problem to be related to example generation rather than the openapi.json. openapi.json works on v1 it seems, but the examples in the requests cause problems. I am going to see if we can set our own examples or something.

fubuloubu commented 10 months ago

@dtdang your error is likely different, though something we will want to look at once Chris moves to Pydantic v2. But we are using a v1 version and I have isolated the problem to be related to example generation rather than the openapi.json. openapi.json works on v1 it seems, but the examples in the requests cause problems. I am going to see if we can set our own examples or something.

yes you can add custom examples to the schema: https://github.com/fubuloubu/cpack/blob/main/cpack/__init__.py#L146

antazoey commented 10 months ago

A simple repro of the problem:

from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class MyRequest(BaseModel):
    type: Union[str, "MyRequest"]

@app.post("/pants")
async def pants(item: MyRequest):
    print(item)

I noticed it that it happened on ABIType alone so that is what prompted me to recreate it here. FastAPI recurses forever here, in the OpenAPI docs when you click on "pants".

antazoey commented 10 months ago

I found a discussion on this topic: https://github.com/tiangolo/fastapi/discussions/10525

antazoey commented 10 months ago

Im closing, as this is unrelated to ethpm-types but appears to be a FastAPI issue. The work-around is customize your own docs page, as described here: https://github.com/tiangolo/fastapi/discussions/10524#discussioncomment-7402496