init
This commit is contained in:
commit
e00aecac1f
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
APP_ENV=dev
|
||||||
|
PORT=8000
|
||||||
|
DEBUG=true
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.sqlite3
|
||||||
|
.ruff_cache
|
||||||
9
.vscode/extensions.json
vendored
Normal file
9
.vscode/extensions.json
vendored
Normal 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
46
.vscode/settings.json
vendored
Normal 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
48
.zed/settings.json
Normal 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
6
README.md
Normal 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
0
app/__init__.py
Normal file
0
app/api/__init__.py
Normal file
0
app/api/__init__.py
Normal file
0
app/api/v1/__init__.py
Normal file
0
app/api/v1/__init__.py
Normal file
44
app/api/v1/posts.py
Normal file
44
app/api/v1/posts.py
Normal 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
0
app/core/__init__.py
Normal file
16
app/core/config.py
Normal file
16
app/core/config.py
Normal 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
8
app/core/logging.py
Normal 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
29
app/main.py
Normal 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
10
app/models/post.py
Normal 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
22
app/schemas/post.py
Normal 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
28
app/services/posts.py
Normal 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
4
dev-requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pre-commit
|
||||||
|
black==24.1.0
|
||||||
|
isort==5.12.0
|
||||||
|
ruff==0.15.0
|
||||||
10
pyproject.toml
Normal file
10
pyproject.toml
Normal 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
0
requirements.txt
Normal file
4
scripts/setup-dev.sh
Executable file
4
scripts/setup-dev.sh
Executable 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."
|
||||||
Loading…
x
Reference in New Issue
Block a user