Fastapi Tiangolo Com Learn-Combined
Fastapi Tiangolo Com Learn-Combined
Here are the introductory sections and the tutorials to learn FastAPI.
You could consider this a book, a course, the official and recommended way to learn FastAPI. 😎
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python Types Intro
Python has support for optional "type hints" (also called "type annotations").
These "type hints" or annotations are a special syntax that allow declaring the type of a variable.
By declaring types for your variables, editors and tools can give you better support.
This is just a quick tutorial / refresher about Python type hints. It covers only the minimum necessary to use them
with FastAPI... which is actually very little.
FastAPI is all based on these type hints, they give it many advantages and benefits.
But even if you never use FastAPI, you would benefit from learning a bit about them.
Note
If you are a Python expert, and you already know everything about type hints, skip to the next chapter.
Motivation
Let's start with a simple example:
print(get_full_name("john", "doe"))
John Doe
Converts the first letter of each one to upper case with title() .
print(get_full_name("john", "doe"))
Edit it
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It's a very simple program.
At some point you would have started the definition of the function, you had the parameters ready...
But then you have to call "that method that converts the first letter to upper case".
Then, you try with the old programmer's friend, editor autocompletion.
You type the first parameter of the function, first_name , then a dot ( . ) and then hit Ctrl+Space to trigger the
completion.
Add types
We will change exactly this fragment, the parameters of the function, from:
first_name, last_name
to:
That's it.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
print(get_full_name("john", "doe"))
That is not the same as declaring default values like would be with:
first_name="john", last_name="doe"
And adding type hints normally doesn't change what happens from what would happen without them.
But now, imagine you are again in the middle of creating that function, but with type hints.
At the same point, you try to trigger the autocomplete with Ctrl+Space and you see:
With that, you can scroll, seeing the options, until you find the one that "rings a bell":
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
More motivation
Check this function, it already has type hints:
Because the editor knows the types of the variables, you don't only get completion, you also get error checks:
Now you know that you have to fix it, convert age to a string with str(age) :
Declaring types
You just saw the main place to declare type hints. As function parameters.
This is also the main place you would use them with FastAPI.
Simple types
You can declare all the standard Python types, not only str .
int
float
bool
bytes
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
There are some data structures that can contain other values, like dict , list , set and tuple . And the internal
values can have their own type too.
These types that have internal types are called "generic" types. And it's possible to declare them, even with their
internal types.
To declare those types and the internal types, you can use the standard Python module typing . It exists specifically
to support these type hints.
The syntax using typing is compatible with all versions, from Python 3.6 to the latest ones, including Python 3.9,
Python 3.10, etc.
As Python advances, newer versions come with improved support for these type annotations and in many cases you
won't even need to import and use the typing module to declare the type annotations.
If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra
simplicity.
In all the docs there are examples compatible with each version of Python (when there's a difference).
For example "Python 3.6+" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And
"Python 3.9+" means it's compatible with Python 3.9 or above (including 3.10, etc).
If you can use the latest versions of Python, use the examples for the latest version, those will have the best and
simplest syntax, for example, "Python 3.10+".
List
As the list is a type that contains some internal types, you put them in square brackets:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
Those internal types in the square brackets are called "type parameters".
In this case, str is the type parameter passed to List (or list in Python 3.9 and above).
That means: "the variable items is a list , and each of the items in this list is a str ".
Tip
If you use Python 3.9 or above, you don't have to import List from typing , you can use the same regular list type instead.
By doing that, your editor can provide support even while processing items from the list:
Notice that the variable item is one of the elements in the list items .
And still, the editor knows it is a str , and provides support for that.
This means:
The variable items_t is a tuple with 3 items, an int , another int , and a str .
The variable items_s is a set , and each of its items is of type bytes .
Dict
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The second type parameter is for the values of the dict :
This means:
The keys of this dict are of type str (let's say, the name of each item).
The values of this dict are of type float (let's say, the price of each item).
Union
You can declare that a variable can be any of several types, for example, an int or a str .
In Python 3.6 and above (including Python 3.10) you can use the Union type from typing and put inside the square
brackets the possible types to accept.
In Python 3.10 there's also a new syntax where you can put the possible types separated by a vertical bar ( | ).
Possibly None
You can declare that a value could have a type, like str , but that it could also be None .
In Python 3.6 and above (including Python 3.10) you can declare it by importing and using Optional from the
typing module.
Using Optional[str] instead of just str will let the editor help you detecting errors where you could be assuming
that a value is always a str , when it could actually be None too.
This also means that in Python 3.10, you can use Something | None :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.8+ Python 3.8+ alternative
If you are using a Python version below 3.10, here's a tip from my very subjective point of view:
Both are equivalent and underneath they are the same, but I would recommend Union instead of Optional because
the word "optional" would seem to imply that the value is optional, and it actually means "it can be None ", even if it's
not optional and is still required.
It's just about the words and names. But those words can affect how you and your teammates think about the code.
The parameter name is defined as Optional[str] , but it is not optional, you cannot call the function without the
parameter:
And then you won't have to worry about names like Optional and Union . 😎
Generic types
These types that take type parameters in square brackets are called Generic types or Generics, for example:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+
You can use the same builtin types as generics (with square brackets and types inside):
list
tuple
set
dict
And the same as with Python 3.8, from the typing module:
Union
...and others.
In Python 3.10, as an alternative to using the generics Union and Optional , you can use the vertical bar ( | ) to
declare unions of types, that's a lot better and simpler.
Classes as types
class Person:
def __init__(self, name: str):
self.name = name
class Person:
def __init__(self, name: str):
self.name = name
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Notice that this means " one_person is an instance of the class Person ".
Pydantic models
Pydantic [↪] is a Python library to perform data validation.
Then you create an instance of that class with some values and it will validate the values, convert them to the
appropriate type (if that's the case) and give you an object with all the data.
And you get all the editor support with that resulting object.
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
Info
You will see a lot more of all this in practice in the Tutorial - User Guide ↪.
Tip
Pydantic has a special behavior when you use Optional or Union[Something, None] without a default value, you can read more about it in
the Pydantic docs about Required Optional fields [↪].
In Python 3.9, Annotated is part of the standard library, so you can import it from typing .
Python itself doesn't do anything with this Annotated . And for editors and other tools, the type is still str .
But you can use this space in Annotated to provide FastAPI with additional metadata about how you want your
application to behave.
The important thing to remember is that the first type parameter you pass to Annotated is the actual type. The rest,
is just metadata for other tools.
For now, you just need to know that Annotated exists, and that it's standard Python. 😎
Later you will see how powerful it can be.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
The fact that this is standard Python means that you will still get the best possible developer experience in your editor, with the tools you use
to analyze and refactor your code, etc. ✨
And also that your code will be very compatible with many other Python tools and libraries. 🚀
With FastAPI you declare parameters with type hints and you get:
Editor support.
Type checks.
Define requirements: from request path parameters, query parameters, headers, bodies, dependencies, etc.
Generating automatic errors returned to the client when the data is invalid.
This might all sound abstract. Don't worry. You'll see all this in action in the Tutorial - User Guide ↪.
The important thing is that by using standard Python types, in a single place (instead of adding more classes,
decorators, etc), FastAPI will do a lot of the work for you.
Info
If you already went through all the tutorial and came back to see more about types, a good resource is the "cheat sheet" from mypy [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Concurrency and async / await
Details about the async def syntax for path operation functions and some background about
asynchronous code, concurrency, and parallelism.
In a hurry?
TL;DR:
If you are using third party libraries that tell you to call them with await , like:
Then, declare your path operation functions with async def like:
@app.get('/')
async def read_results():
results = await some_library()
return results
Note
You can only use await inside of functions created with async def .
If you are using a third party library that communicates with something (a database, an API, the file
system, etc.) and doesn't have support for using await , (this is currently the case for most
database libraries), then declare your path operation functions as normally, with just def , like:
@app.get('/')
def results():
results = some_library()
return results
If your application (somehow) doesn't have to communicate with anything else and wait for it to
respond, use async def .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you just don't know, use normal def .
Note: You can mix def and async def in your path operation functions as much as you need and
define each one using the best option for you. FastAPI will do the right thing with them.
Anyway, in any of the cases above, FastAPI will still work asynchronously and be extremely fast.
But by following the steps above, it will be able to do some performance optimizations.
Technical Details
Modern versions of Python have support for "asynchronous code" using something called
"coroutines", with async and await syntax.
Asynchronous Code
Coroutines
Asynchronous Code
Asynchronous code just means that the language 💬 has a way to tell the computer / program 🤖
that at some point in the code, it 🤖 will have to wait for something else to finish somewhere else.
Let's say that something else is called "slow-file" 📝.
So, during that time, the computer can go and do some other work, while "slow-file" 📝 finishes.
Then the computer / program 🤖 will come back every time it has a chance because it's waiting
again, or whenever it 🤖 finished all the work it had at that point. And it 🤖 will see if any of the tasks
it was waiting for have already finished, doing whatever it had to do.
Next, it 🤖 takes the first task to finish (let's say, our "slow-file" 📝) and continues whatever it had to
do with it.
That "wait for something else" normally refers to I/O operations that are relatively "slow" (compared
to the speed of the processor and the RAM memory), like waiting for:
the data sent by your program to be received by the client through the network
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
the contents of a file in the disk to be read by the system and given to your program
etc.
As the execution time is consumed mostly by waiting for I/O operations, they call them "I/O bound"
operations.
It's called "asynchronous" because the computer / program doesn't have to be "synchronized" with
the slow task, waiting for the exact moment that the task finishes, while doing nothing, to be able to
take the task result and continue the work.
Instead of that, by being an "asynchronous" system, once finished, the task can wait in line a little bit
(some microseconds) for the computer / program to finish whatever it went to do, and then come
back to take the results and continue working with them.
For "synchronous" (contrary to "asynchronous") they commonly also use the term "sequential",
because the computer / program follows all the steps in sequence before switching to a different
task, even if those steps involve waiting.
This idea of asynchronous code described above is also sometimes called "concurrency". It is
different from "parallelism".
Concurrency and parallelism both relate to "different things happening more or less at the same
time".
But the details between concurrency and parallelism are quite different.
Concurrent Burgers
You go with your crush to get fast food, you stand in line while the cashier takes the orders from the
people in front of you. 😍
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Then it's your turn, you place your order of 2 very fancy burgers for your crush and you. 🍔🍔
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The cashier says something to the cook in the kitchen so they know they have to prepare your
burgers (even though they are currently preparing the ones for the previous clients).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You pay. 💸
The cashier gives you the number of your turn.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
While you are waiting, you go with your crush and pick a table, you sit and talk with your crush for a
long time (as your burgers are very fancy and take some time to prepare).
As you are sitting at the table with your crush, while you wait for the burgers, you can spend that
time admiring how awesome, cute and smart your crush is ✨😍✨.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
While waiting and talking to your crush, from time to time, you check the number displayed on the
counter to see if it's your turn already.
Then at some point, it finally is your turn. You go to the counter, get your burgers and come back to
the table.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You and your crush eat the burgers and have a nice time. ✨
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
Then, when it's your turn, you do actual "productive" work, you process the menu, decide what you
want, get your crush's choice, pay, check that you give the correct bill or card, check that you are
charged correctly, check that the order has the correct items, etc.
But then, even though you still don't have your burgers, your work with the cashier is "on pause" ⏸,
because you have to wait 🕙 for your burgers to be ready.
But as you go away from the counter and sit at the table with a number for your turn, you can switch
🔀 your attention to your crush, and "work" ⏯ 🤓 on that. Then you are again doing something very
"productive" as is flirting with your crush 😍.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Then the cashier 💁 says "I'm finished with doing the burgers" by putting your number on the
counter's display, but you don't jump like crazy immediately when the displayed number changes to
your turn number. You know no one will steal your burgers because you have the number of your
turn, and they have theirs.
So you wait for your crush to finish the story (finish the current work ⏯ / task being processed 🤓),
smile gently and say that you are going for the burgers ⏸.
Then you go to the counter 🔀, to the initial task that is now finished ⏯, pick the burgers, say thanks
and take them to the table. That finishes that step / task of interaction with the counter ⏹. That in
turn, creates a new task, of "eating burgers" 🔀 ⏯, but the previous one of "getting burgers" is
finished ⏹.
Parallel Burgers
Now let's imagine these aren't "Concurrent Burgers", but "Parallel Burgers".
You stand in line while several (let's say 8) cashiers that at the same time are cooks take the orders
from the people in front of you.
Everyone before you is waiting for their burgers to be ready before leaving the counter because each
of the 8 cashiers goes and prepares the burger right away before getting the next order.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Then it's finally your turn, you place your order of 2 very fancy burgers for your crush and you.
You pay 💸.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The cashier goes to the kitchen.
You wait, standing in front of the counter 🕙, so that no one else takes your burgers before you do,
as there are no numbers for turns.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
As you and your crush are busy not letting anyone get in front of you and take your burgers
whenever they arrive, you cannot pay attention to your crush. 😞
This is "synchronous" work, you are "synchronized" with the cashier/cook 👨🍳. You have to wait 🕙
and be there at the exact moment that the cashier/cook 👨🍳 finishes the burgers and gives them to
you, or otherwise, someone else might take them.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Then your cashier/cook 👨🍳 finally comes back with your burgers, after a long time waiting 🕙 there
in front of the counter.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You take your burgers and go to the table with your crush.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
There was not much talk or flirting as most of the time was spent waiting 🕙 in front of the counter.
😞
Info
The fast food store has 8 processors (cashiers/cooks). While the concurrent burgers store might
have had only 2 (one cashier and one cook).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Up to recently, most of the banks had multiple cashiers 👨💼👨💼👨💼👨💼 and a big line 🕙🕙🕙🕙🕙🕙
🕙🕙.
All of the cashiers doing all the work with one client after the other 👨💼⏯.
And you have to wait🕙 in the line for a long time or you lose your turn.
You probably wouldn't want to take your crush 😍 with you to run errands at the bank 🏦.
Burger Conclusion
In this scenario of "fast food burgers with your crush", as there is a lot of waiting 🕙, it makes a lot
more sense to have a concurrent system ⏸ 🔀⏯.
This is the case for most of the web applications.
Many, many users, but your server is waiting 🕙 for their not-so-good connection to send their
requests.
That's why it makes a lot of sense to use asynchronous ⏸ 🔀⏯ code for web APIs.
This kind of asynchronicity is what made NodeJS popular (even though NodeJS is not parallel) and
that's the strength of Go as a programming language.
And that's the same level of performance you get with FastAPI.
And as you can have parallelism and asynchronicity at the same time, you get higher performance
than most of the tested NodeJS frameworks and on par with Go, which is a compiled language
closer to C (all thanks to Starlette) [↪].
Concurrency is different than parallelism. And it is better on specific scenarios that involve a lot of
waiting. Because of that, it generally is a lot better than parallelism for web application development.
But not for everything.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You have to clean a big, dirty house.
There's no waiting 🕙 anywhere, just a lot of work to be done, on multiple places of the house.
You could have turns as in the burgers example, first the living room, then the kitchen, but as you are
not waiting 🕙 for anything, just cleaning and cleaning, the turns wouldn't affect anything.
It would take the same amount of time to finish with or without turns (concurrency) and you would
have done the same amount of work.
But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners, and each one of them (plus
you) could take a zone of the house to clean it, you could do all the work in parallel, with the extra
help, and finish much sooner.
In this scenario, each one of the cleaners (including you) would be a processor, doing their part of
the job.
And as most of the execution time is taken by actual work (instead of waiting), and the work in a
computer is done by a CPU, they call these problems "CPU bound".
Common examples of CPU bound operations are things that require complex math processing.
For example:
Computer vision: an image is composed of millions of pixels, each pixel has 3 values / colors,
processing that normally requires computing something on those pixels, all at the same time.
Machine Learning: it normally requires lots of "matrix" and "vector" multiplications. Think of a
huge spreadsheet with numbers and multiplying all of them together at the same time.
Deep Learning: this is a sub-field of Machine Learning, so, the same applies. It's just that there is
not a single spreadsheet of numbers to multiply, but a huge set of them, and in many cases, you
use a special processor to build and / or use those models.
With FastAPI you can take the advantage of concurrency that is very common for web development
(the same main attraction of NodeJS).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
But you can also exploit the benefits of parallelism and multiprocessing (having multiple processes
running in parallel) for CPU bound workloads like those in Machine Learning systems.
That, plus the simple fact that Python is the main language for Data Science, Machine Learning and
especially Deep Learning, make FastAPI a very good match for Data Science / Machine Learning
web APIs and applications (among many others).
To see how to achieve this parallelism in production see the section about Deployment ↪.
When there is an operation that will require waiting before giving the results and has support for
these new Python features, you can code it like:
The key here is the await . It tells Python that it has to wait ⏸ for get_burgers(2) to finish doing
🕙 before storing the results in burgers . With that, Python will know that it can go and do
its thing
something else 🔀 ⏯ in the meanwhile (like receiving another request).
For await to work, it has to be inside a function that supports this asynchronicity. To do that, you
just declare it with async def :
...instead of def :
With async def , Python knows that, inside that function, it has to be aware of await expressions,
and that it can "pause" ⏸ the execution of that function and go do something else 🔀 before coming
back.
When you want to call an async def function, you have to "await" it. So, this won't work:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
# This won't work, because get_burgers was defined with: async def
burgers = get_burgers(2)
So, if you are using a library that tells you that you can call it with await , you need to create the path
operation functions that uses it with async def , like in:
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
You might have noticed that await can only be used inside of functions defined with async def .
But at the same time, functions defined with async def have to be "awaited". So, functions with
async def can only be called inside of functions defined with async def too.
So, about the egg and the chicken, how do you call the first async function?
If you are working with FastAPI you don't have to worry about that, because that "first" function will
be your path operation function, and FastAPI will know how to do the right thing.
But if you want to use async / await without FastAPI, you can do it as well.
Starlette (and FastAPI) are based on AnyIO [↪], which makes it compatible with both Python's
standard library asyncio [↪] and Trio [↪].
In particular, you can directly use AnyIO [↪] for your advanced concurrency use cases that require
more advanced patterns in your own code.
And even if you were not using FastAPI, you could also write your own async applications with
AnyIO [↪] to be highly compatible and get its benefits (e.g. structured concurrency).
This style of using async and await is relatively new in the language.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
This same syntax (or almost identical) was also included recently in modern versions of JavaScript
(in Browser and NodeJS).
But before that, handling asynchronous code was quite more complex and difficult.
In previous versions of Python, you could have used threads or Gevent [↪]. But the code is way more
complex to understand, debug, and think about.
In previous versions of NodeJS / Browser JavaScript, you would have used "callbacks". Which leads
to callback hell [↪].
Coroutines
Coroutine is just the very fancy term for the thing returned by an async def function. Python knows
that it is something like a function that it can start and that it will end at some point, but that it might
be paused ⏸ internally too, whenever there is an await inside of it.
But all this functionality of using asynchronous code with async and await is many times
summarized as using "coroutines". It is comparable to the main key feature of Go, the "Goroutines".
Conclusion
Let's see the same phrase from above:
Modern versions of Python have support for "asynchronous code" using something called
"coroutines", with async and await syntax.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Warning
If you have quite some technical knowledge (coroutines, threads, blocking, etc.) and are curious about how FastAPI
handles async def vs normal def , go ahead.
When you declare a path operation function with normal def instead of async def , it is run in an
external threadpool that is then awaited, instead of being called directly (as it would block the
server).
If you are coming from another async framework that does not work in the way described above and
you are used to defining trivial compute-only path operation functions with plain def for a tiny
performance gain (about 100 nanoseconds), please note that in FastAPI the effect would be quite
opposite. In these cases, it's better to use async def unless your path operation functions use code
that performs blocking I/O.
Still, in both situations, chances are that FastAPI will still be faster ↪ than (or at least comparable
to) your previous framework.
Dependencies
The same applies for dependencies ↪. If a dependency is a standard def function instead of
async def , it is run in the external threadpool.
Sub-dependencies
You can have multiple dependencies and sub-dependencies ↪ requiring each other (as parameters
of the function definitions), some of them might be created with async def and some with normal
def . It would still work, and the ones created with normal def would be called on an external
thread (from the threadpool) instead of being "awaited".
Any other utility function that you call directly can be created with normal def or async def and
FastAPI won't affect the way you call it.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
This is in contrast to the functions that FastAPI calls for you: path operation functions and
dependencies.
If your utility function is a normal function with def , it will be called directly (as you write it in your
code), not in a threadpool, if the function is created with async def then you should await for that
function when you call it in your code.
Again, these are very technical details that would probably be useful if you came searching for them.
Otherwise, you should be good with the guidelines from the section above: In a hurry?.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tutorial - User Guide
This tutorial shows you how to use FastAPI with most of its features, step by step.
Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can
go directly to any specific one to solve your specific API needs.
So you can come back and see exactly what you need.
To run any of the examples, copy the code to a file main.py , and start fastapi dev with:
bash
fast →
$ fastapi dev main.py
INFO Using path main.py
INFO Resolved absolute path /home/user/code/awesomeapp/main.py
INFO Searching for package file structure from directories with
__init__.py files
INFO Importing from /home/user/code/awesomeapp
╭─ Python module file ─╮
│
│
│
🐍main.py
│
│
│
╰──────────────────────╯
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
It is HIGHLY encouraged that you write or copy the code, edit it and run it locally.
Using it in your editor is what really shows you the benefits of FastAPI, seeing how little code you have to
write, all the type checks, autocompletion, etc.
Install FastAPI
The first step is to install FastAPI:
bash
fast →
$ pip install fastapi
Note
When you install with pip install fastapi it comes with some default optional standard dependencies.
If you don't want to have those optional dependencies, you can instead install pip install fastapi-slim .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Advanced User Guide
There is also an Advanced User Guide that you can read later after this Tutorial - User guide.
The Advanced User Guide, builds on this, uses the same concepts, and teaches you some extra features.
But you should first read the Tutorial - User Guide (what you are reading right now).
It's designed so that you can build a complete application with just the Tutorial - User Guide, and then
extend it in different ways, depending on your needs, using some of the additional ideas from the
Advanced User Guide.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
First Steps
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
bash
fast →
$ fastapi d ▋
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In the output, there's a line with something like:
That line shows the URL where your app is being served, in your local machine.
Check it
You will see the automatic interactive API documentation (provided by Swagger UI [↪]):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Alternative API docs
You will see the alternative automatic documentation (provided by ReDoc [↪]):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
OpenAPI
FastAPI generates a "schema" with all your API using the OpenAPI standard for defining APIs.
"Schema"
A "schema" is a definition or description of something. Not the code that implements it, but just an abstract
description.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
API "schema"
In this case, OpenAPI [↪] is a specification that dictates how to define a schema of your API.
This schema definition includes your API paths, the possible parameters they take, etc.
Data "schema"
The term "schema" might also refer to the shape of some data, like a JSON content.
In that case, it would mean the JSON attributes, and data types they have, etc.
OpenAPI defines an API schema for your API. And that schema includes definitions (or "schemas") of the
data sent and received by your API using JSON Schema, the standard for JSON data schemas.
If you are curious about how the raw OpenAPI schema looks like, FastAPI automatically generates a JSON
(schema) with the descriptions of all your API.
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
...
The OpenAPI schema is what powers the two interactive documentation systems included.
And there are dozens of alternatives, all based on OpenAPI. You could easily add any of those alternatives
to your application built with FastAPI.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You could also use it to generate code automatically, for clients that communicate with your API. For
example, frontend, mobile or IoT applications.
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
FastAPI is a Python class that provides all the functionality for your API.
Technical Details
You can use all the Starlette [↪] functionality with FastAPI too.
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
This will be the main point of interaction to create all your API.
Path
"Path" here refers to the last part of the URL starting from the first / .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
https://example.com/items/foo
/items/foo
Info
While building an API, the "path" is the main way to separate "concerns" and "resources".
Operation
One of:
POST
GET
PUT
DELETE
OPTIONS
HEAD
PATCH
TRACE
In the HTTP protocol, you can communicate to each path using one (or more) of these "methods".
When building APIs, you normally use these specific HTTP methods to perform a specific action.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
We are going to call them "operations" too.
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
The @app.get("/") tells FastAPI that the function right below is in charge of handling requests that go to:
the path /
@decorator Info
You put it on top of a function. Like a pretty decorative hat (I guess that's where the term came from).
A "decorator" takes the function below and does something with it.
In our case, this decorator tells FastAPI that the function below corresponds to the path / with an operation get .
@app.post()
@app.put()
@app.delete()
@app.options()
@app.head()
@app.patch()
@app.trace()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
You are free to use each operation (HTTP method) as you wish.
For example, when using GraphQL you normally perform all the actions using only POST operations.
path: is / .
operation: is get .
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
It will be called by FastAPI whenever it receives a request to the URL " / " using a GET operation.
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello World"}
Note
If you don't know the difference, check the Async: "In a hurry?" ↪.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Step 5: return the content
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
You can return a dict , list , singular values as str , int , etc.
You can also return Pydantic models (you'll see more about that later).
There are many other objects and models that will be automatically converted to JSON (including ORMs,
etc). Try using your favorite ones, it's highly probable that they are already supported.
Recap
Import FastAPI .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Path Parameters
You can declare path "parameters" or "variables" with the same syntax used by Python format
strings:
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
The value of the path parameter item_id will be passed to your function as the argument item_id .
So, if you run this example and go to http://127.0.0.1:8000/items/foo [↪], you will see a response of:
{"item_id":"foo"}
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
Check
This will give you editor support inside of your function, with error checks, completion, etc.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Data conversion
If you run this example and open your browser at http://127.0.0.1:8000/items/3 [↪], you will see a
response of:
{"item_id":3}
Check
Notice that the value your function received (and returned) is 3 , as a Python int , not a string "3" .
So, with that type declaration, FastAPI gives you automatic request "parsing".
Data validation
But if you go to the browser at http://127.0.0.1:8000/items/foo [↪], you will see a nice HTTP error of:
{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"item_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
"url": "https://errors.pydantic.dev/2.1/v/int_parsing"
}
]
}
because the path parameter item_id had a value of "foo" , which is not an int .
The same error would appear if you provided a float instead of an int , as in:
http://127.0.0.1:8000/items/4.2 [↪]
Check
So, with the same Python type declaration, FastAPI gives you data validation.
Notice that the error also clearly states exactly the point where the validation didn't pass.
This is incredibly helpful while developing and debugging code that interacts with your API.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Documentation
And when you open your browser at http://127.0.0.1:8000/docs [↪], you will see an automatic,
interactive, API documentation like:
Check
Again, just with that same Python type declaration, FastAPI gives you automatic, interactive documentation (integrating
Swagger UI).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And because the generated schema is from the OpenAPI [↪] standard, there are many compatible
tools.
Because of this, FastAPI itself provides an alternative API documentation (using ReDoc), which you
can access at http://127.0.0.1:8000/redoc [↪]:
The same way, there are many compatible tools. Including code generation tools for many
languages.
Pydantic
All the data validation is performed under the hood by Pydantic [↪], so you get all the benefits from
it. And you know you are in good hands.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can use the same type declarations with str , float , bool and many other complex data
types.
Order matters
When creating path operations, you can find situations where you have a fixed path.
Like /users/me , let's say that it's to get data about the current user.
And then you can also have a path /users/{user_id} to get data about a specific user by some
user ID.
Because path operations are evaluated in order, you need to make sure that the path for /users/me
is declared before the one for /users/{user_id} :
app = FastAPI()
@app.get("/users/me")
async def read_user_me():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
Otherwise, the path for /users/{user_id} would match also for /users/me , "thinking" that it's
receiving a parameter user_id with a value of "me" .
app = FastAPI()
@app.get("/users")
async def read_users():
return ["Rick", "Morty"]
@app.get("/users")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_users2():
return ["Bean", "Elfo"]
The first one will always be used since the path matches first.
Predefined values
If you have a path operation that receives a path parameter, but you want the possible valid path
parameter values to be predefined, you can use a standard Python Enum .
Import Enum and create a sub-class that inherits from str and from Enum .
By inheriting from str the API docs will be able to know that the values must be of type string
and will be able to render correctly.
Then create class attributes with fixed values, which will be the available valid values:
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
Enumerations (or enums) are available in Python [↪] since version 3.4.
Tip
If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning models.
Then create a path parameter with a type annotation using the enum class you created ( ModelName
):
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
Because the available values for the path parameter are predefined, the interactive docs can show
them nicely:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Working with Python enumerations
You can compare it with the enumeration member in your created enum ModelName :
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
You can get the actual value (a str in this case) using model_name.value , or in general,
your_enum_member.value :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
Tip
You can return enum members from your path operation, even nested in a JSON body (e.g. a dict ).
They will be converted to their corresponding values (strings in this case) before returning them to
the client:
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In your client you will get a JSON response like:
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
So, the URL for that file would be something like: /files/home/johndoe/myfile.txt .
OpenAPI support
OpenAPI doesn't support a way to declare a path parameter to contain a path inside, as that could
lead to scenarios that are difficult to test and define.
Nevertheless, you can still do it in FastAPI, using one of the internal tools from Starlette.
And the docs would still work, although not adding any documentation telling that the parameter
should contain a path.
Path convertor
Using an option directly from Starlette you can declare a path parameter containing a path using a
URL like:
/files/{file_path:path}
In this case, the name of the parameter is file_path , and the last part, :path , tells it that the
parameter should match any path.
app = FastAPI()
@app.get("/files/{file_path:path}")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_file(file_path: str):
return {"file_path": file_path}
Tip
You could need the parameter to contain /home/johndoe/myfile.txt , with a leading slash ( / ).
In that case, the URL would be: /files//home/johndoe/myfile.txt , with a double slash ( // ) between files and
home .
Recap
With FastAPI, by using short, intuitive and standard Python type declarations, you get:
Data "parsing"
Data validation
That's probably the main visible advantage of FastAPI compared to alternative frameworks (apart
from the raw performance).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Query Parameters
When you declare other function parameters that are not part of the path parameters, they are
automatically interpreted as "query" parameters.
app = FastAPI()
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
The query is the set of key-value pairs that go after the ? in a URL, separated by & characters.
http://127.0.0.1:8000/items/?skip=0&limit=10
But when you declare them with Python types (in the example above, as int ), they are converted to
that type and validated against it.
All the same process that applied for path parameters also applies for query parameters:
Data "parsing"
Data validation
Automatic documentation
Defaults
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
As query parameters are not a fixed part of a path, they can be optional and can have default values.
In the example above they have default values of skip=0 and limit=10 .
http://127.0.0.1:8000/items/
http://127.0.0.1:8000/items/?skip=0&limit=10
http://127.0.0.1:8000/items/?skip=20
Optional parameters
The same way, you can declare optional query parameters, by setting their default to None :
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
In this case, the function parameter q will be optional, and will be None by default.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Check
Also notice that FastAPI is smart enough to notice that the path parameter item_id is a path parameter and q is not,
so, it's a query parameter.
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None, short: bool = False):
item = {"item_id": item_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item
http://127.0.0.1:8000/items/foo?short=1
or
http://127.0.0.1:8000/items/foo?short=True
or
http://127.0.0.1:8000/items/foo?short=true
or
http://127.0.0.1:8000/items/foo?short=on
or
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
http://127.0.0.1:8000/items/foo?short=yes
or any other case variation (uppercase, first letter in uppercase, etc), your function will see the
parameter short with a bool value of True . Otherwise as False .
app = FastAPI()
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
user_id: int, item_id: str, q: str | None = None, short: bool = False
):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item
If you don't want to add a specific value but just make it optional, set the default as None .
But when you want to make a query parameter required, you can just not declare any default value:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):
item = {"item_id": item_id, "needy": needy}
return item
Here the query parameter needy is a required query parameter of type str .
http://127.0.0.1:8000/items/foo-item
...without adding the required parameter needy , you will see an error like:
{
"detail": [
{
"type": "missing",
"loc": [
"query",
"needy"
],
"msg": "Field required",
"input": null,
"url": "https://errors.pydantic.dev/2.1/v/missing"
}
]
}
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
{
"item_id": "foo-item",
"needy": "sooooneedy"
}
And of course, you can define some parameters as required, some as having a default value, and
some entirely optional:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.8+
app = FastAPI()
@app.get("/items/{item_id}")
async def read_user_item(
item_id: str, needy: str, skip: int = 0, limit: int | None = None
):
item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
return item
Tip
You could also use Enum s the same way as with Path Parameters ↪.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Request Body
When you need to send data from a client (let's say, a browser) to your API, you send it as a request
body.
A request body is data sent by the client to your API. A response body is the data your API sends to
the client.
Your API almost always has to send a response body. But clients don't necessarily need to send
request bodies all the time.
To declare a request body, you use Pydantic [↪] models with all their power and benefits.
Info
To send data, you should use one of: POST (the more common), PUT , DELETE or PATCH .
Sending a body with a GET request has an undefined behavior in the specifications, nevertheless, it is supported by
FastAPI, only for very complex/extreme use cases.
As it is discouraged, the interactive docs with Swagger UI won't show the documentation for the body when using GET ,
and proxies in the middle might not support it.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def create_item(item: Item):
return item
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
The same as when declaring query parameters, when a model attribute has a default value, it is not
required. Otherwise, it is required. Use None to make it just optional.
For example, this model above declares a JSON " object " (or Python dict ) like:
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
...as description and tax are optional (with a default value of None ), this JSON " object " would
also be valid:
{
"name": "Foo",
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"price": 45.2
}
Declare it as a parameter
To add it to your path operation, declare it the same way you declared path and query parameters:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
Results
With just that Python type declaration, FastAPI will:
If the data is invalid, it will return a nice and clear error, indicating exactly where and what
was the incorrect data.
As you declared it in the function to be of type Item , you will also have all the editor
support (completion, etc) for all of the attributes and their types.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Generate JSON Schema [↪] definitions for your model, you can also use them anywhere else
you like if it makes sense for your project.
Those schemas will be part of the generated OpenAPI schema, and used by the automatic
documentation UIs.
Automatic docs
The JSON Schemas of your models will be part of your OpenAPI generated schema, and will be
shown in the interactive API docs:
And will be also used in the API docs inside each path operation that needs them:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Editor support
In your editor, inside your function you will get type hints and completion everywhere (this wouldn't
happen if you received a dict instead of a Pydantic model):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You also get error checks for incorrect type operations:
This is not by chance, the whole framework was built around that design.
And it was thoroughly tested at the design phase, before any implementation, to ensure it would
work with all the editors.
The previous screenshots were taken with Visual Studio Code [↪].
But you would get the same editor support with PyCharm [↪] and most of the other Python editors:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
If you use PyCharm [↪] as your editor, you can use the Pydantic PyCharm Plugin [↪].
auto-completion
type checks
refactoring
searching
inspections
class Item(BaseModel):
name: str
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
FastAPI will recognize that the function parameters that match path parameters should be taken
from the path, and that function parameters that are declared to be Pydantic models should be
taken from the request body.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.dict()}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can also declare body, path and query parameters, all at the same time.
FastAPI will recognize each of them and take the data from the correct place.
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
If the parameter is also declared in the path, it will be used as a path parameter.
If the parameter is of a singular type (like int , float , str , bool , etc) it will be interpreted as
a query parameter.
Note
FastAPI will know that the value of q is not required because of the default value = None .
The Union in Union[str, None] is not used by FastAPI, but will allow your editor to give you better support and detect
errors.
Without Pydantic
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you don't want to use Pydantic models, you can also use Body parameters. See the docs for Body -
Multiple Parameters: Singular values in body ↪.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Query Parameters and String Validations
FastAPI allows you to declare additional information and validation for your parameters.
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
The query parameter q is of type Union[str, None] (or str | None in Python 3.10), that means that it's of type str but
could also be None , and indeed, the default value is None , so FastAPI will know it's not required.
Note
FastAPI will know that the value of q is not required because of the default value = None .
The Union in Union[str, None] will allow your editor to give you better support and detect errors.
Additional validation
We are going to enforce that even though q is optional, whenever it is provided, its length doesn't exceed 50 characters.
In Python 3.9 or above, Annotated is part of the standard library, so you can import it from typing .
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Info
FastAPI added support for Annotated (and started recommending it) in version 0.95.0.
If you have an older version, you would get errors when trying to use Annotated .
Make sure you Upgrade the FastAPI version ↪ to at least 0.95.1 before using Annotated .
Both of those versions mean the same thing, q is a parameter that can be a str or None , and by default, it is None .
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
results.update({"q": q})
return results
Notice that the default value is still None , so the parameter is still optional.
But now, having Query(max_length=50) inside of Annotated , we are telling FastAPI that we want it to have additional
validation for this value, we want it to have maximum 50 characters. 😎
Tip
Here we are using Query() because this is a query parameter. Later we will see others like Path() , Body() , Header() , and Cookie() , that also
accept the same arguments as Query() .
Validate the data making sure that the max length is 50 characters
Show a clear error for the client when the data is not valid
Document the parameter in the OpenAPI schema path operation (so it will show up in the automatic docs UI)
Tip
For new code and whenever possible, use Annotated as explained above. There are multiple advantages (explained below) and no disadvantages.
🍰
This is how you would use Query() as the default value of your function parameter, setting the parameter max_length to
50:
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
As in this case (without using Annotated ) we have to replace the default value None in the function with Query() , we
now need to set the default value with the parameter Query(default=None) , it serves the same purpose of defining that
default value (at least for FastAPI).
So:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
q: Union[str, None] = Query(default=None)
...makes the parameter optional, with a default value of None , the same as:
...makes the parameter optional, with a default value of None , the same as:
Info
Keep in mind that the most important part to make a parameter optional is the part:
= None
or the:
= Query(default=None)
as it will use that None as the default value, and that way make the parameter not required.
The Union[str, None] part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.
Then, we can pass more parameters to Query . In this case, the max_length parameter that applies to strings:
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI
schema path operation.
Keep in mind that when using Query inside of Annotated you cannot use the default parameter for Query .
Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent.
...because it's not clear if the default value should be "rick" or "morty" .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
...or in older code bases you will find:
q: str = Query(default="rick")
Advantages of Annotated
Using Annotated is recommended instead of the default value in function parameters, it is better for multiple reasons. 🤓
The default value of the function parameter is the actual default value, that's more intuitive with Python in general. 😌
You could call that same function in other places without FastAPI, and it would work as expected. If there's a required
parameter (without a default value), your editor will let you know with an error, Python will also complain if you run it
without passing the required parameter.
When you don't use Annotated and instead use the (old) default value style, if you call that function without FastAPI in
other place, you have to remember to pass the arguments to the function for it to work correctly, otherwise the values will
be different from what you expect (e.g. QueryInfo or something similar instead of str ). And your editor won't complain,
and Python won't complain running that function, only when the operations inside error out.
Because Annotated can have more than one metadata annotation, you could now even use the same function with other
tools, like Typer [↪]. 🚀
Add more validations
You can also add a parameter min_length :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
This specific regular expression pattern checks that the received parameter value:
If you feel lost with all these "regular expression" ideas, don't worry. They are a hard topic for many people. You can still do
a lot of stuff without needing regular expressions yet.
But whenever you need them and go and learn them, know that you can already use them directly in FastAPI.
Before Pydantic version 2 and before FastAPI 0.100.0, the parameter was called regex instead of pattern , but it's now
deprecated.
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None, Query(min_length=3, max_length=50, regex="^fixedquery$")
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
But know that this is deprecated and it should be updated to use the new parameter pattern . 🤓
Default values
You can, of course, use default values other than None .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Let's say that you want to declare the q query parameter to have a min_length of 3 , and to have a default value of
"fixedquery" :
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Note
Having a default value of any type, including None , makes the parameter optional (not required).
Make it required
When we don't need to declare more validations or metadata, we can make the q query parameter required just by not
declaring a default value, like:
q: str
instead of:
Annotated non-Annotated
So, when you need to declare a value as required while using Query , you can simply not declare a default value:
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
results.update({"q": q})
return results
There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value ... :
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Info
If you hadn't seen that ... before: it is a special single value, it is part of Python and is called "Ellipsis" [↪].
You can declare that a parameter can accept None , but that it's still required. This would force clients to send a value, even
if the value is None .
To do that, you can declare that None is a valid type but still use ... as the default:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use Optional or
Union[Something, None] without a default value, you can read more about it in the Pydantic docs about Required Optional fields [↪].
Tip
Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use ... .
For example, to declare a query parameter q that can appear multiple times in the URL, you can write:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.9+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
query_items = {"q": q}
return query_items
http://localhost:8000/items/?q=foo&q=bar
you would receive the multiple q query parameters' values ( foo and bar ) in a Python list inside your path operation
function, in the function parameter q .
{
"q": [
"foo",
"bar"
]
}
Tip
To declare a query parameter with a type of list , like in the example above, you need to explicitly use Query , otherwise it would be interpreted as a
request body.
The interactive API docs will update accordingly, to allow multiple values:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Query parameter list / multiple values with defaults
And you can also define a default list of values if none are provided:
Python 3.9+ Python 3.8+ Python 3.9+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
query_items = {"q": q}
return query_items
If you go to:
http://localhost:8000/items/
the default of q will be: ["foo", "bar"] and your response will be:
{
"q": [
"foo",
"bar"
]
}
Using list
You can also use list directly instead of List[str] (or list[str] in Python 3.9+):
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
query_items = {"q": q}
return query_items
Note
Keep in mind that in this case, FastAPI won't check the contents of the list.
For example, List[int] would check (and document) that the contents of the list are integers. But list alone wouldn't.
That information will be included in the generated OpenAPI and used by the documentation user interfaces and external
tools.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Note
Keep in mind that different tools might have different levels of OpenAPI support.
Some of them might not show all the extra information declared yet, although in most of the cases, the missing feature is already planned for
development.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
And a description :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
),
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Alias parameters
Imagine that you want the parameter to be item-query .
Like in:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
http://127.0.0.1:8000/items/?item-query=foobaritems
Then you can declare an alias , and that alias is what will be used to find the parameter value:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
Deprecating parameters
Now let's say you don't like this parameter anymore.
You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as deprecated.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
alias="item-query",
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
max_length=50,
pattern="^fixedquery$",
deprecated=True,
),
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
if q:
results.update({"q": q})
return results
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/items/")
async def read_items(
hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
if hidden_query:
return {"hidden_query": hidden_query}
else:
return {"hidden_query": "Not found"}
Recap
You can declare additional validations and metadata for your parameters.
alias
title
description
deprecated
min_length
max_length
pattern
In these examples you saw how to declare validations for str values.
See the next chapters to see how to declare validations for other types, like numbers.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Path Parameters and Numeric Validations
In the same way that you can declare more validations and metadata for query parameters with Query ,
you can declare the same type of validations and metadata for path parameters with Path .
Import Path
First, import Path from fastapi , and import Annotated :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get")],
q: Annotated[str | None, Query(alias="item-query")] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Info
FastAPI added support for Annotated (and started recommending it) in version 0.95.0.
If you have an older version, you would get errors when trying to use Annotated .
Make sure you Upgrade the FastAPI version ↪ to at least 0.95.1 before using Annotated .
Declare metadata
You can declare all the same parameters as for Query .
For example, to declare a title metadata value for the path parameter item_id you can type:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from typing import Annotated
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get")],
q: Annotated[str | None, Query(alias="item-query")] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Note
A path parameter is always required as it has to be part of the path. Even if you declared it with None or set a default value, it
would not affect anything, it would still be always required.
Tip
Let's say that you want to declare the query parameter q as a required str .
And you don't need to declare anything else for that parameter, so you don't really need to use Query .
But you still need to use Path for the item_id path parameter. And you don't want to use Annotated
for some reason.
Python will complain if you put a value with a "default" before a value that doesn't have a "default".
But you can re-order them, and have the value without a default (the query parameter q ) first.
It doesn't matter for FastAPI. It will detect the parameters by their names, types and default
declarations ( Query , Path , etc), it doesn't care about the order.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
But keep in mind that if you use Annotated , you won't have this problem, it won't matter as you're not
using the function parameter default values for Query() or Path() .
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Tip
Here's a small trick that can be handy, but you won't need it often.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you want to:
declare the q query parameter without a Query nor any default value
Python won't do anything with that * , but it will know that all the following parameters should be called
as keyword arguments (key-value pairs), also known as kwargs . Even if they don't have a default value.
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Keep in mind that if you use Annotated , as you are not using function parameter default values, you
won't have this problem, and you probably won't need to use * .
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
):
results = {"item_id": item_id}
if q:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
results.update({"q": q})
return results
Here, with ge=1 , item_id will need to be an integer number " g reater than or e qual" to 1 .
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
gt : g reater t han
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
q: str,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Here's where it becomes important to be able to declare gt and not just ge . As with it you can require,
for example, that a value must be greater than 0 , even if it is less than 1 .
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
*,
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str,
size: Annotated[float, Query(gt=0, lt=10.5)],
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Recap
With Query , Path (and others you haven't seen yet) you can declare metadata and string validations in
the same ways as with Query Parameters and String Validations ↪.
gt : g reater t han
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
ge : g reater than or e qual
lt : l ess t han
Info
Query , Path , and other classes you will see later are subclasses of a common Param class.
All of them share the same parameters for additional validation and metadata you have seen.
Technical Details
When you import Query , Path and others from fastapi , they are actually functions.
So, you import Query , which is a function. And when you call it, it returns an instance of a class also named Query .
These functions are there (instead of just using the classes directly) so that your editor doesn't mark errors about their types.
That way you can use your normal editor and coding tools without having to add custom configurations to disregard those
errors.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body - Multiple Parameters
Now that we have seen how to use Path and Query , let's see more advanced uses of request body
declarations.
And you can also declare body parameters as optional, by setting the default to None :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str | None = None,
item: Item | None = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
if item:
results.update({"item": item})
return results
Note
Notice that, in this case, the item that would be taken from the body is optional. As it has a None default value.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Multiple body parameters
In the previous example, the path operations would expect a JSON body with the attributes of an Item , like:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
But you can also declare multiple body parameters, e.g. item and user :
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
results = {"item_id": item_id, "item": item, "user": user}
return results
In this case, FastAPI will notice that there are more than one body parameters in the function (two
parameters that are Pydantic models).
So, it will then use the parameter names as keys (field names) in the body, and expect a body like:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"full_name": "Dave Grohl"
}
}
Note
Notice that even though the item was declared the same way as before, it is now expected to be inside of the body with a key
item .
FastAPI will do the automatic conversion from the request, so that the parameter item receives its
specific content and the same for user .
It will perform the validation of the compound data, and will document it like that for the OpenAPI schema
and automatic docs.
For example, extending the previous model, you could decide that you want to have another key
importance in the same body, besides the item and user .
If you declare it as is, because it is a singular value, FastAPI will assume that it is a query parameter.
But you can instruct FastAPI to treat it as another body key using Body :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
As, by default, singular values are interpreted as query parameters, you don't have to explicitly add a Query
, you can just do:
For example:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body(gt=0)],
q: str | None = None,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
results.update({"q": q})
return results
Info
Body also has all the same extra validation and metadata parameters as Query , Path and others you will see later.
But if you want it to expect a JSON with a key item and inside of it the model contents, as it does when
you declare extra body parameters, you can use the special Body parameter embed :
as in:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
instead of:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
Recap
You can add multiple body parameters to your path operation function, even though a request can only have
a single body.
But FastAPI will handle it, give you the correct data in your function, and validate and document the correct
schema in the path operation.
You can also declare singular values to be received as part of the body.
And you can instruct FastAPI to embed the body in a key even when there is only a single parameter
declared.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body - Fields
The same way you can declare additional validation and metadata in path operation function
parameters with Query , Path and Body , you can declare validation and metadata inside of
Pydantic models using Pydantic's Field .
Import Field
First, you have to import it:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
Warning
Notice that Field is imported directly from pydantic , not from fastapi as are all the rest ( Query , Path , Body , etc).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
Field works the same way as Query , Path and Body , it has all the same parameters, etc.
Technical Details
Actually, Query , Path and others you'll see next create objects of subclasses of a common Param class, which is itself
a subclass of Pydantic's FieldInfo class.
Body also returns objects of a subclass of FieldInfo directly. And there are others you will see later that are
subclasses of the Body class.
Remember that when you import Query , Path , and others from fastapi , those are actually functions that return
special classes.
Tip
Notice how each model's attribute with a type, default value and Field has the same structure as a path operation
function's parameter, with Field instead of Path , Query and Body .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can declare extra information in Field , Query , Body , etc. And it will be included in the
generated JSON Schema.
You will learn more about adding extra information later in the docs, when learning to declare
examples.
Warning
Extra keys passed to Field will also be present in the resulting OpenAPI schema for your application. As these keys
may not necessarily be part of the OpenAPI specification, some OpenAPI tools, for example the OpenAPI validator, may
not work with your generated schema.
Recap
You can use Pydantic's Field to declare extra validations and metadata for model attributes.
You can also use the extra keyword arguments to pass additional JSON Schema metadata.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body - Nested Models
With FastAPI, you can define, validate, document, and use arbitrarily deeply nested models (thanks
to Pydantic).
List fields
You can define an attribute to be a subtype. For example, a Python list :
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
This will make tags be a list, although it doesn't declare the type of the elements of the list.
In Python 3.9 and above you can use the standard list to declare these type annotations as we'll
see below. 💡
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
But in Python versions before 3.9 (3.6 and above), you first need to import List from standard
Python's typing module:
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
To declare types that have type parameters (internal types), like list , dict , tuple :
If you are in a Python version lower than 3.9, import their equivalent version from the typing
module
Pass the internal type(s) as "type parameters" using square brackets: [ and ]
my_list: list[str]
my_list: List[str]
Use that same standard syntax for model attributes with internal types.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
So, in our example, we can make tags be specifically a "list of strings":
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Set types
But then we think about it, and realize that tags shouldn't repeat, they would probably be unique
strings.
And Python has a special data type for sets of unique items, the set .
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
With this, even if you receive a request with duplicate data, it will be converted to a set of unique
items.
And whenever you output that data, even if the source had duplicates, it will be output as a set of
unique items.
Nested Models
Each attribute of a Pydantic model has a type.
So, you can declare deeply nested JSON "objects" with specific attribute names, types and
validations.
Define a submodel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
This would mean that FastAPI would expect a body similar to:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
Data conversion
Data validation
Automatic documentation
To see all the options you have, checkout the docs for Pydantic's exotic types [↪]. You will see some
examples in the next chapter.
For example, as in the Image model we have a url field, we can declare it to be an instance of
Pydantic's HttpUrl instead of a str :
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
The string will be checked to be a valid URL, and documented in JSON Schema / OpenAPI as such.
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
This will expect (convert, validate, document, etc.) a JSON body like:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
Info
Notice how the images key now has a list of image objects.
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
Info
Notice how Offer has a list of Item s, which in turn have an optional list of Image s
images: List[Image]
images: list[Image]
as in:
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Even for items inside of lists:
You couldn't get this kind of editor support if you were working directly with dict instead of
Pydantic models.
But you don't have to worry about them either, incoming dicts are converted automatically and your
output is converted automatically to JSON too.
This way, you don't have to know beforehand what the valid field/attribute names are (as would be
the case with Pydantic models).
This would be useful if you want to receive keys that you don't already know.
Another useful case is when you want to have keys of another type (e.g., int ).
In this case, you would accept any dict as long as it has int keys with float values:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
Tip
This means that, even though your API clients can only send strings as keys, as long as those strings contain pure
integers, Pydantic will convert them and validate them.
And the dict you receive as weights will actually have int keys and float values.
Recap
With FastAPI you have the maximum flexibility provided by Pydantic models, while keeping your
code simple, short and elegant.
Data validation
Schema documentation
Automatic docs
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Declare Request Example Data
You can declare examples of the data your app can receive.
Python 3.10+ Pydantic v2 Python 3.10+ Pydantic v1 Python 3.8+ Pydantic v2 Python 3.8+ Pydantic v1
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
model_config = {
"json_schema_extra": {
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
]
}
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
That extra info will be added as-is to the output JSON Schema for that model, and it will be used in the API docs.
Pydantic v2 Pydantic v1
In Pydantic version 2, you would use the attribute model_config , that takes a dict as described in
Pydantic's docs: Model Config [↪].
You can set "json_schema_extra" with a dict containing any additional data you would like to show up in the generated JSON
Schema, including examples .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
You could use the same technique to extend the JSON Schema and add your own custom extra info.
For example you could use it to add metadata for a frontend user interface, etc.
Info
OpenAPI 3.1.0 (used since FastAPI 0.99.0) added support for examples , which is part of the JSON Schema standard.
Before that, it only supported the keyword example with a single example. That is still supported by OpenAPI 3.1.0, but is deprecated and is not part of the
JSON Schema standard. So you are encouraged to migrate example to examples . 🤓
You can read more at the end of this page.
app = FastAPI()
class Item(BaseModel):
name: str = Field(examples=["Foo"])
description: str | None = Field(default=None, examples=["A very nice Item"])
price: float = Field(examples=[35.4])
tax: float | None = Field(default=None, examples=[3.2])
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Path()
Query()
Header()
Cookie()
Body()
Form()
File()
you can also declare a group of examples with additional information that will be added to their JSON Schemas inside of
OpenAPI.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body with examples
Here we pass examples containing one example of the data expected in Body() :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
With any of the methods above it would look like this in the /docs :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body with multiple examples
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
examples=[
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
{
"name": "Bar",
"price": "35.4",
},
{
"name": "Baz",
"price": "thirty five point four",
},
],
),
],
):
results = {"item_id": item_id, "item": item}
return results
When you do this, the examples will be part of the internal JSON Schema for that body data.
Nevertheless, at the time of writing this, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple
examples for the data in JSON Schema. But read below for a workaround.
OpenAPI-specific examples
Since before JSON Schema supported examples OpenAPI had support for a different field also called examples .
This OpenAPI-specific examples goes in another section in the OpenAPI specification. It goes in the details for each path
operation, not inside each JSON Schema.
And Swagger UI has supported this particular examples field for a while. So, you can use it to show different examples in the
docs UI.
The shape of this OpenAPI-specific field examples is a dict with multiple examples (instead of a list ), each with extra
information that will be added to OpenAPI too.
This doesn't go inside of each JSON Schema contained in OpenAPI, this goes outside, in the path operation directly.
You can declare the OpenAPI-specific examples in FastAPI with the parameter openapi_examples for:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Path()
Query()
Header()
Cookie()
Body()
Form()
File()
The keys of the dict identify each example, and each value is another dict .
externalValue : alternative to value , a URL pointing to the example. Although this might not be supported by as many
tools as value .
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
openapi_examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"name": "Bar",
"price": "35.4",
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four",
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Technical Details
Tip
If you are already using FastAPI version 0.99.0 or above, you can probably skip these details.
They are more relevant for older versions, before OpenAPI 3.1.0 was available.
You can consider this a brief OpenAPI and JSON Schema history lesson. 🤓
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Warning
These are very technical details about the standards JSON Schema and OpenAPI.
If the ideas above already work for you, that might be enough, and you probably don't need these details, feel free to skip them.
Before OpenAPI 3.1.0, OpenAPI used an older and modified version of JSON Schema.
JSON Schema didn't have examples , so OpenAPI added it's own example field to its own modified version.
OpenAPI also added example and examples fields to other parts of the specification:
Parameter Object (in the specification) [↪] that was used by FastAPI's:
Path()
Query()
Header()
Cookie()
Request Body Object , in the field content , on the Media Type Object (in the specification) [↪] that was used by
FastAPI's:
Body()
File()
Form()
Info
This old OpenAPI-specific examples parameter is now openapi_examples since FastAPI 0.103.0 .
But then JSON Schema added an examples [↪] field to a new version of the specification.
And then the new OpenAPI 3.1.0 was based on the latest version (JSON Schema 2020-12) that included this new field examples .
And now this new examples field takes precedence over the old single (and custom) example field, that is now deprecated.
This new examples field in JSON Schema is just a list of examples, not a dict with extra metadata as in the other places in
OpenAPI (described above).
Info
Even after OpenAPI 3.1.0 was released with this new simpler integration with JSON Schema, for a while, Swagger UI, the tool that provides the automatic
docs, didn't support OpenAPI 3.1.0 (it does since version 5.0.0 🎉).
Because of that, versions of FastAPI previous to 0.99.0 still used versions of OpenAPI lower than 3.1.0.
When you add examples inside a Pydantic model, using schema_extra or Field(examples=["something"]) that example is
added to the JSON Schema for that Pydantic model.
And that JSON Schema of the Pydantic model is included in the OpenAPI of your API, and then it's used in the docs UI.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1.0) when you used example or examples with
any of the other utilities ( Query() , Body() , etc.) those examples were not added to the JSON Schema that describes that data
(not even to OpenAPI's own version of JSON Schema), they were added directly to the path operation declaration in OpenAPI
(outside the parts of OpenAPI that use JSON Schema).
But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above,
everything is more consistent and the examples are included in JSON Schema.
Now, as Swagger UI didn't support multiple JSON Schema examples (as of 2023-08-26), users didn't have a way to show multiple
examples in the docs.
To solve that, FastAPI 0.103.0 added support for declaring the same old OpenAPI-specific examples field with the new
parameter openapi_examples . 🤓
Summary
I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅
In short, upgrade to FastAPI 0.99.0 or above, and things are much simpler, consistent, and intuitive, and you don't have to know
all these historic details. 😎
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Extra Data Types
int
float
str
bool
And you will still have the same features as seen up to now:
Data validation.
UUID :
datetime.datetime :
A Python datetime.datetime .
In requests and responses will be represented as a str in ISO 8601 format, like:
2008-09-15T15:53:00+05:00 .
datetime.date :
Python datetime.date .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In requests and responses will be represented as a str in ISO 8601 format, like:
2008-09-15 .
datetime.time :
A Python datetime.time .
In requests and responses will be represented as a str in ISO 8601 format, like:
14:23:55.003 .
datetime.timedelta :
A Python datetime.timedelta .
frozenset :
The generated schema will specify that the set values are unique (using JSON
Schema's uniqueItems ).
bytes :
The generated schema will specify that it's a str with binary "format".
Decimal :
You can check all the valid pydantic data types here: Pydantic data types [↪].
Example
Here's an example path operation with parameters using some of the above types.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from typing import Annotated
from uuid import UUID
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Annotated[datetime, Body()],
end_datetime: Annotated[datetime, Body()],
process_after: Annotated[timedelta, Body()],
repeat_at: Annotated[time | None, Body()] = None,
):
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"process_after": process_after,
"repeat_at": repeat_at,
"start_process": start_process,
"duration": duration,
}
Note that the parameters inside the function have their natural data type, and you can, for example,
perform normal date manipulations, like:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Annotated[datetime, Body()],
end_datetime: Annotated[datetime, Body()],
process_after: Annotated[timedelta, Body()],
repeat_at: Annotated[time | None, Body()] = None,
):
start_process = start_datetime + process_after
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"process_after": process_after,
"repeat_at": repeat_at,
"start_process": start_process,
"duration": duration,
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Cookie Parameters
You can define Cookie parameters the same way you define Query and Path parameters.
Import Cookie
First import Cookie :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}
The first value is the default value, you can pass all the extra validation or annotation parameters:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Technical Details
Cookie is a "sister" class of Path and Query . It also inherits from the same common Param class.
But remember that when you import Query , Path , Cookie and others from fastapi , those are actually functions that
return special classes.
Info
To declare cookies, you need to use Cookie , because otherwise the parameters would be interpreted as query
parameters.
Recap
Declare cookies with Cookie , using the same common pattern as Query and Path .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Header Parameters
You can define Header parameters the same way you define Query , Path and Cookie parameters.
Import Header
First import Header :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}
The first value is the default value, you can pass all the extra validation or annotation parameters:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Technical Details
Header is a "sister" class of Path , Query and Cookie . It also inherits from the same common Param class.
But remember that when you import Query , Path , Header , and others from fastapi , those are actually functions that
return special classes.
Info
To declare headers, you need to use Header , because otherwise the parameters would be interpreted as query parameters.
Automatic conversion
Header has a little extra functionality on top of what Path , Query and Cookie provide.
Most of the standard headers are separated by a "hyphen" character, also known as the "minus
symbol" ( - ).
So, by default, Header will convert the parameter names characters from underscore ( _ ) to hyphen (
- ) to extract and document the headers.
Also, HTTP headers are case-insensitive, so, you can declare them with standard Python style (also
known as "snake_case").
So, you can use user_agent as you normally would in Python code, instead of needing to capitalize
the first letters as User_Agent or something similar.
If for some reason you need to disable automatic conversion of underscores to hyphens, set the
parameter convert_underscores of Header to False :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(
strange_header: Annotated[str | None, Header(convert_underscores=False)] = None,
):
return {"strange_header": strange_header}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Warning
Before setting convert_underscores to False , bear in mind that some HTTP proxies and servers disallow the usage of
headers with underscores.
Duplicate headers
It is possible to receive duplicate headers. That means, the same header with multiple values.
You can define those cases using a list in the type declaration.
You will receive all the values from the duplicate header as a Python list .
For example, to declare a header of X-Token that can appear more than once, you can write:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.9+ non-Annotated Py
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
return {"X-Token values": x_token}
If you communicate with that path operation sending two HTTP headers like:
X-Token: foo
X-Token: bar
{
"X-Token values": [
"bar",
"foo"
]
}
Recap
Declare headers with Header , using the same common pattern as Query , Path and Cookie .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And don't worry about underscores in your variables, FastAPI will take care of converting them.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Response Model - Return Type
You can declare the type used for the response by annotating the path operation function return type.
You can use type annotations the same way you would for input data in function parameters, you can use
Pydantic models, lists, dictionaries, scalar values like integers, booleans, etc.
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
@app.get("/items/")
async def read_items() -> list[Item]:
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]
If the data is invalid (e.g. you are missing a field), it means that your app code is broken, not
returning what it should, and it will return a server error instead of returning incorrect data. This
way you and your clients can be certain that they will receive the data and the data shape
expected.
Add a JSON Schema for the response, in the OpenAPI path operation.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It will limit and filter the output data to what is defined in the return type.
This is particularly important for security, we'll see more of that below.
response_model Parameter
There are some cases where you need or want to return some data that is not exactly what the type
declares.
For example, you could want to return a dictionary or a database object, but declare it as a Pydantic
model. This way the Pydantic model would do all the data documentation, validation, etc. for the object
that you returned (e.g. a dictionary or database object).
If you added the return type annotation, tools and editors would complain with a (correct) error telling you
that your function is returning a type (e.g. a dict) that is different from what you declared (e.g. a Pydantic
model).
In those cases, you can use the path operation decorator parameter response_model instead of the return
type.
You can use the response_model parameter in any of the path operations:
@app.get()
@app.post()
@app.put()
@app.delete()
etc.
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.post("/items/", response_model=Item)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
Note
Notice that response_model is a parameter of the "decorator" method ( get , post , etc). Not of your path operation function, like
all the parameters and body.
response_model receives the same type you would declare for a Pydantic model field, so, it can be a
Pydantic model, but it can also be, e.g. a list of Pydantic models, like List[Item] .
FastAPI will use this response_model to do all the data documentation, validation, etc. and also to convert
and filter the output data to its type declaration.
Tip
If you have strict type checks in your editor, mypy, etc, you can declare the function return type as Any .
That way you tell the editor that you are intentionally returning anything. But FastAPI will still do the data documentation,
validation, filtering, etc. with the response_model .
response_model Priority
If you declare both a return type and a response_model , the response_model will take priority and be used
by FastAPI.
This way you can add correct type annotations to your functions even when you are returning a type
different than the response model, to be used by the editor and tools like mypy. And still you can have
FastAPI do the data validation, documentation, etc. using the response_model .
You can also use response_model=None to disable creating a response model for that path operation, you
might need to do it if you are adding type annotations for things that are not valid Pydantic fields, you will
see an example of that in one of the sections below.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.8+
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
Info
And we are using this model to declare our input and the same model to declare our output:
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
Now, whenever a browser is creating a user with a password, the API will return the same password in the
response.
In this case, it might not be a problem, because it's the same user sending the password.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
But if we use the same model for another path operation, we could be sending our user's passwords to
every client.
Danger
Never store the plain password of a user or send it in a response like this, unless you know all the caveats and you know what you
are doing.
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
Here, even though our path operation function is returning the same input user that contains the password:
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
...we declared the response_model to be our model UserOut , that doesn't include the password:
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
So, FastAPI will take care of filtering out all the data that is not declared in the output model (using
Pydantic).
In this case, because the two models are different, if we annotated the function return type as UserOut ,
the editor and tools would complain that we are returning an invalid type, as those are different classes.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
That's why in this example we have to declare it in the response_model parameter.
We want FastAPI to keep filtering the data using the response model.
In the previous example, because the classes were different, we had to use the response_model
parameter. But that also means that we don't get the support from the editor and tools checking the
function return type.
But in most of the cases where we need to do something like this, we want the model just to filter/remove
some of the data as in this example.
And in those cases, we can use classes and inheritance to take advantage of function type annotations to
get better support in the editor and tools, and still get the FastAPI data filtering.
app = FastAPI()
class BaseUser(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(BaseUser):
password: str
@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
return user
With this, we get tooling support, from editors and mypy as this code is correct in terms of types, but we
also get the data filtering from FastAPI.
BaseUser has the base fields. Then UserIn inherits from BaseUser and adds the password field, so, it
will include all the fields from both models.
We annotate the function return type as BaseUser , but we are actually returning a UserIn instance.
The editor, mypy, and other tools won't complain about this because, in typing terms, UserIn is a subclass
of BaseUser , which means it's a valid type when what is expected is anything that is a BaseUser .
Now, for FastAPI, it will see the return type and make sure that what you return includes only the fields that
are declared in the type.
FastAPI does several things internally with Pydantic to make sure that those same rules of class
inheritance are not used for the returned data filtering, otherwise you could end up returning much more
data than what you expected.
This way, you can get the best of both worlds: type annotations with tooling support and data filtering.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And both models will be used for the interactive API documentation:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Other Return Type Annotations
There might be cases where you return something that is not a valid Pydantic field and you annotate it in
the function, only to get the support provided by tooling (the editor, mypy, etc).
The most common case would be returning a Response directly as explained later in the advanced docs ↪.
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response:
if teleport:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
return JSONResponse(content={"message": "Here's your interdimensional portal."})
This simple case is handled automatically by FastAPI because the return type annotation is the class (or a
subclass) of Response .
And tools will also be happy because both RedirectResponse and JSONResponse are subclasses of
Response , so the type annotation is correct.
app = FastAPI()
@app.get("/teleport")
async def get_teleport() -> RedirectResponse:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
This will also work because RedirectResponse is a subclass of Response , and FastAPI will automatically
handle this simple case.
But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object)
and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that
type annotation, and will fail.
The same would happen if you had something like a union between different types where one or more of
them are not valid Pydantic types, for example this would fail 💥:
Python 3.10+ Python 3.8+
app = FastAPI()
@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response | dict:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
if teleport:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
return {"message": "Here's your interdimensional portal."}
...this fails because the type annotation is not a Pydantic type and is not just a single Response class or
subclass, it's a union (any of the two) between a Response and a dict .
Continuing from the example above, you might not want to have the default data validation, documentation,
filtering, etc. that is performed by FastAPI.
But you might want to still keep the return type annotation in the function to get the support from tools like
editors and type checkers (e.g. mypy).
In this case, you can disable the response model generation by setting response_model=None :
app = FastAPI()
@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
if teleport:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
return {"message": "Here's your interdimensional portal."}
This will make FastAPI skip the response model generation and that way you can have any return type
annotations you need without it affecting your FastAPI application. 🤓
Response Model encoding parameters
Your response model could have default values, like:
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
description: Union[str, None] = None (or str | None = None in Python 3.10) has a default of
None .
but you might want to omit them from the result if they were not actually stored.
For example, if you have models with many optional attributes in a NoSQL database, but you don't want to
send very long JSON responses full of default values.
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
and those default values won't be included in the response, only the values actually set.
So, if you send a request to that path operation for the item with ID foo , the response (not including default
values) will be:
{
"name": "Foo",
"price": 50.2
}
Info
In Pydantic v1 the method was called .dict() , it was deprecated (but still supported) in Pydantic v2, and renamed to
.model_dump() .
The examples here use .dict() for compatibility with Pydantic v1, but you should use .model_dump() instead if you can use
Pydantic v2.
Info
FastAPI uses Pydantic model's .dict() with its exclude_unset parameter [↪] to achieve this.
Info
response_model_exclude_defaults=True
response_model_exclude_none=True
But if your data has values for the model's fields with default values, like the item with ID bar :
{
"name": "Bar",
"description": "The bartenders",
"price": 62,
"tax": 20.2
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Data with the same values as the defaults
If the data has the same values as the default ones, like the item with ID baz :
{
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": []
}
FastAPI is smart enough (actually, Pydantic is smart enough) to realize that, even though description ,
tax , and tags have the same values as the defaults, they were set explicitly (instead of taken from the
defaults).
Tip
Notice that the default values can be anything, not only None .
You can also use the path operation decorator parameters response_model_include and
response_model_exclude .
They take a set of str with the name of the attributes to include (omitting the rest) or to exclude
(including the rest).
This can be used as a quick shortcut if you have only one Pydantic model and want to remove some data
from the output.
Tip
But it is still recommended to use the ideas above, using multiple classes, instead of these parameters.
This is because the JSON Schema generated in your app's OpenAPI (and the docs) will still be the one for the complete model,
even if you use response_model_include or response_model_exclude to omit some attributes.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
return items[item_id]
Tip
The syntax {"name", "description"} creates a set with those two values.
If you forget to use a set and use a list or tuple instead, FastAPI will still convert it to a set and it will
work correctly:
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
"baz": {
"name": "Baz",
"description": "There goes my baz",
"price": 50.2,
"tax": 10.5,
},
}
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
return items[item_id]
Recap
Use the path operation decorator's parameter response_model to define response models and especially
to ensure private data is filtered out.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Extra Models
Continuing with the previous example, it will be common to have more than one related model.
Danger
Never store user's plaintext passwords. Always store a "secure hash" that you can then verify.
If you don't know, you will learn what a "password hash" is in the security chapters ↪.
Multiple models
Here's a general idea of how the models could look like with their password fields and the places
where they are used:
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserInDB(BaseModel):
username: str
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
hashed_password: str
email: EmailStr
full_name: str | None = None
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
Info
In Pydantic v1 the method was called .dict() , it was deprecated (but still supported) in Pydantic v2, and renamed to
.model_dump() .
The examples here use .dict() for compatibility with Pydantic v1, but you should use .model_dump() instead if you
can use Pydantic v2.
About **user_in.dict()
Pydantic's .dict()
Pydantic models have a .dict() method that returns a dict with the model's data.
user_dict = user_in.dict()
we now have a dict with the data in the variable user_dict (it's a dict instead of a Pydantic
model object).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And if we call:
print(user_dict)
{
'username': 'john',
'password': 'secret',
'email': '[email protected]',
'full_name': None,
}
Unwrapping a dict
If we take a dict like user_dict and pass it to a function (or class) with **user_dict , Python will
"unwrap" it. It will pass the keys and values of the user_dict directly as key-value arguments.
UserInDB(**user_dict)
UserInDB(
username="john",
password="secret",
email="[email protected]",
full_name=None,
)
Or more exactly, using user_dict directly, with whatever contents it might have in the future:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
user_dict = user_in.dict()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
UserInDB(**user_dict)
UserInDB(**user_in.dict())
So, we get a Pydantic model from the data in another Pydantic model.
And then adding the extra keyword argument hashed_password=hashed_password , like in:
UserInDB(**user_in.dict(), hashed_password=hashed_password)
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
Warning
The supporting additional functions are just to demo a possible flow of the data, but they of course are not providing any
real security.
Reduce duplication
Reducing code duplication is one of the core ideas in FastAPI.
As code duplication increments the chances of bugs, security issues, code desynchronization
issues (when you update in one place but not in the others), etc.
And these models are all sharing a lot of the data and duplicating attribute names and types.
We could do better.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
We can declare a UserBase model that serves as a base for our other models. And then we can
make subclasses of that model that inherit its attributes (type declarations, validation, etc).
All the data conversion, validation, documentation, etc. will still work as normally.
That way, we can declare just the differences between the models (with plaintext password , with
hashed_password and without password):
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Union or anyOf
You can declare a response to be the Union of two types, that means, that the response would be
any of the two.
Note
When defining a Union [↪], include the most specific type first, followed by the less specific type. In the example below,
the more specific PlaneItem comes before CarItem in Union[PlaneItem, CarItem] .
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type: str = "car"
class PlaneItem(BaseItem):
type: str = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"item2": {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
},
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_item(item_id: str):
return items[item_id]
If it was in a type annotation we could have used the vertical bar, as:
But if we put that in response_model=PlaneItem | CarItem we would get an error, because Python
would try to perform an invalid operation between PlaneItem and CarItem instead of interpreting
that as a type annotation.
List of models
The same way, you can declare responses of lists of objects.
For that, use the standard Python typing.List (or just list in Python 3.9 and above):
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=list[Item])
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_items():
return items
This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic
model) beforehand.
In this case, you can use typing.Dict (or just dict in Python 3.9 and above):
app = FastAPI()
Recap
Use multiple Pydantic models and inherit freely for each case.
You don't need to have a single data model per entity if that entity must be able to have different
"states". As the case with the user "entity" with a state including password , password_hash and no
password.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Response Status Code
The same way you can specify a response model, you can also declare the HTTP status code used
for the response with the parameter status_code in any of the path operations:
@app.get()
@app.post()
@app.put()
@app.delete()
etc.
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
Note
Notice that status_code is a parameter of the "decorator" method ( get , post , etc). Not of your path operation function,
like all the parameters and body.
The status_code parameter receives a number with the HTTP status code.
Info
status_code can alternatively also receive an IntEnum , such as Python's http.HTTPStatus [↪].
It will:
Document it as such in the OpenAPI schema (and so, in the user interfaces):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Note
Some response codes (see the next section) indicate that the response does not have a body.
FastAPI knows this, and will produce OpenAPI docs that state there is no response body.
If you already know what HTTP status codes are, skip to the next section.
In HTTP, you send a numeric status code of 3 digits as part of the response.
These status codes have a name associated to recognize them, but the important part is the
number.
In short:
100 and above are for "Information". You rarely use them directly. Responses with these status
codes cannot have a body.
200 and above are for "Successful" responses. These are the ones you would use the most.
200 is the default status code, which means everything was "OK".
Another example would be 201 , "Created". It is commonly used after creating a new record
in the database.
A special case is 204 , "No Content". This response is used when there is no content to
return to the client, and so the response must not have a body.
300 and above are for "Redirection". Responses with these status codes may or may not have a
body, except for 304 , "Not Modified", which must not have one.
400 and above are for "Client error" responses. These are the second type you would probably
use the most.
For generic errors from the client, you can just use 400 .
500 and above are for server errors. You almost never use them directly. When something goes
wrong at some part in your application code, or server, it will automatically return one of these
status codes.
Tip
To know more about each status code and which code is for what, check the
MDN documentation about HTTP status codes [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from fastapi import FastAPI
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
But you don't have to memorize what each of these codes mean.
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}
They are just a convenience, they hold the same number, but that way you can use the editor's
autocomplete to find them:
Technical Details
FastAPI provides the same starlette.status as fastapi.status just as a convenience for you, the developer. But it
comes directly from Starlette.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Changing the default
Later, in the Advanced User Guide ↪, you will see how to return a different status code than the
default you are declaring here.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Form Data
When you need to receive form fields instead of JSON, you can use Form .
Info
Import Form
Import Form from fastapi :
app = FastAPI()
@app.post("/login/")
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
return {"username": username}
app = FastAPI()
@app.post("/login/")
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
return {"username": username}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is
required to send a username and password as form fields.
The spec requires the fields to be exactly named username and password , and to be sent as form
fields, not JSON.
With Form you can declare the same configurations as with Body (and Query , Path , Cookie ),
including validation, examples, an alias (e.g. user-name instead of username ), etc.
Info
Tip
To declare form bodies, you need to use Form explicitly, because without it the parameters would be interpreted as query
parameters or body (JSON) parameters.
FastAPI will make sure to read that data from the right place instead of JSON.
Technical Details
Data from forms is normally encoded using the "media type" application/x-www-form-urlencoded .
But when the form includes files, it is encoded as multipart/form-data . You'll read about handling files in the next
chapter.
If you want to read more about these encodings and form fields, head to the MDN web docs for POST [↪].
Warning
You can declare multiple Form parameters in a path operation, but you can't also declare Body fields that you expect to
receive as JSON, as the request will have the body encoded using application/x-www-form-urlencoded instead of
application/json .
Recap
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Use Form to declare form data input parameters.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Request Files
Info
Import File
Import File and UploadFile from fastapi :
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
Info
But remember that when you import Query , Path , File and others from fastapi , those are actually functions that return
special classes.
Tip
To declare File bodies, you need to use File , because otherwise the parameters would be interpreted as query parameters or
body (JSON) parameters.
If you declare the type of your path operation function parameter as bytes , FastAPI will read the file for
you and you will receive the contents as bytes .
Keep in mind that this means that the whole contents will be stored in memory. This will work well for small
files.
But there are several cases in which you might benefit from using UploadFile .
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
You don't have to use File() in the default value of the parameter.
A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in
disk.
This means that it will work well for large files like images, videos, large binaries, etc. without
consuming all the memory.
It exposes an actual Python SpooledTemporaryFile [↪] object that you can pass directly to other
libraries that expect a file-like object.
UploadFile
filename : A str with the original file name that was uploaded (e.g. myimage.jpg ).
content_type : A str with the content type (MIME type / media type) (e.g. image/jpeg ).
file : A SpooledTemporaryFile [↪] (a file-like [↪] object). This is the actual Python file that you can
pass directly to other functions or libraries that expect a "file-like" object.
UploadFile has the following async methods. They all call the corresponding file methods underneath
(using the internal SpooledTemporaryFile ).
This is especially useful if you run await myfile.read() once and then need to read the contents
again.
As all these methods are async methods, you need to "await" them.
For example, inside of an async path operation function you can get the contents with:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
contents = await myfile.read()
If you are inside of a normal def path operation function, you can access the UploadFile.file directly,
for example:
contents = myfile.file.read()
When you use the async methods, FastAPI runs the file methods in a threadpool and awaits for them.
FastAPI's UploadFile inherits directly from Starlette's UploadFile , but adds some necessary parts to make it compatible with
Pydantic and the other parts of FastAPI.
FastAPI will make sure to read that data from the right place instead of JSON.
Technical Details
Data from forms is normally encoded using the "media type" application/x-www-form-urlencoded when it doesn't include files.
But when the form includes files, it is encoded as multipart/form-data . If you use File , FastAPI will know it has to get the
files from the correct part of the body.
If you want to read more about these encodings and form fields, head to the MDN web docs for POST [↪].
Warning
You can declare multiple File and Form parameters in a path operation, but you can't also declare Body fields that you expect
to receive as JSON, as the request will have the body encoded using multipart/form-data instead of application/json .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes | None, File()] = None):
if not file:
return {"message": "No file sent"}
else:
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None):
if not file:
return {"message": "No upload file sent"}
else:
return {"filename": file.filename}
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(
file: Annotated[UploadFile, File(description="A file read as UploadFile")],
):
return {"filename": file.filename}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It's possible to upload several files at the same time.
They would be associated to the same "form field" sent using "form data".
Python 3.9+ Python 3.8+ Python 3.9+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.post("/files/")
async def create_files(files: Annotated[list[bytes], File()]):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
return {"filenames": [file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
Technical Details
FastAPI provides the same starlette.responses as fastapi.responses just as a convenience for you, the developer. But most
of the available responses come directly from Starlette.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And the same way as before, you can use File() to set additional parameters, even for UploadFile :
Python 3.9+ Python 3.8+ Python 3.9+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.post("/files/")
async def create_files(
files: Annotated[list[bytes], File(description="Multiple files as bytes")],
):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(
files: Annotated[
list[UploadFile], File(description="Multiple files as UploadFile")
],
):
return {"filenames": [file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
Recap
Use File , bytes , and UploadFile to declare files to be uploaded in the request, sent as form data.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Request Forms and Files
You can define files and form fields at the same time using File and Form .
Info
To receive uploaded files and/or form data, first install python-multipart [↪].
app = FastAPI()
@app.post("/files/")
async def create_file(
file: Annotated[bytes, File()],
fileb: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI()
@app.post("/files/")
async def create_file(
file: Annotated[bytes, File()],
fileb: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
}
The files and form fields will be uploaded as form data and you will receive the files and form fields.
And you can declare some of the files as bytes and some as UploadFile .
Warning
You can declare multiple File and Form parameters in a path operation, but you can't also declare Body fields that you
expect to receive as JSON, as the request will have the body encoded using multipart/form-data instead of
application/json .
Recap
Use File and Form together when you need to receive data and files in the same request.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Handling Errors
There are many situations in which you need to notify an error to a client that is using your API.
This client could be a browser with a frontend, a code from someone else, an IoT device, etc.
etc.
In these cases, you would normally return an HTTP status code in the range of 400 (from 400 to 499).
This is similar to the 200 HTTP status codes (from 200 to 299). Those "200" status codes mean that
somehow there was a "success" in the request.
The status codes in the 400 range mean that there was an error from the client.
Use HTTPException
To return HTTP responses with errors to the client you use HTTPException .
Import HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Raise an HTTPException in your code
HTTPException is a normal Python exception with additional data relevant for APIs.
Because it's a Python exception, you don't return it, you raise it.
This also means that if you are inside a utility function that you are calling inside of your path operation
function, and you raise the HTTPException from inside of that utility function, it won't run the rest of
the code in the path operation function, it will terminate that request right away and send the HTTP
error from the HTTPException to the client.
The benefit of raising an exception over return ing a value will be more evident in the section about
Dependencies and Security.
In this example, when the client requests an item by an ID that doesn't exist, raise an exception with a
status code of 404 :
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
If the client requests http://example.com/items/foo (an item_id "foo" ), that client will receive an
HTTP status code of 200, and a JSON response of:
{
"item": "The Foo Wrestlers"
}
{
"detail": "Item not found"
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
When raising an HTTPException , you can pass any value that can be converted to JSON as the parameter detail , not only
str .
But in case you needed it for an advanced scenario, you can add custom headers:
app = FastAPI()
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"},
)
return {"item": items[item_id]}
Let's say you have a custom exception UnicornException that you (or a library you use) might raise .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
)
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise UnicornException(name=name)
return {"unicorn_name": name}
Here, if you request /unicorns/yolo , the path operation will raise a UnicornException .
So, you will receive a clean error, with an HTTP status code of 418 and a JSON content of:
Technical Details
You could also use from starlette.requests import Request and from starlette.responses import JSONResponse .
FastAPI provides the same starlette.responses as fastapi.responses just as a convenience for you, the developer. But
most of the available responses come directly from Starlette. The same with Request .
These handlers are in charge of returning the default JSON responses when you raise an
HTTPException and when the request has invalid data.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can override these exception handlers with your own.
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
Now, if you go to /items/foo , instead of getting the default JSON error with:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
]
}
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
RequestValidationError vs ValidationError
Warning
These are technical details that you might skip if it's not important for you now.
FastAPI uses it so that, if you use a Pydantic model in response_model , and your data has an error,
you will see the error in your log.
But the client/user will not see it. Instead, the client will receive an "Internal Server Error" with a HTTP
status code 500 .
It should be this way because if you have a Pydantic ValidationError in your response or anywhere
in your code (not in the client's request), it's actually a bug in your code.
And while you fix it, your clients/users shouldn't have access to internal information about the error, as
that could expose a security vulnerability.
For example, you could want to return a plain text response instead of JSON for these errors:
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
Technical Details
FastAPI provides the same starlette.responses as fastapi.responses just as a convenience for you, the developer. But
most of the available responses come directly from Starlette.
You could use it while developing your app to log the body and debug it, return it to the user, etc.
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)
class Item(BaseModel):
title: str
size: int
@app.post("/items/")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def create_item(item: Item):
return item
{
"title": "towel",
"size": "XL"
}
You will receive a response telling you that the data is invalid containing the received body:
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
And FastAPI's HTTPException error class inherits from Starlette's HTTPException error class.
The only difference is that FastAPI's HTTPException accepts any JSON-able data for the detail field,
while Starlette's HTTPException only accepts strings for it.
So, you can keep raising FastAPI's HTTPException as normally in your code.
But when you register an exception handler, you should register it for Starlette's HTTPException .
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises a Starlette
HTTPException , your handler will be able to catch and handle it.
In this example, to be able to have both HTTPException s in the same code, Starlette's exceptions is
renamed to StarletteHTTPException :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from starlette.exceptions import HTTPException as StarletteHTTPException
If you want to use the exception along with the same default exception handlers from FastAPI, You
can import and reuse the default exception handlers from fastapi.exception_handlers :
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
print(f"OMG! An HTTP error!: {repr(exc)}")
return await http_exception_handler(request, exc)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
print(f"OMG! The client sent invalid data!: {exc}")
return await request_validation_exception_handler(request, exc)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
In this example you are just print ing the error with a very expressive message, but you get the idea.
You can use the exception and then just reuse the default exception handlers.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Path Operation Configuration
There are several parameters that you can pass to your path operation decorator to configure it.
Warning
Notice that these parameters are passed directly to the path operation decorator, not to your path operation function.
But if you don't remember what each number code is for, you can use the shortcut constants in status :
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
That status code will be used in the response and will be added to the OpenAPI schema.
Technical Details
FastAPI provides the same starlette.status as fastapi.status just as a convenience for you, the developer. But it comes directly from Starlette.
Tags
You can add tags to your path operation, pass the parameter tags with a list of str (commonly just one str ):
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
tax: float | None = None
tags: set[str] = set()
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
They will be added to the OpenAPI schema and used by the automatic documentation interfaces:
If you have a big application, you might end up accumulating several tags, and you would want to make sure you always use the same
tag for related path operations.
app = FastAPI()
class Tags(Enum):
items = "items"
users = "users"
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/items/", tags=[Tags.items])
async def get_items():
return ["Portal gun", "Plumbus"]
@app.get("/users/", tags=[Tags.users])
async def read_users():
return ["Rick", "Morty"]
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
@app.post(
"/items/",
response_model=Item,
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
async def create_item(item: Item):
return item
You can write Markdown [↪] in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"""
Create an item with all the information:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Response description
You can specify the response description with the parameter response_description :
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
@app.post(
"/items/",
response_model=Item,
summary="Create an item",
response_description="The created item",
)
async def create_item(item: Item):
"""
Create an item with all the information:
Info
Notice that response_description refers specifically to the response, the description refers to the path operation in general.
Check
So, if you don't provide one, FastAPI will automatically generate one of "Successful response".
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Deprecate a path operation
If you need to mark a path operation as deprecated, but without removing it, pass the parameter deprecated :
app = FastAPI()
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It will be clearly marked as deprecated in the interactive docs:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Recap
You can configure and add metadata for your path operations easily by passing parameters to the path operation decorators.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
JSON Compatible Encoder
There are some cases where you might need to convert a data type (like a Pydantic model) to
something compatible with JSON (like a dict , list , etc).
For example, it doesn't receive datetime objects, as those are not compatible with JSON.
So, a datetime object would have to be converted to a str containing the data in ISO format [↪].
The same way, this database wouldn't receive a Pydantic model (an object with attributes), only a
dict .
It receives an object, like a Pydantic model, and returns a JSON compatible version:
fake_db = {}
class Item(BaseModel):
title: str
timestamp: datetime
description: str | None = None
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.put("/items/{id}")
def update_item(id: str, item: Item):
json_compatible_item_data = jsonable_encoder(item)
fake_db[id] = json_compatible_item_data
In this example, it would convert the Pydantic model to a dict , and the datetime to a str .
The result of calling it is something that can be encoded with the Python standard
json.dumps() [↪].
It doesn't return a large str containing the data in JSON format (as a string). It returns a Python
standard data structure (e.g. a dict ) with values and sub-values that are all compatible with JSON.
Note
jsonable_encoder is actually used by FastAPI internally to convert data. But it is useful in many other scenarios.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Body - Updates
You can use the jsonable_encoder to convert the input data to data that can be stored as JSON (e.g. with
a NoSQL database). For example, converting datetime to str .
app = FastAPI()
class Item(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
return items[item_id]
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
update_item_encoded = jsonable_encoder(item)
items[item_id] = update_item_encoded
return update_item_encoded
PUT is used to receive data that should replace the existing data.
That means that if you want to update the item bar using PUT with a body containing:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
{
"name": "Barz",
"price": 3,
"description": None,
}
because it doesn't include the already stored attribute "tax": 20.2 , the input model would take the
default value of "tax": 10.5 .
And the data would be saved with that "new" tax of 10.5 .
This means that you can send only the data that you want to update, leaving the rest intact.
Note
And many teams use only PUT , even for partial updates.
You are free to use them however you want, FastAPI doesn't impose any restrictions.
But this guide shows you, more or less, how they are intended to be used.
If you want to receive partial updates, it's very useful to use the parameter exclude_unset in Pydantic's
model's .model_dump() .
Like item.model_dump(exclude_unset=True) .
Info
In Pydantic v1 the method was called .dict() , it was deprecated (but still supported) in Pydantic v2, and renamed to
.model_dump() .
The examples here use .dict() for compatibility with Pydantic v1, but you should use .model_dump() instead if you can use
Pydantic v2.
That would generate a dict with only the data that was set when creating the item model, excluding
default values.
Then you can use this to generate a dict with only the data that was set (sent in the request), omitting
default values:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+
app = FastAPI()
class Item(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
return items[item_id]
@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
Now, you can create a copy of the existing model using .model_copy() , and pass the update parameter
with a dict containing the data to update.
Info
In Pydantic v1 the method was called .copy() , it was deprecated (but still supported) in Pydantic v2, and renamed to
.model_copy() .
The examples here use .copy() for compatibility with Pydantic v1, but you should use .model_copy() instead if you can use
Pydantic v2.
Like stored_item_model.model_copy(update=update_data) :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+
app = FastAPI()
class Item(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
return items[item_id]
@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
Generate a dict without default values from the input model (using exclude_unset ).
This way you can update only the values actually set by the user, instead of overriding values
already stored with default values in your model.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Create a copy of the stored model, updating it's attributes with the received partial updates (using the
update parameter).
Convert the copied model to something that can be stored in your DB (for example, using the
jsonable_encoder ).
This is comparable to using the model's .model_dump() method again, but it makes sure (and
converts) the values to data types that can be converted to JSON, for example, datetime to str .
app = FastAPI()
class Item(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: str):
return items[item_id]
@app.patch("/items/{item_id}", response_model=Item)
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
You can actually use this same technique with an HTTP PUT operation.
But the example here uses PATCH because it was created for these use cases.
Note
So, if you want to receive partial updates that can omit all the attributes, you need to have a model with all the attributes marked
as optional (with default values or None ).
To distinguish from the models with all optional values for updates and models with required values for creation, you can use the
ideas described in Extra Models ↪.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Dependencies
It is designed to be very simple to use, and to make it very easy for any developer to integrate other
components with FastAPI.
And then, that system (in this case FastAPI) will take care of doing whatever is needed to provide
your code with those needed dependencies ("inject" the dependencies).
Have shared logic (the same code logic again and again).
First Steps
Let's see a very simple example. It will be so simple that it is not very useful, for now.
But this way we can focus on how the Dependency Injection system works.
It is just a function that can take all the same parameters that a path operation function can take:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from typing import Annotated
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
That's it.
2 lines.
And it has the same shape and structure that all your path operation functions have.
You can think of it as a path operation function without the "decorator" (without the
@app.get("/some-path") ).
Info
FastAPI added support for Annotated (and started recommending it) in version 0.95.0.
If you have an older version, you would get errors when trying to use Annotated .
Make sure you Upgrade the FastAPI version ↪ to at least 0.95.1 before using Annotated .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Import Depends
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
The same way you use Body , Query , etc. with your path operation function parameters, use
Depends with a new parameter:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
return commons
Although you use Depends in the parameters of your function the same way you use Body , Query ,
etc, Depends works a bit differently.
You don't call it directly (don't add the parenthesis at the end), you just pass it as a parameter to
Depends() .
And that function takes parameters in the same way that path operation functions do.
Tip
You'll see what other "things", apart from functions, can be used as dependencies in the next chapter.
This way you write shared code once and FastAPI takes care of calling it for your path operations.
Check
Notice that you don't have to create a special class and pass it somewhere to FastAPI to "register" it or anything similar.
You just pass it to Depends and FastAPI knows how to do the rest.
When you need to use the common_parameters() dependency, you have to write the whole parameter
with the type annotation and Depends() :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
But because we are using Annotated , we can store that Annotated value in a variable and use it in
multiple places:
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
Tip
This is just standard Python, it's called a "type alias", it's actually not specific to FastAPI.
But because FastAPI is based on the Python standards, including Annotated , you can use this trick in your code. 😎
The dependencies will keep working as expected, and the best part is that the type information will
be preserved, which means that your editor will be able to keep providing you with autocompletion,
inline errors, etc. The same for other tools like mypy .
This will be especially useful when you use it in a large code base where you use the same
dependencies over and over again in many path operations.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can use async def or normal def .
And you can declare dependencies with async def inside of normal def path operation functions,
or def dependencies inside of async def path operation functions, etc.
Note
If you don't know, check the Async: "In a hurry?" ↪ section about async and await in the docs.
So, the interactive docs will have all the information from these dependencies too:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Simple usage
If you look at it, path operation functions are declared to be used whenever a path and operation
matches, and then FastAPI takes care of calling the function with the correct parameters, extracting
the data from the request.
Actually, all (or most) of the web frameworks work in this same way.
You never call those functions directly. They are called by your framework (in this case, FastAPI).
With the Dependency Injection system, you can also tell FastAPI that your path operation function
also "depends" on something else that should be executed before your path operation function, and
FastAPI will take care of executing it and "injecting" the results.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Other common terms for this same idea of "dependency injection" are:
resources
providers
services
injectables
components
FastAPI plug-ins
Integrations and "plug-ins" can be built using the Dependency Injection system. But in fact, there is
actually no need to create "plug-ins", as by using dependencies it's possible to declare an infinite
number of integrations and interactions that become available to your path operation functions.
And dependencies can be created in a very simple and intuitive way that allows you to just import the
Python packages you need, and integrate them with your API functions in a couple of lines of code,
literally.
You will see examples of this in the next chapters, about relational and NoSQL databases, security,
etc.
FastAPI compatibility
The simplicity of the dependency injection system makes FastAPI compatible with:
NoSQL databases
external packages
external APIs
etc.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Although the hierarchical dependency injection system is very simple to define and use, it's still very
powerful.
You can define dependencies that in turn can define dependencies themselves.
In the end, a hierarchical tree of dependencies is built, and the Dependency Injection system takes
care of solving all these dependencies for you (and their sub-dependencies) and providing (injecting)
the results at each step.
For example, let's say you have 4 API endpoints (path operations):
/items/public/
/items/private/
/users/{user_id}/activate
/items/pro/
then you could add different permission requirements for each of them just with dependencies and
sub-dependencies:
FastAPI will take care of adding it all to the OpenAPI schema, so that it is shown in the interactive
documentation systems.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Classes as Dependencies
Before diving deeper into the Dependency Injection system, let's upgrade the previous example.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
But then we get a dict in the parameter commons of the path operation function.
And we know that editors can't provide a lot of support (like completion) for dict s, because they can't
know their keys and value types.
We can do better...
But that's not the only way to declare dependencies (although it would probably be the more common).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
So, if you have an object something (that might not be a function) and you can "call" it (execute it) like:
something()
or
something(some_argument, some_keyword_argument="foo")
then it is a "callable".
Classes as dependencies
You might notice that to create an instance of a Python class, you use that same syntax.
For example:
class Cat:
def __init__(self, name: str):
self.name = name
What FastAPI actually checks is that it is a "callable" (function, class or anything else) and the parameters
defined.
If you pass a "callable" as a dependency in FastAPI, it will analyze the parameters for that "callable", and
process them in the same way as the parameters for a path operation function. Including sub-
dependencies.
That also applies to callables with no parameters at all. The same as it would be for path operation
functions with no parameters.
Then, we can change the dependency "dependable" common_parameters from above to the class
CommonQueryParams :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from typing import Annotated
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
Pay attention to the __init__ method used to create the instance of the class:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
response.update({"items": items})
return response
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
Those parameters are what FastAPI will use to "solve" the dependency.
In both cases the data will be converted, validated, documented on the OpenAPI schema, etc.
Use it
Now you can declare your dependency using this class.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
FastAPI calls the CommonQueryParams class. This creates an "instance" of that class and the instance will
be passed as the parameter commons to your function.
... Depends(CommonQueryParams)
...is what FastAPI will actually use to know what is the dependency.
From it is that FastAPI will extract the declared parameters and that is what FastAPI will actually call.
...doesn't have any special meaning for FastAPI. FastAPI won't use it for data conversion, validation, etc.
(as it is using the Depends(CommonQueryParams) for that).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You could actually write just:
...as in:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
But declaring the type is encouraged as that way your editor will know what will be passed as the
parameter commons , and then it can help you with code completion, type checks, etc:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Shortcut
But you see that we are having some code repetition here, writing CommonQueryParams twice:
FastAPI provides a shortcut for these cases, in where the dependency is specifically a class that FastAPI
will "call" to create an instance of the class itself.
Instead of writing:
...you write:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You declare the dependency as the type of the parameter, and you use Depends() without any parameter,
instead of having to write the full class again inside of Depends(CommonQueryParams) .
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
Tip
If that seems more confusing than helpful, disregard it, you don't need it.
It is just a shortcut. Because FastAPI cares about helping you minimize code repetition.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Sub-dependencies
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10 non-Annotated Python 3.8 non-Annotated
app = FastAPI()
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)],
last_query: Annotated[str | None, Cookie()] = None,
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(
query_or_default: Annotated[str, Depends(query_or_cookie_extractor)],
):
return {"q_or_cookie": query_or_default}
It declares an optional query parameter q as a str , and then it just returns it.
This is quite simple (not very useful), but will help us focus on how the sub-dependencies work.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10 non-Annotated Python 3.8 non-Annotated
app = FastAPI()
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)],
last_query: Annotated[str | None, Cookie()] = None,
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(
query_or_default: Annotated[str, Depends(query_or_cookie_extractor)],
):
return {"q_or_cookie": query_or_default}
Even though this function is a dependency ("dependable") itself, it also declares another dependency (it
"depends" on something else).
It depends on the query_extractor , and assigns the value returned by it to the parameter q .
If the user didn't provide any query q , we use the last query used, which we saved to a cookie before.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10 non-Annotated Python 3.8 non-Annotated
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)],
last_query: Annotated[str | None, Cookie()] = None,
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(
query_or_default: Annotated[str, Depends(query_or_cookie_extractor)],
):
return {"q_or_cookie": query_or_default}
Info
Notice that we are only declaring one dependency in the path operation function, the query_or_cookie_extractor .
But FastAPI will know that it has to solve query_extractor first, to pass the results of that to query_or_cookie_extractor while
calling it.
And it will save the returned value in a "cache" and pass it to all the "dependants" that need it in that specific
request, instead of calling the dependency multiple times for the same request.
In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple
times) in the same request instead of using the "cached" value, you can set the parameter use_cache=False
when using Depends :
Recap
Apart from all the fancy words used here, the Dependency Injection system is quite simple.
Just functions that look the same as the path operation functions.
But still, it is very powerful, and allows you to declare arbitrarily deeply nested dependency "graphs" (trees).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
All this might not seem as useful with these simple examples.
But you will see how useful it is in the chapters about security.
And you will also see the amounts of code it will save you.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Dependencies in path operation decorators
In some cases you don't really need the return value of a dependency inside your path operation
function.
For those cases, instead of declaring a path operation function parameter with Depends , you can
add a list of dependencies to the path operation decorator.
app = FastAPI()
These dependencies will be executed/solved the same way as normal dependencies. But their value
(if they return any) won't be passed to your path operation function.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
Some editors check for unused function parameters, and show them as errors.
Using these dependencies in the path operation decorator you can make sure they are executed while avoiding
editor/tooling errors.
It might also help avoid confusion for new developers that see an unused parameter in your code and could think it's
unnecessary.
Info
But in real cases, when implementing security, you would get more benefits from using the integrated Security utilities
(the next chapter) ↪.
Dependency requirements
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
Raise exceptions
app = FastAPI()
Return values
And they can return values or not, the values won't be used.
So, you can reuse a normal dependency (that returns a value) you already use somewhere else, and
even though the value won't be used, the dependency will be executed:
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
Global Dependencies
Next we will see how to add dependencies to the whole FastAPI application, so that they apply to
each path operation.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Global Dependencies
For some types of applications you might want to add dependencies to the whole application.
Similar to the way you can add dependencies to the path operation decorators ↪, you can add them
to the FastAPI application.
In that case, they will be applied to all the path operations in the application:
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
And all the ideas in the section about adding dependencies to the path operation decorators ↪ still
apply, but in this case, to all of the path operations in the app.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
for a group of path operations.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Dependencies with yield
To do this, use yield instead of return , and write the extra steps (code) after.
Tip
Technical Details
@contextlib.contextmanager [↪] or
@contextlib.asynccontextmanager [↪]
Only the code prior to and including the yield statement is executed before creating a response:
The yielded value is what is injected into path operations and other dependencies:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The code following the yield statement is executed after the response has been delivered:
Tip
FastAPI will do the right thing with each, the same as with normal dependencies.
For example, if some code at some point in the middle, in another dependency or in a path operation,
made a database transaction "rollback" or create any other error, you will receive the exception in
your dependency.
So, you can look for that specific exception inside the dependency with except SomeException .
In the same way, you can use finally to make sure the exit steps are executed, no matter if there
was an exception or not.
FastAPI will make sure that the "exit code" in each dependency with yield is run in the correct
order.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
For example, dependency_c can have a dependency on dependency_b , and dependency_b on
dependency_a :
In this case dependency_c , to execute its exit code, needs the value from dependency_b (here
named dep_b ) to still be available.
And, in turn, dependency_b needs the value from dependency_a (here named dep_a ) to be
available for its exit code.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
try:
yield dep_a
finally:
dep_a.close()
The same way, you could have some dependencies with yield and some other dependencies with
return , and have some of those depend on some of the others.
And you could have a single dependency that requires several other dependencies with yield , etc.
Technical Details
The same way, you could raise an HTTPException or similar in the exit code, after the yield .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
This is a somewhat advanced technique, and in most of the cases you won't really need it, as you can raise exceptions
(including HTTPException ) from inside of the rest of your application code, for example, in the path operation function.
app = FastAPI()
data = {
"plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
"portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}
class OwnerError(Exception):
pass
def get_username():
try:
yield "Rick"
except OwnerError as e:
raise HTTPException(status_code=400, detail=f"Owner error: {e}")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id not in data:
raise HTTPException(status_code=404, detail="Item not found")
item = data[item_id]
if item["owner"] != username:
raise OwnerError(username)
return item
An alternative you could use to catch exceptions (and possibly also raise another HTTPException )
is to create a Custom Exception Handler ↪.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you catch an exception using except in a dependency with yield and you don't raise it again (or
raise a new exception), FastAPI won't be able to notice there was an exception, the same way that
would happen with regular Python:
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("Oops, we didn't raise again, Britney 😱")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
In this case, the client will see an HTTP 500 Internal Server Error response as it should, given that we
are not raising an HTTPException or similar, but the server will not have any logs or any other
indication of what was the error. 😱
Always raise in Dependencies with yield and except
If you catch an exception in a dependency with yield , unless you are raising another
HTTPException or similar, you should re-raise the original exception.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.9+ Python 3.8+ Python 3.8+ non-Annotated
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("We don't swallow the internal error here, we raise again 😎")
raise
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
Now the client will get the same HTTP 500 Internal Server Error response, but the server will have our
custom InternalError in the logs. 😎
Execution of dependencies with yield
The sequence of execution is more or less like this diagram. Time flows from top to bottom. And
each column is one of the parts interacting or executing code.
Info
Only one response will be sent to the client. It might be one of the error responses or it will be the response from the path
operation.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
This diagram shows HTTPException , but you could also raise any other exception that you catch in a dependency with
yield or with a Custom Exception Handler ↪.
If you raise any exception, it will be passed to the dependencies with yield, including HTTPException . In most cases you
will want to re-raise that same exception or a new one from the dependency with yield to make sure it's properly
handled.
Warning
You most probably don't need these technical details, you can skip this section and continue below.
These details are useful mainly if you were using a version of FastAPI prior to 0.106.0 and used resources from
dependencies with yield in background tasks.
Before FastAPI 0.110.0, if you used a dependency with yield , and then you captured an exception
with except in that dependency, and you didn't raise the exception again, the exception would be
automatically raised/forwarded to any exception handlers or the internal server error handler.
This was changed in version 0.110.0 to fix unhandled memory consumption from forwarded
exceptions without a handler (internal server errors), and to make it consistent with the behavior of
regular Python code.
Before FastAPI 0.106.0, raising exceptions after yield was not possible, the exit code in
dependencies with yield was executed after the response was sent, so Exception Handlers ↪
would have already run.
This was designed this way mainly to allow using the same objects "yielded" by dependencies inside
of background tasks, because the exit code would be executed after the background tasks were
finished.
Nevertheless, as this would mean waiting for the response to travel through the network while
unnecessarily holding a resource in a dependency with yield (for example a database connection),
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
this was changed in FastAPI 0.106.0.
Tip
Additionally, a background task is normally an independent set of logic that should be handled separately, with its own
resources (e.g. its own database connection).
If you used to rely on this behavior, now you should create the resources for background tasks inside
the background task itself, and use internally only data that doesn't depend on the resources of
dependencies with yield .
For example, instead of using the same database session, you would create a new database
session inside of the background task, and you would obtain the objects from the database using
this new session. And then instead of passing the object from the database as a parameter to the
background task function, you would pass the ID of that object and then obtain the object again
inside the background task function.
Context Managers
What are "Context Managers"
"Context Managers" are any of those Python objects that you can use in a with statement.
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
When the with block finishes, it makes sure to close the file, even if there were exceptions.
When you create a dependency with yield , FastAPI will internally create a context manager for it,
and combine it with some other related tools.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Warning
If you are just starting with FastAPI you might want to skip it for now.
You can also use them inside of FastAPI dependencies with yield by using with or async with
statements inside of the dependency function:
class MySuperContextManager:
def __init__(self):
self.db = DBSession()
def __enter__(self):
return self.db
Tip
@contextlib.contextmanager [↪] or
@contextlib.asynccontextmanager [↪]
But you don't have to use the decorators for FastAPI dependencies (and you shouldn't).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Security
In many frameworks and systems just handling security and authentication takes a big amount of
effort and code (in many cases it can be 50% or more of all the code written).
FastAPI provides several tools to help you deal with Security easily, rapidly, in a standard way,
without having to study and learn all the security specifications.
In a hurry?
If you don't care about any of these terms and you just need to add security with authentication
based on username and password right now, skip to the next chapters.
OAuth2
OAuth2 is a specification that defines several ways to handle authentication and authorization.
That's what all the systems with "login with Facebook, Google, Twitter, GitHub" use underneath.
OAuth 1
There was an OAuth 1, which is very different from OAuth2, and more complex, as it included direct
specifications on how to encrypt the communication.
OAuth2 doesn't specify how to encrypt the communication, it expects you to have your application
served with HTTPS.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
In the section about deployment you will see how to set up HTTPS for free, using Traefik and Let's Encrypt.
OpenID Connect
OpenID Connect is another specification, based on OAuth2.
It just extends OAuth2 specifying some things that are relatively ambiguous in OAuth2, to try to
make it more interoperable.
For example, Google login uses OpenID Connect (which underneath uses OAuth2).
But Facebook login doesn't support OpenID Connect. It has its own flavor of OAuth2.
There was also an "OpenID" specification. That tried to solve the same thing as OpenID Connect,
but was not based on OAuth2.
OpenAPI
OpenAPI (previously known as Swagger) is the open specification for building APIs (now part of
the Linux Foundation).
That's what makes it possible to have multiple automatic interactive documentation interfaces,
code generation, etc.
By using them, you can take advantage of all these standard-based tools, including these
interactive documentation systems.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
A query parameter.
A header.
A cookie.
bearer : a header Authorization with a value of Bearer plus a token. This is inherited
from OAuth2.
Several of these flows are appropriate for building an OAuth 2.0 authentication provider
(like Google, Facebook, Twitter, GitHub, etc):
implicit
clientCredentials
authorizationCode
But there is one specific "flow" that can be perfectly used for handling authentication in the
same application directly:
Tip
Integrating other authentication/authorization providers like Google, Facebook, Twitter, GitHub, etc. is also possible and
relatively easy.
The most complex problem is building an authentication/authorization provider like those, but FastAPI gives you the
tools to do it easily, while doing the heavy lifting for you.
FastAPI utilities
FastAPI provides several tools for each of these security schemes in the fastapi.security
module that simplify using these security mechanisms.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In the next chapters you will see how to add security to your API using those tools provided by
FastAPI.
And you will also see how it gets automatically integrated into the interactive documentation
system.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Security - First Steps
Let's imagine that you have your backend API in some domain.
And you have a frontend in another domain or in a different path of the same domain (or in a mobile
application).
And you want to have a way for the frontend to authenticate with the backend, using a username and
password.
But let's save you the time of reading the full long specification just to find those little pieces of information
you need.
How it looks
Let's first just use the code and see how it works, and then we'll come back to understand what's
happening.
Create main.py
Copy the example in a file main.py :
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
Run it
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
The python-multipart [↪] package is automatically installed with FastAPI when you run the pip install fastapi command.
However, if you use the pip install fastapi-slim command, the python-multipart package is not included by default. To
install it manually, use the following command:
This is because OAuth2 uses "form data" for sending the username and password .
bash
fast →
$ fasta ▋
Check it
Go to the interactive docs at: http://127.0.0.1:8000/docs [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Authorize button!
And your path operation has a little lock in the top-right corner that you can click.
And if you click it, you have a little authorization form to type a username and password (and other
optional fields):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Note
It doesn't matter what you type in the form, it won't work yet. But we'll get there.
This is of course not the frontend for the final users, but it's a great automatic tool to document
interactively all your API.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It can be used by third party applications and systems.
And it can also be used by yourself, to debug, check and test the same application.
The password "flow" is one of the ways ("flows") defined in OAuth2, to handle security and authentication.
OAuth2 was designed so that the backend or API could be independent of the server that authenticates the
user.
But in this case, the same FastAPI application will handle the API and the authentication.
The user types the username and password in the frontend, and hits Enter .
The frontend (running in the user's browser) sends that username and password to a specific URL in
our API (declared with tokenUrl="token" ).
The API checks that username and password , and responds with a "token" (we haven't implemented
any of this yet).
A "token" is just a string with some content that we can use later to verify this user.
So, the user will have to log in again at some point later.
And if the token is stolen, the risk is less. It is not like a permanent key that will work forever
(in most of the cases).
The user clicks in the frontend to go to another section of the frontend web app.
The frontend needs to fetch some more data from the API.
So, to authenticate with our API, it sends a header Authorization with a value of Bearer plus the
token.
If the token contains foobar , the content of the Authorization header would be:
Bearer foobar .
FastAPI's OAuth2PasswordBearer
FastAPI provides several tools, at different levels of abstraction, to implement these security features.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
In this example we are going to use OAuth2, with the Password flow, using a Bearer token. We do that
using the OAuth2PasswordBearer class.
Info
And it might be the best for most use cases, unless you are an OAuth2 expert and know exactly why there's another option that
suits better your needs.
In that case, FastAPI also provides you with the tools to build it.
When we create an instance of the OAuth2PasswordBearer class we pass in the tokenUrl parameter.
This parameter contains the URL that the client (the frontend running in the user's browser) will use to send
the username and password in order to get a token.
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
Tip
Here tokenUrl="token" refers to a relative URL token that we haven't created yet. As it's a relative URL, it's equivalent to
./token .
Because we are using a relative URL, if your API was located at https://example.com/ , then it would refer to
https://example.com/token . But if your API was located at https://example.com/api/v1/ , then it would refer to
https://example.com/api/v1/token .
Using a relative URL is important to make sure your application keeps working even in an advanced use case like Behind a
Proxy ↪.
This parameter doesn't create that endpoint / path operation, but declares that the URL /token will be the
one that the client should use to get the token. That information is used in OpenAPI, and then in the
interactive API documentation systems.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
If you are a very strict "Pythonista" you might dislike the style of the parameter name tokenUrl instead of token_url .
That's because it is using the same name as in the OpenAPI spec. So that if you need to investigate more about any of these
security schemes you can just copy and paste it to find more information about it.
oauth2_scheme(some, parameters)
Use it
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
This dependency will provide a str that is assigned to the parameter token of the path operation
function.
FastAPI will know that it can use this dependency to define a "security scheme" in the OpenAPI schema
(and the automatic API docs).
Technical Details
FastAPI will know that it can use the class OAuth2PasswordBearer (declared in a dependency) to define the security scheme in
OpenAPI because it inherits from fastapi.security.oauth2.OAuth2 , which in turn inherits from
fastapi.security.base.SecurityBase .
All the security utilities that integrate with OpenAPI (and the automatic API docs) inherit from SecurityBase , that's how FastAPI
can know how to integrate them in OpenAPI.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
What it does
It will go and look in the request for that Authorization header, check if the value is Bearer plus some
token, and will return the token as a str .
If it doesn't see an Authorization header, or the value doesn't have a Bearer token, it will respond with a
401 status code error ( UNAUTHORIZED ) directly.
You don't even have to check if the token exists to return an error. You can be sure that if your function is
executed, it will have a str in that token.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
We are not verifying the validity of the token yet, but that's a start already.
Recap
So, in just 3 or 4 extra lines, you already have some primitive form of security.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Get Current User
In the previous chapter the security system (which is based on the dependency injection system) was
giving the path operation function a token as a str :
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
The same way we use Pydantic to declare bodies, we can use it anywhere else:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
full_name: str | None = None
disabled: bool | None = None
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
get_current_user will have a dependency with the same oauth2_scheme we created before.
The same as we were doing before in the path operation directly, our new dependency
get_current_user will receive a token as a str from the sub-dependency oauth2_scheme :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
def fake_decode_token(token):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
@app.get("/users/me")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
Notice that we declare the type of current_user as the Pydantic model User .
This will help us inside of the function with all the completion and type checks.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
You might remember that request bodies are also declared with Pydantic models.
Here FastAPI won't get confused because you are using Depends .
Check
The way this dependency system is designed allows us to have different dependencies (different "dependables") that all return
a User model.
We are not restricted to having only one dependency that can return that type of data.
Other models
You can now get the current user directly in the path operation functions and deal with the security
mechanisms at the Dependency Injection level, using Depends .
And you can use any model or data for the security requirements (in this case, a Pydantic model User ).
But you are not restricted to using some specific data model, class or type.
Do you want to have an id and email and not have any username in your model? Sure. You can use
these same tools.
Do you want to just have a str ? Or just a dict ? Or a database class model instance directly? It all
works the same way.
You actually don't have users that log in to your application but robots, bots, or other systems, that have
just an access token? Again, it all works the same.
Just use any kind of model, any kind of class, any kind of database that you need for your application.
FastAPI has you covered with the dependency injection system.
Code size
This example might seem verbose. Keep in mind that we are mixing security, data models, utility
functions and path operations in the same file.
And you can make it as complex as you want. And still, have it written only once, in a single place. With
all the flexibility.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
But you can have thousands of endpoints (path operations) using the same security system.
And all of them (or any portion of them that you want) can take the advantage of re-using these
dependencies or any other dependencies you create.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
)
@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
return current_user
Recap
You can now get the current user directly in your path operation function.
We just need to add a path operation for the user/client to actually send the username and password .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Simple OAuth2 with Password and Bearer
Now let's build from the previous chapter and add the missing parts to have a complete security flow.
OAuth2 specifies that when using the "password flow" (that we are using) the client/user must send a
username and password fields as form data.
And the spec says that the fields have to be named like that. So user-name or email wouldn't work.
But don't worry, you can show it as you wish to your final users in the frontend.
And your database models can use any other names you want.
But for the login path operation, we need to use these names to be compatible with the spec (and be
able to, for example, use the integrated API documentation system).
The spec also states that the username and password must be sent as form data (so, no JSON here).
scope
The spec also says that the client can send another form field " scope ".
The form field name is scope (in singular), but it is actually a long string with "scopes" separated by
spaces.
They are normally used to declare specific security permissions, for example:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
OAuth2PasswordRequestForm
First, import OAuth2PasswordRequestForm , and use it as a dependency with Depends in the path
operation for /token :
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
The username .
The password .
An optional grant_type .
Tip
The OAuth2 spec actually requires a field grant_type with a fixed value of password , but OAuth2PasswordRequestForm
doesn't enforce it.
Info
OAuth2PasswordBearer makes FastAPI know that it is a security scheme. So it is added that way to OpenAPI.
But OAuth2PasswordRequestForm is just a class dependency that you could have written yourself, or you could have declared
Form parameters directly.
But as it's a common use case, it is provided by FastAPI directly, just to make it easier.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
The instance of the dependency class OAuth2PasswordRequestForm won't have an attribute scope with the long string
separated by spaces, instead, it will have a scopes attribute with the actual list of strings for each scope sent.
We are not using scopes in this example, but the functionality is there if you need it.
Now, get the user data from the (fake) database, using the username from the form field.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
):
return current_user
At this point we have the user data from our database, but we haven't checked the password.
You should never save plaintext passwords, so, we'll use the (fake) password hashing system.
Password hashing
"Hashing" means: converting some content (a password in this case) into a sequence of bytes (just a
string) that looks like gibberish.
Whenever you pass exactly the same content (exactly the same password) you get exactly the same
gibberish.
But you cannot convert from the gibberish back to the password.
If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes.
So, the thief won't be able to try to use those same passwords in another system (as many users use
the same password everywhere, this would be dangerous).
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
return current_user
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
About **user_dict
UserInDB(**user_dict) means:
Pass the keys and values of the user_dict directly as key-value arguments, equivalent to:
UserInDB(
username = user_dict["username"],
email = user_dict["email"],
full_name = user_dict["full_name"],
disabled = user_dict["disabled"],
hashed_password = user_dict["hashed_password"],
)
Info
For a more complete explanation of **user_dict check back in the documentation for Extra Models ↪.
It should have a token_type . In our case, as we are using "Bearer" tokens, the token type should be "
bearer ".
And it should have an access_token , with a string containing our access token.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
For this simple example, we are going to just be completely insecure and return the same username as
the token.
Tip
In the next chapter, you will see a real secure implementation, with password hashing and JWT tokens.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
hashed_password: str
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
By the spec, you should return a JSON with an access_token and a token_type , the same as in this example.
This is something that you have to do yourself in your code, and make sure you use those JSON keys.
It's almost the only thing that you have to remember to do correctly yourself, to be compliant with the specifications.
Both of these dependencies will just return an HTTP error if the user doesn't exist, or if is inactive.
So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "[email protected]",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def fake_hash_password(password: str):
return "fakehashed" + password
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
Info
The additional header WWW-Authenticate with value Bearer we are returning here is also part of the spec.
Any HTTP (error) status code 401 "UNAUTHORIZED" is supposed to also return a WWW-Authenticate header.
In the case of bearer tokens (our case), the value of that header should be Bearer .
You can actually skip that extra header and it would still work.
Also, there might be tools that expect and use it (now or in the future) and that might be useful for you or your users, now or
in the future.
See it in action
Open the interactive docs: http://127.0.0.1:8000/docs [↪].
Authenticate
User: johndoe
Password: secret
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
After authenticating in the system, you will see it like:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Get your own user data
{
"username": "johndoe",
"email": "[email protected]",
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
"full_name": "John Doe",
"disabled": false,
"hashed_password": "fakehashedsecret"
}
If you click the lock icon and logout, and then try the same operation again, you will get an HTTP 401
error of:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
{
"detail": "Not authenticated"
}
Inactive user
User: alice
Password: secret2
And try to use the operation GET with the path /users/me .
{
"detail": "Inactive user"
}
Recap
You now have the tools to implement a complete security system based on username and password
for your API.
Using these tools, you can make the security system compatible with any database and with any user
or data model.
In the next chapter you'll see how to use a secure password hashing library and JWT tokens.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
OAuth2 with Password (and hashing), Bearer with JWT tokens
Now that we have all the security flow, let's make the application actually secure, using JWT tokens and secure password hashing.
This code is something you can actually use in your application, save the password hashes in your database, etc.
We are going to start from where we left in the previous chapter and increment it.
About JWT
JWT means "JSON Web Tokens".
It's a standard to codify a JSON object in a long dense string without spaces. It looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4f
It is not encrypted, so, anyone could recover the information from the contents.
But it's signed. So, when you receive a token that you emitted, you can verify that you actually emitted it.
That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know that
your system.
After a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. And if the user (or a third part
to change the expiration, you would be able to discover it, because the signatures would not match.
If you want to play with JWT tokens and see how they work, check https://jwt.io [↪].
Install PyJWT
We need to install PyJWT to generate and verify the JWT tokens in Python:
bash
fast →
$ pip install pyjwt
Info
If you are planning to use digital signature algorithms like RSA or ECDSA, you should install the cryptography library dependency pyjwt[crypto] .
You can read more about it in the PyJWT Installation docs [↪].
Password hashing
"Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish.
Whenever you pass exactly the same content (exactly the same password) you get exactly the same gibberish.
But you cannot convert from the gibberish back to the password.
If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous).
Install passlib
PassLib is a great Python package to handle password hashes.
It supports many secure hashing algorithms and utilities to work with them.
bash
fast →
$ pip install "passlib[bc ▋
Tip
With passlib , you could even configure it to be able to read passwords created by Django, a Flask security plug-in or many others.
So, you would be able to, for example, share the same data from a Django application in a database with a FastAPI application. Or gradually migrate a Django application using the same da
And your users would be able to login from your Django app or from your FastAPI app, at the same time.
Create a PassLib "context". This is what will be used to hash and verify passwords.
Tip
The PassLib context also has functionality to use different hashing algorithms, including deprecated old ones only to allow verifying them, etc.
For example, you could use it to read and verify passwords generated by another system (like Django) but hash any new passwords with a different algorithm like Bcrypt.
And another utility to verify if a received password matches the hash stored.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
def get_password_hash(password):
return pwd_context.hash(password)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except InvalidTokenError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
return user
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/users/me/", response_model=User)
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return [{"item_id": "Foo", "owner": current_user.username}]
Note
If you check the new (fake) database fake_users_db , you will see how the hashed password looks like now: "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW" .
Create a random secret key that will be used to sign the JWT tokens.
bash
fast →
$ openssl rand -hex 32 ▋
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And copy the output to the variable SECRET_KEY (don't use the one in the example).
Create a variable ALGORITHM with the algorithm used to sign the JWT token and set it to "HS256" .
Define a Pydantic Model that will be used in the token endpoint for the response.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
def get_password_hash(password):
return pwd_context.hash(password)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/users/me/", response_model=User)
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return [{"item_id": "Foo", "owner": current_user.username}]
Decode the received token, verify it, and return the current user.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def get_password_hash(password):
return pwd_context.hash(password)
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/users/me/", response_model=User)
async def read_users_me(
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return [{"item_id": "Foo", "owner": current_user.username}]
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
class UserInDB(User):
hashed_password: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def get_password_hash(password):
return pwd_context.hash(password)
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/users/me/", response_model=User)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return [{"item_id": "Foo", "owner": current_user.username}]
The JWT specification says that there's a key sub , with the subject of the token.
It's optional to use it, but that's where you would put the user's identification, so we are using it here.
JWT might be used for other things apart from identifying a user and allowing them to perform operations directly on your API.
Then you could add permissions about that entity, like "drive" (for the car) or "edit" (for the blog).
And then, you could give that JWT token to a user (or bot), and they could use it to perform those actions (drive the car, or edit the blog post) without eve
account, just with the JWT token your API generated for that.
Using these ideas, JWT can be used for way more sophisticated scenarios.
In those cases, several of those entities could have the same ID, let's say foo (a user foo , a car foo , and a blog post foo ).
So, to avoid ID collisions, when creating the JWT token for the user, you could prefix the value of the sub key, e.g. with username: . So, in this example, t
have been: username:johndoe .
The important thing to keep in mind is that the sub key should have a unique identifier across the entire application, and it should be a string.
Check it
Run the server and go to the docs: http://127.0.0.1:8000/docs [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Authorize the application the same way as before.
Check
Notice that nowhere in the code is the plaintext password " secret ", we only have the hashed version.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Call the endpoint /users/me/ , you will get the response as:
{
"username": "johndoe",
"email": "[email protected]",
"full_name": "John Doe",
"disabled": false
}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you open the developer tools, you could see how the data sent only includes the token, the password is only sent in the first request to authenticate th
token, but not afterwards:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Note
Notice the header Authorization , with a value that starts with Bearer .
You can use them to add a specific set of permissions to a JWT token.
Then you can give this token to a user directly or a third party, to interact with your API with a set of restrictions.
You can learn how to use them and how they are integrated into FastAPI later in the Advanced User Guide.
Recap
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
With what you have seen up to now, you can set up a secure FastAPI application using standards like OAuth2 and JWT.
In almost any framework handling the security becomes a rather complex subject quite quickly.
Many packages that simplify it a lot have to make many compromises with the data model, database, and available features. And some of these packag
much actually have security flaws underneath.
FastAPI doesn't make any compromise with any database, data model or tool.
It gives you all the flexibility to choose the ones that fit your project the best.
And you can use directly many well maintained and widely used packages like passlib and PyJWT , because FastAPI doesn't require any complex mech
external packages.
But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security.
And you can use and implement secure, standard protocols, like OAuth2 in a relatively simple way.
You can learn more in the Advanced User Guide about how to use OAuth2 "scopes", for a more fine-grained permission system, following these same st
scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc. to authorize third party appl
their APIs on behalf of their users.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Middleware
A "middleware" is a function that works with every request before it is processed by any specific
path operation. And also with every response before returning it.
Then it passes the request to be processed by the rest of the application (by some path
operation).
It then takes the response generated by the application (by some path operation).
Technical Details
If you have dependencies with yield , the exit code will run after the middleware.
If there were any background tasks (documented later), they will run after all the middleware.
Create a middleware
To create a middleware you use the decorator @app.middleware("http") on top of a function.
The request .
This function will pass the request to the corresponding path operation.
You can then modify further the response before returning it.
import time
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
Tip
Keep in mind that custom proprietary headers can be added using the 'X-' prefix [↪].
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS
configurations (CORS (Cross-Origin Resource Sharing) ↪) using the parameter expose_headers documented in
Starlette's CORS docs [↪].
Technical Details
FastAPI provides it as a convenience for you, the developer. But it comes directly from Starlette.
You can add code to be run with the request , before any path operation receives it.
For example, you could add a custom header X-Process-Time containing the time in seconds that it
took to process the request and generate a response:
import time
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Other middlewares
You can later read more about other middlewares in the Advanced User Guide: Advanced
Middleware ↪.
You will read about how to handle CORS with a middleware in the next section.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
CORS (Cross-Origin Resource Sharing)
CORS or "Cross-Origin Resource Sharing" [↪] refers to the situations when a frontend running in a
browser has JavaScript code that communicates with a backend, and the backend is in a different
"origin" than the frontend.
Origin
An origin is the combination of protocol ( http , https ), domain ( myapp.com , localhost ,
localhost.tiangolo.com ), and port ( 80 , 443 , 8080 ).
http://localhost
https://localhost
http://localhost:8080
Even if they are all in localhost , they use different protocols or ports, so, they are different
"origins".
Steps
So, let's say you have a frontend running in your browser at http://localhost:8080 , and its
JavaScript is trying to communicate with a backend running at http://localhost (because we
don't specify a port, the browser will assume the default port 80 ).
Then, the browser will send an HTTP OPTIONS request to the backend, and if the backend sends the
appropriate headers authorizing the communication from this different origin (
http://localhost:8080 ) then the browser will let the JavaScript in the frontend send its request to
the backend.
In this case, it would have to include http://localhost:8080 for the frontend to work correctly.
Wildcards
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It's also possible to declare the list as "*" (a "wildcard") to say that all are allowed.
But that will only allow certain types of communication, excluding everything that involves
credentials: Cookies, Authorization headers like those used with Bearer Tokens, etc.
So, for everything to work correctly, it's better to specify explicitly the allowed origins.
Use CORSMiddleware
You can configure it in your FastAPI application using the CORSMiddleware .
Import CORSMiddleware .
Specific HTTP methods ( POST , PUT ) or all of them with the wildcard "*" .
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def main():
return {"message": "Hello World"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The default parameters used by the CORSMiddleware implementation are restrictive by default, so
you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be
permitted to use them in a Cross-Domain context.
allow_origins - A list of origins that should be permitted to make cross-origin requests. E.g.
['https://example.org', 'https://www.example.org'] . You can use ['*'] to allow any
origin.
allow_methods - A list of HTTP methods that should be allowed for cross-origin requests.
Defaults to ['GET'] . You can use ['*'] to allow all standard methods.
allow_headers - A list of HTTP request headers that should be supported for cross-origin
requests. Defaults to [] . You can use ['*'] to allow all headers. The Accept ,
Accept-Language , Content-Language and Content-Type headers are always allowed for
simple CORS requests [↪].
expose_headers - Indicate any response headers that should be made accessible to the
browser. Defaults to [] .
max_age - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults
to 600 .
These are any OPTIONS request with Origin and Access-Control-Request-Method headers.
In this case the middleware will intercept the incoming request and respond with appropriate CORS
headers, and either a 200 or 400 response for informational purposes.
Simple requests
Any request with an Origin header. In this case the middleware will pass the request through as
normal, but will include appropriate CORS headers on the response.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
More info
For more info about CORS, check the Mozilla CORS documentation [↪].
Technical Details
FastAPI provides several middlewares in fastapi.middleware just as a convenience for you, the developer. But most of
the available middlewares come directly from Starlette.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
SQL (Relational) Databases
Info
The new docs will include Pydantic v2 and will use SQLModel [↪] (which is also based on SQLAlchemy) once it is updated to use
Pydantic v2 as well.
But you can use any relational database that you want.
PostgreSQL
MySQL
SQLite
Oracle
In this example, we'll use SQLite, because it uses a single file and Python has integrated support. So, you
can copy this example and run it as is.
Later, for your production application, you might want to use a database server like PostgreSQL.
Tip
There is an official project generator with FastAPI and PostgreSQL, all based on Docker, including a frontend and more tools:
https://github.com/tiangolo/full-stack-fastapi-postgresql [↪]
Note
Notice that most of the code is the standard SQLAlchemy code you would use with any framework.
ORMs
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
FastAPI works with any database and any style of library to talk to the database.
An ORM has tools to convert ("map") between objects in code and database tables ("relations").
With an ORM, you normally create a class that represents a table in a SQL database, each attribute of the
class represents a column, with a name and a type.
And each instance object of that class represents a row in the database.
For example an object orion_cat (an instance of Pet ) could have an attribute orion_cat.type , for the
column type . And the value of that attribute could be, e.g. "cat" .
These ORMs also have tools to make the connections or relations between tables or entities.
This way, you could also have an attribute orion_cat.owner and the owner would contain the data for this
pet's owner, taken from the table owners.
So, orion_cat.owner.name could be the name (from the name column in the owners table) of this pet's
owner.
And the ORM will do all the work to get the information from the corresponding table owners when you try
to access it from your pet object.
Common ORMs are for example: Django-ORM (part of the Django framework), SQLAlchemy ORM (part of
SQLAlchemy, independent of framework) and Peewee (independent of framework), among others.
Tip
File structure
For these examples, let's say you have a directory named my_super_project that contains a sub-directory
called sql_app with a structure like this:
.
└── sql_app
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
├── __init__.py
├── crud.py
├── database.py
├── main.py
├── models.py
└── schemas.py
The file __init__.py is just an empty file, but it tells Python that sql_app with all its modules (Python
files) is a package.
Install SQLAlchemy
First you need to install SQLAlchemy :
bash
fast →
$ pip install sqlalchemy ▋
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Create a database URL for SQLAlchemy
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
In this example, we are "connecting" to a SQLite database (opening a file with the SQLite database).
The file will be located at the same directory in the file sql_app.db .
If you were using a PostgreSQL database instead, you would just have to uncomment the line:
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
...and adapt it with your database data and credentials (equivalently for MySQL, MariaDB or any other).
Tip
This is the main line that you would have to modify if you wanted to use a different database.
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Base = declarative_base()
Note
The argument:
connect_args={"check_same_thread": False}
...is needed only for SQLite . It's not needed for other databases.
Technical Details
By default SQLite will only allow one thread to communicate with it, assuming that each thread would handle an independent
request.
This is to prevent accidentally sharing the same connection for different things (for different requests).
But in FastAPI, using normal functions ( def ) more than one thread could interact with the database for the same request, so we
need to make SQLite know that it should allow that with connect_args={"check_same_thread": False} .
Also, we will make sure each request gets its own database connection session in a dependency, so there's no need for that
default mechanism.
Each instance of the SessionLocal class will be a database session. The class itself is not a database
session yet.
But once we create an instance of the SessionLocal class, this instance will be the actual database
session.
We name it SessionLocal to distinguish it from the Session we are importing from SQLAlchemy.
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Create a Base class
Later we will inherit from this class to create each of the database models or classes (the ORM models):
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
We will use this Base class we created before to create the SQLAlchemy models.
Tip
SQLAlchemy uses the term "model" to refer to these classes and instances that interact with the database.
But Pydantic also uses the term "model" to refer to something different, the data validation, conversion, and documentation
classes and instances.
class User(Base):
__tablename__ = "users"
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
The __tablename__ attribute tells SQLAlchemy the name of the table to use in the database for each of
these models.
And we pass a SQLAlchemy class "type", as Integer , String , and Boolean , that defines the type in the
database, as an argument.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
class Item(Base):
__tablename__ = "items"
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
This will become, more or less, a "magic" attribute that will contain the values from other tables related to
this one.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
When accessing the attribute items in a User , as in my_user.items , it will have a list of Item
SQLAlchemy models (from the items table) that have a foreign key pointing to this record in the users
table.
When you access my_user.items , SQLAlchemy will actually go and fetch the items from the database in
the items table and populate them here.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And when accessing the attribute owner in an Item , it will contain a User SQLAlchemy model from the
users table. It will use the owner_id attribute/column with its foreign key to know which record to get
from the users table.
Tip
To avoid confusion between the SQLAlchemy models and the Pydantic models, we will have the file models.py with the
SQLAlchemy models, and the file schemas.py with the Pydantic models.
These Pydantic models define more or less a "schema" (a valid data shape).
Create an ItemBase and UserBase Pydantic models (or let's say "schemas") to have common attributes
while creating or reading data.
And create an ItemCreate and UserCreate that inherit from them (so they will have the same attributes),
plus any additional data (attributes) needed for creation.
So, the user will also have a password when creating it.
But for security, the password won't be in other Pydantic models, for example, it won't be sent from the API
when reading a user.
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
Notice that SQLAlchemy models define attributes using = , and pass the type as a parameter to Column ,
like in:
name = Column(String)
while Pydantic models declare the types using : , the new type annotation syntax/type hints:
name: str
Keep these in mind, so you don't get confused when using = and : with them.
Now create Pydantic models (schemas) that will be used when reading data, when returning it from the
API.
For example, before creating an item, we don't know what will be the ID assigned to it, but when reading it
(when returning it from the API) we will already know its ID.
The same way, when reading a user, we can now declare that items will contain the items that belong to
this user.
Not only the IDs of those items, but all the data that we defined in the Pydantic model for reading items:
Item .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
Tip
Notice that the User , the Pydantic model that will be used when reading a user (returning it from the API) doesn't include the
password .
Now, in the Pydantic models for reading, Item and User , add an internal Config class.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
Tip
orm_mode = True
Pydantic's orm_mode will tell the Pydantic model to read the data even if it is not a dict , but an ORM
model (or any other arbitrary object with attributes).
This way, instead of only trying to get the id value from a dict , as in:
id = data["id"]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
it will also try to get it from an attribute, as in:
id = data.id
And with this, the Pydantic model is compatible with ORMs, and you can just declare it in the
response_model argument in your path operations.
You will be able to return a database model and it will read the data from it.
That means, for example, that they don't fetch the data for relationships from the database unless you try
to access the attribute that would contain that data.
current_user.items
would make SQLAlchemy go to the items table and get the items for this user, but not before.
Without orm_mode , if you returned a SQLAlchemy model from your path operation, it wouldn't include the
relationship data.
But with ORM mode, as Pydantic itself will try to access the data it needs from attributes (instead of
assuming a dict ), you can declare the specific data you want to return and it will be able to go and get it,
even from ORMs.
CRUD utils
Now let's see the file sql_app/crud.py .
In this file we will have reusable functions to interact with the data in the database.
Read data
Import Session from sqlalchemy.orm , this will allow you to declare the type of the db parameters and
have better type checks and completion in your functions.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Import models (the SQLAlchemy models) and schemas (the Pydantic models / schemas).
Tip
By creating functions that are only dedicated to interacting with the database (get a user or an item) independent of your path
operation function, you can more easily reuse them in multiple parts and also add unit tests for them.
Create data
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Now create utility functions to create data.
commit the changes to the database (so that they are saved).
refresh your instance (so that it contains any new data from the database, like the generated ID).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
In Pydantic v1 the method was called .dict() , it was deprecated (but still supported) in Pydantic v2, and renamed to
.model_dump() .
The examples here use .dict() for compatibility with Pydantic v1, but you should use .model_dump() instead if you can use
Pydantic v2.
Tip
The SQLAlchemy model for User contains a hashed_password that should contain a secure hashed version of the password.
But as what the API client provides is the original password, you need to extract it and generate the hashed password in your
application.
And then pass the hashed_password argument with the value to save.
Warning
In a real life application you would need to hash the password and never save them in plaintext.
Tip
Instead of passing each of the keyword arguments to Item and reading each one of them from the Pydantic model, we are
generating a dict with the Pydantic model's data with:
item.dict()
and then we are passing the dict 's key-value pairs as the keyword arguments to the SQLAlchemy Item , with:
Item(**item.dict())
And then we pass the extra keyword argument owner_id that is not provided by the Pydantic model, with:
Item(**item.dict(), owner_id=user_id)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Python 3.9+ Python 3.8+
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Alembic Note
Normally you would probably initialize your database (create tables, etc) with Alembic [↪].
And you would also use Alembic for "migrations" (that's its main job).
A "migration" is the set of steps needed whenever you change the structure of your SQLAlchemy models,
add a new attribute, etc. to replicate those changes in the database, add a new column, a new table, etc.
You can find an example of Alembic in a FastAPI project in the templates from Project Generation -
Template ↪. Specifically in the alembic directory in the source code [↪].
Create a dependency
Now use the SessionLocal class we created in the sql_app/database.py file to create a dependency.
We need to have an independent database session/connection ( SessionLocal ) per request, use the same
session through all the request and then close it after the request is finished.
And then a new session will be created for the next request.
For that, we will create a new dependency with yield , as explained before in the section about
Dependencies with yield ↪.
Our dependency will create a new SQLAlchemy SessionLocal that will be used in a single request, and
then close it once the request is finished.
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Info
We put the creation of the SessionLocal() and handling of the requests in a try block.
This way we make sure the database session is always closed after the request. Even if there was an exception while processing
the request.
But you can't raise another exception from the exit code (after yield ). See more in Dependencies with yield and
HTTPException ↪
And then, when using the dependency in a path operation function, we declare it with the type Session we
imported directly from SQLAlchemy.
This will then give us better editor support inside the path operation function, because the editor will know
that the db parameter is of type Session :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Technical Details
The parameter db is actually of type SessionLocal , but this class (created with sessionmaker() ) is a "proxy" of a SQLAlchemy
Session , so, the editor doesn't really know what methods are provided.
But by declaring the type as Session , the editor now can know the available methods ( .add() , .query() , .commit() , etc) and
can provide better support (like completion). The type declaration doesn't affect the actual object.
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
We are creating the database session before each request in the dependency with yield , and then closing
it afterwards.
And then we can create the required dependency in the path operation function, to get that session directly.
With that, we can just call crud.get_user directly from inside of the path operation function and use that
session.
Tip
Notice that the values you return are SQLAlchemy models, or lists of SQLAlchemy models.
But as all the path operations have a response_model with Pydantic models / schemas using orm_mode , the data declared in
your Pydantic models will be extracted from them and returned to the client, with all the normal filtering and validation.
Tip
Also notice that there are response_models that have standard Python types like List[schemas.Item] .
But as the content/parameter of that List is a Pydantic model with orm_mode , the data will be retrieved and returned to the
client as normally, without problems.
Here we are using SQLAlchemy code inside of the path operation function and in the dependency, and, in
turn, it will go and communicate with an external database.
But as SQLAlchemy doesn't have compatibility for using await directly, as would be with something like:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
user = db.query(User).first()
Then we should declare the path operation functions and the dependency without async def , just with a
normal def , as:
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
...
Info
If you need to connect to your relational database asynchronously, see Async SQL (Relational) Databases ↪.
If you are curious and have a deep technical knowledge, you can check the very technical details of how this async def vs def
is handled in the Async ↪ docs.
Migrations
Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with
FastAPI, we could integrate database migrations with Alembic [↪] directly.
And as the code related to SQLAlchemy and the SQLAlchemy models lives in separate independent files,
you would even be able to perform the migrations with Alembic without having to install FastAPI, Pydantic,
or anything else.
The same way, you would be able to use the same SQLAlchemy models and utilities in other parts of your
code that are not related to FastAPI.
For example, in a background task worker with Celery [↪], RQ [↪], or ARQ [↪].
sql_app/database.py :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
sql_app/models.py :
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
sql_app/schemas.py :
class ItemBase(BaseModel):
title: str
description: str | None = None
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
sql_app/crud.py :
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
sql_app/main.py :
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Check it
You can copy this code and use it as is.
Info
In fact, the code shown here is part of the tests. As most of the code in these docs.
bash
And you will be able to interact with your FastAPI application, reading data from a real database:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Interact with the database directly
If you want to explore the SQLite database (file) directly, independently of FastAPI, to debug its contents,
add tables, columns, records, modify data, etc. you can use DB Browser for SQLite [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You can also use an online SQLite browser like SQLite Viewer [↪] or ExtendsClass [↪].
A "middleware" is basically a function that is always executed for each request, with some code executed
before, and some code executed after the endpoint function.
Create a middleware
The middleware we'll add (just a function) will create a new SQLAlchemy SessionLocal for each request,
add it to the request and then close it once the request is finished.
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
response = Response("Internal server error", status_code=500)
try:
request.state.db = SessionLocal()
response = await call_next(request)
finally:
request.state.db.close()
return response
# Dependency
def get_db(request: Request):
return request.state.db
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
We put the creation of the SessionLocal() and handling of the requests in a try block.
This way we make sure the database session is always closed after the request. Even if there was an exception while processing
the request.
About request.state
request.state is a property of each Request object. It is there to store arbitrary objects attached to the
request itself, like the database session in this case. You can read more about it in
Starlette's docs about Request state [↪].
For us in this case, it helps us ensure a single database session is used through all the request, and then
closed afterwards (in the middleware).
Adding a middleware here is similar to what a dependency with yield does, with some differences:
If there is code in it that has to "wait" for the network, it could "block" your application there and
degrade performance a bit.
Although it's probably not very problematic here with the way SQLAlchemy works.
But if you added more code to the middleware that had a lot of I/O waiting, it could then be
problematic.
Even when the path operation that handles that request didn't need the DB.
Tip
It's probably better to use dependencies with yield when they are enough for the use case.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
A previous version of this tutorial only had the examples with a middleware and there are probably several applications using the
middleware for database session management.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Bigger Applications - Multiple Files
If you are building an application or a web API, it's rarely the case that you can put everything on a single
file.
FastAPI provides a convenience tool to structure your application while keeping all the flexibility.
Info
If you come from Flask, this would be the equivalent of Flask's Blueprints.
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
Tip
This is what allows importing code from one file into another.
The app directory contains everything. And it has an empty file app/__init__.py , so it is a "Python
package" (a collection of "Python modules"): app .
It contains an app/main.py file. As it is inside a Python package (a directory with a file __init__.py ),
it is a "module" of that package: app.main .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
There's a subdirectory app/routers/ with another file __init__.py , so it's a "Python subpackage":
app.routers .
There's also a subdirectory app/internal/ with another file __init__.py , so it's another "Python
subpackage": app.internal .
Package app
app/__init__.py
Module app.main
app/main.py Subpackage app.routers Subpackage app.internal
app/routers/__init__.py app/internal/__init__.py
Module app.routers.users
app/routers/users.py
.
├── app # "app" is a Python package
│ ├── __init__.py # this file makes "app" a "Python package"
│ ├── main.py # "main" module, e.g. import app.main
│ ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│ └── routers # "routers" is a "Python subpackage"
│ │ ├── __init__.py # makes "routers" a "Python subpackage"
│ │ ├── items.py # "items" submodule, e.g. import app.routers.items
│ │ └── users.py # "users" submodule, e.g. import app.routers.users
│ └── internal # "internal" is a "Python subpackage"
│ ├── __init__.py # makes "internal" a "Python subpackage"
│ └── admin.py # "admin" submodule, e.g. import app.internal.admin
APIRouter
Let's say the file dedicated to handling just users is the submodule at /app/routers/users.py .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
You want to have the path operations related to your users separated from the rest of the code, to keep it
organized.
But it's still part of the same FastAPI application/web API (it's part of the same "Python Package").
You can create the path operations for that module using APIRouter .
Import APIRouter
You import it and create an "instance" the same way you would with the class FastAPI :
app/routers/users.py
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Use it the same way you would use the FastAPI class:
app/routers/users.py
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Tip
In this example, the variable is called router , but you can name it however you want.
We are going to include this APIRouter in the main FastAPI app, but first, let's check the dependencies
and another APIRouter .
Dependencies
We see that we are going to need some dependencies used in several places of the application.
app/dependencies.py
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
But in real cases you will get better results using the integrated Security utilities ↪.
/items/
/items/{item_id}
We know all the path operations in this module have the same:
Extra responses .
So, instead of adding all that to each path operation, we can add it to the APIRouter .
app/routers/items.py
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
@router.get("/")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
As the path of each path operation has to start with / , like in:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
We can also add a list of tags and extra responses that will be applied to all the path operations included
in this router.
And we can add a list of dependencies that will be added to all the path operations in the router and will be
executed/solved for each request made to them.
Tip
Note that, much like dependencies in path operation decorators ↪, no value will be passed to your path operation function.
/items/
/items/{item_id}
...as we intended.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
They will be marked with a list of tags that contain a single string "items" .
These "tags" are especially useful for the automatic interactive documentation systems (using
OpenAPI).
All these path operations will have the list of dependencies evaluated/executed before them.
If you also declare dependencies in a specific path operation, they will be executed too.
The router dependencies are executed first, then the dependencies in the decorator ↪, and then
the normal parameter dependencies.
Tip
Having dependencies in the APIRouter can be used, for example, to require authentication for a whole group of path operations.
Even if the dependencies are not added individually to each one of them.
Check
The prefix , tags , responses , and dependencies parameters are (as in many other cases) just a feature from FastAPI to help
you avoid code duplication.
And we need to get the dependency function from the module app.dependencies , the file
app/dependencies.py .
app/routers/items.py
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Tip
If you know perfectly how imports work, continue to the next section below.
would mean:
Starting in the same package that this module (the file app/routers/items.py ) lives in (the directory
app/routers/ )...
But that file doesn't exist, our dependencies are in a file at app/dependencies.py .
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Package app
app/__init__.py
Module app.main
app/main.py Subpackage app.routers Subpackage app.internal
app/routers/__init__.py app/internal/__init__.py
Module app.routers.users
app/routers/users.py
mean:
Starting in the same package that this module (the file app/routers/items.py ) lives in (the directory
app/routers/ )...
and in there, find the module dependencies (the file at app/dependencies.py )...
Starting in the same package that this module (the file app/routers/items.py ) lives in (the directory
app/routers/ )...
then go to the parent of that package (there's no parent package, app is the top level 😱)...
and in there, find the module dependencies (the file at app/dependencies.py )...
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
and from it, import the function get_token_header .
That would refer to some package above app/ , with its own file __init__.py , etc. But we don't have that.
So, that would throw an error in our example. 🚨
But now you know how it works, so you can use relative imports in your own apps no matter how complex
they are. 🤓
Add some custom tags , responses , and dependencies
We are not adding the prefix /items nor the tags=["items"] to each path operation because we added
them to the APIRouter .
But we can still add more tags that will be applied to a specific path operation, and also some extra
responses specific to that path operation:
app/routers/items.py
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Tip
This last path operation will have the combination of tags: ["items", "custom"] .
And it will also have both responses in the documentation, one for 404 and one for 403 .
This will be the main file in your application that ties everything together.
And as most of your logic will now live in its own specific module, the main file will be quite simple.
Import FastAPI
And we can even declare global dependencies ↪ that will be combined with the dependencies for each
APIRouter :
app/main.py
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
app/main.py
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
As the files app/routers/users.py and app/routers/items.py are submodules that are part of the same
Python package app , we can use a single dot . to import them using "relative imports".
The section:
means:
Starting in the same package that this module (the file app/main.py ) lives in (the directory app/ )...
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
and from it, import the submodule items (the file at app/routers/items.py ) and users (the file at
app/routers/users.py )...
The module items will have a variable router ( items.router ). This is the same one we created in the
file app/routers/items.py , it's an APIRouter object.
Info
To learn more about Python Packages and Modules, read the official Python documentation about Modules [↪].
We are importing the submodule items directly, instead of importing just its variable router .
This is because we also have another variable named router in the submodule users .
the router from users would overwrite the one from items and we wouldn't be able to use them at the
same time.
So, to be able to use both of them in the same file, we import the submodules directly:
app/main.py
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Now, let's include the router s from the submodules users and items :
app/main.py
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Info
With app.include_router() we can add each APIRouter to the main FastAPI application.
It will include all the routes from that router as part of it.
Technical Details
It will actually internally create a path operation for each path operation that was declared in the APIRouter .
So, behind the scenes, it will actually work as if everything was the same single app.
Check
Now, let's imagine your organization gave you the app/internal/admin.py file.
It contains an APIRouter with some admin path operations that your organization shares between several
projects.
For this example it will be super simple. But let's say that because it is shared with other projects in the
organization, we cannot modify it and add a prefix , dependencies , tags , etc. directly to the APIRouter :
app/internal/admin.py
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
But we still want to set a custom prefix when including the APIRouter so that all its path operations
start with /admin , we want to secure it with the dependencies we already have for this project, and we
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
want to include tags and responses .
We can declare all that without having to modify the original APIRouter by passing those parameters to
app.include_router() :
app/main.py
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
That way, the original APIRouter will keep unmodified, so we can still share that same
app/internal/admin.py file with other projects in the organization.
The result is that in our app, each of the path operations from the admin module will have:
So, for example, other projects could use the same APIRouter with a different authentication method.
app = FastAPI(dependencies=[Depends(get_query_token)])
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
and it will work correctly, together with all the other path operations added with app.include_router() .
Note: this is a very technical detail that you probably can just skip.
The APIRouter s are not "mounted", they are not isolated from the rest of the application.
This is because we want to include their path operations in the OpenAPI schema and the user interfaces.
As we cannot just isolate them and "mount" them independently of the rest, the path operations are "cloned" (re-created), not
included directly.
bash
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
And open the docs at http://127.0.0.1:8000/docs [↪].
You will see the automatic API docs, including the paths from all the submodules, using the correct paths
(and prefixes) and the correct tags:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Include the same router multiple times with different prefix
You can also use .include_router() multiple times with the same router using different prefixes.
This could be useful, for example, to expose the same API under different prefixes, e.g. /api/v1 and
/api/latest .
This is an advanced usage that you might not really need, but it's there in case you do.
router.include_router(other_router)
Make sure you do it before including router in the FastAPI app, so that the path operations from
other_router are also included.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Background Tasks
This is useful for operations that need to happen after a request, but that the client doesn't really have to be
waiting for the operation to complete before receiving the response.
As connecting to an email server and sending an email tends to be "slow" (several seconds), you
can return the response right away and send the email notification in the background.
Processing data:
For example, let's say you receive a file that must go through a slow process, you can return a
response of "Accepted" (HTTP 202) and process it in the background.
Using BackgroundTasks
First, import BackgroundTasks and define a parameter in your path operation function with a type
declaration of BackgroundTasks :
app = FastAPI()
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
FastAPI will create the object of type BackgroundTasks for you and pass it as that parameter.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It is just a standard function that can receive parameters.
It can be an async def or normal def function, FastAPI will know how to handle it correctly.
In this case, the task function will write to a file (simulating sending an email).
And as the write operation doesn't use async and await , we define the function with normal def :
app = FastAPI()
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
app = FastAPI()
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
Any sequence of arguments that should be passed to the task function in order ( email ).
Any keyword arguments that should be passed to the task function ( message="some notification" ).
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Dependency Injection
Using BackgroundTasks also works with the dependency injection system, you can declare a parameter of
type BackgroundTasks at multiple levels: in a path operation function, in a dependency (dependable), in a
sub-dependency, etc.
FastAPI knows what to do in each case and how to reuse the same object, so that all the background tasks
are merged together and are run in the background afterwards:
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
app = FastAPI()
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
):
message = f"message to {email}\n"
background_tasks.add_task(write_log, message)
return {"message": "Message sent"}
In this example, the messages will be written to the log.txt file after the response is sent.
If there was a query in the request, it will be written to the log in a background task.
And then another background task generated at the path operation function will write a message using the
email path parameter.
Technical Details
The class BackgroundTasks comes directly from starlette.background [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It is imported/included directly into FastAPI so that you can import it from fastapi and avoid accidentally
importing the alternative BackgroundTask (without the s at the end) from starlette.background .
By only using BackgroundTasks (and not BackgroundTask ), it's then possible to use it as a path operation
function parameter and have FastAPI handle the rest for you, just like when using the Request object
directly.
It's still possible to use BackgroundTask alone in FastAPI, but you have to create the object in your code
and return a Starlette Response including it.
You can see more details in Starlette's official docs for Background Tasks [↪].
Caveat
If you need to perform heavy background computation and you don't necessarily need it to be run by the
same process (for example, you don't need to share memory, variables, etc), you might benefit from using
other bigger tools like Celery [↪].
They tend to require more complex configurations, a message/job queue manager, like RabbitMQ or Redis,
but they allow you to run background tasks in multiple processes, and especially, in multiple servers.
To see an example, check the Project Generators ↪, they all include Celery already configured.
But if you need to access variables and objects from the same FastAPI app, or you need to perform small
background tasks (like sending an email notification), you can simply just use BackgroundTasks .
Recap
Import and use BackgroundTasks with parameters in path operation functions and dependencies to add
background tasks.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Metadata and Docs URLs
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Parameter Type Description
summary str A short summary of the API. Available since OpenAPI 3.1.0, FastAPI 0.99.0.
version string The version of the API. This is the version of your own application, not of OpenAPI.
For example 2.5.0 .
terms_of_s str A URL to the Terms of Service for the API. If provided, this has to be a URL.
ervice
contact dict The contact information for the exposed API. It can contain several fields.
contact fields
license_in dict The license information for the exposed API. It can contain several fields.
fo
license_info fields
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Available since OpenAPI 3.1.0, FastAPI 0.99.0.
description = """
ChimichangApp API helps you do awesome stuff. 🚀
## Items
## Users
app = FastAPI(
title="ChimichangApp",
description=description,
summary="Deadpool's favorite app. Nuff said.",
version="0.0.1",
terms_of_service="http://example.com/terms/",
contact={
"name": "Deadpoolio the Amazing",
"url": "http://x-force.example.com/contact/",
"email": "[email protected]",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
@app.get("/items/")
async def read_items():
return [{"name": "Katana"}]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
You can write Markdown in the description field and it will be rendered in the output.
With this configuration, the automatic API docs would look like:
License identifier
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Since OpenAPI 3.1.0 and FastAPI 0.99.0, you can also set the license_info with an identifier
instead of a url .
For example:
description = """
ChimichangApp API helps you do awesome stuff. 🚀
## Items
## Users
app = FastAPI(
title="ChimichangApp",
description=description,
summary="Deadpool's favorite app. Nuff said.",
version="0.0.1",
terms_of_service="http://example.com/terms/",
contact={
"name": "Deadpoolio the Amazing",
"url": "http://x-force.example.com/contact/",
"email": "[email protected]",
},
license_info={
"name": "Apache 2.0",
"identifier": "MIT",
},
)
@app.get("/items/")
async def read_items():
return [{"name": "Katana"}]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
It takes a list containing one dictionary for each tag.
name (required): a str with the same tag name you use in the tags parameter in your path
operations and APIRouter s.
description : a str with a short description for the tag. It can have Markdown and will be
shown in the docs UI.
url (required): a str with the URL for the external documentation.
Let's try that in an example with tags for users and items .
Create metadata for your tags and pass it to the openapi_tags parameter:
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://fastapi.tiangolo.com/",
},
},
]
app = FastAPI(openapi_tags=tags_metadata)
@app.get("/users/", tags=["users"])
async def get_users():
return [{"name": "Harry"}, {"name": "Ron"}]
@app.get("/items/", tags=["items"])
async def get_items():
return [{"name": "wand"}, {"name": "flying broom"}]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Notice that you can use Markdown inside of the descriptions, for example "login" will be shown in
bold (login) and "fancy" will be shown in italics (fancy).
Tip
You don't have to add metadata for all the tags that you use.
Use the tags parameter with your path operations (and APIRouter s) to assign them to different
tags:
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://fastapi.tiangolo.com/",
},
},
]
app = FastAPI(openapi_tags=tags_metadata)
@app.get("/users/", tags=["users"])
async def get_users():
return [{"name": "Harry"}, {"name": "Ron"}]
@app.get("/items/", tags=["items"])
async def get_items():
return [{"name": "wand"}, {"name": "flying broom"}]
Info
Order of tags
The order of each tag metadata dictionary also defines the order shown in the docs UI.
For example, even though users would go after items in alphabetical order, it is shown before
them, because we added their metadata as the first dictionary in the list.
OpenAPI URL
By default, the OpenAPI schema is served at /openapi.json .
app = FastAPI(openapi_url="/api/v1/openapi.json")
@app.get("/items/")
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
async def read_items():
return [{"name": "Foo"}]
If you want to disable the OpenAPI schema completely you can set openapi_url=None , that will
also disable the documentation user interfaces that use it.
Docs URLs
You can configure the two documentation user interfaces included:
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Static Files
You can serve static files automatically from a directory using StaticFiles .
Use StaticFiles
Import StaticFiles .
app = FastAPI()
Technical Details
FastAPI provides the same starlette.staticfiles as fastapi.staticfiles just as a convenience for you, the
developer. But it actually comes directly from Starlette.
What is "Mounting"
"Mounting" means adding a complete "independent" application in a specific path, that then takes
care of handling all the sub-paths.
This is different from using an APIRouter as a mounted application is completely independent. The
OpenAPI and docs from your main application won't include anything from the mounted application,
etc.
You can read more about this in the Advanced User Guide ↪.
Details
The first "/static" refers to the sub-path this "sub-application" will be "mounted" on. So, any path
that starts with "/static" will be handled by it.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The directory="static" refers to the name of the directory that contains your static files.
All these parameters can be different than " static ", adjust them with the needs and specific details
of your own application.
More info
For more details and options check Starlette's docs about Static Files [↪].
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Testing
It is based on HTTPX [↪], which in turn is designed based on Requests, so it's very familiar and intuitive.
With it, you can use pytest [↪] directly with FastAPI.
Using TestClient
Info
Import TestClient .
Create functions with a name that starts with test_ (this is standard pytest conventions).
Use the TestClient object the same way as you do with httpx .
Write simple assert statements with the standard Python expressions that you need to check (again,
standard pytest ).
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tip
Notice that the testing functions are normal def , not async def .
And the calls to the client are also normal calls, not using await .
Technical Details
FastAPI provides the same starlette.testclient as fastapi.testclient just as a convenience for you, the developer. But it
comes directly from Starlette.
Tip
If you want to call async functions in your tests apart from sending requests to your FastAPI application (e.g. asynchronous
database functions), have a look at the Async Tests ↪ in the advanced tutorial.
Separating tests
In a real application, you probably would have your tests in a different file.
And your FastAPI application might also be composed of several files/modules, etc.
.
├── app
│ ├── __init__.py
│ └── main.py
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Testing file
Then you could have a file test_main.py with your tests. It could live on the same Python package (the
same directory with a __init__.py file):
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
Because this file is in the same package, you can use relative imports to import the object app from the
main module ( main.py ):
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
...and have the code for the tests just like before.
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
Let's say that now the file main.py with your FastAPI app has some other path operations.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Both path operations require an X-Token header.
Python 3.10+ Python 3.9+ Python 3.8+ Python 3.10+ non-Annotated Python 3.8+ non-Annotated
fake_secret_token = "coneofsilence"
fake_db = {
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}
app = FastAPI()
class Item(BaseModel):
id: str
title: str
description: str | None = None
@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]
@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=400, detail="Item already exists")
fake_db[item.id] = item
return item
client = TestClient(app)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
def test_read_item():
response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "There goes my hero",
}
def test_read_item_bad_token():
response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
assert response.status_code == 400
assert response.json() == {"detail": "Invalid X-Token header"}
def test_read_nonexistent_item():
response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
def test_create_item():
response = client.post(
"/items/",
headers={"X-Token": "coneofsilence"},
json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
)
assert response.status_code == 200
assert response.json() == {
"id": "foobar",
"title": "Foo Bar",
"description": "The Foo Barters",
}
def test_create_item_bad_token():
response = client.post(
"/items/",
headers={"X-Token": "hailhydra"},
json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
)
assert response.status_code == 400
assert response.json() == {"detail": "Invalid X-Token header"}
def test_create_existing_item():
response = client.post(
"/items/",
headers={"X-Token": "coneofsilence"},
json={
"id": "foo",
"title": "The Foo ID Stealers",
"description": "There goes my stealer",
},
)
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
assert response.status_code == 409
assert response.json() == {"detail": "Item already exists"}
Whenever you need the client to pass information in the request and you don't know how to, you can search
(Google) how to do it in httpx , or even how to do it with requests , as HTTPX's design is based on
Requests' design.
E.g.:
To pass a JSON body, pass a Python object (e.g. a dict ) to the parameter json .
If you need to send Form Data instead of JSON, use the data parameter instead.
For more information about how to pass data to the backend (using httpx or the TestClient ) check the
HTTPX documentation [↪].
Info
Note that the TestClient receives data that can be converted to JSON, not Pydantic models.
If you have a Pydantic model in your test and you want to send its data to the application during testing, you can use the
jsonable_encoder described in JSON Compatible Encoder ↪.
Run it
After that, you just need to install pytest :
bash
fast →
$ pip install pytest
███ 8%
It will detect the files and tests automatically, execute them, and report the results back to you.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
bash
fast →
$ pytest
█████████████ 33%
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Debugging
You can connect the debugger in your editor, for example with Visual Studio Code or PyCharm.
Call uvicorn
In your FastAPI application, import and run uvicorn directly:
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
a = "a"
b = "b" + a
return {"hello world": b}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
The main purpose of the __name__ == "__main__" is to have some code that is executed when your file is
called with:
bash
$ python myapp.py
restart ↻
but is not called when another file imports it, like in:
More details
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
If you run it with:
bash
$ python myapp.py
restart ↻
then the internal variable __name__ in your file, created automatically by Python, will have as value the
string "__main__" .
will run.
in that case, the automatically created variable inside of myapp.py will not have the variable __name__
with a value of "__main__" .
Info
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Because you are running the Uvicorn server directly from your code, you can call your Python program (your
FastAPI application) directly from the debugger.
"Add configuration...".
Select "Python"
Run the debugger with the option " Python: Current File (Integrated Terminal) ".
It will then start the server with your FastAPI code, stop at your breakpoints, etc.
It will then start the server with your FastAPI code, stop at your breakpoints, etc.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Here's how it might look:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF