-
Notifications
You must be signed in to change notification settings - Fork 0
20251217 #1 postgre sql 연결 및 테이블 생성 #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "20251217_#1_PostgreSQL_\uC5F0\uACB0_\uBC0F_\uD14C\uC774\uBE14_\uC0DD\uC131"
Changes from all commits
47413ea
9066e5c
45655d0
280c968
5beeb5c
5cb7cef
a7fec07
8e26b45
6ec67cd
3b3d060
c7e3c0e
7aeea4d
474b474
53a8285
5fae0f8
483169e
3bb40da
9adbab9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json | ||
| language: "ko-KR" | ||
| early_access: false | ||
| reviews: | ||
| profile: "chill" | ||
| request_changes_workflow: false | ||
| high_level_summary: true | ||
| poem: true | ||
| review_status: true | ||
| collapse_walkthrough: false | ||
| auto_review: | ||
| enabled: true | ||
| drafts: false | ||
| base_branches: | ||
| - main | ||
| - test | ||
| chat: | ||
| auto_reply: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,7 @@ celerybeat-schedule | |
|
|
||
| # Environments | ||
| .env | ||
| .env.* | ||
| .venv | ||
| env/ | ||
| venv/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,2 +1,40 @@ | ||||||
| # 빈 파일 - DB 연결 설정 | ||||||
| from typing import AsyncGenerator | ||||||
|
|
||||||
| from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine, AsyncEngine | ||||||
|
|
||||||
| from app.config.settings import get_settings | ||||||
| from app.utils.logger import logger | ||||||
|
|
||||||
| _settings = get_settings() | ||||||
|
|
||||||
| _engine = create_async_engine( | ||||||
| _settings.postgres_url, | ||||||
| pool_pre_ping=True, | ||||||
| echo=False, # SQL 쿼리 로깅 | ||||||
| ) | ||||||
|
|
||||||
| _async_session_factory = async_sessionmaker( | ||||||
| bind=_engine, | ||||||
| expire_on_commit=False, | ||||||
| autoflush=False, | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def get_async_engine() -> AsyncEngine: | ||||||
| return _engine | ||||||
|
|
||||||
|
|
||||||
| def get_async_session_factory() -> async_sessionmaker[AsyncSession]: | ||||||
| return _async_session_factory | ||||||
|
|
||||||
|
|
||||||
| async def get_async_session() -> AsyncGenerator[AsyncSession, None]: | ||||||
| async with _async_session_factory() as session: | ||||||
| try: | ||||||
| yield session | ||||||
| except Exception as e: | ||||||
| logger.error(f"Postgres DB 에러: {e}") | ||||||
| await session.rollback() | ||||||
| raise | ||||||
| finally: | ||||||
| await session.close() | ||||||
|
Comment on lines
+39
to
+40
|
||||||
| finally: | |
| await session.close() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,47 @@ | ||
| # 빈 파일 - 환경 변수 설정 | ||
| from functools import lru_cache | ||
|
|
||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||
|
|
||
|
|
||
| class Settings(BaseSettings): | ||
| """ | ||
| .env 에서 환경변수 로딩 | ||
| """ | ||
|
|
||
| model_config = SettingsConfigDict( | ||
| env_file=".env", | ||
| env_file_encoding="utf-8", | ||
| case_sensitive=False, | ||
| extra="ignore" | ||
| ) | ||
|
|
||
| # GitHub API 설정 | ||
| github_api_base_url: str | ||
| github_api_token: str | None = None | ||
|
|
||
| # Ollama 설정 | ||
| ollama_base_url: str | ||
| ollama_api_key: str | ||
| ollama_model: str | ||
| ollama_timeout_seconds: int | ||
|
|
||
| # Qdrant 설정 | ||
| qdrant_base_url: str | ||
| qdrant_collection: str | ||
| qdrant_api_key: str | ||
|
|
||
| # 텍스트 청크 설정 | ||
| text_chunk_max_chars: int | ||
| text_chunk_overlap_chars: int | ||
| text_chunk_hard_max_chars: int | ||
|
|
||
| # 동시성 설정 | ||
| concurrency_embedding_max_concurrency: int | ||
|
|
||
| # PostgreSQL 설정 | ||
| postgres_url: str | ||
|
Comment on lines
+18
to
+42
|
||
|
|
||
|
|
||
| @lru_cache(maxsize=1) | ||
| def get_settings() -> Settings: | ||
| return Settings() | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||
| from sqlalchemy.ext.asyncio import AsyncEngine | ||||||
|
|
||||||
| from app.models.base import Base | ||||||
| from app.utils.logger import logger | ||||||
|
|
||||||
|
|
||||||
| def _import_all_models() -> None: | ||||||
| from app.models.github_cursor import GithubCursorEntity | ||||||
|
|
||||||
|
|
||||||
| async def create_tables_if_not_exists(engine: AsyncEngine) -> None: | ||||||
| _import_all_models() | ||||||
|
|
||||||
| async with engine.begin() as conn: | ||||||
| await conn.run_sync(Base.metadata.create_all) | ||||||
|
|
||||||
| logger.info("DB 테이블 생성완료") | ||||||
|
||||||
| logger.info("DB 테이블 생성완료") | |
| logger.info("DB 테이블 생성 완료") |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||
| import uuid | ||||||||||||||
| from datetime import datetime | ||||||||||||||
|
|
||||||||||||||
| from sqlalchemy import DateTime, func | ||||||||||||||
| from sqlalchemy.dialects.postgresql.base import UUID | ||||||||||||||
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class Base(DeclarativeBase): | ||||||||||||||
| """ | ||||||||||||||
| SQLAlchemy Declarative Base | ||||||||||||||
| - 모든 엔티티는 Base 상속 | ||||||||||||||
| """ | ||||||||||||||
| pass | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class TimestampMixin: | ||||||||||||||
| """ | ||||||||||||||
| created_at, updated_at 자동 관리 Mixin | ||||||||||||||
| - created_at: DB 레벨 자동 설정 | ||||||||||||||
| - updated_at: 애플리케이션 레벨에서 명시적 관리 | ||||||||||||||
|
Comment on lines
+19
to
+21
|
||||||||||||||
| created_at, updated_at 자동 관리 Mixin | |
| - created_at: DB 레벨 자동 설정 | |
| - updated_at: 애플리케이션 레벨에서 명시적 관리 | |
| created_at, updated_at 타임스탬프 관리 Mixin | |
| - created_at: DB 레벨에서 INSERT 시 자동 설정 | |
| - updated_at: DB 레벨에서 INSERT 시 자동 설정, UPDATE 시에는 별도 애플리케이션/DB 로직으로 명시적으로 갱신 필요 |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The updated_at field is missing an onupdate trigger to automatically update the timestamp when records are modified. Currently, with only server_default=func.now(), the field will be set on INSERT but not automatically updated on UPDATE operations. Add onupdate=func.now() to ensure the timestamp is updated on modifications, or explicitly update it in application code when records change.
| server_default=func.now(), # DB 레벨 - INSERT 시 자동 | |
| server_default=func.now(), # DB 레벨 - INSERT 시 자동 | |
| onupdate=func.now(), # UPDATE 시 자동 |
Chuseok22 marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing blank line before the class definition. According to PEP 8, there should be two blank lines before top-level class definitions for better readability.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from enum import Enum | ||
|
|
||
|
|
||
| class SourceType(str, Enum): | ||
| """ | ||
| 깃허브 임베딩 대상 SourceType | ||
| - Repository: 레포 파일/문서 (README 등) | ||
| - ISSUE: 이슈 | ||
| - PULL_REQUEST: PR | ||
| - COMMIT: 커밋 | ||
| - RELEASE: 릴리즈 | ||
| """ | ||
| REPOSITORY = "REPOSITORY" | ||
| ISSUE = "ISSUE" | ||
| PULL_REQUEST = "PULL_REQUEST" | ||
| COMMIT = "COMMIT" | ||
| RELEASE = "RELEASE" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| from sqlalchemy import Enum as SqlEnum | ||
| from sqlalchemy import UniqueConstraint, Index, String | ||
| from sqlalchemy.orm import Mapped, mapped_column | ||
|
|
||
| from app.models.base import Base, PrimaryKeyMixin, TimestampMixin | ||
| from app.models.enums.source_type import SourceType | ||
|
|
||
|
|
||
| class GithubCursorEntity(Base, PrimaryKeyMixin, TimestampMixin): | ||
| __tablename__ = "github_cursor" | ||
| __table_args__ = ( | ||
| UniqueConstraint("repository_name", "source_type", name="uq_github_cursor"), | ||
| Index("idx_github_cursor_repo_type", "repository_name", "source_type") | ||
| ) | ||
|
|
||
| repository_name: Mapped[str] = mapped_column(String(200), nullable=False) | ||
|
|
||
| source_type: Mapped[SourceType] = mapped_column(SqlEnum(SourceType, native_enum=False), nullable=False) | ||
|
|
||
| cursor_value: Mapped[str] = mapped_column(String(500), nullable=False) |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import uuid | ||
| from typing import Optional | ||
|
|
||
| from sqlalchemy import select, func | ||
| from sqlalchemy.dialects.postgresql import insert | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
|
|
||
| from app.models.enums.source_type import SourceType | ||
| from app.models.github_cursor import GithubCursorEntity | ||
|
|
||
|
|
||
| class GithubCursorRepository: | ||
| async def find_by_repository_name_and_source_type( | ||
| self, | ||
| session: AsyncSession, | ||
| repository_name: str, | ||
| source_type: SourceType, | ||
| ) -> Optional[GithubCursorEntity]: | ||
| """ | ||
| 특정 repository + source_type 커서 조회 | ||
| """ | ||
| query = select(GithubCursorEntity).where( | ||
| GithubCursorEntity.repository_name == repository_name, | ||
| GithubCursorEntity.source_type == source_type, | ||
| ) | ||
| result = await session.execute(query) | ||
| return result.scalar_one_or_none() | ||
|
|
||
| async def upsert( | ||
| self, | ||
| session: AsyncSession, | ||
| repository_name: str, | ||
| source_type: SourceType, | ||
| cursor_value: str, | ||
| ) -> GithubCursorEntity: | ||
| """ | ||
| 커서 upsert (없으면 생성, 있으면 업데이트) | ||
| """ | ||
| query = insert(GithubCursorEntity).values( | ||
| id=uuid.uuid4(), | ||
| repository_name=repository_name, | ||
| source_type=source_type, | ||
| cursor_value=cursor_value, | ||
| ).on_conflict_do_update( | ||
| index_elements=["repository_name", "source_type"], | ||
| set_={ | ||
| "cursor_value": cursor_value, | ||
| "updated_at": func.now(), | ||
| }, | ||
| ).returning(GithubCursorEntity) | ||
|
|
||
| result = await session.execute(query) | ||
| return result.scalar_one() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message uses "Postgres" but the official name of the database is "PostgreSQL". For consistency and accuracy, use "PostgreSQL" in error messages and logging.