event to functon handler completed

This commit is contained in:
2025-01-15 19:18:11 +03:00
parent 25539c56cc
commit 76d286b519
55 changed files with 1271 additions and 1092 deletions

View File

View File

@@ -0,0 +1,357 @@
import typing
from ApiValidations.Custom.token_objects import (
OccupantTokenObject,
EmployeeTokenObject,
)
from Schemas import (
BuildLivingSpace,
AccountRecords,
BuildIbans,
BuildDecisionBookPayments,
ApiEnumDropdown,
)
from ApiLibrary import system_arrow
from ApiValidations.Request import (
InsertAccountRecord,
UpdateAccountRecord,
ListOptions,
)
from Services.PostgresDb.Models.alchemy_response import AlchemyJsonResponse
from ApiEvents.abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
)
from ApiValidations.Response import AccountRecordResponse
class AccountRecordsListEventMethods(MethodToEvent):
event_type = "SELECT"
event_description = ""
event_category = ""
__event_keys__ = {
"7192c2aa-5352-4e36-98b3-dafb7d036a3d": "account_records_list",
"208e6273-17ef-44f0-814a-8098f816b63a": "account_records_list_flt_res",
}
__event_validation__ = {
"7192c2aa-5352-4e36-98b3-dafb7d036a3d": AccountRecordResponse,
"208e6273-17ef-44f0-814a-8098f816b63a": AccountRecordResponse,
}
@classmethod
def account_records_list(
cls,
list_options: ListOptions,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
if isinstance(token_dict, OccupantTokenObject):
AccountRecords.pre_query = AccountRecords.filter_all(
AccountRecords.company_id
== token_dict.selected_occupant.responsible_company_id,
db=db_session,
).query
elif isinstance(token_dict, EmployeeTokenObject):
AccountRecords.pre_query = AccountRecords.filter_all(
AccountRecords.company_id == token_dict.selected_company.company_id,
db=db_session,
).query
AccountRecords.filter_attr = list_options
records = AccountRecords.filter_all(db=db_session)
return AlchemyJsonResponse(
completed=True,
message="Account records listed successfully",
result=records,
cls_object=AccountRecords,
filter_attributes=list_options,
response_model=AccountRecordResponse,
)
@classmethod
def account_records_list_flt_res(
cls,
list_options: ListOptions,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
if not isinstance(token_dict, OccupantTokenObject):
raise AccountRecords.raise_http_exception(
status_code="HTTP_404_NOT_FOUND",
error_case="UNAUTHORIZED",
message="Only Occupant can see this data",
data={},
)
return_list = []
living_space: BuildLivingSpace = BuildLivingSpace.filter_by_one(
id=token_dict.selected_occupant.living_space_id
).data
if not living_space:
raise AccountRecords.raise_http_exception(
status_code="HTTP_404_NOT_FOUND",
error_case="UNAUTHORIZED",
message="Living space not found",
data={},
)
if not list_options:
list_options = ListOptions()
main_filters = [
AccountRecords.living_space_id
== token_dict.selected_occupant.living_space_id,
BuildDecisionBookPayments.process_date
>= str(system_arrow.now().shift(months=-3).date()),
BuildDecisionBookPayments.process_date
< str(system_arrow.find_last_day_of_month(living_space.expiry_ends)),
BuildDecisionBookPayments.process_date
>= str(system_arrow.get(living_space.expiry_starts)),
BuildDecisionBookPayments.is_confirmed == True,
AccountRecords.active == True,
]
order_type = "desc"
if list_options.order_type:
order_type = "asc" if list_options.order_type[0] == "a" else "desc"
order_by_list = BuildDecisionBookPayments.process_date.desc()
if list_options.order_field:
if list_options.order_field == "process_date":
order_by_list = (
BuildDecisionBookPayments.process_date.asc()
if order_type == "asc"
else BuildDecisionBookPayments.process_date.desc()
)
if list_options.order_field == "bank_date":
order_by_list = (
AccountRecords.bank_date.desc()
if order_type == "asc"
else AccountRecords.bank_date.asc()
)
if list_options.order_field == "currency_value":
order_by_list = (
AccountRecords.currency_value.desc()
if order_type == "asc"
else AccountRecords.currency_value.asc()
)
if list_options.order_field == "process_comment":
order_by_list = (
AccountRecords.process_comment.desc()
if order_type == "asc"
else AccountRecords.process_comment.asc()
)
if list_options.order_field == "payment_amount":
order_by_list = (
BuildDecisionBookPayments.payment_amount.desc()
if order_type == "asc"
else BuildDecisionBookPayments.payment_amount.asc()
)
if list_options.query:
for key, value in list_options.query.items():
if key == "process_date":
main_filters.append(BuildDecisionBookPayments.process_date == value)
if key == "bank_date":
main_filters.append(AccountRecords.bank_date == value)
if key == "currency":
main_filters.append(BuildDecisionBookPayments.currency == value)
if key == "currency_value":
main_filters.append(AccountRecords.currency_value == value)
if key == "process_comment":
main_filters.append(AccountRecords.process_comment == value)
if key == "payment_amount":
main_filters.append(
BuildDecisionBookPayments.payment_amount == value
)
query = (
AccountRecords.session.query(
BuildDecisionBookPayments.process_date,
BuildDecisionBookPayments.payment_amount,
BuildDecisionBookPayments.currency,
AccountRecords.bank_date,
AccountRecords.currency_value,
AccountRecords.process_comment,
BuildDecisionBookPayments.uu_id,
)
.join(
AccountRecords,
AccountRecords.id == BuildDecisionBookPayments.account_records_id,
)
.filter(*main_filters)
).order_by(order_by_list)
query.limit(list_options.size or 5).offset(
(list_options.page or 1 - 1) * list_options.size or 5
)
for list_of_values in query.all() or []:
return_list.append(
{
"process_date": list_of_values[0],
"payment_amount": list_of_values[1],
"currency": list_of_values[2],
"bank_date": list_of_values[3],
"currency_value": list_of_values[4],
"process_comment": list_of_values[5],
}
)
return AlchemyJsonResponse(
completed=True,
message="Account records listed successfully",
result=return_list,
cls_object=AccountRecords,
filter_attributes=list_options,
response_model=AccountRecordResponse,
)
class AccountRecordsCreateEventMethods(MethodToEvent):
event_type = "CREATE"
event_description = ""
event_category = ""
__event_keys__ = {
"31f4f32f-0cd4-4995-8a6a-f9f56335848a": "account_records_create",
}
__event_validation__ = {
"31f4f32f-0cd4-4995-8a6a-f9f56335848a": InsertAccountRecord,
}
@classmethod
def account_records_create(
cls,
data: InsertAccountRecord,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
data_dict = data.excluded_dump()
if isinstance(token_dict, OccupantTokenObject):
db_session = AccountRecords.new_session()
build_iban = BuildIbans.filter_one(
BuildIbans.iban == data.iban,
BuildIbans.build_id == token_dict.selected_occupant.build_id,
db=db_session,
).data
if not build_iban:
raise BuildIbans.raise_http_exception(
status_code="HTTP_404_NOT_FOUND",
error_case="UNAUTHORIZED",
message=f"{data.iban} is not found in company related to your organization",
data={
"iban": data.iban,
},
)
account_record = AccountRecords.find_or_create(**data.excluded_dump())
return AlchemyJsonResponse(
completed=True,
message="Account record created successfully",
result=account_record,
)
elif isinstance(token_dict, EmployeeTokenObject):
# Build.pre_query = Build.select_action(
# employee_id=token_dict.selected_employee.employee_id,
# )
# build_ids_list = Build.filter_all(
# )
# build_iban = BuildIbans.filter_one(
# BuildIbans.iban == data.iban,
# BuildIbans.build_id.in_([build.id for build in build_ids_list.data]),
# ).data
# if not build_iban:
# BuildIbans.raise_http_exception(
# status_code="HTTP_404_NOT_FOUND",
# error_case="UNAUTHORIZED",
# message=f"{data.iban} is not found in company related to your organization",
# data={
# "iban": data.iban,
# },
# )
bank_date = system_arrow.get(data.bank_date)
data_dict["bank_date_w"] = bank_date.weekday()
data_dict["bank_date_m"] = bank_date.month
data_dict["bank_date_d"] = bank_date.day
data_dict["bank_date_y"] = bank_date.year
if int(data.currency_value) < 0:
debit_type = ApiEnumDropdown.filter_by_one(
system=True, enum_class="DebitTypes", key="DT-D"
).data
data_dict["receive_debit"] = debit_type.id
data_dict["receive_debit_uu_id"] = str(debit_type.uu_id)
else:
debit_type = ApiEnumDropdown.filter_by_one(
system=True, enum_class="DebitTypes", key="DT-R"
).data
data_dict["receive_debit"] = debit_type.id
data_dict["receive_debit_uu_id"] = str(debit_type.uu_id)
account_record = AccountRecords.insert_one(data_dict).data
return AlchemyJsonResponse(
completed=True,
message="Account record created successfully",
result=account_record,
)
class AccountRecordsUpdateEventMethods(MethodToEvent):
event_type = "UPDATE"
event_description = ""
event_category = ""
__event_keys__ = {
"ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": "account_records_update",
}
__event_validation__ = {
"ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": UpdateAccountRecord,
}
@classmethod
def build_area_update(
cls,
build_uu_id: str,
data: UpdateAccountRecord,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
if isinstance(token_dict, OccupantTokenObject):
pass
elif isinstance(token_dict, EmployeeTokenObject):
pass
AccountRecords.build_parts_id = token_dict.selected_occupant.build_part_id
account_record = AccountRecords.update_one(build_uu_id, data).data
return AlchemyJsonResponse(
completed=True,
message="Account record updated successfully",
result=account_record,
cls_object=AccountRecords,
response_model=UpdateAccountRecord,
)
class AccountRecordsPatchEventMethods(MethodToEvent):
event_type = "PATCH"
event_description = ""
event_category = ""
__event_keys__ = {
"34c38937-42a2-45f1-b2ef-a23978650aee": "account_records_patch",
}
__event_validation__ = {
"34c38937-42a2-45f1-b2ef-a23978650aee": None,
}
@classmethod
def build_area_patch(
cls,
build_uu_id: str,
data,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
account_record = AccountRecords.patch_one(build_uu_id, data).data
return AlchemyJsonResponse(
completed=True,
message="Account record patched successfully",
result=account_record,
)

View File

@@ -0,0 +1,99 @@
"""
Route configuration registry.
This module collects and registers all route configurations from different modules
to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any, Callable
from fastapi import Request
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig
from ApiEvents.EventServiceApi.utils import with_token_event
from ApiEvents.EventServiceApi.account.account_records import (
AccountRecordsListEventMethods,
ListOptions,
InsertAccountRecord,
SearchAddress,
UpdateAccountRecord,
)
@with_token_event
def address_list(request: Request, list_options: ListOptions):
"""Handle address list endpoint."""
pass
@with_token_event
def address_create(request: Request, data: InsertAccountRecord):
"""Handle address creation endpoint."""
pass
@with_token_event
def address_search(request: Request, data: SearchAddress):
"""Handle address search endpoint."""
pass
@with_token_event
def address_update(request: Request, address_uu_id: str, data: UpdateAccountRecord):
"""Handle address update endpoint."""
pass
# Account Records Router Configuration
ACCOUNT_RECORDS_CONFIG = {
'name': 'account_records',
'prefix': '/account/records',
'tags': ['Account Records'],
'include_in_schema': True,
'endpoints': [
EndpointFactoryConfig(
endpoint="/list",
method="POST",
summary="List Active/Delete/Confirm Address",
description="List Active/Delete/Confirm Address",
is_auth_required=True,
is_event_required=True,
request_model=ListOptions,
endpoint_function=address_list
),
EndpointFactoryConfig(
endpoint="/create",
method="POST",
summary="Create Address with given auth levels",
description="Create Address with given auth levels",
is_auth_required=True,
is_event_required=True,
request_model=InsertAccountRecord,
endpoint_function=address_create
),
EndpointFactoryConfig(
endpoint="/search",
method="POST",
summary="Search Address with given auth levels",
description="Search Address with given auth levels",
is_auth_required=True,
is_event_required=True,
request_model=SearchAddress,
endpoint_function=address_search
),
EndpointFactoryConfig(
endpoint="/update/{address_uu_id}",
method="POST",
summary="Update Address with given auth levels",
description="Update Address with given auth levels",
is_auth_required=True,
is_event_required=True,
request_model=UpdateAccountRecord,
endpoint_function=address_update
)
]
}
# Registry of all route configurations
ROUTE_CONFIGS = [
ACCOUNT_RECORDS_CONFIG,
# Add other route configurations here
]
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return ROUTE_CONFIGS

View File

@@ -0,0 +1,51 @@
"""
Utility functions for API event handling.
"""
from functools import wraps
from inspect import signature
from typing import Callable, TypeVar, ParamSpec, Any
from fastapi import Request
from Services.PostgresDb.Models.token_models import parse_token_object_to_dict
P = ParamSpec('P')
R = TypeVar('R')
def with_token_event(func: Callable[P, R]) -> Callable[P, R]:
"""
Decorator that handles token parsing and event execution.
This decorator:
1. Parses the token from the request
2. Calls the appropriate event with the token and other arguments
Args:
func: The endpoint function to wrap
Returns:
Wrapped function that handles token parsing and event execution
"""
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
# Extract request from args or kwargs
request = next(
(arg for arg in args if isinstance(arg, Request)),
kwargs.get('request')
)
if not request:
raise ValueError("Request object not found in arguments")
# Parse token
token_dict = parse_token_object_to_dict(request=request)
# Add token_dict to kwargs
kwargs['token_dict'] = token_dict
# Call the original function
return token_dict.available_event(**{
k: v for k, v in kwargs.items()
if k in signature(token_dict.available_event).parameters
})
return wrapper

181
ApiEvents/abstract_class.py Normal file
View File

@@ -0,0 +1,181 @@
"""
Abstract base classes for API route and event handling.
This module provides core abstractions for route configuration and factory,
with support for authentication and event handling.
"""
from typing import Optional, Dict, Any, List, Type, Union, ClassVar, Tuple, TypeVar
from dataclasses import dataclass, field
from pydantic import BaseModel
ResponseModel = TypeVar('ResponseModel', bound=BaseModel)
@dataclass
class EndpointFactoryConfig:
"""Configuration class for API endpoints.
Attributes:
endpoint: URL path for this endpoint
method: HTTP method (GET, POST, etc.)
summary: Short description for API documentation
description: Detailed description for API documentation
is_auth_required: Whether authentication is required
is_event_required: Whether event handling is required
request_model: Expected request model type
extra_options: Additional endpoint options
response_model: Expected response model type
"""
endpoint: str
method: str
summary: str
description: str
is_auth_required: bool
is_event_required: bool
request_model: Type[BaseModel]
extra_options: Dict[str, Any] = field(default_factory=dict)
response_model: Optional[Type[BaseModel]] = None
@dataclass
class RouteFactoryConfig:
"""Configuration class for API route factories.
Attributes:
name: Route name
tags: List of tags for API documentation
prefix: URL prefix for all endpoints in this route
include_in_schema: Whether to include in OpenAPI schema
endpoints: List of endpoint configurations
extra_options: Additional route options
"""
name: str
tags: Union[str, List[str]]
prefix: str
include_in_schema: bool = True
endpoints: List[EndpointFactoryConfig] = field(default_factory=list)
extra_options: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
"""Validate and normalize configuration after initialization."""
if isinstance(self.tags, str):
self.tags = [self.tags]
def as_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary format."""
return {
"name": self.name,
"tags": self.tags,
"prefix": self.prefix,
"include_in_schema": self.include_in_schema,
"endpoints": [
{
"endpoint": ep.endpoint,
"method": ep.method,
"summary": ep.summary,
"description": ep.description,
"is_auth_required": ep.is_auth_required,
"is_event_required": ep.is_event_required,
"response_model": ep.response_model.__name__ if ep.response_model else None,
"request_model": ep.request_model.__name__,
"extra_options": ep.extra_options
}
for ep in self.endpoints
],
"extra_options": self.extra_options
}
class ActionsSchema:
"""Base class for defining API action schemas.
This class handles endpoint registration and validation in the database.
Subclasses should implement specific validation logic.
"""
def __init__(self, endpoint: str):
"""Initialize with an API endpoint path.
Args:
endpoint: The API endpoint path (e.g. "/users/create")
"""
self.endpoint = endpoint
def retrieve_action_from_endpoint(self) -> Dict[str, Any]:
"""Retrieve the endpoint registration from the database.
Returns:
Dict containing the endpoint registration data
Raises:
HTTPException: If endpoint is not found in database
"""
raise NotImplementedError("Subclasses must implement retrieve_action_from_endpoint")
class ActionsSchemaFactory:
"""Factory class for creating and validating action schemas.
This class ensures proper initialization and validation of API endpoints
through their action schemas.
"""
def __init__(self, action: ActionsSchema):
"""Initialize with an action schema.
Args:
action: The action schema to initialize
Raises:
HTTPException: If action initialization fails
"""
self.action = action
self.action_match = self.action.retrieve_action_from_endpoint()
class MethodToEvent:
"""Base class for mapping methods to API events with type safety.
This class provides a framework for handling API events with proper
type checking for tokens and response models.
Type Parameters:
TokenType: Type of authentication token
ResponseModel: Type of response model
"""
action_key: ClassVar[Optional[str]] = None
event_type: ClassVar[Optional[str]] = None
event_description: ClassVar[str] = ""
event_category: ClassVar[str] = ""
__event_keys__: ClassVar[Dict[str, str]] = {}
__event_validation__: ClassVar[List[Tuple[Type[ResponseModel], List[Dict[str, Any]]]]] = []
@classmethod
def retrieve_language_parameters(
cls, language: str, function_code: str
) -> Dict[str, str]:
"""Retrieve language-specific parameters for an event.
Args:
language: Language code (e.g. 'tr', 'en')
function_code: Function identifier
Returns:
Dictionary of language-specific field mappings
"""
validation_dict = dict(cls.__event_validation__)
if function_code not in validation_dict:
return {}
event_response_model, event_language_models = validation_dict[function_code]
# Collect language-specific field mappings
language_models = {}
for model in event_language_models:
language_models.update(model.get(language, model.get("tr", {})))
# Map response model fields to language-specific values
return {
field: language_models[field]
for field in event_response_model.model_fields
if field in language_models
}