Skip to content

Commit b28aaf2

Browse files
committed
Add CORS middleware and update documentation
1 parent 21fa78f commit b28aaf2

File tree

5 files changed

+145
-113
lines changed

5 files changed

+145
-113
lines changed

README.md

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
</a>
3737
</p>
3838

39-
---
39+
______________________________________________________________________
4040

4141
## 📖 Documentation
4242

@@ -52,7 +52,7 @@
5252
5353
This README provides a quick reference for LLMs and developers, but the full documentation contains detailed guides, examples, and best practices.
5454

55-
---
55+
______________________________________________________________________
5656

5757
## 0. About
5858

@@ -78,6 +78,7 @@ This README provides a quick reference for LLMs and developers, but the full doc
7878
💬 **[Join our Discord community](https://discord.com/invite/TEmPs22gqB)** - Connect with other developers using the FastAPI boilerplate!
7979

8080
Our Discord server features:
81+
8182
- **🤝 Networking** - Connect with fellow developers and share experiences
8283
- **💡 Product Updates** - Stay updated with FastroAI and our other products
8384
- **📸 Showcase** - Share what you've built using our tools
@@ -140,7 +141,7 @@ Whether you're just getting started or building production applications, our com
140141
1. [Admin Panel](#513-admin-panel)
141142
1. [Running](#514-running)
142143
1. [Create Application](#515-create-application)
143-
2. [Opting Out of Services](#516-opting-out-of-services)
144+
1. [Opting Out of Services](#516-opting-out-of-services)
144145
1. [Running in Production](#6-running-in-production)
145146
1. [Uvicorn Workers with Gunicorn](#61-uvicorn-workers-with-gunicorn)
146147
1. [Running With NGINX](#62-running-with-nginx)
@@ -289,6 +290,7 @@ CRUD_ADMIN_REDIS_SSL=false # default=false, use SSL for Redis co
289290
```
290291

291292
**Session Backend Options:**
293+
292294
- **Memory** (default): Development-friendly, sessions reset on restart
293295
- **Redis** (production): High performance, scalable, persistent sessions
294296
- **Database**: Audit-friendly with admin visibility
@@ -600,7 +602,7 @@ And to apply the migration
600602
uv run alembic upgrade head
601603
```
602604

603-
> [!NOTE]
605+
> \[!NOTE\]
604606
> If you do not have uv, you may run it without uv after running `pip install alembic`
605607
606608
## 5. Extending
@@ -1057,11 +1059,7 @@ router = APIRouter(tags=["entities"])
10571059

10581060

10591061
@router.get("/entities/{id}", response_model=EntityRead)
1060-
async def read_entity(
1061-
request: Request,
1062-
id: int,
1063-
db: Annotated[AsyncSession, Depends(async_get_db)]
1064-
):
1062+
async def read_entity(request: Request, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]):
10651063
entity = await crud_entity.get(db=db, id=id)
10661064

10671065
if entity is None: # Explicit None check
@@ -1071,10 +1069,7 @@ async def read_entity(
10711069

10721070

10731071
@router.get("/entities", response_model=List[EntityRead])
1074-
async def read_entities(
1075-
request: Request,
1076-
db: Annotated[AsyncSession, Depends(async_get_db)]
1077-
):
1072+
async def read_entities(request: Request, db: Annotated[AsyncSession, Depends(async_get_db)]):
10781073
entities = await crud_entity.get_multi(db=db, is_deleted=False)
10791074
return entities
10801075
```
@@ -1150,10 +1145,7 @@ from app.schemas.entity import EntityRead
11501145

11511146
@router.get("/entities", response_model=PaginatedListResponse[EntityRead])
11521147
async def read_entities(
1153-
request: Request,
1154-
db: Annotated[AsyncSession, Depends(async_get_db)],
1155-
page: int = 1,
1156-
items_per_page: int = 10
1148+
request: Request, db: Annotated[AsyncSession, Depends(async_get_db)], page: int = 1, items_per_page: int = 10
11571149
):
11581150
entities_data = await crud_entity.get_multi(
11591151
db=db,
@@ -1173,18 +1165,15 @@ async def read_entities(
11731165
To add exceptions you may just import from `app/core/exceptions/http_exceptions` and optionally add a detail:
11741166

11751167
```python
1176-
from app.core.exceptions.http_exceptions import (
1177-
NotFoundException,
1178-
ForbiddenException,
1179-
DuplicateValueException
1180-
)
1168+
from app.core.exceptions.http_exceptions import NotFoundException, ForbiddenException, DuplicateValueException
1169+
11811170

11821171
@router.post("/entities", response_model=EntityRead, status_code=201)
11831172
async def create_entity(
11841173
request: Request,
11851174
entity_data: EntityCreate,
11861175
db: Annotated[AsyncSession, Depends(async_get_db)],
1187-
current_user: Annotated[UserRead, Depends(get_current_user)]
1176+
current_user: Annotated[UserRead, Depends(get_current_user)],
11881177
):
11891178
# Check if entity already exists
11901179
if await crud_entity.exists(db=db, name=entity_data.name) is True:
@@ -1204,11 +1193,7 @@ async def create_entity(
12041193

12051194

12061195
@router.get("/entities/{id}", response_model=EntityRead)
1207-
async def read_entity(
1208-
request: Request,
1209-
id: int,
1210-
db: Annotated[AsyncSession, Depends(async_get_db)]
1211-
):
1196+
async def read_entity(request: Request, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]):
12121197
entity = await crud_entity.get(db=db, id=id)
12131198

12141199
if entity is None: # Explicit None check
@@ -1399,7 +1384,7 @@ For `client-side caching`, all you have to do is let the `Settings` class define
13991384
14001385
Depending on the problem your API is solving, you might want to implement a job queue. A job queue allows you to run tasks in the background, and is usually aimed at functions that require longer run times and don't directly impact user response in your frontend. As a rule of thumb, if a task takes more than 2 seconds to run, can be executed asynchronously, and its result is not needed for the next step of the user's interaction, then it is a good candidate for the job queue.
14011386

1402-
> [!TIP]
1387+
> \[!TIP\]
14031388
> Very common candidates for background functions are calls to and from LLM endpoints (e.g. OpenAI or Openrouter). This is because they span tens of seconds and often need to be further parsed and saved.
14041389
14051390
#### Background task creation
@@ -1418,6 +1403,7 @@ Then add the function to the `WorkerSettings` class `functions` variable in `app
14181403
from .functions import sample_background_task
14191404
from .your_module import sample_complex_background_task
14201405

1406+
14211407
class WorkerSettings:
14221408
functions = [sample_background_task, sample_complex_background_task]
14231409
...
@@ -1442,7 +1428,7 @@ async def get_task(task_id: str):
14421428

14431429
And finally run the worker in parallel to your fastapi application.
14441430

1445-
> [!IMPORTANT]
1431+
> \[!IMPORTANT\]
14461432
> For any change to the `sample_background_task` to be reflected in the worker, you need to restart the worker (e.g. the docker container).
14471433
14481434
If you are using `docker compose`, the worker is already running.
@@ -1462,6 +1448,7 @@ To do this, you can add the database session to the `ctx` object in the `startup
14621448
from arq.worker import Worker
14631449
from ...core.db.database import async_get_db
14641450

1451+
14651452
async def startup(ctx: Worker) -> None:
14661453
ctx["db"] = await anext(async_get_db())
14671454
logging.info("Worker Started")
@@ -1477,17 +1464,16 @@ This will allow you to have the async database session always available in any b
14771464
```python
14781465
from arq.worker import Worker
14791466

1467+
14801468
async def your_background_function(
14811469
ctx: Worker,
14821470
post_id: int,
1483-
...
14841471
) -> Any:
14851472
db = ctx["db"]
14861473
post = crud_posts.get(db=db, schema_to_select=PostRead, id=post_id)
1487-
...
14881474
```
14891475

1490-
> [!WARNING]
1476+
> \[!WARNING\]
14911477
> When using database sessions, you will want to use Pydantic objects. However, these objects don't mingle well with the seralization required by ARQ tasks and will be retrieved as a dictionary.
14921478
14931479
### 5.11 Rate Limiting
@@ -1661,6 +1647,7 @@ This authentication setup in the provides a robust, secure, and user-friendly wa
16611647
The boilerplate includes a powerful web-based admin interface built with [CRUDAdmin](https://github.com/benavlabs/crudadmin) that provides a comprehensive database management system.
16621648

16631649
> **About CRUDAdmin**: CRUDAdmin is a modern admin interface generator for FastAPI applications. Learn more at:
1650+
>
16641651
> - **📚 Documentation**: [benavlabs.github.io/crudadmin](https://benavlabs.github.io/crudadmin/)
16651652
> - **💻 GitHub**: [github.com/benavlabs/crudadmin](https://github.com/benavlabs/crudadmin)
16661653
@@ -1685,6 +1672,7 @@ http://localhost:8000/admin
16851672
```
16861673

16871674
Use the admin credentials you defined in your `.env` file:
1675+
16881676
- Username: `ADMIN_USERNAME`
16891677
- Password: `ADMIN_PASSWORD`
16901678

@@ -1709,14 +1697,15 @@ To add new models to the admin panel, edit `src/app/admin/views.py`:
17091697
from your_app.models import YourModel
17101698
from your_app.schemas import YourCreateSchema, YourUpdateSchema
17111699

1700+
17121701
def register_admin_views(admin: CRUDAdmin) -> None:
17131702
# ... existing models ...
17141703

17151704
admin.add_view(
17161705
model=YourModel,
17171706
create_schema=YourCreateSchema,
17181707
update_schema=YourUpdateSchema,
1719-
allowed_actions={"view", "create", "update", "delete"}
1708+
allowed_actions={"view", "create", "update", "delete"},
17201709
)
17211710
```
17221711

@@ -1731,7 +1720,7 @@ admin.add_view(
17311720
create_schema=ArticleCreate,
17321721
update_schema=ArticleUpdate,
17331722
select_schema=ArticleSelect, # Exclude problematic fields from read operations
1734-
allowed_actions={"view", "create", "update", "delete"}
1723+
allowed_actions={"view", "create", "update", "delete"},
17351724
)
17361725

17371726
# Password field handling
@@ -1740,15 +1729,15 @@ admin.add_view(
17401729
create_schema=UserCreateWithPassword,
17411730
update_schema=UserUpdateWithPassword,
17421731
password_transformer=password_transformer, # Handles password hashing
1743-
allowed_actions={"view", "create", "update"}
1732+
allowed_actions={"view", "create", "update"},
17441733
)
17451734

17461735
# Read-only models
17471736
admin.add_view(
17481737
model=AuditLog,
17491738
create_schema=AuditLogSchema,
17501739
update_schema=AuditLogSchema,
1751-
allowed_actions={"view"} # Only viewing allowed
1740+
allowed_actions={"view"}, # Only viewing allowed
17521741
)
17531742
```
17541743

@@ -1758,9 +1747,9 @@ For production environments, consider using Redis for better performance:
17581747

17591748
```python
17601749
# Enable Redis sessions in your environment
1761-
CRUD_ADMIN_REDIS_ENABLED=true
1762-
CRUD_ADMIN_REDIS_HOST=localhost
1763-
CRUD_ADMIN_REDIS_PORT=6379
1750+
CRUD_ADMIN_REDIS_ENABLED = true
1751+
CRUD_ADMIN_REDIS_HOST = localhost
1752+
CRUD_ADMIN_REDIS_PORT = 6379
17641753
```
17651754

17661755
### 5.14 Running
@@ -1783,6 +1772,7 @@ And for the worker:
17831772
```sh
17841773
uv run arq src.app.core.worker.settings.WorkerSettings
17851774
```
1775+
17861776
### 5.15 Create Application
17871777

17881778
If you want to stop tables from being created every time you run the api, you should disable this here:
@@ -1823,6 +1813,7 @@ env_path = os.path.join(current_file_dir, "..", "..", ".env")
18231813
config = Config(env_path)
18241814
...
18251815

1816+
18261817
class Settings(
18271818
AppSettings,
18281819
PostgresSettings,
@@ -1836,6 +1827,7 @@ class Settings(
18361827
DefaultRateLimitSettings,
18371828
CRUDAdminSettings,
18381829
EnvironmentSettings,
1830+
CORSSettings,
18391831
):
18401832
pass
18411833

@@ -1855,6 +1847,7 @@ class Settings(
18551847
ClientSideCacheSettings,
18561848
DefaultRateLimitSettings,
18571849
EnvironmentSettings,
1850+
CORSSettings,
18581851
):
18591852
pass
18601853
```
@@ -2126,6 +2119,7 @@ import pytest
21262119
from unittest.mock import AsyncMock, patch
21272120
from src.app.api.v1.users import write_user
21282121
2122+
21292123
class TestWriteUser:
21302124
@pytest.mark.asyncio
21312125
async def test_create_user_success(self, mock_db, sample_user_data):

0 commit comments

Comments
 (0)