This commit is contained in:
Reza 2025-09-20 18:13:00 +03:30
commit e00aecac1f
22 changed files with 293 additions and 0 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
APP_ENV=dev
PORT=8000
DEBUG=true

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__/
*.py[cod]
.env.*
!.env.example
*.sqlite3
.ruff_cache

9
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"charliermarsh.ruff"
],
"unwantedRecommendations": []
}

46
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,46 @@
{
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "always",
"source.fixAll": "always"
}
},
// Black: tell the Python extension / Black extension to use this args
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length=88"],
// isort: use the isort extension key (NOT python.sortImports.args)
"isort.args": ["--profile", "black"],
// Ruff: extension exposes lint/format args keys (use ruff.lint.args or ruff.format.args)
"ruff.lint.args": ["--line-length=88"],
"ruff.format.args": ["--line-length=88"],
"files.exclude": {
"**/.vscode": true,
"**/.zed": true,
"**/.ruff_cache": true,
// optional extras you might want hidden too:
"**/.venv": true,
"**/__pycache__": true
},
// exclude them from full-text search
"search.exclude": {
"**/.vscode": true,
"**/.zed": true,
"**/.ruff_cache": true
},
// stop the file watcher watching these (improves perf on large repos)
"files.watcherExclude": {
"**/.vscode/**": true,
"**/.zed/**": true,
"**/.ruff_cache/**": true,
"**/.venv/**": true
}
}

48
.zed/settings.json Normal file
View File

@ -0,0 +1,48 @@
{
// Project-local Zed settings (supports // comments)
"languages": {
"Python": {
// format-on-save for Python files
"format_on_save": "on",
// Use an external formatter: run isort (stdin) then black (stdin)
"formatter": {
"external": {
"command": "bash",
"arguments": [
"-lc",
"isort - --filename \"{buffer_path}\" --profile black | python -m black - --stdin-filename \"{buffer_path}\" --quiet"
]
}
},
// Tell Zed which language servers to enable in this project (optional)
// keep ruff in language servers if you want inline linting; pyright for types
"language_servers": [
"ruff",
"basedpyright"
]
}
},
"file_scan_exclusions": [
"**/.git",
"**/.svn",
"**/.hg",
"**/.jj",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
"**/.classpath",
"**/.settings",
// ---- add your project-specific folders to hide ----
"**/.vscode", // hide VS Code workspace config
"**/.zed", // hide Zed project folder (see WARNING below)
"**/.ruff_cache", // hide Ruff cache (correct name is .ruff_cache)
"**/.venv",
"**/__pycache__"
],
// If you want to still include some usually-ignored files, use file_scan_inclusions:
"file_scan_inclusions": [
".env*"
]
}

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# FastAPI Template
Run locally:
1. copy `.env.example` to `.env` and edit if needed
2. python -m pip install -r dev-requirements.txt
3. uvicorn app.main:app --reload

0
app/__init__.py Normal file
View File

0
app/api/__init__.py Normal file
View File

0
app/api/v1/__init__.py Normal file
View File

44
app/api/v1/posts.py Normal file
View File

@ -0,0 +1,44 @@
from typing import List
from fastapi import APIRouter, HTTPException
from app.schemas.post import PostCreate, PostRead, PostUpdate
from app.services import posts as posts_service
router = APIRouter(prefix="/posts", tags=["posts"])
@router.get("/", response_model=List[PostRead])
def read_posts():
posts = posts_service.list_posts()
return posts
@router.post("/", response_model=PostRead, status_code=201)
def create_post(payload: PostCreate):
post = posts_service.create_post(payload.name, payload.description, payload.price)
return post
@router.get("/{post_id}", response_model=PostRead)
def read_post(post_id: int):
post = posts_service.get_post(post_id)
if not post:
raise HTTPException(status_code=404, detail="Post not found")
return post
@router.patch("/{post_id}", response_model=PostRead)
def update_post(post_id: int, payload: PostUpdate):
post = posts_service.update_post(post_id, **payload.dict())
if not post:
raise HTTPException(status_code=404, detail="Post not found")
return post
@router.delete("/{post_id}", status_code=204)
def delete_post(post_id: int):
deleted = posts_service.delete_post(post_id)
if not deleted:
raise HTTPException(status_code=404, detail="Post not found")
return None

0
app/core/__init__.py Normal file
View File

16
app/core/config.py Normal file
View File

@ -0,0 +1,16 @@
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "FastAPI App"
app_env: str = "development"
host: str = "127.0.0.1"
port: int = 8000
debug: bool = True
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()

8
app/core/logging.py Normal file
View File

@ -0,0 +1,8 @@
import logging
def configure_logging():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
)

29
app/main.py Normal file
View File

@ -0,0 +1,29 @@
from fastapi import FastAPI
from app.api.v1 import posts as posts_router
from app.core.config import settings
from app.core.logging import configure_logging
def create_app() -> FastAPI:
configure_logging()
app = FastAPI(title=settings.app_name)
# include routers
app.include_router(posts_router.router, prefix="/api/v1")
return app
app = create_app()
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host=settings.host,
port=settings.port,
reload=settings.app_env == "development",
log_level="info",
)

10
app/models/post.py Normal file
View File

@ -0,0 +1,10 @@
from dataclasses import dataclass
from typing import Optional
@dataclass
class Item:
id: int
name: str
description: Optional[str]
price: float

22
app/schemas/post.py Normal file
View File

@ -0,0 +1,22 @@
from typing import Optional
from pydantic import BaseModel
class PostCreate(BaseModel):
name: str
description: Optional[str] = None
price: float
class PostUpdate(BaseModel):
name: Optional[str]
description: Optional[str]
price: Optional[float]
class PostRead(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float

28
app/services/posts.py Normal file
View File

@ -0,0 +1,28 @@
from typing import List, Optional
from app.models.post import Post
def list_posts() -> List[Post]:
# get all posts from database
pass
def get_post(post_id: int) -> Optional[Post]:
# get single post
pass
def create_post(name: str, description: str | None, price: float) -> Post:
# create a post
pass
def update_post(post_id: int, **fields) -> Optional[Post]:
# update post
pass
def delete_post(post_id: int) -> bool:
# delete post
pass

4
dev-requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pre-commit
black==24.1.0
isort==5.12.0
ruff==0.15.0

0
main.py Normal file
View File

10
pyproject.toml Normal file
View File

@ -0,0 +1,10 @@
[tool.black]
line-length = 88
target-version = ["py310"]
[tool.isort]
profile = "black"
line_length = 88
[tool.ruff]
line-length = 88

0
requirements.txt Normal file
View File

4
scripts/setup-dev.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
python -m pip install --upgrade pip
python -m pip install -r dev-requirements.txt
echo "Done. Run 'pre-commit run --all-files' to check repo style now."