initializer service deployed and tested
This commit is contained in:
0
api_services/api_validations/a.txt
Normal file
0
api_services/api_validations/a.txt
Normal file
56
api_services/api_validations/defaults/validations.py
Normal file
56
api_services/api_validations/defaults/validations.py
Normal 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,
|
||||
}
|
||||
17
api_services/api_validations/pydantic_core.py
Normal file
17
api_services/api_validations/pydantic_core.py
Normal 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()}
|
||||
53
api_services/api_validations/response/api.py
Normal file
53
api_services/api_validations/response/api.py
Normal 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,
|
||||
}
|
||||
|
||||
193
api_services/api_validations/response/base.py
Normal file
193
api_services/api_validations/response/base.py
Normal 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}
|
||||
19
api_services/api_validations/response/example.py
Normal file
19
api_services/api_validations/response/example.py
Normal 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()}
|
||||
70
api_services/api_validations/response/pagination.py
Normal file
70
api_services/api_validations/response/pagination.py
Normal 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()
|
||||
183
api_services/api_validations/response/result.py
Normal file
183
api_services/api_validations/response/result.py
Normal 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
|
||||
Reference in New Issue
Block a user