Skip to content

Commit a59b308

Browse files
Modernize project deps, tooling, tests, and Docker setup
1 parent 92debe7 commit a59b308

File tree

12 files changed

+264
-179
lines changed

12 files changed

+264
-179
lines changed

.dockerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.git
2+
.gitignore
3+
.venv
4+
__pycache__
5+
.pytest_cache
6+
*.pyc
7+
*.pyo
8+
*.pyd
9+
*.swp
10+
*.swo
11+
tests

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DATABASE_URL=postgresql://postgres:postgres@db:5432/fastapi_template

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,5 @@ dmypy.json
128128
# Pyre type checker
129129
.pyre/
130130

131-
.idea
131+
.idea
132+
test.db

Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.10-slim
2+
3+
WORKDIR /app
4+
5+
ENV PYTHONDONTWRITEBYTECODE=1
6+
ENV PYTHONUNBUFFERED=1
7+
8+
COPY requirements.txt .
9+
RUN pip install --no-cache-dir -r requirements.txt
10+
11+
COPY . .
12+
13+
EXPOSE 8000
14+
15+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

README.md

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,89 @@
11
<p align="center">
22
<img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FAST API"/>
33
<h2 align="center"> FastAPI Template </h2>
4-
<h4 align="center"> A template for the beginners </h4>
5-
4+
<h4 align="center"> A template for beginners </h4>
65

76
---
87
## About
9-
This is a beginner's template for getting started with FastAPI.
10-
It uses SQLAlchemy as the ORM.
11-
12-
Contributions are welcome.
8+
This is a beginner-friendly template for getting started with FastAPI and SQLAlchemy.
139

1410
## Features
11+
- [x] Database connection using SQLAlchemy
12+
- [x] FastAPI server
13+
- [x] Unit testing with PyTest
14+
- [x] Basic CRUD for posts
1515

16-
- [x] Database Connection Using SQLAlchemy
17-
- [x] FastAPI Server
18-
- [x] Unit Testing with PyTest
19-
- [x] Basic CRUD for Posts
16+
## Requirements
17+
- Python 3.10+
18+
- `pip`
19+
- PostgreSQL database
2020

21-
<br>
21+
## Setup
22+
1. Create and activate a virtual environment:
2223

23-
## Dependencies
24+
```bash
25+
python3 -m venv .venv
26+
source .venv/bin/activate
27+
```
28+
29+
2. Install dependencies:
30+
31+
```bash
32+
pip install -r requirements.txt
33+
```
2434

25-
- Python 3.7+
26-
- Pip
27-
- Other listed in requirements.txt
35+
3. Set environment variables:
2836

29-
## Running
37+
| Key | Value |
38+
| --- | --- |
39+
| `DATABASE_URL` | `postgresql://user:password@host:port/db` |
3040

31-
- Clone the repo using
41+
Example (`.env`):
3242

33-
```bash
34-
git clone https://github.com/mdhishaamakhtar/fastapi-sqlalchemy-postgres-template
43+
```env
44+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/fastapi_template
3545
```
3646

37-
- Create a Virtual Environment using
47+
4. Run the API:
3848

3949
```bash
40-
sudo pip install virtualenv
41-
virtualenv env
50+
uvicorn main:app --reload
4251
```
4352

44-
- Activate the virtualenv
53+
## API Docs (Swagger / OpenAPI)
54+
Once the app is running locally, open:
55+
- Swagger UI: `http://127.0.0.1:8000/docs`
56+
- ReDoc: `http://127.0.0.1:8000/redoc`
57+
- OpenAPI JSON: `http://127.0.0.1:8000/openapi.json`
4558

59+
## Running tests
4660
```bash
47-
env\Scripts\activate # for windows
48-
source env/bin/activate # for linux and mac
61+
pytest
4962
```
5063

51-
- Install dependencies
64+
## Formatting (Black)
65+
Run formatter:
5266

5367
```bash
54-
pip install -r requirements.txt
68+
black .
5569
```
5670

57-
- Setting up environment variables
71+
## Docker (Local)
72+
1. Create `.env` from example:
5873

59-
| Key | Value |
60-
| ----------- | ----------- |
61-
| DATABASE_URL | postgresql://user:password@host:port/db|
74+
```bash
75+
cp .env.example .env
76+
```
6277

63-
- To run the project
78+
2. Start API + Postgres with Docker Compose:
6479

6580
```bash
66-
uvicorn main:app
81+
docker compose up --build
6782
```
6883

69-
## Contributors
70-
71-
<table>
72-
<tr align="center">
73-
<td>
74-
Md Hishaam Akhtar
75-
<p align="center">
76-
<img src = "https://user-images.githubusercontent.com/58990970/103586688-9cde9700-4f0b-11eb-915c-0d8b9a555159.JPG" width="150" height="150" alt="Md Hishaam Akhtar">
77-
</p>
78-
<p align="center">
79-
<a href = "https://github.com/mdhishaamakhtar">
80-
<img src = "https://www.iconninja.com/files/241/825/211/round-collaboration-social-github-code-circle-network-icon.svg" width="36" height = "36" alt="GitHub"/>
81-
</a>
82-
<a href = "https://www.linkedin.com/in/mdhishaamakhtar">
83-
<img src = "https://www.iconninja.com/files/863/607/751/network-linkedin-social-connection-circular-circle-media-icon.svg" width="36" height="36" alt="LinkedIn"/>
84-
</a>
85-
</p>
86-
</td>
87-
</tr>
88-
</table>
84+
3. API will be available at:
85+
- `http://127.0.0.1:8000`
86+
- Swagger: `http://127.0.0.1:8000/docs`
87+
- ReDoc: `http://127.0.0.1:8000/redoc`
88+
89+
`DATABASE_URL` is passed to the API container from `.env` through `docker-compose.yml`.

database/connection.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from typing import cast
2+
13
from decouple import config
24
from sqlalchemy import create_engine
3-
from sqlalchemy.ext.declarative import declarative_base
4-
from sqlalchemy.orm import sessionmaker
5+
from sqlalchemy.orm import declarative_base, sessionmaker
56

6-
SQLALCHEMY_DATABASE_URL = config("DATABASE_URL")
7+
SQLALCHEMY_DATABASE_URL = cast(str, config("DATABASE_URL"))
78

89
engine = create_engine(SQLALCHEMY_DATABASE_URL)
910

docker-compose.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
services:
2+
api:
3+
build:
4+
context: .
5+
dockerfile: Dockerfile
6+
container_name: fastapi_app
7+
ports:
8+
- "8000:8000"
9+
environment:
10+
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@db:5432/fastapi_template}
11+
depends_on:
12+
db:
13+
condition: service_healthy
14+
15+
db:
16+
image: postgres:16-alpine
17+
container_name: fastapi_db
18+
environment:
19+
POSTGRES_DB: fastapi_template
20+
POSTGRES_USER: postgres
21+
POSTGRES_PASSWORD: postgres
22+
ports:
23+
- "5432:5432"
24+
volumes:
25+
- postgres_data:/var/lib/postgresql/data
26+
healthcheck:
27+
test: ["CMD-SHELL", "pg_isready -U postgres -d fastapi_template"]
28+
interval: 5s
29+
timeout: 5s
30+
retries: 10
31+
32+
volumes:
33+
postgres_data:

requirements.txt

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,33 @@
1-
aiofiles==0.5.0
2-
aniso8601==7.0.0
3-
appdirs==1.4.4
4-
async-exit-stack==1.0.1
5-
async-generator==1.10
6-
atomicwrites==1.4.0
7-
attrs==20.3.0
8-
black==20.8b1
9-
certifi==2020.12.5
10-
chardet==4.0.0
11-
click==7.1.2
12-
colorama==0.4.4
13-
dnspython==2.1.0
14-
email-validator==1.1.2
15-
fastapi==0.63.0
16-
graphene==2.1.8
17-
graphql-core==2.3.2
18-
graphql-relay==2.0.1
19-
greenlet==1.0.0
20-
h11==0.12.0
21-
idna==2.10
22-
iniconfig==1.1.1
23-
isort==5.8.0
24-
itsdangerous==1.1.0
25-
Jinja2==2.11.3
26-
MarkupSafe==1.1.1
27-
mypy-extensions==0.4.3
28-
orjson==3.5.2
29-
packaging==20.9
30-
pathspec==0.8.1
31-
pluggy==0.13.1
32-
promise==2.3
33-
psycopg2==2.8.6
34-
py==1.10.0
35-
pydantic==1.8.2
36-
pyparsing==2.4.7
37-
pytest==6.2.3
38-
pytest-dependency==0.5.1
39-
python-decouple==3.4
40-
python-dotenv==0.17.0
41-
python-multipart==0.0.5
42-
PyYAML==5.4.1
43-
regex==2021.4.4
44-
requests==2.25.1
45-
Rx==1.6.1
46-
six==1.15.0
47-
SQLAlchemy==1.4.11
48-
starlette==0.13.6
49-
toml==0.10.2
50-
typed-ast==1.4.3
51-
typing-extensions==3.7.4.3
52-
ujson==3.2.0
53-
urllib3==1.26.5
54-
uvicorn==0.13.4
55-
watchgod==0.7
56-
websockets==9.1
1+
Pygments==2.19.2
2+
SQLAlchemy==2.0.48
3+
annotated-doc==0.0.4
4+
annotated-types==0.7.0
5+
anyio==4.12.1
6+
black==26.1.0
7+
certifi==2026.2.25
8+
click==8.3.1
9+
exceptiongroup==1.3.1
10+
fastapi==0.135.1
11+
h11==0.16.0
12+
httpcore==1.0.9
13+
httpx==0.28.1
14+
idna==3.11
15+
iniconfig==2.3.0
16+
mypy_extensions==1.1.0
17+
packaging==26.0
18+
pathspec==1.0.4
19+
platformdirs==4.9.4
20+
pluggy==1.6.0
21+
psycopg2-binary==2.9.11
22+
pydantic==2.12.5
23+
pydantic_core==2.41.5
24+
pytest-dependency==0.6.1
25+
pytest==9.0.2
26+
python-decouple==3.8
27+
python-multipart==0.0.22
28+
pytokens==0.4.1
29+
starlette==0.52.1
30+
tomli==2.4.0
31+
typing-inspection==0.4.2
32+
typing_extensions==4.15.0
33+
uvicorn==0.41.0

routes/posts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import List
2+
from uuid import UUID
23

34
from fastapi import APIRouter, Depends, HTTPException, status
45
from sqlalchemy.orm import Session
@@ -27,14 +28,14 @@ def get_all_posts(db: Session = Depends(get_db)):
2728

2829

2930
@router.get("/get/{id}", status_code=status.HTTP_200_OK, response_model=Post)
30-
def get_one_post(id, db: Session = Depends(get_db)):
31+
def get_one_post(id: UUID, db: Session = Depends(get_db)):
3132
return post_get_one(db=db, id=id)
3233

3334

3435
@router.delete(
3536
"/delete/{id}", status_code=status.HTTP_200_OK, response_model=DeletePostResponse
3637
)
37-
def delete_post(id, db: Session = Depends(get_db)):
38+
def delete_post(id: UUID, db: Session = Depends(get_db)):
3839
delete_status = post_delete(db=db, id=id)
3940
if delete_status.detail == "Doesnt Exist":
4041
raise HTTPException(

schemas/models.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
from typing import Optional
22
from uuid import UUID
33

4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, ConfigDict
55

66

77
class HealthResponse(BaseModel):
88
status: str
99

1010

1111
class Post(BaseModel):
12-
id: Optional[UUID]
12+
id: Optional[UUID] = None
1313
title: str
1414
description: str
1515

16-
class Config:
17-
orm_mode = True
16+
model_config = ConfigDict(from_attributes=True)
1817

1918

2019
class DeletePostResponse(BaseModel):
@@ -26,5 +25,4 @@ class UpdatePost(BaseModel):
2625
title: str
2726
description: str
2827

29-
class Config:
30-
orm_mode = True
28+
model_config = ConfigDict(from_attributes=True)

0 commit comments

Comments
 (0)