Learning from masters
Learning from masters

Reputation: 2802

Pydantic Optional parameters not yet prepared so type is still a ForwardRef

I have the following code:

endpoints.py

from __future__ import annotations # this is important to have at the top
from fastapi import FastAPI
from pydantic import BaseModel

class MyOpt(BaseModel):
    fieldfirst: str = "S"
    fieldsecond: int = 1
    fieldthird: str = None
    fieldforth: list = []
    fieldfifth: int
    fieldsixth: list = [None, None]
    fieldseventh: str = None 
    fieldeighth: Optional[int] = None
    fieldnineth: Optional[str] = None
    fieldtenth: Optional[list] = [None, None] 

from typing import List, Optional

# Create object for fastAPI
app = FastAPI()

# START API ENDPOINTS
@app.get("/", tags=["root"])
def root():
    return {"message": "REST API."}

# -- TESTING ENDPOINTS --
@app.get(
    "/health", 
    name="health", 
    tags=["root"],
    summary="Test connection to API",
    status_code=200
)
def health():
    return {"type": "complete"}

@app.post(
        "/send_instruction", 
        name="send_instruction", 
        tags=["web"],
    )
def send_instruction(myopts: MyOpt = None):    
    return {"result": "ok"}

run_api.py:

import uvicorn

if __name__ == "__main__":
    uvicorn.run("endpoints:app", host="0.0.0.0", port=8822, reload=True, access_log=False)

When I call the api (python3 run_api.py), I get the following error only if I send any of the Optional values of MyOpt:

  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/applications.py", line 269, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/exceptions.py", line 93, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/exceptions.py", line 82, in __call__
    await self.app(scope, receive, sender)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 670, in __call__
    await route.handle(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 266, in handle
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 65, in app
    response = await func(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 217, in app
    solved_result = await solve_dependencies(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/dependencies/utils.py", line 557, in solve_dependencies
    ) = await request_body_to_args(  # body_params checked above
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/dependencies/utils.py", line 692, in request_body_to_args
    v_, errors_ = field.validate(value, values, loc=loc)
  File "pydantic/fields.py", line 857, in pydantic.fields.ModelField.validate
  File "pydantic/fields.py", line 1074, in pydantic.fields.ModelField._validate_singleton
  File "pydantic/fields.py", line 1121, in pydantic.fields.ModelField._apply_validators
  File "pydantic/class_validators.py", line 313, in pydantic.class_validators._generic_validator_basic.lambda12
  File "pydantic/main.py", line 686, in pydantic.main.BaseModel.validate
  File "pydantic/main.py", line 339, in pydantic.main.BaseModel.__init__
  File "pydantic/main.py", line 1038, in pydantic.main.validate_model
  File "pydantic/fields.py", line 833, in pydantic.fields.ModelField.validate
pydantic.errors.ConfigError: field "fieldnineth" not yet prepared so type is still a ForwardRef, you might need to call SatOpt.update_forward_refs().

To send the request I use Postman and in Body as raw formated as JSON I have:

{"fieldfirst": "S", "fieldsecond": 1, "fieldthird": "a test", "fieldforth": [1,2], "fieldfifth": 42, "fieldsixth": [[1, 8, 3], [1, 9, 2]], "fieldseventh": "yey", "fieldnineth": "VV"}

What is wrong with my code?

Upvotes: 2

Views: 9651

Answers (2)

Daniel Lahyani
Daniel Lahyani

Reputation: 999

I don't see how the accepted answer could have solved the problem. If the problem was missing imports from typing I would expect to see a NameError exception for List and Optional as soon as the code is executed, before any incoming request is received. I guess the example in the question was simplified and adjusted, and probably some other change solved the problem.

I ran into a similar problem and what worked from me, is just adding what the error message suggests. Specifically:

from typing import Literal
from pydantic import BaseModel

class BaseEntity(BaseModel):
  type: str
  id: str
  name: str

class EntityA(BaseEntity):
  type: Literal["a"] = "a"
  children: list['EntityB']   # This is a forward-ref, this is what causes the Pydantic error.

class EntityB(BaseEntity):
  type: Literal["b"] = "b"
  data: str | None

Entity = EntityA | EntityB

# This is where the Pydantic exception gets raised.
# Saying: 'pydantic.errors.ConfigError: field "children" not yet prepared so type is still a ForwardRef, you might need to call `EntityA.update_forward_refs()`.
a = EntityA(
  id="foo",
  name="Foo",
  children=[EntityB(id="foo.bar", name="Bar", data="This is my data")]
) 

The solution is to add this line:

EntityA.update_forward_refs()

Right after all the forward refs are defined, in this case after EntityB is defined.

Upvotes: 3

Learning from masters
Learning from masters

Reputation: 2802

from typing import List, Optional

Should be before the class definition

Upvotes: 3

Related Questions