Spaces:
Sleeping
Sleeping
Praneeth Yerrapragada
commited on
Commit
•
5753020
1
Parent(s):
d5684b3
refactor: make postgresdb operations async
Browse files- app/api/routers/user.py +17 -24
- app/engine/postgresdb.py +57 -23
- app/model/base.py +5 -4
- app/model/transaction.py +11 -9
- app/model/user.py +54 -6
- app/schema/index.py +1 -1
- config/index.py +34 -0
- main.py +38 -51
- migration/env.py +23 -23
- poetry.lock +82 -82
- pyproject.toml +2 -1
app/api/routers/user.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1 |
-
|
2 |
from fastapi import APIRouter, Depends, HTTPException
|
3 |
-
from sqlalchemy.
|
4 |
|
5 |
-
from app.engine.postgresdb import
|
6 |
from app.schema.index import UserCreate, User as UserSchema
|
7 |
from app.model.user import User as UserModel
|
8 |
|
9 |
|
10 |
user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"])
|
|
|
11 |
|
12 |
|
13 |
@r.post(
|
@@ -20,18 +21,15 @@ user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"])
|
|
20 |
500: {"description": "Internal server error"},
|
21 |
},
|
22 |
)
|
23 |
-
async def create_user(user: UserCreate, db:
|
24 |
try:
|
25 |
-
|
26 |
-
|
27 |
-
)
|
28 |
if db_user and not db_user.is_deleted:
|
29 |
raise HTTPException(status_code=409, detail="User already exists")
|
30 |
|
31 |
-
|
32 |
-
db.
|
33 |
-
db.commit()
|
34 |
-
db.refresh(db_user)
|
35 |
return db_user
|
36 |
except Exception as e:
|
37 |
raise HTTPException(status_code=500, detail=str(e))
|
@@ -46,8 +44,8 @@ async def create_user(user: UserCreate, db: Session = Depends(get_db)):
|
|
46 |
500: {"description": "Internal server error"},
|
47 |
},
|
48 |
)
|
49 |
-
async def get_user(user_id: int, db:
|
50 |
-
user =
|
51 |
if not user:
|
52 |
raise HTTPException(status_code=404, detail="User not found")
|
53 |
return user
|
@@ -62,14 +60,11 @@ async def get_user(user_id: int, db: Session = Depends(get_db)):
|
|
62 |
500: {"description": "Internal server error"},
|
63 |
},
|
64 |
)
|
65 |
-
async def update_user(user_id: int, user_payload: UserCreate, db:
|
66 |
-
user =
|
67 |
if not user:
|
68 |
raise HTTPException(status_code=404, detail="User not found")
|
69 |
-
user.
|
70 |
-
user.email = user_payload.email
|
71 |
-
db.commit()
|
72 |
-
db.refresh(user)
|
73 |
return user
|
74 |
|
75 |
|
@@ -82,11 +77,9 @@ async def update_user(user_id: int, user_payload: UserCreate, db: Session = Depe
|
|
82 |
500: {"description": "Internal server error"},
|
83 |
},
|
84 |
)
|
85 |
-
async def delete_user(user_id: int, db:
|
86 |
-
user =
|
87 |
if not user:
|
88 |
raise HTTPException(status_code=404, detail="User not found")
|
89 |
-
user.
|
90 |
-
db.commit()
|
91 |
-
db.refresh(user)
|
92 |
return user
|
|
|
1 |
+
import logging
|
2 |
from fastapi import APIRouter, Depends, HTTPException
|
3 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
4 |
|
5 |
+
from app.engine.postgresdb import get_db_session
|
6 |
from app.schema.index import UserCreate, User as UserSchema
|
7 |
from app.model.user import User as UserModel
|
8 |
|
9 |
|
10 |
user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"])
|
11 |
+
logger = logging.getLogger(__name__)
|
12 |
|
13 |
|
14 |
@r.post(
|
|
|
21 |
500: {"description": "Internal server error"},
|
22 |
},
|
23 |
)
|
24 |
+
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db_session)):
|
25 |
try:
|
26 |
+
logger.info(f"Checking if user exists: {user.dict()}")
|
27 |
+
db_user = await UserModel.get(db, email=user.email)
|
|
|
28 |
if db_user and not db_user.is_deleted:
|
29 |
raise HTTPException(status_code=409, detail="User already exists")
|
30 |
|
31 |
+
logger.info(f"Creating user: {user.dict()}")
|
32 |
+
db_user = await UserModel.create(db, **user.dict())
|
|
|
|
|
33 |
return db_user
|
34 |
except Exception as e:
|
35 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
44 |
500: {"description": "Internal server error"},
|
45 |
},
|
46 |
)
|
47 |
+
async def get_user(user_id: int, db: AsyncSession = Depends(get_db_session)):
|
48 |
+
user = await UserModel.get(db, id=user_id)
|
49 |
if not user:
|
50 |
raise HTTPException(status_code=404, detail="User not found")
|
51 |
return user
|
|
|
60 |
500: {"description": "Internal server error"},
|
61 |
},
|
62 |
)
|
63 |
+
async def update_user(user_id: int, user_payload: UserCreate, db: AsyncSession = Depends(get_db_session)):
|
64 |
+
user = await UserModel.get(db, id=user_id)
|
65 |
if not user:
|
66 |
raise HTTPException(status_code=404, detail="User not found")
|
67 |
+
user = await UserModel.update(db, id=user_id, **user_payload.dict())
|
|
|
|
|
|
|
68 |
return user
|
69 |
|
70 |
|
|
|
77 |
500: {"description": "Internal server error"},
|
78 |
},
|
79 |
)
|
80 |
+
async def delete_user(user_id: int, db: AsyncSession = Depends(get_db_session)):
|
81 |
+
user = await UserModel.get(db, id=user_id)
|
82 |
if not user:
|
83 |
raise HTTPException(status_code=404, detail="User not found")
|
84 |
+
user = await UserModel.delete(db, id=user_id)
|
|
|
|
|
85 |
return user
|
app/engine/postgresdb.py
CHANGED
@@ -1,35 +1,69 @@
|
|
1 |
-
import os
|
2 |
import logging
|
3 |
-
|
4 |
-
from
|
5 |
-
from fastapi import Request
|
6 |
|
7 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
-
load_dotenv()
|
10 |
logger = logging.getLogger(__name__)
|
11 |
|
12 |
-
|
13 |
-
assert SQLALCHEMY_DATABASE_URL, f"SQLALCHEMY_DATABASE_URL is not set"
|
14 |
-
logger.info(f"SQLALCHEMY_DATABASE_URL: {SQLALCHEMY_DATABASE_URL}")
|
15 |
|
16 |
-
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
17 |
|
18 |
-
|
19 |
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
|
23 |
-
|
24 |
-
# session = SessionLocal()
|
25 |
-
# logger.info("Postgres Session created successfully!")
|
26 |
-
# try:
|
27 |
-
# yield session
|
28 |
-
# finally:
|
29 |
-
# session.close()
|
30 |
-
# logger.info("Postgres Session closed successfully!")
|
31 |
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
|
|
|
|
1 |
import logging
|
2 |
+
import contextlib
|
3 |
+
from typing import Any, AsyncIterator
|
|
|
4 |
|
5 |
+
from sqlalchemy.ext.asyncio import (
|
6 |
+
AsyncConnection,
|
7 |
+
AsyncSession,
|
8 |
+
async_sessionmaker,
|
9 |
+
create_async_engine,
|
10 |
+
)
|
11 |
+
from sqlalchemy.orm import declarative_base
|
12 |
+
|
13 |
+
from config.index import config as env
|
14 |
|
|
|
15 |
logger = logging.getLogger(__name__)
|
16 |
|
17 |
+
Base = declarative_base()
|
|
|
|
|
18 |
|
|
|
19 |
|
20 |
+
class PostgresDatabase:
|
21 |
|
22 |
+
def __init__(self, host: str, engine_kwargs: dict[str, Any] = {}):
|
23 |
+
self._engine = create_async_engine(host, **engine_kwargs)
|
24 |
+
self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine)
|
25 |
+
|
26 |
+
async def close(self):
|
27 |
+
if self._engine is None:
|
28 |
+
raise Exception("DatabaseSessionManager is not initialized")
|
29 |
+
await self._engine.dispose()
|
30 |
+
|
31 |
+
self._engine = None
|
32 |
+
self._sessionmaker = None
|
33 |
+
|
34 |
+
@contextlib.asynccontextmanager
|
35 |
+
async def connect(self) -> AsyncIterator[AsyncConnection]:
|
36 |
+
if self._engine is None:
|
37 |
+
raise Exception("DatabaseSessionManager is not initialized")
|
38 |
+
|
39 |
+
async with self._engine.begin() as connection:
|
40 |
+
try:
|
41 |
+
yield connection
|
42 |
+
except Exception:
|
43 |
+
await connection.rollback()
|
44 |
+
raise
|
45 |
+
|
46 |
+
@contextlib.asynccontextmanager
|
47 |
+
async def session(self) -> AsyncIterator[AsyncSession]:
|
48 |
+
if self._sessionmaker is None:
|
49 |
+
raise Exception("DatabaseSessionManager is not initialized")
|
50 |
+
|
51 |
+
session = self._sessionmaker()
|
52 |
+
try:
|
53 |
+
yield session
|
54 |
+
except Exception:
|
55 |
+
await session.rollback()
|
56 |
+
raise
|
57 |
+
finally:
|
58 |
+
await session.close()
|
59 |
+
|
60 |
+
def get_engine(self):
|
61 |
+
return self._engine
|
62 |
|
63 |
|
64 |
+
postgresdb = PostgresDatabase(env.SQLALCHEMY_DATABASE_URL, {"echo": True, "future": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
|
67 |
+
async def get_db_session():
|
68 |
+
async with postgresdb.session() as session:
|
69 |
+
yield session
|
app/model/base.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
-
from datetime import datetime
|
2 |
from sqlalchemy import Column, DateTime, Integer
|
3 |
|
4 |
from app.engine.postgresdb import Base
|
5 |
|
6 |
-
|
|
|
7 |
id = Column(Integer, primary_key=True, index=True)
|
8 |
-
created_at = Column(DateTime, default=datetime.now())
|
9 |
-
updated_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now())
|
|
|
1 |
+
from datetime import datetime, timezone
|
2 |
from sqlalchemy import Column, DateTime, Integer
|
3 |
|
4 |
from app.engine.postgresdb import Base
|
5 |
|
6 |
+
|
7 |
+
class BaseModel:
|
8 |
id = Column(Integer, primary_key=True, index=True)
|
9 |
+
created_at = Column(DateTime, default=datetime.now(timezone.utc))
|
10 |
+
updated_at = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
|
app/model/transaction.py
CHANGED
@@ -1,16 +1,18 @@
|
|
1 |
-
from
|
2 |
-
from sqlalchemy
|
|
|
3 |
|
4 |
from app.model.base import BaseModel
|
5 |
from app.engine.postgresdb import Base
|
6 |
|
|
|
7 |
class Transaction(Base, BaseModel):
|
8 |
__tablename__ = "transactions"
|
9 |
-
transaction_date
|
10 |
-
category
|
11 |
-
name_description
|
12 |
-
amount
|
13 |
-
type
|
14 |
|
15 |
-
user_id =
|
16 |
-
user = relationship("User", back_populates="transactions")
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from sqlalchemy import ForeignKey
|
3 |
+
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
4 |
|
5 |
from app.model.base import BaseModel
|
6 |
from app.engine.postgresdb import Base
|
7 |
|
8 |
+
|
9 |
class Transaction(Base, BaseModel):
|
10 |
__tablename__ = "transactions"
|
11 |
+
transaction_date: Mapped[datetime]
|
12 |
+
category: Mapped[str]
|
13 |
+
name_description: Mapped[str]
|
14 |
+
amount: Mapped[float]
|
15 |
+
type: Mapped[str]
|
16 |
|
17 |
+
user_id = mapped_column(ForeignKey("users.id"))
|
18 |
+
user = relationship("User", back_populates="transactions")
|
app/model/user.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
from sqlalchemy import
|
2 |
-
from sqlalchemy.
|
|
|
3 |
|
4 |
from app.model.base import BaseModel
|
5 |
from app.engine.postgresdb import Base
|
@@ -8,9 +9,56 @@ from app.engine.postgresdb import Base
|
|
8 |
class User(Base, BaseModel):
|
9 |
__tablename__ = "users"
|
10 |
|
11 |
-
name
|
12 |
-
email
|
13 |
-
hashed_password
|
14 |
-
is_deleted =
|
15 |
|
16 |
transactions = relationship("Transaction", back_populates="user")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
2 |
+
from sqlalchemy.sql import expression as sql
|
3 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
4 |
|
5 |
from app.model.base import BaseModel
|
6 |
from app.engine.postgresdb import Base
|
|
|
9 |
class User(Base, BaseModel):
|
10 |
__tablename__ = "users"
|
11 |
|
12 |
+
name: Mapped[str]
|
13 |
+
email: Mapped[str]
|
14 |
+
hashed_password: Mapped[str]
|
15 |
+
is_deleted: Mapped[bool] = mapped_column(default=False)
|
16 |
|
17 |
transactions = relationship("Transaction", back_populates="user")
|
18 |
+
|
19 |
+
@classmethod
|
20 |
+
async def create(cls: "type[User]", db: AsyncSession, **kwargs) -> "User":
|
21 |
+
query = sql.insert(cls).values(**kwargs).returning(cls.id)
|
22 |
+
users = await db.execute(query)
|
23 |
+
await db.commit()
|
24 |
+
return users.first()
|
25 |
+
|
26 |
+
@classmethod
|
27 |
+
async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs) -> "User":
|
28 |
+
query = (
|
29 |
+
sql.update(cls)
|
30 |
+
.where(cls.id == id)
|
31 |
+
.values(**kwargs)
|
32 |
+
.execution_options(synchronize_session="fetch")
|
33 |
+
.returning(cls.id)
|
34 |
+
)
|
35 |
+
users = await db.execute(query)
|
36 |
+
await db.commit()
|
37 |
+
return users.first()
|
38 |
+
|
39 |
+
@classmethod
|
40 |
+
async def get(cls: "type[User]", db: AsyncSession, id: int) -> "User":
|
41 |
+
query = sql.select(cls).where(cls.id == id)
|
42 |
+
users = await db.execute(query)
|
43 |
+
(user,) = users.first()
|
44 |
+
return user
|
45 |
+
|
46 |
+
@classmethod
|
47 |
+
async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
|
48 |
+
query = sql.select(cls).where(cls.email == email)
|
49 |
+
users = await db.execute(query)
|
50 |
+
(user,) = users.first()
|
51 |
+
return user
|
52 |
+
|
53 |
+
@classmethod
|
54 |
+
async def delete(cls: "type[User]", db: AsyncSession, id: int) -> "User":
|
55 |
+
query = (
|
56 |
+
sql.update(cls)
|
57 |
+
.where(cls.id == id)
|
58 |
+
.values(is_deleted=True)
|
59 |
+
.execution_options(synchronize_session="fetch")
|
60 |
+
.returning(cls.id)
|
61 |
+
)
|
62 |
+
users = await db.execute(query)
|
63 |
+
await db.commit()
|
64 |
+
return users.first()
|
app/schema/index.py
CHANGED
@@ -21,7 +21,7 @@ class User(BaseModel):
|
|
21 |
email: str
|
22 |
hashed_password: str
|
23 |
is_deleted: bool = False
|
24 |
-
transactions: "List[Transaction]"
|
25 |
|
26 |
|
27 |
class Transaction(BaseModel):
|
|
|
21 |
email: str
|
22 |
hashed_password: str
|
23 |
is_deleted: bool = False
|
24 |
+
transactions: "List[Transaction]" = []
|
25 |
|
26 |
|
27 |
class Transaction(BaseModel):
|
config/index.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
|
4 |
+
load_dotenv()
|
5 |
+
|
6 |
+
|
7 |
+
class Config:
|
8 |
+
DEBUG = os.getenv("DEBUG", False)
|
9 |
+
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
|
10 |
+
SQLALCHEMY_TRACK_MODIFICATIONS = os.getenv("SQLALCHEMY_TRACK_MODIFICATIONS", False)
|
11 |
+
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev")
|
12 |
+
APP_HOST = os.getenv("APP_HOST", "0.0.0.0")
|
13 |
+
APP_PORT = int(os.getenv("APP_PORT", "8000"))
|
14 |
+
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
|
15 |
+
PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME")
|
16 |
+
PINECONE_ENVIRONMENT = os.getenv("PINECONE_ENVIRONMENT")
|
17 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
18 |
+
OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
|
19 |
+
MODEL = os.getenv("MODEL", "gpt-3.5-turbo")
|
20 |
+
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-ada-002")
|
21 |
+
EMBEDDING_DIM = os.getenv("EMBEDDING_DIM", "1536")
|
22 |
+
CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1024"))
|
23 |
+
CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "20"))
|
24 |
+
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "4000"))
|
25 |
+
|
26 |
+
POSTGRES_USER = os.getenv("POSTGRES_USER")
|
27 |
+
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
|
28 |
+
POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME")
|
29 |
+
POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST")
|
30 |
+
POSTGRES_DB_PORT = os.getenv("POSTGRES_DB_PORT")
|
31 |
+
SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_DB_NAME}"
|
32 |
+
|
33 |
+
|
34 |
+
config = Config
|
main.py
CHANGED
@@ -1,89 +1,76 @@
|
|
1 |
-
from dotenv import load_dotenv
|
2 |
-
|
3 |
-
load_dotenv()
|
4 |
-
|
5 |
import logging
|
6 |
import os
|
7 |
import uvicorn
|
8 |
from contextlib import asynccontextmanager
|
9 |
-
from fastapi import FastAPI
|
10 |
from fastapi.middleware.cors import CORSMiddleware
|
11 |
from fastapi.responses import RedirectResponse
|
12 |
from app.api.routers.chat import chat_router
|
13 |
from app.api.routers.user import user_router
|
14 |
from app.settings import init_settings
|
15 |
-
from app.observability import init_observability
|
16 |
from fastapi.staticfiles import StaticFiles
|
17 |
from alembic.config import Config
|
18 |
from alembic import command
|
19 |
|
20 |
|
21 |
-
from app.engine.postgresdb import
|
|
|
22 |
|
23 |
logger = logging.getLogger("uvicorn")
|
24 |
|
25 |
-
# Create all tables in the database
|
26 |
-
Base.metadata.create_all(bind=engine)
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
32 |
|
33 |
|
34 |
@asynccontextmanager
|
35 |
async def lifespan(app_: FastAPI):
|
36 |
-
logger.info("Starting up...")
|
37 |
-
logger.info("Run 'alembic upgrade head' to apply migrations...")
|
38 |
-
run_migrations()
|
39 |
yield
|
40 |
-
|
|
|
41 |
|
42 |
|
43 |
-
|
|
|
44 |
|
45 |
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
55 |
|
|
|
|
|
|
|
|
|
56 |
|
57 |
-
|
58 |
-
init_observability()
|
59 |
|
60 |
-
environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set
|
61 |
|
62 |
-
|
63 |
-
logger.warning("Running in development mode - allowing CORS for all origins")
|
64 |
-
app.add_middleware(
|
65 |
-
CORSMiddleware,
|
66 |
-
allow_origins=["*"],
|
67 |
-
allow_credentials=True,
|
68 |
-
allow_methods=["*"],
|
69 |
-
allow_headers=["*"],
|
70 |
-
)
|
71 |
|
|
|
72 |
# Redirect to documentation page when accessing base URL
|
73 |
@app.get("/")
|
74 |
async def redirect_to_docs():
|
75 |
return RedirectResponse(url="/docs")
|
76 |
|
77 |
-
|
78 |
-
if os.path.exists("data"):
|
79 |
-
app.mount("/api/data", StaticFiles(directory="data"), name="static")
|
80 |
-
app.include_router(chat_router, prefix="/api/chat")
|
81 |
-
app.include_router(user_router)
|
82 |
-
|
83 |
-
|
84 |
-
if __name__ == "__main__":
|
85 |
-
app_host = os.getenv("APP_HOST", "0.0.0.0")
|
86 |
-
app_port = int(os.getenv("APP_PORT", "8000"))
|
87 |
-
reload = True if environment == "dev" else False
|
88 |
-
|
89 |
-
uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload)
|
|
|
|
|
|
|
|
|
|
|
1 |
import logging
|
2 |
import os
|
3 |
import uvicorn
|
4 |
from contextlib import asynccontextmanager
|
5 |
+
from fastapi import FastAPI
|
6 |
from fastapi.middleware.cors import CORSMiddleware
|
7 |
from fastapi.responses import RedirectResponse
|
8 |
from app.api.routers.chat import chat_router
|
9 |
from app.api.routers.user import user_router
|
10 |
from app.settings import init_settings
|
|
|
11 |
from fastapi.staticfiles import StaticFiles
|
12 |
from alembic.config import Config
|
13 |
from alembic import command
|
14 |
|
15 |
|
16 |
+
from app.engine.postgresdb import postgresdb
|
17 |
+
from config.index import config
|
18 |
|
19 |
logger = logging.getLogger("uvicorn")
|
20 |
|
|
|
|
|
21 |
|
22 |
+
def run_migrations(connection, cfg):
|
23 |
+
cfg.attributes["connection"] = connection
|
24 |
+
command.upgrade(cfg, "head")
|
25 |
+
|
26 |
+
|
27 |
+
async def run_async_migrations():
|
28 |
+
logger.info("Running migrations...")
|
29 |
+
async_engine = postgresdb.get_engine()
|
30 |
|
31 |
+
async with async_engine.begin() as conn:
|
32 |
+
await conn.run_sync(run_migrations, Config("alembic.ini"))
|
33 |
+
|
34 |
+
logger.info("Finished running migrations")
|
35 |
|
36 |
|
37 |
@asynccontextmanager
|
38 |
async def lifespan(app_: FastAPI):
|
|
|
|
|
|
|
39 |
yield
|
40 |
+
if postgresdb.get_engine() is not None:
|
41 |
+
await postgresdb.close()
|
42 |
|
43 |
|
44 |
+
init_settings()
|
45 |
+
# init_observability()
|
46 |
|
47 |
|
48 |
+
def init_app():
|
49 |
+
app = FastAPI(lifespan=lifespan)
|
50 |
+
if config.ENVIRONMENT == "dev":
|
51 |
+
logger.warning("Running in development mode - allowing CORS for all origins")
|
52 |
+
app.add_middleware(
|
53 |
+
CORSMiddleware,
|
54 |
+
allow_origins=["*"],
|
55 |
+
allow_credentials=True,
|
56 |
+
allow_methods=["*"],
|
57 |
+
allow_headers=["*"],
|
58 |
+
)
|
59 |
|
60 |
+
if os.path.exists("data"):
|
61 |
+
app.mount("/api/data", StaticFiles(directory="data"), name="static")
|
62 |
+
app.include_router(chat_router, prefix="/api/chat")
|
63 |
+
app.include_router(user_router)
|
64 |
|
65 |
+
return app
|
|
|
66 |
|
|
|
67 |
|
68 |
+
app = init_app()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
+
if __name__ == "__main__":
|
71 |
# Redirect to documentation page when accessing base URL
|
72 |
@app.get("/")
|
73 |
async def redirect_to_docs():
|
74 |
return RedirectResponse(url="/docs")
|
75 |
|
76 |
+
uvicorn.run(app="main:app", host=config.APP_HOST, port=config.APP_PORT, reload=(config.ENVIRONMENT == "dev"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
migration/env.py
CHANGED
@@ -1,21 +1,20 @@
|
|
1 |
-
import
|
2 |
from logging.config import fileConfig
|
3 |
|
4 |
from app.engine.postgresdb import Base
|
5 |
-
from sqlalchemy import engine_from_config
|
6 |
from sqlalchemy import pool
|
|
|
7 |
|
8 |
-
from
|
9 |
-
|
10 |
-
from dotenv import load_dotenv
|
11 |
|
12 |
-
|
13 |
|
14 |
# this is the Alembic Config object, which provides
|
15 |
# access to the values within the .ini file in use.
|
16 |
config = context.config
|
17 |
|
18 |
-
config.set_main_option("sqlalchemy.url",
|
19 |
|
20 |
# Interpret the config file for Python logging.
|
21 |
# This line sets up loggers basically.
|
@@ -37,17 +36,14 @@ target_metadata = Base.metadata
|
|
37 |
# ... etc.
|
38 |
|
39 |
|
40 |
-
def run_migrations_offline()
|
41 |
"""Run migrations in 'offline' mode.
|
42 |
-
|
43 |
This configures the context with just a URL
|
44 |
and not an Engine, though an Engine is acceptable
|
45 |
here as well. By skipping the Engine creation
|
46 |
we don't even need a DBAPI to be available.
|
47 |
-
|
48 |
Calls to context.execute() here emit the given string to the
|
49 |
script output.
|
50 |
-
|
51 |
"""
|
52 |
url = config.get_main_option("sqlalchemy.url")
|
53 |
context.configure(
|
@@ -61,29 +57,33 @@ def run_migrations_offline() -> None:
|
|
61 |
context.run_migrations()
|
62 |
|
63 |
|
64 |
-
def
|
65 |
-
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
In this scenario we need to create an Engine
|
68 |
and associate a connection with the context.
|
69 |
-
|
70 |
"""
|
71 |
-
|
72 |
-
|
|
|
|
|
73 |
prefix="sqlalchemy.",
|
74 |
poolclass=pool.NullPool,
|
75 |
)
|
76 |
|
77 |
-
with connectable.connect() as connection:
|
78 |
-
|
79 |
-
connection=connection, target_metadata=target_metadata, compare_type=True
|
80 |
-
)
|
81 |
|
82 |
-
|
83 |
-
context.run_migrations()
|
84 |
|
85 |
|
86 |
if context.is_offline_mode():
|
87 |
run_migrations_offline()
|
88 |
else:
|
89 |
-
run_migrations_online()
|
|
|
1 |
+
import asyncio
|
2 |
from logging.config import fileConfig
|
3 |
|
4 |
from app.engine.postgresdb import Base
|
5 |
+
from sqlalchemy import engine_from_config, Connection
|
6 |
from sqlalchemy import pool
|
7 |
+
from sqlalchemy.ext.asyncio import AsyncEngine, async_engine_from_config
|
8 |
|
9 |
+
from config.index import config as env
|
|
|
|
|
10 |
|
11 |
+
from alembic import context
|
12 |
|
13 |
# this is the Alembic Config object, which provides
|
14 |
# access to the values within the .ini file in use.
|
15 |
config = context.config
|
16 |
|
17 |
+
config.set_main_option("sqlalchemy.url", env.SQLALCHEMY_DATABASE_URL)
|
18 |
|
19 |
# Interpret the config file for Python logging.
|
20 |
# This line sets up loggers basically.
|
|
|
36 |
# ... etc.
|
37 |
|
38 |
|
39 |
+
def run_migrations_offline():
|
40 |
"""Run migrations in 'offline' mode.
|
|
|
41 |
This configures the context with just a URL
|
42 |
and not an Engine, though an Engine is acceptable
|
43 |
here as well. By skipping the Engine creation
|
44 |
we don't even need a DBAPI to be available.
|
|
|
45 |
Calls to context.execute() here emit the given string to the
|
46 |
script output.
|
|
|
47 |
"""
|
48 |
url = config.get_main_option("sqlalchemy.url")
|
49 |
context.configure(
|
|
|
57 |
context.run_migrations()
|
58 |
|
59 |
|
60 |
+
def do_run_migrations(connection: Connection) -> None:
|
61 |
+
context.configure(connection=connection, target_metadata=target_metadata)
|
62 |
|
63 |
+
with context.begin_transaction():
|
64 |
+
context.run_migrations()
|
65 |
+
|
66 |
+
|
67 |
+
async def run_migrations_online():
|
68 |
+
"""Run migrations in 'online' mode.
|
69 |
In this scenario we need to create an Engine
|
70 |
and associate a connection with the context.
|
|
|
71 |
"""
|
72 |
+
configuration = config.get_section(config.config_ini_section)
|
73 |
+
configuration["sqlalchemy.url"] = env.SQLALCHEMY_DATABASE_URL
|
74 |
+
connectable = async_engine_from_config(
|
75 |
+
configuration,
|
76 |
prefix="sqlalchemy.",
|
77 |
poolclass=pool.NullPool,
|
78 |
)
|
79 |
|
80 |
+
async with connectable.connect() as connection:
|
81 |
+
await connection.run_sync(do_run_migrations)
|
|
|
|
|
82 |
|
83 |
+
await connectable.dispose()
|
|
|
84 |
|
85 |
|
86 |
if context.is_offline_mode():
|
87 |
run_migrations_offline()
|
88 |
else:
|
89 |
+
asyncio.run(run_migrations_online())
|
poetry.lock
CHANGED
@@ -198,6 +198,87 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin
|
|
198 |
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
199 |
trio = ["trio (>=0.23)"]
|
200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
[[package]]
|
202 |
name = "attrs"
|
203 |
version = "23.2.0"
|
@@ -2661,87 +2742,6 @@ files = [
|
|
2661 |
{file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
|
2662 |
]
|
2663 |
|
2664 |
-
[[package]]
|
2665 |
-
name = "psycopg2-binary"
|
2666 |
-
version = "2.9.9"
|
2667 |
-
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
2668 |
-
optional = false
|
2669 |
-
python-versions = ">=3.7"
|
2670 |
-
files = [
|
2671 |
-
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
|
2672 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
|
2673 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
|
2674 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
|
2675 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
|
2676 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
|
2677 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
|
2678 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
|
2679 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
|
2680 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
|
2681 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
|
2682 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
|
2683 |
-
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
|
2684 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
|
2685 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
|
2686 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
|
2687 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
|
2688 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
|
2689 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
|
2690 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
|
2691 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
|
2692 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
|
2693 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
|
2694 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
2695 |
-
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
2696 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
2697 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
|
2698 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
2699 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
2700 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
2701 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
|
2702 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
|
2703 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
2704 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
2705 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
2706 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
|
2707 |
-
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
|
2708 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
2709 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
2710 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
2711 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
|
2712 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
|
2713 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
|
2714 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
|
2715 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
|
2716 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
|
2717 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
|
2718 |
-
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
|
2719 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
|
2720 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
|
2721 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
|
2722 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
|
2723 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
|
2724 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
|
2725 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
|
2726 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
|
2727 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
|
2728 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
|
2729 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
|
2730 |
-
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
|
2731 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
|
2732 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
|
2733 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
|
2734 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
|
2735 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
|
2736 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
|
2737 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
|
2738 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
|
2739 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
|
2740 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
|
2741 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
|
2742 |
-
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
|
2743 |
-
]
|
2744 |
-
|
2745 |
[[package]]
|
2746 |
name = "pydantic"
|
2747 |
version = "2.7.2"
|
@@ -4210,4 +4210,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more
|
|
4210 |
[metadata]
|
4211 |
lock-version = "2.0"
|
4212 |
python-versions = "^3.11,<3.12"
|
4213 |
-
content-hash = "
|
|
|
198 |
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
199 |
trio = ["trio (>=0.23)"]
|
200 |
|
201 |
+
[[package]]
|
202 |
+
name = "async-timeout"
|
203 |
+
version = "4.0.3"
|
204 |
+
description = "Timeout context manager for asyncio programs"
|
205 |
+
optional = false
|
206 |
+
python-versions = ">=3.7"
|
207 |
+
files = [
|
208 |
+
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
209 |
+
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
210 |
+
]
|
211 |
+
|
212 |
+
[[package]]
|
213 |
+
name = "asyncio"
|
214 |
+
version = "3.4.3"
|
215 |
+
description = "reference implementation of PEP 3156"
|
216 |
+
optional = false
|
217 |
+
python-versions = "*"
|
218 |
+
files = [
|
219 |
+
{file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"},
|
220 |
+
{file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"},
|
221 |
+
{file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"},
|
222 |
+
{file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"},
|
223 |
+
]
|
224 |
+
|
225 |
+
[[package]]
|
226 |
+
name = "asyncpg"
|
227 |
+
version = "0.29.0"
|
228 |
+
description = "An asyncio PostgreSQL driver"
|
229 |
+
optional = false
|
230 |
+
python-versions = ">=3.8.0"
|
231 |
+
files = [
|
232 |
+
{file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
|
233 |
+
{file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
|
234 |
+
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"},
|
235 |
+
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"},
|
236 |
+
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"},
|
237 |
+
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"},
|
238 |
+
{file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"},
|
239 |
+
{file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"},
|
240 |
+
{file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
|
241 |
+
{file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
|
242 |
+
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
|
243 |
+
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
|
244 |
+
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
|
245 |
+
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
|
246 |
+
{file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
|
247 |
+
{file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
|
248 |
+
{file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
|
249 |
+
{file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
|
250 |
+
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
|
251 |
+
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
|
252 |
+
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
|
253 |
+
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
|
254 |
+
{file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
|
255 |
+
{file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
|
256 |
+
{file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"},
|
257 |
+
{file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"},
|
258 |
+
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"},
|
259 |
+
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"},
|
260 |
+
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"},
|
261 |
+
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"},
|
262 |
+
{file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"},
|
263 |
+
{file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"},
|
264 |
+
{file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"},
|
265 |
+
{file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"},
|
266 |
+
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"},
|
267 |
+
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"},
|
268 |
+
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"},
|
269 |
+
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"},
|
270 |
+
{file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"},
|
271 |
+
{file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"},
|
272 |
+
{file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"},
|
273 |
+
]
|
274 |
+
|
275 |
+
[package.dependencies]
|
276 |
+
async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
|
277 |
+
|
278 |
+
[package.extras]
|
279 |
+
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
280 |
+
test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
|
281 |
+
|
282 |
[[package]]
|
283 |
name = "attrs"
|
284 |
version = "23.2.0"
|
|
|
2742 |
{file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
|
2743 |
]
|
2744 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2745 |
[[package]]
|
2746 |
name = "pydantic"
|
2747 |
version = "2.7.2"
|
|
|
4210 |
[metadata]
|
4211 |
lock-version = "2.0"
|
4212 |
python-versions = "^3.11,<3.12"
|
4213 |
+
content-hash = "ea7579f435b0c476a57fe23453277a8e90d2815e0a86efc7ff300a5ddbd434bb"
|
pyproject.toml
CHANGED
@@ -28,11 +28,12 @@ pydantic = "^2.7.2"
|
|
28 |
dateparser = "^1.2.0"
|
29 |
pandas = "^2.2.2"
|
30 |
path = "^16.14.0"
|
31 |
-
psycopg2-binary = "^2.9.9"
|
32 |
pytest-xdist = "^3.6.1"
|
33 |
pytest-asyncio = "^0.23.7"
|
34 |
httpx = "^0.27.0"
|
35 |
alembic = "^1.13.1"
|
|
|
|
|
36 |
|
37 |
[tool.poetry.dependencies.uvicorn]
|
38 |
extras = [ "standard" ]
|
|
|
28 |
dateparser = "^1.2.0"
|
29 |
pandas = "^2.2.2"
|
30 |
path = "^16.14.0"
|
|
|
31 |
pytest-xdist = "^3.6.1"
|
32 |
pytest-asyncio = "^0.23.7"
|
33 |
httpx = "^0.27.0"
|
34 |
alembic = "^1.13.1"
|
35 |
+
asyncpg = "^0.29.0"
|
36 |
+
asyncio = "^3.4.3"
|
37 |
|
38 |
[tool.poetry.dependencies.uvicorn]
|
39 |
extras = [ "standard" ]
|