21
loading...
This website collects cookies to deliver better user experience
./main.py
with the following code:from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
if __name__ == "__main__":
uvicorn.run(app)
$ python ./main.py
INFO: Started server process [4418]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
http://127.0.0.1:8000/docs
, etc.uvicorn
command, and that's what you would normally do. But for this example, it will be useful to see everything from the point of view of the python
command.python
. And to give it the file main.py
as a parameter.python.exe
instead of just python
.python
(or python.exe
) is written in another programming language called "C". Maybe you knew that.python
does is read the file main.py
, interpret the code that we wrote in it using the Python Programming Language, and execute it step by step.python
: the program that runs our code (which is actually written in the C programming language)python
(the program) can read Python (the programming language).python
is executing our code written in the Python programming language, we call that "runtime"../main.py
, it is not running, so we are not at runtime.Item
class above, we have:class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
name
is a str
and price
is a float
. And if we send a JSON request with a price
that is not a float
, they will be able to validate the data for us.str
and a float
together:name = "Rick"
price = 1.99
total = name + price
str
with a float
.name + price
that might save you hours debugging. That is also static analysis.mypy
, the official and main Static Type Checker
flake8
, checks for style and correctnessblack
, autoformats the code in a consistent way that improves efficiencymypy
and others to help developers while writing the code. And that was the main focus for a while.dataclasses
(from the standard library) and Samuel Colvin's Pydantic started using these type annotations to do more than only static analysis, and to use these same type annotations at runtime. In the case of Pydantic, to extract that information to do data conversion, validation, and documentation.from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
Person
that could have a child, that would also be a Person
. It all looks fine, right?child: Optional[Person]
inside the body of the class Person
. So, when that part of the code is run by python
, the Person
inside of name: Optional[Person]
doesn't exist yet (that class is still being created).Person
as a literal string, like this:from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional["Person"] = None
python
is running, it will see that as a literal string, so it will not break.Person
class.Optional["Person"]
actually refers to the Person
class, static analysis tools can, for example, detect that this would be an error:parent = Person(name="Beth")
parent.child = 3
parent.child = 3
is an error because it expects a Person
.dataclasses
), and other types of changes. Or in some cases, they just provide information and establish conventions.Optional["Person"]
part before, you might have cringed a bit. I did the first time I discovered that was valid, but it was understandable as it would solve the problem.from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
python
read our file ./main.py
it would see it as if it was written like this:from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: "str"
child: "Optional[Person]" = None
python
would run our code happily and without breaking.from __future__ import annotations
:from __future__ import annotations
from typing import Optional
from pydantic import BaseModel
class Person(BaseModel):
name: str
child: Optional[Person] = None
Person | None
instead of Optional[Person]
, avoiding the extra Optional
and the extra import, even in Python 3.7 (that feature is available in Python 3.10 but not in Python 3.7):from __future__ import annotations
class Person:
name: str
child: Person | None = None
from __future__ import annotations
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
# ✅ Pydantic models outside of functions will always work
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
from __future__ import annotations
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
def create_app():
# 🚨 Pydantic models INSIDE of functions would not work
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
return app
app = create_app()
NameError: name 'Item' is not defined
Item
class outside of the function. And there are some other similar corner cases.from __future__ import annotations
), and the caveats and problems still didn't have a solution.We can’t risk breaking even a small subset of the FastAPI/pydantic users, not to mention other uses of evaluated type annotations that we’re not aware of yet.
from __future__ import annotations
in the code, as defined by PEP 563, but not as the default behavior.