Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ec3f724

Browse files
committed
updated packages
1 parent e156b18 commit ec3f724

File tree

9 files changed

+72
-94
lines changed

9 files changed

+72
-94
lines changed

‎Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
dev:
2+
docker-compose up -d
3+
4+
dev-down:
5+
docker-compose down
6+
7+
server:
8+
uvicorn app.main:app --reload

‎README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Build a CRUD App with FastAPI and SQLAlchemy
2-
# backend
2+
3+
## backend
4+
35
have docker open
46
use bash:
57

@@ -13,8 +15,7 @@ open on: http://127.0.0.1:8000/docs
1315

1416
docker: docker-compose up -d
1517

16-
17-
(frontend: frontend-reactjs-crud-crypto-app https://github.com/rafgger/frontend-reactjs-crud-crypto-app)
18+
(frontend: frontend-reactjs-crud-crypto-app https://github.com/rafgger/frontend-reactjs-crud-crypto-app)
1819

1920
In this article, I'll provide you with a simple and straightforward guide on how you can build a CRUD app with FastAPI and SQLAlchemy. The FastAPI app will run on a Starlette web server, use Pydantic for data validation, and store data in an SQLite database.
2021

@@ -29,16 +30,15 @@ In this article, I'll provide you with a simple and straightforward guide on how
2930
- Setup SQLAlchemy with SQLite
3031
- Setup SQLAlchemy with PostgreSQL
3132
- Create Database Model with SQLAlchemy
32-
- Database Model for SQLite Database
33-
- Database Model for Postgres Database
33+
- Database Model for SQLite Database
34+
- Database Model for Postgres Database
3435
- Create Validation Schemas with Pydantic
3536
- Define the Path Operation Functions
36-
- Get All Records
37-
- Create a Record
38-
- Update a Record
39-
- Retrieve a Single Record
40-
- Delete a Single Record
37+
- Get All Records
38+
- Create a Record
39+
- Update a Record
40+
- Retrieve a Single Record
41+
- Delete a Single Record
4142
- Connect the API Router to the App
4243

4344
Read the entire article here: [https://codevoweb.com/build-a-crud-app-with-fastapi-and-sqlalchemy](https://codevoweb.com/build-a-crud-app-with-fastapi-and-sqlalchemy)
44-

‎app/database.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from .config import settings
55
from fastapi_utils.guid_type import setup_guids_postgresql
66

7+
8+
79
POSTGRES_URL = (
8-
f"postgresql://{settings.POSTGRES_USER}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}/{settings.POSTGRES_DB}"
10+
f"postgresql://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}/{settings.POSTGRES_DB}"
911
)
1012

1113

@@ -17,7 +19,6 @@
1719

1820
Base = declarative_base()
1921

20-
2122
def get_db():
2223
db = SessionLocal()
2324
try:

‎app/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Note(Base):
88
__tablename__ = 'notes'
99
id = Column(GUID, primary_key=True,
1010
server_default=GUID_SERVER_DEFAULT_POSTGRESQL)
11-
title = Column(String, nullable=False)
11+
title = Column(String, nullable=False, unique=True)
1212
content = Column(String, nullable=False)
1313
category = Column(String, nullable=True)
1414
published = Column(Boolean, nullable=False, server_default='True')

‎app/note.py

Lines changed: 31 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
from . import schemas, models
22
from sqlalchemy.orm import Session
33
from fastapi import Depends, HTTPException, status, APIRouter, Response
4+
from sqlalchemy.exc import IntegrityError
45
from .database import get_db
5-
import requests
6-
import os
7-
from dotenv import load_dotenv
8-
9-
load_dotenv()
106

117
router = APIRouter()
128

@@ -19,104 +15,68 @@ def get_notes(db: Session = Depends(get_db), limit: int = 10, page: int = 1, sea
1915
models.Note.title.contains(search)).limit(limit).offset(skip).all()
2016
return {'status': 'success', 'results': len(notes), 'notes': notes}
2117

22-
def get_coin_id(symbol_or_name):
23-
"""
24-
Retrieve the CoinGecko coin ID for a given symbol or name.
25-
"""
26-
url = "https://api.coingecko.com/api/v3/coins/list"
27-
response = requests.get(url)
28-
if response.status_code == 200:
29-
coins = response.json()
30-
symbol_or_name = symbol_or_name.lower()
31-
for coin in coins:
32-
if coin['symbol'].lower() == symbol_or_name or coin['name'].lower() == symbol_or_name:
33-
return coin['id']
34-
raise HTTPException(status_code=404, detail="Coin not found")
35-
else:
36-
raise HTTPException(status_code=response.status_code, detail="Failed to fetch coin list from CoinGecko")
37-
3818

3919
@router.post('/', status_code=status.HTTP_201_CREATED)
4020
def create_note(payload: schemas.NoteBaseSchema, db: Session = Depends(get_db)):
21+
new_note = models.Note(
22+
title=payload.title,
23+
content=payload.content,
24+
category=payload.category,
25+
published=payload.published
26+
)
27+
28+
db.add(new_note)
4129
try:
42-
# Get the API key
43-
api_key = os.getenv("api_key")
44-
if not api_key:
45-
raise HTTPException(status_code=500, detail="API key not configured")
46-
47-
# Retrieve the CoinGecko coin ID using the payload title
48-
coin_id = get_coin_id(payload.title)
49-
50-
# Build the API URL using the retrieved coin ID
51-
url = (
52-
f"https://api.coingecko.com/api/v3/simple/price"
53-
f"?ids={coin_id}"
54-
f"&vs_currencies=usd"
55-
f"&x_cg_demo_api_key={api_key}"
56-
)
57-
58-
# Make the request to get the crypto price
59-
response = requests.get(url)
60-
if response.status_code != 200:
61-
raise HTTPException(status_code=response.status_code, detail="API call failed")
62-
63-
data = response.json()
64-
price = data.get(coin_id, {}).get("usd")
65-
66-
if price is None:
67-
raise HTTPException(status_code=500, detail="Invalid response from crypto API")
68-
69-
# Create the note with the title from the payload and content as the crypto price
70-
new_note = models.Note(
71-
title=payload.title,
72-
content=f"Current {payload.title} price (USD):\n${price}",
73-
category=payload.category,
74-
published=payload.published
75-
)
76-
77-
db.add(new_note)
7830
db.commit()
7931
db.refresh(new_note)
80-
81-
return {"status": "success", "note": new_note}
82-
83-
except Exception as e:
84-
raise HTTPException(status_code=500, detail=str(e))
85-
32+
except IntegrityError:
33+
db.rollback()
34+
raise HTTPException(status_code=400, detail="Note with this title already exists.")
35+
36+
return {"status": "success", "note": new_note}
8637

8738

8839
@router.patch('/{noteId}')
89-
def update_note(noteId: str, payload: schemas.NoteBaseSchema, db: Session = Depends(get_db)):
40+
def update_note(noteId: str, payload: schemas.NotePatchSchema, db: Session = Depends(get_db)):
9041
note_query = db.query(models.Note).filter(models.Note.id == noteId)
9142
db_note = note_query.first()
9243

9344
if not db_note:
9445
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
9546
detail=f'No note with this id: {noteId} found')
96-
update_data = payload.dict(exclude_unset=True)
97-
note_query.filter(models.Note.id == noteId).update(update_data,
98-
synchronize_session=False)
47+
48+
update_data = payload.model_dump(exclude_unset=True)
49+
50+
if 'title' in update_data:
51+
existing_title = db.query(models.Note).filter(
52+
models.Note.title == update_data['title'],
53+
models.Note.id != noteId
54+
).first()
55+
if existing_title:
56+
raise HTTPException(status_code=400, detail="Another note with this title already exists.")
57+
58+
note_query.update(update_data, synchronize_session=False)
9959
db.commit()
10060
db.refresh(db_note)
10161
return {"status": "success", "note": db_note}
10262

10363

10464
@router.get('/{noteId}')
105-
def get_post(noteId: str, db: Session = Depends(get_db)):
65+
def get_note(noteId: str, db: Session = Depends(get_db)):
10666
note = db.query(models.Note).filter(models.Note.id == noteId).first()
10767
if not note:
10868
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
109-
detail=f"No note with this id: {id} found")
69+
detail=f"No note with this id: {noteId} found")
11070
return {"status": "success", "note": note}
11171

11272

11373
@router.delete('/{noteId}')
114-
def delete_post(noteId: str, db: Session = Depends(get_db)):
74+
def delete_note(noteId: str, db: Session = Depends(get_db)):
11575
note_query = db.query(models.Note).filter(models.Note.id == noteId)
11676
note = note_query.first()
11777
if not note:
11878
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
119-
detail=f'No note with this id: {id} found')
79+
detail=f'No note with this id: {noteId} found')
12080
note_query.delete(synchronize_session=False)
12181
db.commit()
12282
return Response(status_code=status.HTTP_204_NO_CONTENT)

‎app/schemas.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
from datetime import datetime
2-
from typing import List
2+
from typing import List, Optional
33
from pydantic import BaseModel
44

5-
65
class NoteBaseSchema(BaseModel):
7-
id: str| None =None
6+
id: Optional[str] = None
87
title: str
98
content: str
10-
category: str| None =None
9+
category: Optional[str] = None
1110
published: bool = False
12-
createdAt: datetime|None = None
13-
updatedAt: datetime|None = None
11+
createdAt: Optional[datetime] = None
12+
updatedAt: Optional[datetime] = None
1413

1514
class Config:
16-
orm_mode = True
17-
allow_population_by_field_name = True
18-
arbitrary_types_allowed = True
15+
orm_mode = True
16+
arbitrary_types_allowed = True
1917

18+
# Schema for partial updates (PATCH request)
19+
class NotePatchSchema(NoteBaseSchema):
20+
title: Optional[str] = None
21+
content: Optional[str] = None
22+
category: Optional[str] = None
23+
published: Optional[bool] = None
2024

2125
class ListNoteResponse(BaseModel):
2226
status: str

‎env_variables.PNG

-11 KB
Binary file not shown.

‎example.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DATABASE_PORT=6500
2+
POSTGRES_PASSWORD=password123
3+
POSTGRES_USER=postgres
4+
POSTGRES_DB=fastapi
5+
POSTGRES_HOSTNAME=127.0.0.1

‎output.PNG

-15.6 KB
Binary file not shown.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /