initializer service deployed and tested

This commit is contained in:
2025-05-12 17:42:33 +03:00
parent 834c78d814
commit 1d4f00e8b2
96 changed files with 11881 additions and 0 deletions

View File

View File

@@ -0,0 +1,56 @@
from fastapi import Header, Request, Response
from pydantic import BaseModel
from api_services.api_initializer.config import api_config
class CommonHeaders(BaseModel):
language: str | None = None
domain: str | None = None
timezone: str | None = None
token: str | None = None
request: Request | None = None
response: Response | None = None
operation_id: str | None = None
model_config = {
"arbitrary_types_allowed": True
}
@classmethod
def as_dependency(
cls,
request: Request,
response: Response,
language: str = Header(None, alias="language"),
domain: str = Header(None, alias="domain"),
tz: str = Header(None, alias="timezone"),
):
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
# Extract operation_id from the route
operation_id = None
if hasattr(request.scope.get("route"), "operation_id"):
operation_id = request.scope.get("route").operation_id
return cls(
language=language,
domain=domain,
timezone=tz,
token=token,
request=request,
response=response,
operation_id=operation_id,
)
def get_headers_dict(self):
"""Convert the headers to a dictionary format used in the application"""
import uuid
return {
"language": self.language or "",
"domain": self.domain or "",
"eys-ext": f"{str(uuid.uuid4())}",
"tz": self.timezone or "GMT+3",
"token": self.token,
}

View File

@@ -0,0 +1,17 @@
class BaseModelCore(BaseModel):
"""
BaseModelCore
model_dump override for alias support Users.name -> Table[Users] Field(alias="name")
"""
__abstract__ = True
class Config:
validate_by_name = True
use_enum_values = True
def model_dump(self, *args, **kwargs):
data = super().model_dump(*args, **kwargs)
return {self.__class__.model_fields[field].alias: value for field, value in data.items()}

View File

@@ -0,0 +1,53 @@
from .result import PaginationResult
from .base import PostgresResponseSingle
from pydantic import BaseModel
from typing import Any
class EndpointResponse(BaseModel):
"""Endpoint response model."""
completed: bool = True
message: str = "Success"
pagination_result: PaginationResult
@property
def response(self):
"""Convert response to dictionary format."""
result_data = getattr(self.pagination_result, "data", None)
if not result_data:
return {
"completed": False,
"message": "MSG0004-NODATA",
"data": None,
"pagination": None,
}
result_pagination = getattr(self.pagination_result, "pagination", None)
if not result_pagination:
raise ValueError("Invalid pagination result pagination.")
pagination_dict = getattr(result_pagination, "as_dict", None)
if not pagination_dict:
raise ValueError("Invalid pagination result as_dict.")
return {
"completed": self.completed,
"message": self.message,
"data": result_data,
"pagination": pagination_dict,
}
class CreateEndpointResponse(BaseModel):
"""Create endpoint response model."""
completed: bool = True
message: str = "Success"
data: PostgresResponseSingle
@property
def response(self):
"""Convert response to dictionary format."""
return {
"completed": self.completed,
"message": self.message,
"data": self.data.data,
}

View File

@@ -0,0 +1,193 @@
"""
Response handler for PostgreSQL query results.
This module provides a wrapper class for SQLAlchemy query results,
adding convenience methods for accessing data and managing query state.
"""
from typing import Any, Dict, Optional, TypeVar, Generic, Union
from pydantic import BaseModel
from sqlalchemy.orm import Query
T = TypeVar("T")
class PostgresResponse(Generic[T]):
"""
Wrapper for PostgreSQL/SQLAlchemy query results.
Properties:
count: Total count of results
query: Get query object
as_dict: Convert response to dictionary format
"""
def __init__(self, query: Query, base_model: Optional[BaseModel] = None):
self._query = query
self._count: Optional[int] = None
self._base_model: Optional[BaseModel] = base_model
self.single = False
@property
def query(self) -> Query:
"""Get query object."""
return self._query
@property
def data(self) -> Union[list[T], T]:
"""Get query object."""
return self._query.all()
@property
def count(self) -> int:
"""Get query object."""
return self._query.count()
@property
def to_dict(self, **kwargs) -> list[dict]:
"""Get query object."""
if self._base_model:
return [self._base_model(**item.to_dict()).model_dump(**kwargs) for item in self.data]
return [item.to_dict() for item in self.data]
@property
def as_dict(self) -> Dict[str, Any]:
"""Convert response to dictionary format."""
return {
"query": str(self.query),
"count": self.count,
"data": self.to_dict,
}
class PostgresResponseSingle(Generic[T]):
"""
Wrapper for PostgreSQL/SQLAlchemy query results.
Properties:
count: Total count of results
query: Get query object
as_dict: Convert response to dictionary format
data: Get query object
"""
def __init__(self, query: Query, base_model: Optional[BaseModel] = None):
self._query = query
self._count: Optional[int] = None
self._base_model: Optional[BaseModel] = base_model
self.single = True
@property
def query(self) -> Query:
"""Get query object."""
return self._query
@property
def to_dict(self, **kwargs) -> dict:
"""Get query object."""
if self._base_model:
return self._base_model(**self._query.first().to_dict()).model_dump(**kwargs)
return self._query.first().to_dict()
@property
def data(self) -> T:
"""Get query object."""
return self._query.first()
@property
def count(self) -> int:
"""Get query object."""
return self._query.count()
@property
def as_dict(self) -> Dict[str, Any]:
"""Convert response to dictionary format."""
return {"query": str(self.query),"data": self.to_dict, "count": self.count}
class ResultQueryJoin:
"""
ResultQueryJoin
params:
list_of_instrumented_attributes: list of instrumented attributes
query: query object
"""
def __init__(self, list_of_instrumented_attributes, query):
"""Initialize ResultQueryJoin"""
self.list_of_instrumented_attributes = list_of_instrumented_attributes
self._query = query
@property
def query(self):
"""Get query object."""
return self._query
@property
def to_dict(self):
"""Convert response to dictionary format."""
list_of_dictionaries, result = [], dict()
for user_orders_shipping_iter in self.query.all():
for index, instrumented_attribute_iter in enumerate(self.list_of_instrumented_attributes):
result[str(instrumented_attribute_iter)] = user_orders_shipping_iter[index]
list_of_dictionaries.append(result)
return list_of_dictionaries
@property
def count(self):
"""Get count of query."""
return self.query.count()
@property
def data(self):
"""Get query object."""
return self.query.all()
@property
def as_dict(self):
"""Convert response to dictionary format."""
return {"query": str(self.query), "data": self.data, "count": self.count}
class ResultQueryJoinSingle:
"""
ResultQueryJoinSingle
params:
list_of_instrumented_attributes: list of instrumented attributes
query: query object
"""
def __init__(self, list_of_instrumented_attributes, query):
"""Initialize ResultQueryJoinSingle"""
self.list_of_instrumented_attributes = list_of_instrumented_attributes
self._query = query
@property
def query(self):
"""Get query object."""
return self._query
@property
def to_dict(self):
"""Convert response to dictionary format."""
data, result = self.query.first(), dict()
for index, instrumented_attribute_iter in enumerate(self.list_of_instrumented_attributes):
result[str(instrumented_attribute_iter)] = data[index]
return result
@property
def count(self):
"""Get count of query."""
return self.query.count()
@property
def data(self):
"""Get query object."""
return self._query.first()
@property
def as_dict(self):
"""Convert response to dictionary format."""
return {"query": str(self.query), "data": self.data, "count": self.count}

View File

@@ -0,0 +1,19 @@
class UserPydantic(BaseModel):
username: str = Field(..., alias='user.username')
account_balance: float = Field(..., alias='user.account_balance')
preferred_category_id: Optional[int] = Field(None, alias='user.preferred_category_id')
last_ordered_product_id: Optional[int] = Field(None, alias='user.last_ordered_product_id')
supplier_rating_id: Optional[int] = Field(None, alias='user.supplier_rating_id')
other_rating_id: Optional[int] = Field(None, alias='product.supplier_rating_id')
id: int = Field(..., alias='user.id')
class Config:
validate_by_name = True
use_enum_values = True
def model_dump(self, *args, **kwargs):
data = super().model_dump(*args, **kwargs)
return {self.__class__.model_fields[field].alias: value for field, value in data.items()}

View File

@@ -0,0 +1,70 @@
from typing import Any, Dict, Optional, Union, TypeVar, Type
from sqlalchemy import desc, asc
from pydantic import BaseModel
from .base import PostgresResponse
# Type variable for class methods returning self
T = TypeVar("T", bound="BaseModel")
class PaginateConfig:
"""
Configuration for pagination settings.
Attributes:
DEFAULT_SIZE: Default number of items per page (10)
MIN_SIZE: Minimum allowed page size (10)
MAX_SIZE: Maximum allowed page size (40)
"""
DEFAULT_SIZE = 10
MIN_SIZE = 5
MAX_SIZE = 100
class ListOptions(BaseModel):
"""
Query for list option abilities
"""
page: Optional[int] = 1
size: Optional[int] = 10
orderField: Optional[Union[tuple[str], list[str]]] = ["uu_id"]
orderType: Optional[Union[tuple[str], list[str]]] = ["asc"]
# include_joins: Optional[list] = None
class PaginateOnly(ListOptions):
"""
Query for list option abilities
"""
query: Optional[dict] = None
class PaginationConfig(BaseModel):
"""
Configuration for pagination settings.
Attributes:
page: Current page number (default: 1)
size: Items per page (default: 10)
orderField: Field to order by (default: "created_at")
orderType: Order direction (default: "desc")
"""
page: int = 1
size: int = 10
orderField: Optional[Union[tuple[str], list[str]]] = ["created_at"]
orderType: Optional[Union[tuple[str], list[str]]] = ["desc"]
def __init__(self, **data):
super().__init__(**data)
if self.orderField is None:
self.orderField = ["created_at"]
if self.orderType is None:
self.orderType = ["desc"]
default_paginate_config = PaginateConfig()

View File

@@ -0,0 +1,183 @@
from .pagination import default_paginate_config
from .base import PostgresResponse
from typing import Optional, Union
from sqlalchemy.orm import Query
from pydantic import BaseModel
class Pagination:
"""
Handles pagination logic for query results.
Manages page size, current page, ordering, and calculates total pages
and items based on the data source.
Attributes:
DEFAULT_SIZE: Default number of items per page (10)
MIN_SIZE: Minimum allowed page size (10)
MAX_SIZE: Maximum allowed page size (40)
"""
DEFAULT_SIZE = default_paginate_config.DEFAULT_SIZE
MIN_SIZE = default_paginate_config.MIN_SIZE
MAX_SIZE = default_paginate_config.MAX_SIZE
def __init__(self, data: PostgresResponse):
self.data = data
self.size: int = self.DEFAULT_SIZE
self.page: int = 1
self.orderField: Optional[Union[tuple[str], list[str]]] = ["uu_id"]
self.orderType: Optional[Union[tuple[str], list[str]]] = ["asc"]
self.page_count: int = 1
self.total_count: int = 0
self.all_count: int = 0
self.total_pages: int = 1
self._update_page_counts()
def change(self, **kwargs) -> None:
"""Update pagination settings from config."""
config = PaginationConfig(**kwargs)
self.size = (
config.size
if self.MIN_SIZE <= config.size <= self.MAX_SIZE
else self.DEFAULT_SIZE
)
self.page = config.page
self.orderField = config.order_field
self.orderType = config.order_type
self._update_page_counts()
def feed(self, data: PostgresResponse) -> None:
"""Calculate pagination based on data source."""
self.data = data
self._update_page_counts()
def _update_page_counts(self) -> None:
"""Update page counts and validate current page."""
if self.data:
self.total_count = self.data.count
self.all_count = self.data.total_count
self.size = (
self.size
if self.MIN_SIZE <= self.size <= self.MAX_SIZE
else self.DEFAULT_SIZE
)
self.total_pages = max(1, (self.total_count + self.size - 1) // self.size)
self.page = max(1, min(self.page, self.total_pages))
self.page_count = (
self.total_count % self.size
if self.page == self.total_pages and self.total_count % self.size
else self.size
)
def refresh(self) -> None:
"""Reset pagination state to defaults."""
self._update_page_counts()
def reset(self) -> None:
"""Reset pagination state to defaults."""
self.size = self.DEFAULT_SIZE
self.page = 1
self.orderField = "uu_id"
self.orderType = "asc"
@property
def next_available(self) -> bool:
if self.page < self.total_pages:
return True
return False
@property
def back_available(self) -> bool:
if self.page > 1:
return True
return False
@property
def as_dict(self) -> Dict[str, Any]:
"""Convert pagination state to dictionary format."""
self.refresh()
return {
"size": self.size,
"page": self.page,
"allCount": self.all_count,
"totalCount": self.total_count,
"totalPages": self.total_pages,
"pageCount": self.page_count,
"orderField": self.orderField,
"orderType": self.orderType,
"next": self.next_available,
"back": self.back_available,
}
class PaginationResult:
"""
Result of a paginated query.
Contains the query result and pagination state.
data: PostgresResponse of query results
pagination: Pagination state
Attributes:
_query: Original query object
pagination: Pagination state
"""
def __init__(
self,
data: PostgresResponse,
pagination: Pagination,
response_model: Type[T] = None,
):
self._query = data.query
self.pagination = pagination
self.response_type = data.is_list
self.limit = self.pagination.size
self.offset = self.pagination.size * (self.pagination.page - 1)
self.order_by = self.pagination.orderField
self.response_model = response_model
def dynamic_order_by(self):
"""
Dynamically order a query by multiple fields.
Returns:
Ordered query object.
"""
if not len(self.order_by) == len(self.pagination.orderType):
raise ValueError(
"Order by fields and order types must have the same length."
)
order_criteria = zip(self.order_by, self.pagination.orderType)
for field, direction in order_criteria:
if hasattr(self._query.column_descriptions[0]["entity"], field):
if direction.lower().startswith("d"):
self._query = self._query.order_by(
desc(
getattr(self._query.column_descriptions[0]["entity"], field)
)
)
else:
self._query = self._query.order_by(
asc(
getattr(self._query.column_descriptions[0]["entity"], field)
)
)
return self._query
@property
def data(self) -> Union[list | dict]:
"""Get query object."""
query_ordered = self.dynamic_order_by()
query_paginated = query_ordered.limit(self.limit).offset(self.offset)
queried_data = (
query_paginated.all() if self.response_type else query_paginated.first()
)
data = (
[result.get_dict() for result in queried_data]
if self.response_type
else queried_data.get_dict()
)
if self.response_model:
return [self.response_model(**item).model_dump() for item in data]
return data