Skip to content

Commit e8b567d

Browse files
committed
align fastapi app with current best practices
1 parent c478298 commit e8b567d

17 files changed

Lines changed: 118 additions & 85 deletions

File tree

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ coverage.xml
2020
.github
2121
.hypothesis
2222
.venv
23+
.DS_Store
24+
build
25+
dist
26+
*.egg-info

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ API_PASSWORD=debian
1111
API_SECRET_KEY=09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
1212
API_ALGORITHM=HS256
1313
API_ACCESS_TOKEN_EXPIRE_MINUTES=30
14+
CORS_ALLOW_ORIGINS=["http://localhost","http://localhost:5002","http://127.0.0.1","http://127.0.0.1:5002"]

README.md

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ directory structure.
2424
- OAuth2 authentication with [Argon2][argon2] password hashing via [pwdlib][pwdlib]
2525
and Bearer JWT tokens via [PyJWT][pyjwt].
2626

27-
- [CORS (Cross Origin Resource Sharing)][cors] enabled.
27+
- [CORS (Cross Origin Resource Sharing)][cors] enabled with explicit allowed origins
28+
so credentialed requests follow FastAPI's current CORS guidance.
2829

2930
- Flask inspired divisional directory structure, suitable for small to medium backend
3031
development.
@@ -128,6 +129,9 @@ If you want to run the app locally, without using Docker, then:
128129
- Lint with [ruff] and check types with [mypy] using `make lint`.
129130
- Update dependencies with `make dep-update`.
130131
- Stop containers with `make kill-container`.
132+
- Configure credentialed CORS origins with `CORS_ALLOW_ORIGINS` in `.env`. The value is
133+
parsed as a JSON array, for example
134+
`["http://localhost","http://localhost:5002"]`.
131135
132136
## Directory structure
133137
@@ -141,10 +145,11 @@ fastapi-nano
141145
│ │ │ ├── __init__.py # empty init file to make the api_a folder a package
142146
│ │ │ ├── mainmod.py # main module of api_a package
143147
│ │ │ └── submod.py # submodule of api_a package
144-
│ │ └── api_b # api_b package
145-
│ │ ├── __init__.py # empty init file to make the api_b folder a package
146-
│ │ ├── mainmod.py # main module of api_b package
147-
│ │ └── submod.py # submodule of api_b package
148+
│ │ ├── api_b # api_b package
149+
│ │ │ ├── __init__.py # empty init file to make the api_b folder a package
150+
│ │ │ ├── mainmod.py # main module of api_b package
151+
│ │ │ └── submod.py # submodule of api_b package
152+
│ │ └── schemas.py # shared Pydantic response models
148153
│ ├── core # this is where the configs live
149154
│ │ ├── auth.py # authentication with OAuth2
150155
│ │ ├── config.py # typed environment settings
@@ -175,54 +180,50 @@ then assemble their endpoints in the routes directory. The following snippets sh
175180
behind the dummy APIs.
176181
177182
This is a dummy submodule that houses a function called `rand_gen` which generates a
178-
dictionary of random integers.
183+
Pydantic response model with random integers.
179184
180185
```python
181-
# This is a dummy module.
182-
# This gets called in mainmod.py.
183-
from __future__ import annotations
184186
import random
185187
188+
from svc.apis.schemas import RandomNumbers
186189
187-
def rand_gen(num: int) -> dict[str, int]:
190+
191+
def rand_gen(num: int) -> RandomNumbers:
188192
num = int(num)
189-
d = {
190-
"seed": num,
191-
"random_first": random.randint(0, num),
192-
"random_second": random.randint(0, num),
193-
}
194-
return d
193+
return RandomNumbers(
194+
seed=num,
195+
random_first=random.randint(0, num),
196+
random_second=random.randint(0, num),
197+
)
195198
```
196199
197200
The `main_func` in the primary module calls the `rand_gen` function from the submodule.
198201
199202
```python
200-
from __future__ import annotations
203+
from svc.apis.schemas import RandomNumbers
201204
from svc.apis.api_a.submod import rand_gen
202205
203206
204-
def main_func(num: int) -> dict[str, int]:
205-
d = rand_gen(num)
206-
return d
207+
def main_func(num: int) -> RandomNumbers:
208+
return rand_gen(num)
207209
```
208210
209211
The endpoint is exposed like this:
210212
211213
```python
212214
# svc/routes/views.py
213-
from __future__ import annotations
214-
215215
from typing import Annotated
216216
217217
from fastapi import Depends
218218
219+
from svc.apis.schemas import RandomNumbers
219220
from svc.core.auth import UserInDB, get_current_user
220221
221222
CurrentUser = Annotated[UserInDB, Depends(get_current_user)]
222223
223224
# endpoint for api_a (api_b looks identical)
224225
@router.get("/api_a/{num}", tags=["api_a"])
225-
async def view_a(num: int, _auth: CurrentUser) -> dict[str, int]:
226+
async def view_a(num: int, _auth: CurrentUser) -> RandomNumbers:
226227
return main_func_a(num)
227228
```
228229

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ target-version = "py313"
5959

6060
[tool.ruff.lint]
6161
# Enable Pyflakes `E` and `F` codes by default
62-
select = ["E", "F", "PT", "C4", "I"]
62+
select = ["E", "F", "PT", "C4", "I", "UP"]
6363
ignore = ["E501"]
6464

6565
per-file-ignores = {}

svc/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
from svc.core.logger import configure_logger
2-
3-
configure_logger()

svc/apis/api_a/mainmod.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from __future__ import annotations
1+
from svc.apis.schemas import RandomNumbers
22

33
from .submod import rand_gen
44

55

6-
def main_func(num: int) -> dict[str, int]:
7-
d = rand_gen(num)
8-
return d
6+
def main_func(num: int) -> RandomNumbers:
7+
return rand_gen(num)

svc/apis/api_a/submod.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
# This is a dummy module.
22
# This gets called in mainmod.py.
33

4-
from __future__ import annotations
5-
64
import random
75

6+
from svc.apis.schemas import RandomNumbers
7+
88

9-
def rand_gen(num: int) -> dict[str, int]:
9+
def rand_gen(num: int) -> RandomNumbers:
1010
num = int(num)
11-
d = {
12-
"seed": num,
13-
"random_first": random.randint(0, num),
14-
"random_second": random.randint(0, num),
15-
}
16-
return d
11+
return RandomNumbers(
12+
seed=num,
13+
random_first=random.randint(0, num),
14+
random_second=random.randint(0, num),
15+
)

svc/apis/api_b/mainmod.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from __future__ import annotations
1+
from svc.apis.schemas import RandomNumbers
22

33
from .submod import rand_gen
44

55

6-
def main_func(num: int) -> dict[str, int]:
7-
d = rand_gen(num)
8-
return d
6+
def main_func(num: int) -> RandomNumbers:
7+
return rand_gen(num)

svc/apis/api_b/submod.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
# This is a dummy module.
22
# This gets called in mainmod.py.
33

4-
from __future__ import annotations
5-
64
import random
75

6+
from svc.apis.schemas import RandomNumbers
7+
88

9-
def rand_gen(num: int) -> dict[str, int]:
9+
def rand_gen(num: int) -> RandomNumbers:
1010
num = int(num)
11-
d = {
12-
"seed": num,
13-
"random_first": random.randint(0, num),
14-
"random_second": random.randint(0, num),
15-
}
16-
return d
11+
return RandomNumbers(
12+
seed=num,
13+
random_first=random.randint(0, num),
14+
random_second=random.randint(0, num),
15+
)

svc/apis/schemas.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pydantic import BaseModel
2+
3+
4+
class RandomNumbers(BaseModel):
5+
seed: int
6+
random_first: int
7+
random_second: int

0 commit comments

Comments
 (0)