error raise now locates loc

This commit is contained in:
2025-01-16 19:49:21 +03:00
parent 56b693989d
commit fbd0e336e0
22 changed files with 438 additions and 337 deletions

View File

@@ -6,7 +6,11 @@ from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
from api_objects.errors.errorHandlers import HTTPExceptionEvyos, HTTPExceptionAnyHandler, HTTPExceptionEvyosHandler
from api_objects.errors.errorHandlers import (
HTTPExceptionEvyos,
HTTPExceptionAnyHandler,
HTTPExceptionEvyosHandler,
)
from middlewares.token_middleware import AuthHeaderMiddleware
from application.create_file import create_app
@@ -27,14 +31,16 @@ app.add_middleware(
app.add_middleware(AuthHeaderMiddleware)
# Initialize Exception and ExceptionInstance handlers
CustomExceptionHandler = HTTPExceptionEvyosHandler(**dict(
statuses=status,
exceptions=HTTPException,
response_model=JSONResponse,
exceptions_dict=EXCEPTION_DICTS,
errors_dict=ERRORS_DICT,
error_language_dict=ERRORS_LANG
))
CustomExceptionHandler = HTTPExceptionEvyosHandler(
**dict(
statuses=status,
exceptions=HTTPException,
response_model=JSONResponse,
exceptions_dict=EXCEPTION_DICTS,
errors_dict=ERRORS_DICT,
error_language_dict=ERRORS_LANG,
)
)
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
# Register error handlers with bound methods

View File

@@ -1,11 +1,8 @@
import json
from typing import Union
from fastapi import status
from fastapi.requests import Request
from fastapi.exceptions import HTTPException
from api_objects import OccupantTokenObject, EmployeeTokenObject
from api_objects.auth.token_objects import CompanyToken, OccupantToken
from api_services.templates.password_templates import (
password_is_changed_template,
change_your_password_template,
@@ -36,6 +33,13 @@ from api_validations.validations_response import (
from ApiServices.api_handlers.auth_actions.auth import AuthActions
from api_configs import Auth, ApiStatic
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from api_objects import (
OccupantTokenObject,
EmployeeTokenObject,
CompanyToken,
OccupantToken,
HTTPExceptionEvyos,
)
from databases import (
Companies,
@@ -55,9 +59,7 @@ from databases import (
RelationshipEmployee2Build,
)
from api_services import (
send_email,
)
from api_services import send_email
class AuthenticationLoginEventMethods(MethodToEvent):
@@ -74,26 +76,12 @@ class AuthenticationLoginEventMethods(MethodToEvent):
@classmethod
def authentication_login_with_domain_and_creds(cls, data: Login, request: Request):
from api_objects import HTTPExceptionEvyos
raise HTTPExceptionEvyos(
error_code="UNKNOWN_ERROR",
lang="en",
)
access_dict = Users.login_user_with_credentials(data=data, request=request)
found_user = access_dict.get("user")
Users.client_arrow = DateTimeLocal(
is_client=True, timezone=found_user.local_timezone
)
if not found_user:
# UserLogger.log_login_attempt(
# request,
# None,
# data.domain,
# data.access_key,
# success=False,
# error="Invalid credentials",
# )
return ResponseHandler.unauthorized("Invalid credentials")
return ResponseHandler.success(
message="User logged in successfully",
@@ -104,11 +92,6 @@ class AuthenticationLoginEventMethods(MethodToEvent):
"user": found_user.get_dict(),
},
)
# except Exception as e:
# # UserLogger.log_login_attempt(
# # request, None, data.domain, data.access_key, success=False, error=str(e)
# # )
# raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e))
class AuthenticationSelectEventMethods(MethodToEvent):
@@ -128,9 +111,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
cls, data: EmployeeSelection, token_dict: EmployeeTokenObject, request: Request
):
"""Handle employee company selection"""
Users.client_arrow = DateTimeLocal(
is_client=True, timezone=token_dict.local_timezone
)
Users.client_arrow = DateTimeLocal(is_client=True, timezone=token_dict.timezone)
if data.company_uu_id not in token_dict.companies_uu_id_list:
return ResponseHandler.unauthorized(
"Company not found in user's company list"

View File

@@ -5,9 +5,15 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
from api_objects.errors.errorHandlers import (
HTTPExceptionEvyos,
HTTPExceptionAnyHandler,
HTTPExceptionEvyosHandler,
)
from middlewares.token_middleware import AuthHeaderMiddleware
from application.create_file import create_app
from api_objects.errors.errors_dictionary import ErrorHandlers
from prometheus_fastapi_instrumentator import Instrumentator
app = create_app(routers=routers)
@@ -24,17 +30,22 @@ app.add_middleware(
)
app.add_middleware(AuthHeaderMiddleware)
# Initialize error handlers
error_handlers = ErrorHandlers.create(
requests=Request,
exceptions=HTTPException,
response_model=JSONResponse,
status=status,
# Initialize Exception and ExceptionInstance handlers
CustomExceptionHandler = HTTPExceptionEvyosHandler(
**dict(
statuses=status,
exceptions=HTTPException,
response_model=JSONResponse,
exceptions_dict=EXCEPTION_DICTS,
errors_dict=ERRORS_DICT,
error_language_dict=ERRORS_LANG,
)
)
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
# Register error handlers with bound methods
app.add_exception_handler(HTTPException, error_handlers.exception_handler_http)
app.add_exception_handler(Exception, error_handlers.exception_handler_exception)
app.add_exception_handler(HTTPExceptionEvyos, CustomExceptionHandler.handle_exception)
app.add_exception_handler(Exception, CustomExceptionAnyHandler.any_exception_handler)
if __name__ == "__main__":
uvicorn_config = {

View File

@@ -58,7 +58,6 @@ from .decision_book.project_decision_book_items.router import (
from .decision_book.project_decision_book_person.router import (
build_decision_book_project_people_route,
)
from .validations.router import validations_route
__all__ = [
"account_records_router",
@@ -97,5 +96,4 @@ __all__ = [
"build_decision_book_project_route",
"build_decision_book_project_items_route",
"build_decision_book_project_people_route",
"validations_route",
]

View File

@@ -1,123 +0,0 @@
from fastapi import status
from fastapi.routing import APIRouter
from fastapi.requests import Request
from fastapi.exceptions import HTTPException
from api_validations.validations_request import (
EndpointValidation,
)
from pydantic import BaseModel
validations_route = APIRouter(prefix="/validations", tags=["Validations"])
validations_route.include_router(validations_route, include_in_schema=True)
class EndpointValidationResponse(BaseModel):
language: str
headers: dict
validation: dict
class ValidationParser:
def __init__(self, active_validation):
self.annotations = (
active_validation.__annotations__.items() if active_validation else None
)
self.schema = {}
self.parse()
def parse(self):
for key, value in self.annotations or {}:
field_type, required = "string", False
if str(value) == "<class 'str'>" or str(value) == "typing.Optional[str]":
field_type = "string"
required = not str(value) == "typing.Optional[str]"
elif str(value) == "<class 'int'>" or str(value) == "typing.Optional[int]":
field_type = "integer"
required = not str(value) == "typing.Optional[int]"
elif (
str(value) == "<class 'bool'>" or str(value) == "typing.Optional[bool]"
):
field_type = "boolean"
required = not str(value) == "typing.Optional[bool]"
elif (
str(value) == "<class 'float'>"
or str(value) == "typing.Optional[float]"
):
field_type = "float"
required = not str(value) == "typing.Optional[bool]"
elif (
str(value) == "<class 'datetime.datetime'>"
or str(value) == "typing.Optional[datetime.datetime]"
):
field_type = "datetime"
required = not str(value) == "typing.Optional[datetime.datetime]"
self.schema[key] = {"type": field_type, "required": required}
def retrieve_validation_from_class(selected_event, events):
event_function_class = getattr(selected_event, "function_class", None)
event_function_code = getattr(selected_event, "function_code", None)
function_class = getattr(events, event_function_class, None)
return function_class.__event_validation__.get(event_function_code, None)
@validations_route.post(path="/endpoint", summary="Retrieve validation of endpoint")
def user_list(request: Request, validation: EndpointValidation):
import api_events.events as events
from api_services.redis.functions import get_object_via_access_key
from databases import (
EndpointRestriction,
Events,
)
valid_token = get_object_via_access_key(request=request)
if not valid_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"No valid token found in the request.",
)
endpoint_active = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_name.ilike(f"%{str(validation.endpoint)}%"),
system=True,
).data
if not endpoint_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"This endpoint {str(validation.endpoint)} is not active for this user, please contact your responsible company for further information.",
)
if valid_token.user_type == 1 and not valid_token.selected_company:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Selected company is not found in the token object.",
)
elif valid_token.user_type == 2 and not valid_token.selected_occupant:
raise HTTPException(
status_code=status.HTTP_418_IM_A_TEAPOT,
detail="Selected occupant is not found in the token object.",
)
selected_event = Events.filter_one(
Events.endpoint_id == endpoint_active.id,
Events.id.in_(valid_token.selected_company.reachable_event_list_id),
).data
if not selected_event:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="This endpoint requires event validation. Please contact your responsible company to use this event.",
)
active_validation = retrieve_validation_from_class(selected_event, events)
if not active_validation:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No validation found for this endpoint.",
)
headers = getattr(
active_validation, str(valid_token.lang).lower(), active_validation.tr
)
validation_parse = ValidationParser(active_validation=active_validation)
return EndpointValidationResponse(
language=valid_token.lang,
headers=headers,
validation=validation_parse.schema,
)

View File

@@ -5,12 +5,17 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
from api_objects.errors.errorHandlers import (
HTTPExceptionEvyos,
HTTPExceptionAnyHandler,
HTTPExceptionEvyosHandler,
)
from middlewares.token_middleware import AuthHeaderMiddleware
from application.create_file import create_app
from api_objects.errors.errors_dictionary import ErrorHandlers
from prometheus_fastapi_instrumentator import Instrumentator
app = create_app(routers=routers)
Instrumentator().instrument(app=app).expose(app=app)
@@ -25,17 +30,22 @@ app.add_middleware(
)
app.add_middleware(AuthHeaderMiddleware)
# Initialize error handlers
error_handlers = ErrorHandlers.create(
requests=Request,
exceptions=HTTPException,
response_model=JSONResponse,
status=status,
# Initialize Exception and ExceptionInstance handlers
CustomExceptionHandler = HTTPExceptionEvyosHandler(
**dict(
statuses=status,
exceptions=HTTPException,
response_model=JSONResponse,
exceptions_dict=EXCEPTION_DICTS,
errors_dict=ERRORS_DICT,
error_language_dict=ERRORS_LANG,
)
)
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
# Register error handlers with bound methods
app.add_exception_handler(HTTPException, error_handlers.exception_handler_http)
app.add_exception_handler(Exception, error_handlers.exception_handler_exception)
app.add_exception_handler(HTTPExceptionEvyos, CustomExceptionHandler.handle_exception)
app.add_exception_handler(Exception, CustomExceptionAnyHandler.any_exception_handler)
if __name__ == "__main__":
uvicorn_config = {

View File

@@ -1 +1,3 @@
__all__ = []
from .validations_by_endpoint.router import validations_router
__all__ = ["validations_router"]

View File

@@ -0,0 +1,139 @@
import json
from fastapi.routing import APIRouter
from fastapi.requests import Request
from api_validations.validations_request import EndpointValidation
from ApiServices.api_handlers.core_response import HTTPExceptionEvyos
from pydantic import BaseModel
validations_router = APIRouter(prefix="/validations", tags=["Validations"])
validations_router.include_router(validations_router, include_in_schema=True)
class EndpointValidationResponse(BaseModel):
language: str
headers: dict
validation: dict
class ValidationParser:
def __init__(self, active_validation):
self.core_validation = active_validation
self.annotations = (
active_validation.model_json_schema() if active_validation else None
)
self.annotations = json.loads(json.dumps(self.annotations))
self.schema = {}
self.parse()
def parse(self):
from api_validations.validations_request import PydanticBaseModel, CrudRecords
properties = self.annotations.get("properties").items()
for key, value in properties:
default, title, type_field = value.get("default", None), value.get("title", ""), value.get("type", None)
field_type, required, possible_types = "string", False, []
if not type_field:
for _ in value.get("anyOf") or []:
type_opt = json.loads(json.dumps(_))
if not type_opt.get("type") == "null":
possible_types.append(type_opt.get("type"))
field_type = possible_types[0]
else:
field_type, required = type_field, True
total_class_annotations = {
**self.core_validation.__annotations__
**PydanticBaseModel.__annotations__,
**CrudRecords.__annotations__,
}
attribute_of_class = total_class_annotations.get(key, None)
if (
str(attribute_of_class) == "<class 'str'>"
or str(attribute_of_class) == "typing.Optional[str]"
):
field_type = "string"
required = not str(attribute_of_class) == "typing.Optional[str]"
elif (
str(attribute_of_class) == "<class 'int'>"
or str(attribute_of_class) == "typing.Optional[int]"
):
field_type = "integer"
required = not str(attribute_of_class) == "typing.Optional[int]"
elif (
str(attribute_of_class) == "<class 'bool'>"
or str(attribute_of_class) == "typing.Optional[bool]"
):
field_type = "boolean"
required = not str(attribute_of_class) == "typing.Optional[bool]"
elif (
str(attribute_of_class) == "<class 'float'>"
or str(attribute_of_class) == "typing.Optional[float]"
):
field_type = "float"
required = not str(attribute_of_class) == "typing.Optional[bool]"
elif (
str(attribute_of_class) == "<class 'datetime.datetime'>"
or str(attribute_of_class) == "typing.Optional[datetime.datetime]"
):
field_type = "datetime"
required = (
not str(attribute_of_class) == "typing.Optional[datetime.datetime]"
)
self.schema[key] = {"type": field_type, "required": required, "default": default}
@classmethod
def retrieve_validation_from_class(cls, selected_event, events, lang):
event_function_class = getattr(selected_event, "function_class", None)
event_function_code = getattr(selected_event, "function_code", None)
function_class = getattr(events, event_function_class, None)
event_headers = function_class.retrieve_language_parameters(
language=lang, function_code=event_function_code
)
event_validation = function_class.__event_validation__.get(
event_function_code, [None]
)[0]
if not event_validation:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=lang)
return event_validation, event_headers
@validations_router.post(path="/validations", summary="Get validations by endpoint")
def validation_by_endpoint(request: Request, validation: EndpointValidation):
import api_events.events as events
from api_services.redis.functions import RedisActions
from databases import EndpointRestriction, Events
valid_token = RedisActions.get_object_via_access_key(request=request)
language = str(valid_token.lang).lower()
if not valid_token:
raise HTTPExceptionEvyos(error_code="", lang=language)
endpoint_active = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_name.ilike(f"%{str(validation.endpoint)}%"),
system=True,
).data
if not endpoint_active:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
if valid_token.user_type == 1 and not valid_token.selected_company:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
elif valid_token.user_type == 2 and not valid_token.selected_occupant:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
selected_event = Events.filter_one(
Events.endpoint_id == endpoint_active.id,
Events.id.in_(valid_token.selected_company.reachable_event_list_id),
).data
if not selected_event:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
active_validation, active_headers = ValidationParser.retrieve_validation_from_class(
selected_event=selected_event, events=events, lang=language
)
if not active_validation:
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
validation_parse = ValidationParser(active_validation=active_validation)
return EndpointValidationResponse(
language=language,
headers=active_headers,
validation=validation_parse.schema,
)

View File

@@ -6,8 +6,8 @@ from pydantic import BaseModel
from api_validations.validations_request import PydanticBaseModel, BaseModelRegular
from databases.sql_models.response_model import AlchemyResponse
from sqlalchemy.orm import Query
from api_objects.errors.errorHandlers import HTTPExceptionEvyos
MODEL_TYPE = Callable[[Any], Any]
class Pagination:
size: int = 10
@@ -18,12 +18,24 @@ class Pagination:
totalCount: int = 1
totalPage: int = 1
def change(self, page=None, size=None, order_field=None, order_type=None):
def change(
self,
page: int = 1,
size: int = 10,
order_field: str = "id",
order_type: str = "asc",
):
self.size = size if 10 < size < 40 else 10
self.page = page or self.page
self.size = size or self.size
self.orderField = order_field or self.orderField
self.orderType = order_type or self.orderType
self.setter_page()
if self.page > self.totalPage:
self.page = self.totalPage
elif self.page < 1:
self.page = 1
self.setter_page()
def feed(self, data):
if isinstance(data, list):
@@ -32,14 +44,16 @@ class Pagination:
self.totalCount = data.count
elif isinstance(data, Query):
self.totalCount = data.count()
self.setter_page()
def setter_page(self):
self.pageCount = self.size
self.totalPage = int(round(self.totalCount / self.size, 0))
if self.totalCount % self.size > 0:
if self.page == self.totalPage:
self.pageCount = self.totalCount % self.size
remainder = self.totalCount % self.size
if remainder > 0:
self.totalPage = int(round(self.totalCount / self.size, 0)) + 1
if self.page == self.totalPage:
self.pageCount = remainder
def as_dict(self):
return {
@@ -56,17 +70,19 @@ class Pagination:
class SingleAlchemyResponse:
status_code = "HTTP_200_OK"
result: AlchemyResponse
response_model: MODEL_TYPE
response_model: Any
message: str
completed: bool
cls_object: Any = (None,)
def __new__(
cls,
message: str,
response_model: MODEL_TYPE,
response_model: Any,
status_code: str = "HTTP_200_OK",
result: AlchemyResponse = None,
completed: bool = True,
cls_object: Any = None,
):
cls.status_code = getattr(status, status_code, "HTTP_200_OK")
cls.message = message
@@ -75,10 +91,16 @@ class SingleAlchemyResponse:
cls.response_model = response_model
if not isinstance(cls.result, AlchemyResponse):
raise Exception("Invalid response type 4 single alchemy response")
HTTPExceptionEvyos(
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
)
if not cls.result.first:
raise Exception("Invalid data type 4 single alchemy response")
HTTPExceptionEvyos(
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
)
pagination = Pagination()
pagination.change(page=1)
@@ -103,7 +125,7 @@ class AlchemyJsonResponse:
result: AlchemyResponse
completed: bool
filter_attributes: Any = None
response_model: MODEL_TYPE = None
response_model: Any = None
cls_object: Any = None
def __new__(
@@ -112,7 +134,7 @@ class AlchemyJsonResponse:
status_code: str = "HTTP_200_OK",
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
completed: bool = True,
response_model: MODEL_TYPE = None,
response_model: Any = None,
cls_object: Any = None,
filter_attributes: Any = None,
):
@@ -126,11 +148,15 @@ class AlchemyJsonResponse:
pagination = Pagination()
if cls.result.first:
raise Exception("Invalid data type 4 alchemy response")
HTTPExceptionEvyos(
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
)
if filter_attributes:
pagination.change(
page=filter_attributes.page, size=filter_attributes.size,
page=filter_attributes.page,
size=filter_attributes.size,
order_field=filter_attributes.order_field,
order_type=filter_attributes.order_type,
)
@@ -158,8 +184,8 @@ class ListJsonResponse:
message: str
completed: bool
filter_attributes: Any
response_model: MODEL_TYPE = None,
cls_object: Any = None,
response_model: Any = (None,)
cls_object: Any = (None,)
def __new__(
cls,
@@ -167,7 +193,7 @@ class ListJsonResponse:
status_code: str = "HTTP_200_OK",
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
completed: bool = True,
response_model: MODEL_TYPE = None,
response_model: Any = None,
cls_object: Any = None,
filter_attributes: Any = None,
):
@@ -176,15 +202,20 @@ class ListJsonResponse:
cls.result = result
cls.completed = completed
cls.filter_attributes = filter_attributes
cls.response_model: MODEL_TYPE = response_model
cls.response_model: Any = response_model
if not isinstance(cls.result, list):
raise Exception("Invalid data type 4 list json response")
HTTPExceptionEvyos(
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
)
pagination = Pagination()
pagination.change(page=1)
data = list(cls.result)
if cls.response_model:
data = [cls.response_model(**data_object).dump() for data_object in cls.result]
data = [
cls.response_model(**data_object).dump() for data_object in cls.result
]
pagination.feed(data)
return JSONResponse(
status_code=cls.status_code,
@@ -196,14 +227,15 @@ class ListJsonResponse:
),
)
class DictJsonResponse:
status_code = "HTTP_200_OK"
result: dict
message: str
completed: bool
filter_attributes: Any
response_model: MODEL_TYPE = None,
cls_object: Any = None,
response_model: Any = (None,)
cls_object: Any = (None,)
def __new__(
cls,
@@ -211,7 +243,7 @@ class DictJsonResponse:
status_code: str = "HTTP_200_OK",
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
completed: bool = True,
response_model: MODEL_TYPE = None,
response_model: Any = None,
cls_object: Any = None,
filter_attributes: Any = None,
):
@@ -220,10 +252,13 @@ class DictJsonResponse:
cls.result = result
cls.completed = completed
cls.filter_attributes = filter_attributes
cls.response_model: MODEL_TYPE = response_model
cls.response_model: Any = response_model
if not isinstance(cls.result, dict):
raise Exception("Invalid data type 4 dict json response")
HTTPExceptionEvyos(
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
)
pagination = Pagination()
pagination.change(page=1)