Skip to content

Commit d47bb85

Browse files
Updates
1 parent 76a7b23 commit d47bb85

File tree

9 files changed

+130
-24
lines changed

9 files changed

+130
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
__pycache__
22
*.pytest_cache
33
venv/
4+
.venv
45
# .env
56
*.db
67
*.idea

README.md

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,122 @@
1-
# Pytest FastAPI CRUD Example
1+
# User Service API Example
22

3-
This repo contains the sample code for the article - [Building And Testing FastAPI CRUD APIs With Pytest (Hands-On Tutorial)](https://pytest-with-eric.com/pytest-advanced/pytest-fastapi-testing/)
3+
## Overview
44

5-
This project explains how to Build and Test A CRUD Rest API using FastAPI, SQLite (via SQLAlchemy) and Pytest.
5+
This is a simple User Service CRUD (Create, Read, Update, Delete) API built with FastAPI and SQLite. The API allows you to create, read, update, and delete users. It uses Pydantic models for request and response validation and SQLAlchemy for database operations.
66

7-
# Requirements
8-
* Python (3.12)
7+
## Architecture
8+
This project follows a clean architecture pattern, separating concerns to enhance maintainability and scalability. Here's a brief overview:
99

10-
This repo uses [Poetry](https://python-poetry.org/) for dependency management.
10+
- API Layer (FastAPI): Handles HTTP requests and responses, routing, and interaction with the service layer.
11+
- Service Layer: Contains business logic and communicates with the database layer.
12+
- Database Layer (SQLite): Manages data persistence and database operations.
13+
- Testing: Unit tests are written in Pytest to test the service layer functions.
14+
15+
## Getting Started
16+
17+
### Prerequisites and Dependencies
18+
- Python 3.12
19+
- FastAPI
20+
- SQLite
21+
- Uvicorn (for running the server)
22+
23+
#### Poetry
24+
25+
This project uses [Poetry](https://python-poetry.org/) for dependency management.
26+
27+
If you're not familiar with Poetry, please follow [these instructions](https://python-poetry.org/docs/#installation) to install it.
28+
29+
Once you've installed Poetry, you can install the dependencies using the following command:
1130

12-
Please install the dependencies using
1331
```shell
1432
$ poetry install
1533
```
1634

17-
# How To Run the Unit Tests
35+
Then run the below command to activate the virtual environment.
36+
37+
```shell
38+
$ poetry shell
39+
```
40+
41+
#### Pip
42+
43+
If you prefer using `pip`, you can create a virtual environment and then install the dependencies using the following command:
44+
45+
```shell
46+
$ pip install -r requirements.txt
47+
```
48+
49+
## How To Run the Server
50+
51+
To run the server, use the following command:
52+
53+
```shell
54+
$ uvicorn app.main:app --host localhost --port 8000 --reload
55+
```
56+
57+
This will spin up the server at `http://localhost:8000` with a local SQLite database `users.db`.
58+
59+
## API Endpoints
60+
61+
### Create User
62+
63+
- `POST /api/users/`: Create a new user.
64+
65+
To create a user, send a POST request to `http://localhost:8000/api/users` with the following JSON payload:
66+
67+
```json
68+
{
69+
"first_name": "John",
70+
"last_name": "Doe",
71+
"address": "123 Fake St",
72+
"activated": true
73+
}
74+
```
75+
76+
As we use Pydantic models, the API will validate the request payload and return an error if the payload is invalid.
77+
78+
### Get Users
79+
80+
- `GET /api/users/`: Get all users.
81+
82+
To get all users, send a GET request to `http://localhost:8000/api/users`.
83+
84+
### Get User by ID
85+
86+
- `GET /api/users/{userId}/`: Get a user by ID.
87+
88+
To get a user by ID, send a GET request to `http://localhost:8000/api/users/{userId}`.
89+
90+
If the user with the specified ID does not exist, the API will return a 404 Not Found response. The same logic is carried out for the Update and Delete endpoints.
91+
92+
93+
### Update User
94+
95+
- `PATCH /api/users/{userId}/`: Update a user by ID.
96+
97+
To update a user by ID, send a PATCH request to `http://localhost:8000/api/users/{userId}` with the following JSON payload:
98+
99+
```json
100+
{
101+
"first_name": "Jane",
102+
"last_name": "Doe",
103+
"address": "321 Fake St",
104+
"activated": true
105+
}
106+
```
107+
108+
### Delete User
109+
110+
- `DELETE /api/users/{userId}/`: Delete a user by ID.
111+
112+
To delete a user by ID, send a DELETE request to `http://localhost:8000/api/users/{userId}`.
113+
114+
## How To Run the Unit Tests
18115
To run the Unit Tests, from the root of the repo run
19116
```shell
20-
pytest
117+
$ pytest
21118
```
22119

23-
If you have any questions about the project please raise an Issue on GitHub.
120+
This will spin up a test database in SQLite `test_db.db`, run the tests and then tear down the database.
121+
122+
You can use `pytest -v` for verbose output and `pytest -s` to disable output capture for better debugging.

app/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from app.database import Base
2-
from sqlalchemy import TIMESTAMP, Column, String, Boolean, Index
2+
from sqlalchemy import TIMESTAMP, Column, String, Boolean
33
from sqlalchemy.sql import func
44
from sqlalchemy_utils import UUIDType
55
import uuid

app/schemas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import datetime
33
from typing import List
44
from pydantic import BaseModel, Field
5-
from uuid import uuid4, UUID
5+
from uuid import UUID
66

77

88
class UserBaseSchema(BaseModel):

app/user.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import app.schemas as schemas, app.models as models
1+
import app.schemas as schemas
2+
import app.models as models
23
from sqlalchemy.orm import Session
34
from sqlalchemy.exc import IntegrityError
4-
from pydantic import ValidationError
55
from fastapi import Depends, HTTPException, status, APIRouter
66
from app.database import get_db
77

@@ -14,7 +14,7 @@
1414
def create_user(payload: schemas.UserBaseSchema, db: Session = Depends(get_db)):
1515
try:
1616
# Create a new user instance from the payload
17-
new_user = models.User(**payload.dict())
17+
new_user = models.User(**payload.model_dump())
1818
db.add(new_user)
1919
db.commit()
2020
db.refresh(new_user)
@@ -55,7 +55,7 @@ def get_user(userId: str, db: Session = Depends(get_db)):
5555

5656
try:
5757
return schemas.GetUserResponse(
58-
Status=schemas.Status.Success, User=schemas.UserBaseSchema.from_orm(db_user)
58+
Status=schemas.Status.Success, User=schemas.UserBaseSchema.model_validate(db_user)
5959
)
6060
except Exception as e:
6161
raise HTTPException(
@@ -86,7 +86,7 @@ def update_user(
8686
user_query.update(update_data, synchronize_session=False)
8787
db.commit()
8888
db.refresh(db_user)
89-
user_schema = schemas.UserBaseSchema.from_orm(db_user)
89+
user_schema = schemas.UserBaseSchema.model_validate(db_user)
9090
return schemas.UserResponse(Status=schemas.Status.Success, User=user_schema)
9191
except IntegrityError as e:
9292
db.rollback()

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
2-
name = "pytest-fastapi-crud-example"
2+
name = "user-service-example"
33
version = "0.1.0"
4-
description = "Example of testing a FastAPI CRUD API in Pytest"
4+
description = "A simple user service example"
55
authors = ["ericsalesdeandrade <sdaeric19@gmail.com>"]
66
readme = "README.md"
77

requirements.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
fastapi~=0.110.3
3+
httpx~=0.27.0
4+
pydantic~=2.7.1
5+
sqlalchemy-utils~=0.41.2
6+
sqlalchemy~=2.0.29
7+
starlette~=0.37.2
8+
uvicorn~=0.29.0
9+
pytest~=8.2.0
10+
pytest-randomly~=3.15.0

tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import uuid
33
from sqlalchemy import create_engine
44
from sqlalchemy.orm import sessionmaker
5-
from sqlalchemy.ext.declarative import declarative_base
65
from sqlalchemy.pool import StaticPool
76
from fastapi.testclient import TestClient
87
from app.main import app

tests/test_crud_api.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ def test_create_delete_user(test_client, user_payload):
6565
response = test_client.get(f"/api/users/{user_payload['id']}")
6666
assert response.status_code == 404
6767
response_json = response.json()
68-
assert (
69-
response_json["detail"]
70-
== f"No User with this id: `{user_payload['id']}` found",
71-
)
68+
assert response_json["detail"] == f"No User with this id: `{user_payload['id']}` found"
7269

7370

7471
def test_get_user_not_found(test_client, user_id):

0 commit comments

Comments
 (0)