initializer service deployed and tested
This commit is contained in:
31
api_services/api_controllers/postgres/config.py
Normal file
31
api_services/api_controllers/postgres/config.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
Postgresql configuration settings.
|
||||
"""
|
||||
|
||||
DB: str = ""
|
||||
USER: str = ""
|
||||
PASSWORD: str = ""
|
||||
HOST: str = ""
|
||||
PORT: int = 0
|
||||
ENGINE: str = "postgresql+psycopg2"
|
||||
POOL_PRE_PING: bool = True
|
||||
POOL_SIZE: int = 20
|
||||
MAX_OVERFLOW: int = 10
|
||||
POOL_RECYCLE: int = 600
|
||||
POOL_TIMEOUT: int = 30
|
||||
ECHO: bool = True
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Generate the database URL."""
|
||||
return f"{self.ENGINE}://{self.USER}:{self.PASSWORD}@{self.HOST}:{self.PORT}/{self.DB}"
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="POSTGRES_")
|
||||
|
||||
|
||||
# singleton instance of the POSTGRESQL configuration settings
|
||||
postgres_configs = Configs()
|
||||
63
api_services/api_controllers/postgres/engine.py
Normal file
63
api_services/api_controllers/postgres/engine.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from contextlib import contextmanager
|
||||
from functools import lru_cache
|
||||
from typing import Generator
|
||||
from api_controllers.postgres.config import postgres_configs
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session
|
||||
|
||||
|
||||
# Configure the database engine with proper pooling
|
||||
engine = create_engine(
|
||||
postgres_configs.url,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10, # Reduced from 20 to better match your CPU cores
|
||||
max_overflow=5, # Reduced from 10 to prevent too many connections
|
||||
pool_recycle=600, # Keep as is
|
||||
pool_timeout=30, # Keep as is
|
||||
echo=False, # Consider setting to False in production
|
||||
)
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Create a cached session factory
|
||||
@lru_cache()
|
||||
def get_session_factory() -> scoped_session:
|
||||
"""Create a thread-safe session factory."""
|
||||
session_local = sessionmaker(
|
||||
bind=engine,
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
expire_on_commit=True, # Prevent expired object issues
|
||||
)
|
||||
return scoped_session(session_local)
|
||||
|
||||
|
||||
# Get database session with proper connection management
|
||||
@contextmanager
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
"""Get database session with proper connection management.
|
||||
|
||||
This context manager ensures:
|
||||
- Proper connection pooling
|
||||
- Session cleanup
|
||||
- Connection return to pool
|
||||
- Thread safety
|
||||
|
||||
Yields:
|
||||
Session: SQLAlchemy session object
|
||||
"""
|
||||
|
||||
session_factory = get_session_factory()
|
||||
session = session_factory()
|
||||
try:
|
||||
yield session
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
session_factory.remove() # Clean up the session from the registry
|
||||
141
api_services/api_controllers/postgres/mixin.py
Normal file
141
api_services/api_controllers/postgres/mixin.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import arrow
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Float, ForeignKey, UUID, TIMESTAMP, Boolean, SmallInteger, Numeric, func, text
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy_mixins.serialize import SerializeMixin
|
||||
from sqlalchemy_mixins.repr import ReprMixin
|
||||
from sqlalchemy_mixins.smartquery import SmartQueryMixin
|
||||
from sqlalchemy_mixins.activerecord import ActiveRecordMixin
|
||||
from api_controllers.postgres.engine import get_db, Base
|
||||
|
||||
|
||||
class BasicMixin(
|
||||
Base,
|
||||
ActiveRecordMixin,
|
||||
SerializeMixin,
|
||||
ReprMixin,
|
||||
SmartQueryMixin,
|
||||
):
|
||||
|
||||
__abstract__ = True
|
||||
__repr__ = ReprMixin.__repr__
|
||||
|
||||
@classmethod
|
||||
def new_session(cls):
|
||||
"""Get database session."""
|
||||
return get_db()
|
||||
|
||||
|
||||
class CrudMixin(BasicMixin):
|
||||
"""
|
||||
Base mixin providing CRUD operations and common fields for PostgreSQL models.
|
||||
|
||||
Features:
|
||||
- Automatic timestamps (created_at, updated_at)
|
||||
- Soft delete capability
|
||||
- User tracking (created_by, updated_by)
|
||||
- Data serialization
|
||||
- Multi-language support
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Primary and reference fields
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
uu_id: Mapped[str] = mapped_column(
|
||||
UUID,
|
||||
server_default=text("gen_random_uuid()"),
|
||||
index=True,
|
||||
unique=True,
|
||||
comment="Unique identifier UUID",
|
||||
)
|
||||
|
||||
# Common timestamp fields for all models
|
||||
expiry_starts: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
comment="Record validity start timestamp",
|
||||
)
|
||||
expiry_ends: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
default=str(arrow.get("2099-12-31")),
|
||||
server_default=func.now(),
|
||||
comment="Record validity end timestamp",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Record creation timestamp",
|
||||
)
|
||||
updated_at: Mapped[TIMESTAMP] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="Last update timestamp",
|
||||
)
|
||||
|
||||
|
||||
class CrudCollection(CrudMixin):
|
||||
"""
|
||||
Full-featured model class with all common fields.
|
||||
|
||||
Includes:
|
||||
- UUID and reference ID
|
||||
- Timestamps
|
||||
- User tracking
|
||||
- Confirmation status
|
||||
- Soft delete
|
||||
- Notification flags
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
__repr__ = ReprMixin.__repr__
|
||||
|
||||
# Outer reference fields
|
||||
ref_id: Mapped[str] = mapped_column(
|
||||
String(100), nullable=True, index=True, comment="External reference ID"
|
||||
)
|
||||
replication_id: Mapped[int] = mapped_column(
|
||||
SmallInteger, server_default="0", comment="Replication identifier"
|
||||
)
|
||||
|
||||
# Cryptographic and user tracking
|
||||
cryp_uu_id: Mapped[str] = mapped_column(
|
||||
String, nullable=True, index=True, comment="Cryptographic UUID"
|
||||
)
|
||||
|
||||
# Token fields of modification
|
||||
created_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Created Credentials token"
|
||||
)
|
||||
updated_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Updated Credentials token"
|
||||
)
|
||||
confirmed_credentials_token: Mapped[str] = mapped_column(
|
||||
String, nullable=True, comment="Confirmed Credentials token"
|
||||
)
|
||||
|
||||
# Status flags
|
||||
is_confirmed: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Record confirmation status"
|
||||
)
|
||||
deleted: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Soft delete flag"
|
||||
)
|
||||
active: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="1", comment="Record active status"
|
||||
)
|
||||
is_notification_send: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Notification sent flag"
|
||||
)
|
||||
is_email_send: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Email sent flag"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user