Skip to content

Commit ea4f41c

Browse files
committed
cache decorator created
1 parent b49e5ee commit ea4f41c

File tree

10 files changed

+450
-46
lines changed

10 files changed

+450
-46
lines changed

README.md

Lines changed: 177 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,61 @@
11
# FastAPI-boilerplate
22
>A template to speed your FastAPI development up.
33
4-
## About
4+
## 0. About
55
**FastAPI boilerplate** creates an extendable async API using FastAPI, Pydantic V2, SQLAlchemy 2.0 and PostgreSQL:
66
- [`FastAPI`](https://fastapi.tiangolo.com): modern Python web framework for building APIs
7-
- [`Pydantic V2`](https://docs.pydantic.dev/2.4/): the most widely used data validation library for Python now rewritten in Rust [`(5x to 50x speed improvement)`](https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/)
7+
- [`Pydantic V2`](https://docs.pydantic.dev/2.4/): the most widely used data validation library for Python, now rewritten in Rust [`(5x to 50x speed improvement)`](https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/)
88
- [`SQLAlchemy 2.0`](https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html): Python SQL toolkit and Object Relational Mapper
99
- [`PostgreSQL`](https://www.postgresql.org): The World's Most Advanced Open Source Relational Database
1010

11-
## Features
11+
## 1. Features
1212
- Fully async
1313
- Pydantic V2 and SQLAlchemy 2.0
1414
- User authentication with JWT
1515
- Easily extendable
1616
- Flexible
1717

18+
### 1.1 To do
19+
- [ ] Redis cache
20+
- [ ] Google SSO
21+
- [ ] Arq job queues
22+
23+
## 2. Contents
24+
0. [About](#0-about)
25+
1. [Features](#1-features)
26+
1. [To do](#11-to-do)
27+
2. [Contents](#2-contents)
28+
3. [Usage](#3-usage)
29+
4. [Requirements](#4-requirements)
30+
1. [Packages](#41-packages)
31+
2. [Environment Variables](#42-environment-variables)
32+
5. [Running PostgreSQL with docker](#5-running-postgresql-with-docker)
33+
6. [Running the api](#6-running-the-api)
34+
7. [Creating the first superuser](#7-creating-the-first-superuser)
35+
8. [Database Migrations](#8-database-migrations)
36+
9. [Extending](#9-extending)
37+
1. [Database Model](#91-database-model)
38+
2. [SQLAlchemy Models](#92-sqlalchemy-model)
39+
3. [Pydantic Schemas](#93-pydantic-schemas)
40+
4. [Alembic Migrations](#94-alembic-migration)
41+
5. [CRUD](#95-crud)
42+
6. [Routes](#96-routes)
43+
7. [Running](#97-running)
44+
10. [Testing](#10-testing)
45+
11. [Contributing](#11-contributing)
46+
12. [References](#12-references)
47+
13. [License](#13-license)
48+
14. [Contact](#14-contact)
49+
1850
___
19-
# Usage
20-
## Start by cloning the repository
51+
## 3. Usage
52+
Start by cloning the repository
2153
```sh
2254
git clone https://github.com/igormagalhaesr/FastAPI-boilerplate
2355
```
2456
___
25-
## Requirements
26-
### Packages
57+
## 4. Requirements
58+
### 4.1 Packages
2759
Then install poetry:
2860
```sh
2961
pip install poetry
@@ -34,7 +66,7 @@ In the **src** directory, run to install required packages:
3466
poetry install
3567
```
3668

37-
### Environment Variables
69+
### 4.2 Environment Variables
3870
Then create a .env file:
3971
```sh
4072
touch .env
@@ -85,7 +117,7 @@ ADMIN_PASSWORD="your_password"
85117
```
86118

87119
___
88-
## Running PostgreSQL with docker:
120+
## 5. Running PostgreSQL with docker:
89121
Install docker if you don't have it yet, then run:
90122
```sh
91123
docker pull postgres
@@ -114,24 +146,24 @@ docker run -d \
114146
[`If you didn't create the .env variables yet, click here.`](#environment-variables)
115147

116148
___
117-
## Running the api
149+
## 6. Running the api
118150
While in the **src** folder, run to start the application with uvicorn server:
119151
```sh
120152
poetry run uvicorn app.main:app --reload
121153
```
122154

123155
___
124-
## Creating the first superuser:
156+
## 7. Creating the first superuser:
125157
While in the **src** folder, run (after you started the application at least once to create the tables):
126158
```sh
127159
poetry run python -m scripts.create_first_superuser
128160
```
129161

130162
___
131-
## Database Migrations
163+
## 8. Database Migrations
132164
Migrations done via [Alembic](https://alembic.sqlalchemy.org/en/latest/):
133165

134-
Whenever you change somethin in the database, in the **src** directory, run to create the script:
166+
Whenever you change something in the database, in the **src** directory, run to create the script:
135167
```sh
136168
poetry run alembic revision --autogenerate
137169
```
@@ -142,7 +174,130 @@ poetry run alembic upgrade head
142174
```
143175

144176
___
145-
## Testing
177+
## 9. Extending
178+
### 9.1 Database Model
179+
Create the new entities and relationships and add them to the model
180+
![diagram](https://user-images.githubusercontent.com/43156212/274053323-31bbdb41-15bf-45f2-8c8e-0b04b71c5b0b.png)
181+
182+
### 9.2 SQLAlchemy Model
183+
Inside **app/models**, create a new **entity.py** for each new entity (replacing entity with the name) and define the attributes according to [SQLAlchemy 2.0 standards](https://docs.sqlalchemy.org/en/20/orm/mapping_styles.html#orm-mapping-styles):
184+
```python
185+
from sqlalchemy import String, DateTime
186+
from sqlalchemy.orm import Mapped, mapped_column, relationship
187+
188+
from app.core.database import Base
189+
190+
class Entity(Base):
191+
__tablename__ = "entity"
192+
193+
id: Mapped[int] = mapped_column(
194+
"id", autoincrement=True, nullable=False, unique=True, primary_key=True, init=False
195+
)
196+
name: Mapped[str] = mapped_column(String(30))
197+
...
198+
```
199+
200+
### 9.3 Pydantic Schemas
201+
Inside app/schemas, create a new entity.py for for each new entity (replacing entity with the name) and create the schemas according to [Pydantic V2](https://docs.pydantic.dev/latest/#pydantic-examples) standards:
202+
```python
203+
from typing import Annotated
204+
205+
from pydantic import BaseModel, EmailStr, Field, HttpUrl, ConfigDict
206+
207+
class EntityBase(BaseModel):
208+
name: Annotated[
209+
str,
210+
Field(min_length=2, max_length=30, examples=["Entity Name"])
211+
...
212+
]
213+
214+
class Entity(EntityBase):
215+
...
216+
217+
class EntityRead(EntityBase):
218+
...
219+
220+
class EntityCreate(EntityBase):
221+
...
222+
223+
class EntityCreateInternal(EntityCreate):
224+
...
225+
226+
class EntityUpdate(BaseModel):
227+
...
228+
229+
class EntityUpdateInternal(BaseModel):
230+
...
231+
232+
class EntityDelete(BaseModel):
233+
model_config = ConfigDict(extra='forbid')
234+
235+
is_deleted: bool
236+
deleted_at: datetime
237+
238+
```
239+
240+
### 9.4 Alembic Migration
241+
Then, while in the **src** folder, run Alembic migrations:
242+
```sh
243+
poetry run alembic revision --autogenerate
244+
```
245+
246+
And to apply the migration
247+
```sh
248+
poetry run alembic upgrade head
249+
```
250+
251+
### 9.5 CRUD
252+
Inside **app/crud**, create a new crud_entities.py inheriting from CRUDBase for each new entity:
253+
```python
254+
from app.crud.crud_base import CRUDBase
255+
from app.models.entity import Entity
256+
from app.schemas.entity import EntityCreateInternal, EntityUpdate, EntityUpdateInternal, EntityDelete
257+
258+
CRUDEntity = CRUDBase[Entity, EntityCreateInternal, EntityUpdate, EntityUpdateInternal, EntityDelete]
259+
crud_entity = CRUDEntity(Entity)
260+
```
261+
262+
### 9.6 Routes
263+
Inside **app/api/v1**, create a new entities.py file and create the desired routes
264+
```python
265+
from typing import Annotated
266+
267+
from fastapi import Depends
268+
269+
from app.schemas.entity import EntityRead
270+
from app.core.database import async_get_db
271+
...
272+
273+
router = fastapi.APIRouter(tags=["entities"])
274+
275+
@router.get("/entities", response_model=List[EntityRead])
276+
async def read_entities(db: Annotated[AsyncSession, Depends(async_get_db)]):
277+
entities = await crud_entities.get_multi(db=db)
278+
return entities
279+
280+
...
281+
```
282+
Then in **app/api/v1/__init__.py** add the router such as:
283+
```python
284+
from fastapi import APIRouter
285+
from app.api.v1.entity import router as entity_router
286+
...
287+
288+
router = APIRouter(prefix="/v1") # this should be there already
289+
...
290+
router.include_router(entity_router)
291+
```
292+
293+
### 9.7 Running
294+
While in the **src** folder, run to start the application with uvicorn server:
295+
```sh
296+
poetry run uvicorn app.main:app --reload
297+
```
298+
299+
___
300+
## 10. Testing
146301
For tests, create in .env:
147302
```
148303
# ------------- test -------------
@@ -152,7 +307,7 @@ TEST_USERNAME="testeruser"
152307
TEST_PASSWORD="Str1ng$t"
153308
```
154309

155-
While in the tests folder, create your test file with the name "test_{object}.py", replacing object with what you're testing
310+
While in the tests folder, create your test file with the name "test_{entity}.py", replacing entity with what you're testing
156311
```sh
157312
touch test_items.py
158313
```
@@ -162,24 +317,24 @@ Finally create your tests (you may want to copy the structure in test_user.py),
162317
poetry run python -m pytest
163318
```
164319
___
165-
# Other stuff
166-
## Contributing
320+
## 11. Contributing
321+
Contributions are appreciated, even if just reporting bugs, documenting stuff or answering questions. To contribute with a feature:
167322
1. Fork it (https://github.com/igormagalhaesr/FastAPI-boilerplate)
168323
2. Create your feature branch (`git checkout -b feature/fooBar`)
169324
3. Test your changes while in the src folder `poetry run python -m pytest`
170325
4. Commit your changes (`git commit -am 'Add some fooBar'`)
171326
5. Push to the branch (`git push origin feature/fooBar`)
172327
6. Create a new Pull Request
173328

174-
## References
329+
## 12. References
175330
This project was inspired by a few projects, it's based on them with things changed to the way I like (and pydantic, sqlalchemy updated)
176331
* [`Full Stack FastAPI and PostgreSQL`](https://github.com/tiangolo/full-stack-fastapi-postgresql) by @tiangolo himself
177332
* [`FastAPI Microservices`](https://github.com/Kludex/fastapi-microservices) by @kludex which heavily inspired this boilerplate
178-
* [Async Web API with FastAPI + SQLAlchemy 2.0](https://github.com/rhoboro/async-fastapi-sqlalchemy)
333+
* [`Async Web API with FastAPI + SQLAlchemy 2.0`](https://github.com/rhoboro/async-fastapi-sqlalchemy)
179334

180-
## License
335+
## 13. License
181336
[`MIT`](LICENSE.md)
182337

183-
## Contact
338+
## 14. Contact
184339
Igor Magalhaes – [@igormagalhaesr](https://twitter.com/igormagalhaesr)[email protected]
185-
[github.com/igormagalhaesr](https://github.com/igormagalhaesr/)
340+
[github.com/igormagalhaesr](https://github.com/igormagalhaesr/)

src/app/api/v1/login.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
@router.post("/login", response_model=Token)
1717
async def login_for_access_token(
1818
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
19-
db: AsyncSession = Depends(async_get_db)
19+
db: Annotated[AsyncSession, Depends(async_get_db)]
2020
):
2121

2222
user = await authenticate_user(

src/app/api/v1/posts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List, Annotated
22

3-
from fastapi import Depends, HTTPException
3+
from fastapi import Request, Depends, HTTPException
44
from sqlalchemy.ext.asyncio import AsyncSession
55
import fastapi
66

@@ -16,6 +16,7 @@
1616

1717
@router.post("/{username}/post", response_model=PostRead, status_code=201)
1818
async def write_post(
19+
request: Request,
1920
username: str,
2021
post: PostCreate,
2122
current_user: Annotated[UserRead, Depends(get_current_user)],
@@ -36,6 +37,7 @@ async def write_post(
3637

3738
@router.get("/{username}/posts", response_model=List[PostRead])
3839
async def read_posts(
40+
request: Request,
3941
username: str,
4042
db: Annotated[AsyncSession, Depends(async_get_db)]
4143
):
@@ -49,6 +51,7 @@ async def read_posts(
4951

5052
@router.get("/{username}/post/{id}", response_model=PostRead)
5153
async def read_post(
54+
request: Request,
5255
username: str,
5356
id: int,
5457
db: Annotated[AsyncSession, Depends(async_get_db)]
@@ -66,6 +69,7 @@ async def read_post(
6669

6770
@router.patch("/{username}/post/{id}", response_model=PostRead)
6871
async def patch_post(
72+
request: Request,
6973
username: str,
7074
id: int,
7175
values: PostUpdate,
@@ -88,6 +92,7 @@ async def patch_post(
8892

8993
@router.delete("/{username}/post/{id}")
9094
async def erase_post(
95+
request: Request,
9196
username: str,
9297
id: int,
9398
current_user: Annotated[UserRead, Depends(get_current_user)],
@@ -115,6 +120,7 @@ async def erase_post(
115120

116121
@router.delete("/{username}/db_post/{id}", dependencies=[Depends(get_current_superuser)])
117122
async def erase_db_post(
123+
request: Request,
118124
username: str,
119125
id: int,
120126
db: Annotated[AsyncSession, Depends(async_get_db)]

0 commit comments

Comments
 (0)