aioia-core 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aioia_core/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """
2
+ AIoIA Core - Core infrastructure for AIoIA projects.
3
+
4
+ Provides:
5
+ - Database: SQLAlchemy base models and CRUD manager
6
+ - Errors: Standardized error codes and responses
7
+ - Settings: Common settings classes
8
+ """
9
+
10
+ __version__ = "0.1.0"
11
+
12
+ from aioia_core.errors import (
13
+ INTERNAL_SERVER_ERROR,
14
+ RESOURCE_NOT_FOUND,
15
+ UNAUTHORIZED,
16
+ VALIDATION_ERROR,
17
+ ErrorResponse,
18
+ extract_error_code_from_exception,
19
+ get_error_detail_from_exception,
20
+ )
21
+ from aioia_core.factories.base_manager_factory import BaseManagerFactory
22
+ from aioia_core.managers import BaseManager
23
+ from aioia_core.models import Base, BaseModel
24
+ from aioia_core.protocols import CrudManagerProtocol, DatabaseManagerProtocol
25
+ from aioia_core.settings import DatabaseSettings, JWTSettings, OpenAIAPISettings
26
+
27
+ __all__ = [
28
+ # Database
29
+ "Base",
30
+ "BaseModel",
31
+ "BaseManager",
32
+ "BaseManagerFactory",
33
+ "CrudManagerProtocol",
34
+ "DatabaseManagerProtocol",
35
+ # Errors
36
+ "ErrorResponse",
37
+ "UNAUTHORIZED",
38
+ "VALIDATION_ERROR",
39
+ "RESOURCE_NOT_FOUND",
40
+ "INTERNAL_SERVER_ERROR",
41
+ "extract_error_code_from_exception",
42
+ "get_error_detail_from_exception",
43
+ # Settings
44
+ "DatabaseSettings",
45
+ "OpenAIAPISettings",
46
+ "JWTSettings",
47
+ ]
aioia_core/auth.py ADDED
@@ -0,0 +1,46 @@
1
+ """Authentication and authorization utilities for AIoIA projects."""
2
+
3
+ from enum import Enum
4
+ from typing import Protocol
5
+
6
+ from sqlalchemy.orm import Session
7
+
8
+
9
+ class UserRole(str, Enum):
10
+ """Standard user roles for all AIoIA projects."""
11
+
12
+ ADMIN = "admin"
13
+ USER = "user"
14
+
15
+
16
+ class UserRoleProvider(Protocol):
17
+ """
18
+ Protocol for retrieving user roles and context.
19
+
20
+ Projects implement this to integrate their user management system
21
+ with BaseCrudRouter's authentication/authorization.
22
+ """
23
+
24
+ def get_user_role( # pylint: disable=unnecessary-ellipsis
25
+ self, user_id: str, db: Session
26
+ ) -> UserRole | None:
27
+ """
28
+ Get user's role by ID.
29
+
30
+ Args:
31
+ user_id: User identifier
32
+ db: Database session
33
+ """
34
+ ...
35
+
36
+ def get_user_context( # pylint: disable=unnecessary-ellipsis
37
+ self, user_id: str, db: Session
38
+ ) -> dict | None:
39
+ """
40
+ Get user context for monitoring/observability tools.
41
+
42
+ Args:
43
+ user_id: User identifier
44
+ db: Database session
45
+ """
46
+ ...
@@ -0,0 +1,56 @@
1
+ """
2
+ Error handling infrastructure for AIoIA projects.
3
+
4
+ Provides standardized error codes and responses for FastAPI applications.
5
+ """
6
+
7
+ # Error codes
8
+ from aioia_core.errors.error_codes import (
9
+ DATABASE_ERROR,
10
+ EXTERNAL_SERVICE_ERROR,
11
+ FORBIDDEN,
12
+ INTERNAL_SERVER_ERROR,
13
+ INVALID_JSON,
14
+ INVALID_QUERY_PARAMS,
15
+ INVALID_TOKEN,
16
+ JWT_SECRET_NOT_CONFIGURED,
17
+ MISSING_REQUIRED_FIELD,
18
+ RESOURCE_CREATION_FAILED,
19
+ RESOURCE_DELETE_FAILED,
20
+ RESOURCE_NOT_FOUND,
21
+ RESOURCE_UPDATE_FAILED,
22
+ TOKEN_EXPIRED,
23
+ UNAUTHORIZED,
24
+ VALIDATION_ERROR,
25
+ )
26
+
27
+ # Error responses
28
+ from aioia_core.errors.error_responses import (
29
+ ErrorResponse,
30
+ extract_error_code_from_exception,
31
+ get_error_detail_from_exception,
32
+ )
33
+
34
+ __all__ = [
35
+ # Error codes
36
+ "UNAUTHORIZED",
37
+ "FORBIDDEN",
38
+ "INVALID_TOKEN",
39
+ "TOKEN_EXPIRED",
40
+ "JWT_SECRET_NOT_CONFIGURED",
41
+ "VALIDATION_ERROR",
42
+ "INVALID_JSON",
43
+ "MISSING_REQUIRED_FIELD",
44
+ "INVALID_QUERY_PARAMS",
45
+ "RESOURCE_NOT_FOUND",
46
+ "RESOURCE_CREATION_FAILED",
47
+ "RESOURCE_UPDATE_FAILED",
48
+ "RESOURCE_DELETE_FAILED",
49
+ "INTERNAL_SERVER_ERROR",
50
+ "DATABASE_ERROR",
51
+ "EXTERNAL_SERVICE_ERROR",
52
+ # Error responses
53
+ "ErrorResponse",
54
+ "extract_error_code_from_exception",
55
+ "get_error_detail_from_exception",
56
+ ]
@@ -0,0 +1,26 @@
1
+ # Error codes for consistent error handling across FastAPI endpoints
2
+ # These constants ensure standardized error identification for frontend applications
3
+
4
+ # Authentication & Authorization errors
5
+ UNAUTHORIZED = "UNAUTHORIZED" # User is not authenticated
6
+ FORBIDDEN = "FORBIDDEN" # User lacks permission for the requested resource
7
+ INVALID_TOKEN = "INVALID_TOKEN" # JWT token is invalid or malformed
8
+ TOKEN_EXPIRED = "TOKEN_EXPIRED" # JWT token has expired
9
+ JWT_SECRET_NOT_CONFIGURED = "JWT_SECRET_NOT_CONFIGURED" # Server configuration error
10
+
11
+ # Validation & Request errors
12
+ VALIDATION_ERROR = "VALIDATION_ERROR" # Request data validation failed
13
+ INVALID_JSON = "INVALID_JSON" # Request body contains invalid JSON
14
+ MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD" # Required field is missing
15
+ INVALID_QUERY_PARAMS = "INVALID_QUERY_PARAMS" # Query parameters are invalid
16
+
17
+ # Resource Management errors
18
+ RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND" # Requested resource does not exist
19
+ RESOURCE_CREATION_FAILED = "RESOURCE_CREATION_FAILED" # Failed to create resource
20
+ RESOURCE_UPDATE_FAILED = "RESOURCE_UPDATE_FAILED" # Failed to update resource
21
+ RESOURCE_DELETE_FAILED = "RESOURCE_DELETE_FAILED" # Failed to delete resource
22
+
23
+ # System & Server errors
24
+ INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR" # Unexpected server error
25
+ DATABASE_ERROR = "DATABASE_ERROR" # Database operation failed
26
+ EXTERNAL_SERVICE_ERROR = "EXTERNAL_SERVICE_ERROR" # External service unavailable
@@ -0,0 +1,92 @@
1
+ """Error response models and utility functions for FastAPI applications."""
2
+
3
+ from typing import Any, cast
4
+
5
+ from fastapi import HTTPException
6
+ from pydantic import BaseModel, Field
7
+
8
+ from aioia_core.errors import error_codes
9
+
10
+
11
+ class ErrorResponse(BaseModel):
12
+ """
13
+ Standardized error response model following RFC 9457 Problem Details for HTTP APIs.
14
+ This model provides consistent error information across all FastAPI endpoints.
15
+ """
16
+
17
+ status: int = Field(
18
+ ...,
19
+ description="HTTP status code",
20
+ )
21
+ detail: str = Field(
22
+ ...,
23
+ description="Human-readable error message",
24
+ )
25
+ code: str = Field(
26
+ ...,
27
+ description="Machine-readable error code for client-side error handling",
28
+ )
29
+
30
+ class Config:
31
+ json_schema_extra = {
32
+ "examples": [
33
+ {
34
+ "status": 401,
35
+ "detail": "Invalid authentication credentials",
36
+ "code": "INVALID_TOKEN",
37
+ },
38
+ {
39
+ "status": 404,
40
+ "detail": "Resource not found: item_123",
41
+ "code": "RESOURCE_NOT_FOUND",
42
+ },
43
+ {
44
+ "status": 422,
45
+ "detail": "Validation error in field 'email': Invalid email format",
46
+ "code": "VALIDATION_ERROR",
47
+ },
48
+ ]
49
+ }
50
+
51
+
52
+ def extract_error_code_from_exception(exc: HTTPException) -> str:
53
+ """
54
+ HTTPException에서 에러 코드를 추출합니다.
55
+ detail이 dict 형태인 경우 code 필드를 찾고, 없으면 기본값 반환
56
+
57
+ Args:
58
+ exc: HTTPException 인스턴스
59
+
60
+ Returns:
61
+ str: 에러 코드
62
+ """
63
+ if isinstance(exc.detail, dict) and "code" in exc.detail:
64
+ return cast(dict[str, Any], exc.detail)["code"]
65
+
66
+ # 상태 코드별 기본 에러 코드 매핑
67
+ default_codes = {
68
+ 401: error_codes.UNAUTHORIZED,
69
+ 403: error_codes.FORBIDDEN,
70
+ 404: error_codes.RESOURCE_NOT_FOUND,
71
+ 422: error_codes.VALIDATION_ERROR,
72
+ 500: error_codes.INTERNAL_SERVER_ERROR,
73
+ }
74
+
75
+ return default_codes.get(exc.status_code, error_codes.INTERNAL_SERVER_ERROR)
76
+
77
+
78
+ def get_error_detail_from_exception(exc: HTTPException) -> str:
79
+ """
80
+ HTTPException에서 에러 상세 메시지를 추출합니다.
81
+
82
+ Args:
83
+ exc: HTTPException 인스턴스
84
+
85
+ Returns:
86
+ str: 에러 상세 메시지
87
+ """
88
+ if isinstance(exc.detail, dict) and "detail" in exc.detail:
89
+ return cast(dict[str, Any], exc.detail)["detail"]
90
+ if isinstance(exc.detail, str):
91
+ return exc.detail
92
+ return str(exc.detail)
@@ -0,0 +1,5 @@
1
+ """Factory patterns for AIoIA projects."""
2
+
3
+ from aioia_core.factories.base_manager_factory import BaseManagerFactory
4
+
5
+ __all__ = ["BaseManagerFactory"]
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from typing import Generic, TypeVar
5
+
6
+ from sqlalchemy.orm import Session, sessionmaker
7
+
8
+ from aioia_core.protocols import DatabaseManagerProtocol
9
+
10
+ # ManagerType을 DatabaseManagerProtocol에 바인딩
11
+ ManagerType = TypeVar("ManagerType", bound=DatabaseManagerProtocol)
12
+
13
+
14
+ class BaseManagerFactory(ABC, Generic[ManagerType]):
15
+ """
16
+ 기본 매니저 팩토리 클래스
17
+
18
+ 이 클래스는 데이터베이스 저장소를 사용하는 매니저 인스턴스를 생성하기 위한
19
+ 기본 팩토리 패턴을 구현합니다.
20
+
21
+ Args:
22
+ db_session_factory: SQLAlchemy 세션 팩토리
23
+ manager_class: 생성할 매니저 클래스
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ db_session_factory: sessionmaker,
29
+ manager_class: type[ManagerType],
30
+ ):
31
+ """
32
+ BaseManagerFactory 초기화
33
+
34
+ Args:
35
+ db_session_factory: SQLAlchemy 세션 팩토리
36
+ manager_class: 생성할 매니저 클래스
37
+ """
38
+ self.db_session_factory = db_session_factory
39
+ self.manager_class = manager_class
40
+
41
+ def create_manager(self, db_session: Session | None = None) -> ManagerType:
42
+ """
43
+ 매니저 인스턴스를 생성합니다.
44
+
45
+ 이 메서드는 매니저 클래스의 인스턴스를 생성하고 반환합니다.
46
+ db_session이 제공되지 않은 경우 새로운 세션을 생성합니다.
47
+
48
+ Args:
49
+ db_session: 선택적 데이터베이스 세션. None인 경우 새로운 세션을 생성합니다.
50
+
51
+ Returns:
52
+ 생성된 매니저 인스턴스
53
+ """
54
+ session = db_session if db_session is not None else self.db_session_factory()
55
+ return self.manager_class(session)
@@ -0,0 +1,15 @@
1
+ """FastAPI utilities for AIoIA projects."""
2
+
3
+ from aioia_core.fastapi.base_crud_router import (
4
+ BaseCrudRouter,
5
+ DeleteResponse,
6
+ PaginatedResponse,
7
+ SingleItemResponse,
8
+ )
9
+
10
+ __all__ = [
11
+ "BaseCrudRouter",
12
+ "PaginatedResponse",
13
+ "SingleItemResponse",
14
+ "DeleteResponse",
15
+ ]