[add] backend
This commit is contained in:
parent
0fe588f164
commit
0edd2c615b
|
|
@ -0,0 +1,2 @@
|
||||||
|
.env
|
||||||
|
wallet/
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
from sqlmodel import Session
|
||||||
|
from analysis_session.domain.repository.analysis_session_repo import IAnalysisSessionRepository
|
||||||
|
|
||||||
|
class AnalysisService:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
analysis_session_repo: IAnalysisSessionRepository,
|
||||||
|
db_session: Session
|
||||||
|
):
|
||||||
|
self.analysis_session_repo = analysis_session_repo
|
||||||
|
self.db_session = db_session
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class IAnalysisSessionRepository(ABC):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
from datetime import datetime,date
|
||||||
|
from sqlmodel import SQLModel, Field, JSON
|
||||||
|
import uuid
|
||||||
|
import enum
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy.dialects import oracle
|
||||||
|
from utils.auth import Role
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class AnalysisStatus(str, enum.Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
RUNNING = "running"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
FAILED = "failed"
|
||||||
|
CANCELLED = "cancelled"
|
||||||
|
|
||||||
|
|
||||||
|
class AnalysisSession(SQLModel, table=True):
|
||||||
|
__tablename__ = "analysis_sessions"
|
||||||
|
id: str = Field(default=None, max_length=36, primary_key=True)
|
||||||
|
member_id: str = Field(foreign_key="members.id")
|
||||||
|
ticker: str
|
||||||
|
analysis_date: date
|
||||||
|
analysts_selected: list[str] = Field(sa_column=Column(JSON))
|
||||||
|
research_depth: int
|
||||||
|
llm_provider: str
|
||||||
|
backend_url: str
|
||||||
|
shallow_thinker: str
|
||||||
|
deep_thinker: str
|
||||||
|
status: AnalysisStatus = Field(default=AnalysisStatus.PENDING)
|
||||||
|
final_report: str | None = None
|
||||||
|
error_message: str | None = None
|
||||||
|
completed_at: datetime | None = None
|
||||||
|
created_at : datetime = Field(nullable=False)
|
||||||
|
updated_at : datetime = Field(nullable=False)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from analysis_session.domain.repository.analysis_session_repo import IAnalysisSessionRepository
|
||||||
|
|
||||||
|
class AnalysisSessionRepository(IAnalysisSessionRepository):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/analysis_session", tags=["analysis_session"])
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from .config import get_settings
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from functools import lru_cache
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
# MySQL 데이터베이스 설정
|
||||||
|
DB_HOST: str
|
||||||
|
DB_PORT: int
|
||||||
|
DB_USER: str
|
||||||
|
DB_PASSWORD: str
|
||||||
|
DB_NAME: str
|
||||||
|
SECRET_KEY: str
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_settings():
|
||||||
|
return Settings()
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from utils.database import create_db_and_tables
|
||||||
|
from utils.containers import Container
|
||||||
|
|
||||||
|
|
||||||
|
from analysis_session.interface.controller.analysis_session_controller import router as analysis_session_router
|
||||||
|
from member.interface.controller.member_controller import router as member_router
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.include_router(analysis_session_router)
|
||||||
|
app.include_router(member_router)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup_db_client():
|
||||||
|
create_db_and_tables()
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
from sqlmodel import Session
|
||||||
|
from utils.crypto import Crypto
|
||||||
|
from member.domain.repository.member_repo import IMemberRepository
|
||||||
|
from utils.auth import Role
|
||||||
|
from member.domain.member import Member as MemberVO
|
||||||
|
from fastapi import HTTPException, status
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ulid import ULID
|
||||||
|
|
||||||
|
class MemberService:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
member_repo: IMemberRepository,
|
||||||
|
crypto: Crypto,
|
||||||
|
db_session: Session,
|
||||||
|
ulid: ULID
|
||||||
|
):
|
||||||
|
self.member_repo = member_repo
|
||||||
|
self.crypto = crypto
|
||||||
|
self.db_session = db_session
|
||||||
|
self.ulid = ulid
|
||||||
|
|
||||||
|
def create_member(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
email: str,
|
||||||
|
password: str,
|
||||||
|
role: Role
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
if self.member_repo.find_by_email(email):
|
||||||
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already exists")
|
||||||
|
except Exception as e:
|
||||||
|
self.db_session.rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
member_vo = MemberVO(
|
||||||
|
id=self.ulid.generate(),
|
||||||
|
name=name,
|
||||||
|
email=email,
|
||||||
|
password=self.crypto.encrypt(password),
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
role=role
|
||||||
|
)
|
||||||
|
|
||||||
|
saved_member = self.member_repo.save(member_vo)
|
||||||
|
self.db_session.commit()
|
||||||
|
|
||||||
|
return saved_member
|
||||||
|
|
||||||
|
|
||||||
|
def get_members(
|
||||||
|
self,
|
||||||
|
page: int,
|
||||||
|
items_per_page: int
|
||||||
|
)->tuple[int, list[MemberVO]] :
|
||||||
|
return self.member_repo.get_members(page, items_per_page)
|
||||||
|
|
||||||
|
def get_member(
|
||||||
|
self,
|
||||||
|
id: str
|
||||||
|
)->MemberVO | None:
|
||||||
|
member = self.member_repo.find_by_id(id)
|
||||||
|
if not member:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
|
||||||
|
return member
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from utils.auth import Role
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class Member(BaseModel):
|
||||||
|
id: str | None = None
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
role: Role
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class IMemberRepository(ABC):
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from member.domain.member import Member as MemberVO
|
||||||
|
|
||||||
|
class IMemberRepository(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_by_email(self, email: str) -> MemberVO | None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self, member: MemberVO) -> MemberVO:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_by_id(self, id: str) -> MemberVO | None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_members(self, page: int, items_per_page: int) -> tuple[int, list[MemberVO]]:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
from sqlalchemy import Column, UUID, Numeric, VARCHAR # 필요한 타입들을 sqlalchemy에서 가져옵니다.
|
||||||
|
from utils.auth import Role
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Member(SQLModel, table=True):
|
||||||
|
__tablename__ = "members"
|
||||||
|
id : str = Field(default=None, max_length=36, primary_key=True)
|
||||||
|
email : str = Field(max_length=64, unique=True, nullable=False)
|
||||||
|
name : str = Field(max_length=32, nullable=False)
|
||||||
|
password : str = Field(max_length=64, nullable=False)
|
||||||
|
is_active : bool = Field(default=True, nullable=False)
|
||||||
|
created_at : datetime = Field(nullable=False)
|
||||||
|
updated_at : datetime = Field(nullable=False)
|
||||||
|
role : Role = Field(default=Role.USER, nullable=False)
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
from member.domain.repository import IMemberRepository
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from member.domain.member import Member as MemberVO
|
||||||
|
from member.infra.db_models.member import Member
|
||||||
|
from utils.db_utils import row_to_dict
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
|
||||||
|
class MemberRepository(IMemberRepository):
|
||||||
|
def __init__(self, session: Session):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
def find_by_email(self, email: str) -> MemberVO | None:
|
||||||
|
query = select(Member).where(Member.email == email)
|
||||||
|
member = self.session.exec(query).first()
|
||||||
|
|
||||||
|
if not member:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return MemberVO(**row_to_dict(member))
|
||||||
|
|
||||||
|
def save(self, member: MemberVO) -> MemberVO:
|
||||||
|
new_member = Member(
|
||||||
|
id=member.id,
|
||||||
|
email=member.email,
|
||||||
|
name=member.name,
|
||||||
|
password=member.password,
|
||||||
|
role=member.role,
|
||||||
|
created_at=member.created_at,
|
||||||
|
updated_at=member.updated_at
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session.add(new_member)
|
||||||
|
self.session.flush()
|
||||||
|
self.session.refresh(new_member)
|
||||||
|
|
||||||
|
member.id = new_member.id
|
||||||
|
return member
|
||||||
|
|
||||||
|
|
||||||
|
def get_members(self, page: int, items_per_page: int) -> tuple[int, list[MemberVO]]:
|
||||||
|
offset = (page - 1) * items_per_page
|
||||||
|
total_count_query = select(func.count(Member.id))
|
||||||
|
total_count = self.session.exec(total_count_query).one()
|
||||||
|
|
||||||
|
if total_count == 0:
|
||||||
|
return 0, []
|
||||||
|
|
||||||
|
query = (
|
||||||
|
select(Member)
|
||||||
|
.order_by(Member.created_at.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(items_per_page)
|
||||||
|
)
|
||||||
|
|
||||||
|
members = self.session.exec(query).all()
|
||||||
|
|
||||||
|
return total_count, [MemberVO(**row_to_dict(member)) for member in members]
|
||||||
|
|
||||||
|
def find_by_id(self, id: str) -> MemberVO | None:
|
||||||
|
query = select(Member).where(Member.id == id)
|
||||||
|
member = self.session.exec(query).first()
|
||||||
|
|
||||||
|
if not member:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return MemberVO(**row_to_dict(member))
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from fastapi import APIRouter, status, Depends
|
||||||
|
from member.interface.dto import CreateUserBody, MemberResponse
|
||||||
|
from member.application.member_service import MemberService
|
||||||
|
from typing import Annotated
|
||||||
|
from utils.containers import Container
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
@router.post("", status_code=status.HTTP_201_CREATED, response_model=MemberResponse)
|
||||||
|
async def create_user(
|
||||||
|
member: CreateUserBody,
|
||||||
|
member_service: Annotated[MemberService, Depends(Container.member_service)]
|
||||||
|
):
|
||||||
|
created_member = member_service.create_member(
|
||||||
|
member.name,
|
||||||
|
member.email,
|
||||||
|
member.password,
|
||||||
|
member.role
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from typing import Annotated
|
||||||
|
from pydantic import BaseModel, Field, EmailStr
|
||||||
|
from utils.auth import Role
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class CreateUserBody(BaseModel):
|
||||||
|
name : Annotated[str, Field(min_length=1, max_length=32)]
|
||||||
|
email : Annotated[EmailStr, Field(max_length=32)]
|
||||||
|
password : Annotated[str, Field(max_length=32)]
|
||||||
|
role : Annotated[Role, Field(default=Role.USER)]
|
||||||
|
|
||||||
|
class MemberResponse(BaseModel):
|
||||||
|
id : str
|
||||||
|
name : str | None = None
|
||||||
|
email : str
|
||||||
|
created_at : datetime
|
||||||
|
updated_at : datetime
|
||||||
|
role : Role
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
amqp==5.3.1
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.9.0
|
||||||
|
bcrypt==4.0.1
|
||||||
|
billiard==4.2.1
|
||||||
|
celery==5.5.3
|
||||||
|
certifi==2025.6.15
|
||||||
|
cffi==1.17.1
|
||||||
|
click==8.2.1
|
||||||
|
click-didyoumean==0.3.1
|
||||||
|
click-plugins==1.1.1.2
|
||||||
|
click-repl==0.3.0
|
||||||
|
colorama==0.4.6
|
||||||
|
cryptography==45.0.4
|
||||||
|
dependency-injector==4.48.1
|
||||||
|
dnspython==2.7.0
|
||||||
|
ecdsa==0.19.1
|
||||||
|
email_validator==2.2.0
|
||||||
|
eventlet==0.40.1
|
||||||
|
fastapi==0.115.14
|
||||||
|
fastapi-cli==0.0.7
|
||||||
|
freezegun==1.5.2
|
||||||
|
greenlet==3.2.3
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httptools==0.6.4
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.10
|
||||||
|
iniconfig==2.1.1
|
||||||
|
Jinja2==3.1.6
|
||||||
|
kombu==5.5.4
|
||||||
|
markdown-it-py==3.0.0
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
mdurl==0.1.2
|
||||||
|
mocker==1.1.1
|
||||||
|
packaging==25.0
|
||||||
|
passlib==1.7.4
|
||||||
|
pluggy==1.6.0
|
||||||
|
prompt_toolkit==3.0.51
|
||||||
|
py-ulid==1.0.3
|
||||||
|
pyasn1==0.6.1
|
||||||
|
pycparser==2.22
|
||||||
|
pydantic==2.11.7
|
||||||
|
pydantic-settings==2.10.1
|
||||||
|
pydantic_core==2.33.2
|
||||||
|
Pygments==2.19.2
|
||||||
|
PyMySQL==1.1.1
|
||||||
|
pytest==8.4.1
|
||||||
|
pytest-mock==3.14.1
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
python-jose==3.5.0
|
||||||
|
python-multipart==0.0.20
|
||||||
|
PyYAML==6.0.2
|
||||||
|
redis==5.2.1
|
||||||
|
rich==14.0.0
|
||||||
|
rich-toolkit==0.14.7
|
||||||
|
rsa==4.9.1
|
||||||
|
setuptools==78.1.1
|
||||||
|
shellingham==1.5.4
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
SQLAlchemy==2.0.41
|
||||||
|
sqlmodel==0.0.24
|
||||||
|
starlette==0.46.2
|
||||||
|
typer==0.16.0
|
||||||
|
typing-inspection==0.4.1
|
||||||
|
typing_extensions==4.14.0
|
||||||
|
tzdata==2025.2
|
||||||
|
uvicorn==0.34.3
|
||||||
|
vine==5.1.0
|
||||||
|
watchfiles==1.1.0
|
||||||
|
wcwidth==0.2.13
|
||||||
|
websockets==15.0.1
|
||||||
|
wheel==0.45.1
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from fastapi import HTTPException, status, Depends
|
||||||
|
from jose import jwt, JWTError
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from enum import StrEnum
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from config import get_settings
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
SECRET_KEY = settings.SECRET_KEY
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
|
||||||
|
class Role(StrEnum):
|
||||||
|
ADMIN = "ADMIN"
|
||||||
|
USER = "USER"
|
||||||
|
|
||||||
|
class CurrentUser(BaseModel):
|
||||||
|
id : int
|
||||||
|
role : Role
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.id}({self.role})"
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/login")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_access_token(
|
||||||
|
payload: dict,
|
||||||
|
role: Role,
|
||||||
|
expires_delta: timedelta = timedelta(hours=6)
|
||||||
|
):
|
||||||
|
expire = datetime.utcnow() + expires_delta
|
||||||
|
payload.update({"exp": expire, "role": role})
|
||||||
|
encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
def decode_access_token(token: str):
|
||||||
|
try:
|
||||||
|
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
except JWTError:
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
||||||
|
|
||||||
|
|
||||||
|
# ✅ 수정된 부분: Annotated 올바른 사용법
|
||||||
|
def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
payload = decode_access_token(token)
|
||||||
|
user_id = payload.get("user_id")
|
||||||
|
role = payload.get("role")
|
||||||
|
if not user_id or not role:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token")
|
||||||
|
|
||||||
|
return CurrentUser(id=user_id, role=Role(role))
|
||||||
|
|
||||||
|
def get_admin_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
payload = decode_access_token(token)
|
||||||
|
user_id = payload.get("user_id")
|
||||||
|
role = payload.get("role")
|
||||||
|
|
||||||
|
if not role or role != Role.ADMIN:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token")
|
||||||
|
|
||||||
|
return CurrentUser(id=user_id, role=Role(role))
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from utils.database import get_session
|
||||||
|
from utils.crypto import Crypto
|
||||||
|
from member.infra.repository.member_repo import MemberRepository
|
||||||
|
from member.application.member_service import MemberService
|
||||||
|
from ulid import ULID
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
wiring_config = containers.WiringConfiguration(
|
||||||
|
packages=["member", "session"]
|
||||||
|
)
|
||||||
|
|
||||||
|
db_session = providers.Resource(get_session)
|
||||||
|
crypto = providers.Factory(Crypto)
|
||||||
|
ulid = providers.Factory(ULID)
|
||||||
|
|
||||||
|
member_repo = providers.Factory(
|
||||||
|
MemberRepository,
|
||||||
|
session=db_session
|
||||||
|
)
|
||||||
|
|
||||||
|
member_service = providers.Factory(
|
||||||
|
MemberService,
|
||||||
|
member_repo=member_repo,
|
||||||
|
crypto=crypto,
|
||||||
|
db_session=db_session,
|
||||||
|
ulid=ulid
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
class Crypto:
|
||||||
|
def __init__(self):
|
||||||
|
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
def encrypt(self, secret):
|
||||||
|
return self.pwd_context.hash(secret)
|
||||||
|
|
||||||
|
def verify(self, secret, hash):
|
||||||
|
return self.pwd_context.verify(secret, hash)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from sqlmodel import SQLModel, create_engine, Session
|
||||||
|
from config.config import get_settings
|
||||||
|
from analysis_session.infra.db_models.analysis_session import AnalysisSession
|
||||||
|
from member.infra.db_models.member import Member
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
print(settings)
|
||||||
|
# MySQL 데이터베이스 URL 구성
|
||||||
|
DATABASE_URL = f"mysql+pymysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_NAME}?charset=utf8mb4"
|
||||||
|
|
||||||
|
# MySQL 엔진 생성
|
||||||
|
engine = create_engine(
|
||||||
|
DATABASE_URL,
|
||||||
|
echo=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_session():
|
||||||
|
with Session(engine) as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
def create_db_and_tables():
|
||||||
|
# 테이블 생성
|
||||||
|
# SQLModel.metadata.drop_all(engine)
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
create_db_and_tables()
|
||||||
|
print(DATABASE_URL)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
def row_to_dict(row)->dict:
|
||||||
|
return {key : getattr(row, key) for key in inspect(row).attrs.keys()}
|
||||||
Loading…
Reference in New Issue