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