Spaces:
Runtime error
Runtime error
Ved Gupta
commited on
Commit
Β·
d67439e
1
Parent(s):
15ea62b
initial setup correct
Browse files- Pipfile +1 -1
- README.md +10 -3
- app/__init__.py +1 -1
- app/api/__init__.py +5 -6
- app/api/endpoints/__init__.py +1 -1
- app/api/endpoints/users.py +8 -6
- app/api/models/item.py +1 -4
- app/api/models/user.py +2 -2
- app/core/config.py +22 -9
- app/core/database.py +1 -1
- app/core/errors.py +31 -0
- app/core/security.py +14 -1
- app/main.py +1 -1
- app/tests/__init__.py +1 -1
- app/tests/test_api/__init__.py +1 -1
- requirements.txt +2 -1
Pipfile
CHANGED
@@ -5,7 +5,6 @@ name = "pypi"
|
|
5 |
|
6 |
[packages]
|
7 |
fastapi = "*"
|
8 |
-
uvicorn = {extras = ["standard"], version = "*"}
|
9 |
python-dotenv = "*"
|
10 |
sqlalchemy = "*"
|
11 |
psycopg2-binary = "*"
|
@@ -13,6 +12,7 @@ pytest = "*"
|
|
13 |
pytest-cov = "*"
|
14 |
faker = "*"
|
15 |
requests-mock = "*"
|
|
|
16 |
|
17 |
[dev-packages]
|
18 |
|
|
|
5 |
|
6 |
[packages]
|
7 |
fastapi = "*"
|
|
|
8 |
python-dotenv = "*"
|
9 |
sqlalchemy = "*"
|
10 |
psycopg2-binary = "*"
|
|
|
12 |
pytest-cov = "*"
|
13 |
faker = "*"
|
14 |
requests-mock = "*"
|
15 |
+
uvicorn = {extras = ["standard"], version = "*"}
|
16 |
|
17 |
[dev-packages]
|
18 |
|
README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
-
#
|
2 |
|
3 |
This is a production level project structure for a Python FastAPI project.
|
4 |
|
5 |
## Project Structure
|
6 |
|
7 |
```
|
8 |
-
|
9 |
βββ app
|
10 |
β βββ __init__.py
|
11 |
β βββ api
|
@@ -67,4 +67,11 @@ The project structure is organized as follows:
|
|
67 |
- `Dockerfile`: specifies the Docker image configuration.
|
68 |
- `requirements.txt`: specifies the Python dependencies.
|
69 |
- `README.md`: this file.
|
70 |
-
- `.vscode`: contains Visual Studio Code configuration files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# whisper.api
|
2 |
|
3 |
This is a production level project structure for a Python FastAPI project.
|
4 |
|
5 |
## Project Structure
|
6 |
|
7 |
```
|
8 |
+
whisper.api
|
9 |
βββ app
|
10 |
β βββ __init__.py
|
11 |
β βββ api
|
|
|
67 |
- `Dockerfile`: specifies the Docker image configuration.
|
68 |
- `requirements.txt`: specifies the Python dependencies.
|
69 |
- `README.md`: this file.
|
70 |
+
- `.vscode`: contains Visual Studio Code configuration files.
|
71 |
+
|
72 |
+
|
73 |
+
## Run this Project
|
74 |
+
|
75 |
+
```bash
|
76 |
+
uvicorn app.main:app --reload
|
77 |
+
```
|
app/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
from fastapi import FastAPI
|
2 |
|
3 |
-
from app.api
|
4 |
from app.core.config import settings
|
5 |
|
6 |
|
|
|
1 |
from fastapi import FastAPI
|
2 |
|
3 |
+
from app.api import api_router
|
4 |
from app.core.config import settings
|
5 |
|
6 |
|
app/api/__init__.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1 |
-
# File:
|
2 |
|
3 |
-
from fastapi import
|
4 |
from .endpoints import items, users
|
5 |
-
from .models import item, user
|
6 |
|
7 |
-
|
8 |
|
9 |
-
|
10 |
-
|
|
|
1 |
+
# File: whisper.api/app/api/__init__.py
|
2 |
|
3 |
+
from fastapi import APIRouter
|
4 |
from .endpoints import items, users
|
|
|
5 |
|
6 |
+
api_router = APIRouter()
|
7 |
|
8 |
+
api_router.include_router(items.router, prefix="/items", tags=["items"])
|
9 |
+
api_router.include_router(users.router, prefix="/users", tags=["users"])
|
app/api/endpoints/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# File:
|
2 |
|
3 |
from fastapi import APIRouter
|
4 |
|
|
|
1 |
+
# File: whisper.api/app/api/endpoints/__init__.py
|
2 |
|
3 |
from fastapi import APIRouter
|
4 |
|
app/api/endpoints/users.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1 |
from fastapi import APIRouter
|
2 |
|
3 |
from app.api.models.user import UserInDB
|
4 |
-
from app.
|
5 |
|
6 |
-
|
7 |
|
|
|
8 |
|
9 |
-
|
|
|
10 |
async def create_user(user: UserInDB):
|
11 |
query = UserInDB.insert().values(
|
12 |
username=user.username,
|
@@ -17,14 +19,14 @@ async def create_user(user: UserInDB):
|
|
17 |
return {**user.dict(), "id": user_id}
|
18 |
|
19 |
|
20 |
-
@
|
21 |
async def read_user(id: int):
|
22 |
query = UserInDB.select().where(UserInDB.c.id == id)
|
23 |
user = await database.fetch_one(query)
|
24 |
return user
|
25 |
|
26 |
|
27 |
-
@
|
28 |
async def update_user(id: int, user: UserInDB):
|
29 |
query = (
|
30 |
UserInDB
|
@@ -41,7 +43,7 @@ async def update_user(id: int, user: UserInDB):
|
|
41 |
return {**user.dict(), "id": user_id}
|
42 |
|
43 |
|
44 |
-
@
|
45 |
async def delete_user(id: int):
|
46 |
query = UserInDB.delete().where(UserInDB.c.id == id)
|
47 |
user_id = await database.execute(query)
|
|
|
1 |
from fastapi import APIRouter
|
2 |
|
3 |
from app.api.models.user import UserInDB
|
4 |
+
from app.core.database import SessionLocal
|
5 |
|
6 |
+
database = SessionLocal()
|
7 |
|
8 |
+
users_router = router = APIRouter()
|
9 |
|
10 |
+
|
11 |
+
@router.post("/", response_model=UserInDB, status_code=201)
|
12 |
async def create_user(user: UserInDB):
|
13 |
query = UserInDB.insert().values(
|
14 |
username=user.username,
|
|
|
19 |
return {**user.dict(), "id": user_id}
|
20 |
|
21 |
|
22 |
+
@router.get("/{id}/", response_model=UserInDB)
|
23 |
async def read_user(id: int):
|
24 |
query = UserInDB.select().where(UserInDB.c.id == id)
|
25 |
user = await database.fetch_one(query)
|
26 |
return user
|
27 |
|
28 |
|
29 |
+
@router.put("/{id}/", response_model=UserInDB)
|
30 |
async def update_user(id: int, user: UserInDB):
|
31 |
query = (
|
32 |
UserInDB
|
|
|
43 |
return {**user.dict(), "id": user_id}
|
44 |
|
45 |
|
46 |
+
@router.delete("/{id}/", response_model=int)
|
47 |
async def delete_user(id: int):
|
48 |
query = UserInDB.delete().where(UserInDB.c.id == id)
|
49 |
user_id = await database.execute(query)
|
app/api/models/item.py
CHANGED
@@ -13,7 +13,4 @@ class ItemCreate(ItemBase):
|
|
13 |
|
14 |
class Item(ItemBase):
|
15 |
id: int
|
16 |
-
owner_id: int
|
17 |
-
|
18 |
-
class Config:
|
19 |
-
orm_mode = True
|
|
|
13 |
|
14 |
class Item(ItemBase):
|
15 |
id: int
|
16 |
+
owner_id: int
|
|
|
|
|
|
app/api/models/user.py
CHANGED
@@ -10,5 +10,5 @@ class User(UserBase):
|
|
10 |
id: int
|
11 |
is_active: bool
|
12 |
|
13 |
-
|
14 |
-
|
|
|
10 |
id: int
|
11 |
is_active: bool
|
12 |
|
13 |
+
class UserInDB(User):
|
14 |
+
hashed_password: str
|
app/core/config.py
CHANGED
@@ -1,18 +1,24 @@
|
|
1 |
from typing import Any, Dict, List, Optional, Union
|
2 |
-
from pydantic import AnyHttpUrl,
|
3 |
|
|
|
|
|
|
|
4 |
|
5 |
class Settings(BaseSettings):
|
6 |
API_V1_STR: str = "/api/v1"
|
7 |
PROJECT_NAME: str = "Whisper API"
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
16 |
|
17 |
@validator("SECRET_KEY", pre=True)
|
18 |
def secret_key_must_be_set(cls, v: Optional[str], values: Dict[str, Any]) -> str:
|
@@ -56,5 +62,12 @@ class Settings(BaseSettings):
|
|
56 |
raise ValueError("POSTGRES_DB must be set")
|
57 |
return v
|
58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
settings = Settings()
|
|
|
1 |
from typing import Any, Dict, List, Optional, Union
|
2 |
+
from pydantic import AnyHttpUrl, validator
|
3 |
|
4 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
5 |
+
|
6 |
+
from os import environ as env
|
7 |
|
8 |
class Settings(BaseSettings):
|
9 |
API_V1_STR: str = "/api/v1"
|
10 |
PROJECT_NAME: str = "Whisper API"
|
11 |
+
PROJECT_VERSION: str = "0.1.0"
|
12 |
+
SECRET_KEY: str = env.get("SECRET_KEY")
|
13 |
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = env.get("ACCESS_TOKEN_EXPIRE_MINUTES")
|
14 |
+
SERVER_NAME: str = env.get("SERVER_NAME")
|
15 |
+
SERVER_HOST: AnyHttpUrl = env.get("SERVER_HOST")
|
16 |
+
POSTGRES_SERVER: str = env.get("POSTGRES_SERVER")
|
17 |
+
POSTGRES_USER: str = env.get("POSTGRES_USER")
|
18 |
+
POSTGRES_PASSWORD: str = env.get("POSTGRES_PASSWORD")
|
19 |
+
POSTGRES_DB: str = env.get("POSTGRES_DB")
|
20 |
+
POSTGRES_DATABASE_URL: str = env.get("POSTGRES_DATABASE_URL")
|
21 |
+
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = ['http://localhost:3000']
|
22 |
|
23 |
@validator("SECRET_KEY", pre=True)
|
24 |
def secret_key_must_be_set(cls, v: Optional[str], values: Dict[str, Any]) -> str:
|
|
|
62 |
raise ValueError("POSTGRES_DB must be set")
|
63 |
return v
|
64 |
|
65 |
+
@validator("POSTGRES_DATABASE_URL", pre=True)
|
66 |
+
def postgres_db_url_must_be_set(cls, v: Optional[str], values: Dict[str, Any]) -> str:
|
67 |
+
if not v:
|
68 |
+
raise ValueError("POSTGRES_DATABASE_URL must be set")
|
69 |
+
return v
|
70 |
+
|
71 |
+
|
72 |
|
73 |
settings = Settings()
|
app/core/database.py
CHANGED
@@ -4,7 +4,7 @@ from sqlalchemy.orm import sessionmaker
|
|
4 |
|
5 |
from app.core.config import settings
|
6 |
|
7 |
-
SQLALCHEMY_DATABASE_URL = settings.
|
8 |
|
9 |
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
10 |
|
|
|
4 |
|
5 |
from app.core.config import settings
|
6 |
|
7 |
+
SQLALCHEMY_DATABASE_URL = settings.POSTGRES_DATABASE_URL
|
8 |
|
9 |
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
10 |
|
app/core/errors.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import Request
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from fastapi.exceptions import HTTPException , RequestValidationError
|
4 |
+
|
5 |
+
async def http_exception_handler(request: Request, exc: HTTPException):
|
6 |
+
"""
|
7 |
+
Exception handler for HTTP errors
|
8 |
+
"""
|
9 |
+
return JSONResponse(
|
10 |
+
status_code=exc.status_code,
|
11 |
+
content={"message": exc.detail},
|
12 |
+
)
|
13 |
+
|
14 |
+
async def http_error_handler(request: Request, exc: HTTPException):
|
15 |
+
"""
|
16 |
+
Exception handler for HTTP errors
|
17 |
+
"""
|
18 |
+
return JSONResponse(
|
19 |
+
status_code=exc.status_code,
|
20 |
+
content={"message": exc.detail},
|
21 |
+
)
|
22 |
+
|
23 |
+
|
24 |
+
def http422_error_handler(request: Request, exc: RequestValidationError):
|
25 |
+
"""
|
26 |
+
Exception handler for HTTP 422 errors
|
27 |
+
"""
|
28 |
+
return JSONResponse(
|
29 |
+
status_code=422,
|
30 |
+
content={"message": "Validation error", "details": exc.errors()},
|
31 |
+
)
|
app/core/security.py
CHANGED
@@ -4,4 +4,17 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
4 |
|
5 |
ALGORITHM = "HS256"
|
6 |
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
7 |
-
SECRET_KEY = "mysecretkey"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
ALGORITHM = "HS256"
|
6 |
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
7 |
+
SECRET_KEY = "mysecretkey"
|
8 |
+
|
9 |
+
|
10 |
+
def get_password_hash(password: str) -> str:
|
11 |
+
"""
|
12 |
+
Hashes a password using bcrypt algorithm
|
13 |
+
"""
|
14 |
+
return pwd_context.hash(password)
|
15 |
+
|
16 |
+
def verify_password(password: str, hash: str) -> bool:
|
17 |
+
"""
|
18 |
+
Verifies a password against a bcrypt hash
|
19 |
+
"""
|
20 |
+
return pwd_context.verify(password, hash)
|
app/main.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
from fastapi import FastAPI
|
2 |
-
from app.api
|
3 |
from app.core.config import settings
|
4 |
from app.core.errors import http_error_handler
|
5 |
from app.core.errors import http422_error_handler
|
|
|
1 |
from fastapi import FastAPI
|
2 |
+
from app.api import api_router
|
3 |
from app.core.config import settings
|
4 |
from app.core.errors import http_error_handler
|
5 |
from app.core.errors import http422_error_handler
|
app/tests/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# File:
|
2 |
|
3 |
# Import necessary modules
|
4 |
import pytest
|
|
|
1 |
+
# File: whisper.api/app/tests/__init__.py
|
2 |
|
3 |
# Import necessary modules
|
4 |
import pytest
|
app/tests/test_api/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# File:
|
2 |
|
3 |
from fastapi.testclient import TestClient
|
4 |
from my_fastapi_project.app.api import app
|
|
|
1 |
+
# File: whisper.api/app/tests/test_api/__init__.py
|
2 |
|
3 |
from fastapi.testclient import TestClient
|
4 |
from my_fastapi_project.app.api import app
|
requirements.txt
CHANGED
@@ -6,4 +6,5 @@ psycopg2-binary
|
|
6 |
pytest
|
7 |
pytest-cov
|
8 |
faker
|
9 |
-
requests-mock
|
|
|
|
6 |
pytest
|
7 |
pytest-cov
|
8 |
faker
|
9 |
+
requests-mock
|
10 |
+
passlib
|