diff --git a/nest/core/decorators/class_based_view.py b/nest/core/decorators/class_based_view.py index 18cf0b8..c599dd3 100644 --- a/nest/core/decorators/class_based_view.py +++ b/nest/core/decorators/class_based_view.py @@ -19,13 +19,12 @@ from fastapi import APIRouter, Depends from starlette.routing import Route, WebSocketRoute -T = TypeVar("T") -K = TypeVar("K", bound=Callable[..., Any]) +C = TypeVar("C", bound=object) CBV_CLASS_KEY = "__cbv_class__" -def class_based_view(router: APIRouter, cls: Type[T]) -> Type[T]: +def class_based_view(router: APIRouter, cls: Type[C]) -> Type[C]: """ Replaces any methods of the provided class `cls` that are endpoints of routes in `router` with updated function calls that will properly inject an instance of `cls`. @@ -48,7 +47,7 @@ def class_based_view(router: APIRouter, cls: Type[T]) -> Type[T]: return cls -def _init_cbv(cls: Type[Any]) -> None: +def _init_cbv(cls: Type[C]) -> None: """ Idempotently modifies the provided `cls`, performing the following modifications: * The `__init__` function is updated to set any class-annotated dependencies as instance attributes @@ -110,4 +109,4 @@ def _update_cbv_route_endpoint_signature( for parameter in old_parameters[1:] ] new_signature = old_signature.replace(parameters=new_parameters) - setattr(route.endpoint, "__signature__", new_signature) \ No newline at end of file + setattr(route.endpoint, "__signature__", new_signature) diff --git a/nest/core/decorators/controller.py b/nest/core/decorators/controller.py index 8073083..98a8636 100644 --- a/nest/core/decorators/controller.py +++ b/nest/core/decorators/controller.py @@ -1,4 +1,4 @@ -from typing import Optional, Type +from typing import Optional, Type, TypeVar, Callable from fastapi.routing import APIRouter @@ -6,8 +6,9 @@ from nest.core.decorators.http_method import HTTPMethod from nest.core.decorators.utils import get_instance_variables, parse_dependencies +C = TypeVar('C', bound=object) -def Controller(prefix: Optional[str] = None, tag: Optional[str] = None): +def Controller(prefix: Optional[str] = None, tag: Optional[str] = None) -> Callable[[Type[C]], Type[C]]: """ Decorator that turns a class into a controller, allowing you to define routes using FastAPI decorators. @@ -23,7 +24,7 @@ def Controller(prefix: Optional[str] = None, tag: Optional[str] = None): # Default route_prefix to tag_name if route_prefix is not provided route_prefix = process_prefix(prefix, tag) - def wrapper(cls: Type) -> Type[ClassBasedView]: + def wrapper(cls: Type[C]) -> Type[C]: router = APIRouter(tags=[tag] if tag else None) # Process class dependencies diff --git a/nest/core/decorators/database.py b/nest/core/decorators/database.py index 970ce51..c73afd1 100644 --- a/nest/core/decorators/database.py +++ b/nest/core/decorators/database.py @@ -3,12 +3,21 @@ from fastapi.exceptions import HTTPException from sqlalchemy.ext.asyncio import AsyncSession +from typing import TypeVar, Callable, Union logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec -def db_request_handler(func): +P = ParamSpec("P") +R = TypeVar("R") + + +def db_request_handler(func: Callable[[P], R]) -> Callable[[object, P], Union[R, HTTPException]]: """ Decorator that handles database requests, including error handling and session management. @@ -19,7 +28,7 @@ def db_request_handler(func): function: The decorated function. """ - def wrapper(self, *args, **kwargs): + def wrapper(self, *args: P.args, **kwargs: P.kwargs) -> Union[R, HTTPException]: try: s = time.time() result = func(self, *args, **kwargs) @@ -40,7 +49,7 @@ def wrapper(self, *args, **kwargs): return wrapper -def async_db_request_handler(func): +def async_db_request_handler(func: Callable[[P], R]) -> Callable[[P], R]: """ Asynchronous decorator that handles database requests, including error handling, session management, and logging for async functions. @@ -52,7 +61,7 @@ def async_db_request_handler(func): function: The decorated async function. """ - async def wrapper(*args, **kwargs): + async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: start_time = time.time() result = await func(*args, **kwargs) # Awaiting the async function diff --git a/nest/core/decorators/http_code.py b/nest/core/decorators/http_code.py index 4a0a7e0..9857e31 100644 --- a/nest/core/decorators/http_code.py +++ b/nest/core/decorators/http_code.py @@ -1,4 +1,15 @@ from nest.common.constants import STATUS_CODE_TOKEN +from typing import TypeVar, Callable +import sys + +if sys.version_info >= (3, 10): + from typing import ParamSpec, TypeAlias +else: + from typing_extensions import ParamSpec, TypeAlias + +P = ParamSpec("P") +R = TypeVar("R") +Func: TypeAlias = Callable[[P], R] def HttpCode(status_code: int): @@ -9,7 +20,7 @@ def HttpCode(status_code: int): status_code (int): The HTTP status code for the response. """ - def decorator(func): + def decorator(func: Func) -> Func: if not hasattr(func, STATUS_CODE_TOKEN): setattr(func, STATUS_CODE_TOKEN, status_code) return func diff --git a/nest/core/decorators/http_method.py b/nest/core/decorators/http_method.py index 9067033..d12f7a4 100644 --- a/nest/core/decorators/http_method.py +++ b/nest/core/decorators/http_method.py @@ -1,6 +1,16 @@ +from typing import Callable, List, Union, TypeVar from enum import Enum -from typing import Any, Callable, List, Union +import sys +if sys.version_info >= (3, 10): + from typing import ParamSpec, TypeAlias +else: + from typing_extensions import ParamSpec, TypeAlias + + +P = ParamSpec("P") +R = TypeVar("R") +Func: TypeAlias = Callable[[P], R] class HTTPMethod(Enum): GET = "GET" @@ -12,20 +22,22 @@ class HTTPMethod(Enum): OPTIONS = "OPTIONS" -def route(http_method: HTTPMethod, route_path: Union[str, List[str]] = "/", **kwargs): +def route(http_method: HTTPMethod, route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[[Func], Func]: """ Decorator that defines a route for the controller. Args: http_method (HTTPMethod): The HTTP method for the route (GET, POST, DELETE, PUT, PATCH). - route_path (Union[str, List[str]]): The route path for the route. example: "/users" + route_path (Union[str, List[str]]): The route path for the route. **kwargs: Additional keyword arguments to configure the route. Returns: - function: The decorated function. + function: The decorated function, preserving the signature. """ - def decorator(func): + def decorator(func: Func) -> Func: + + # Add custom route metadata func.__http_method__ = http_method func.__route_path__ = route_path func.__kwargs__ = kwargs @@ -35,29 +47,29 @@ def decorator(func): return decorator -def Get(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Get(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.GET, route_path, **kwargs) -def Post(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Post(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.POST, route_path, **kwargs) -def Delete(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Delete(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.DELETE, route_path, **kwargs) -def Put(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Put(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.PUT, route_path, **kwargs) -def Patch(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Patch(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.PATCH, route_path, **kwargs) -def Head(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Head(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.HEAD, route_path, **kwargs) -def Options(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]: +def Options(route_path: Union[str, List[str]] = "/", **kwargs): return route(HTTPMethod.OPTIONS, route_path, **kwargs) diff --git a/nest/core/decorators/injectable.py b/nest/core/decorators/injectable.py index 2e00aea..2878ea0 100644 --- a/nest/core/decorators/injectable.py +++ b/nest/core/decorators/injectable.py @@ -1,12 +1,14 @@ -from typing import Callable, Optional, Type +from typing import Callable, Optional, Type, TypeVar from injector import inject from nest.common.constants import DEPENDENCIES, INJECTABLE_NAME, INJECTABLE_TOKEN from nest.core.decorators.utils import parse_dependencies +C = TypeVar('C', bound=object) -def Injectable(target_class: Optional[Type] = None, *args, **kwargs) -> Callable: + +def Injectable(target_class: Optional[Type[C]] = None, *args, **kwargs) -> Callable: """ Decorator to mark a class as injectable and handle its dependencies. @@ -17,7 +19,7 @@ def Injectable(target_class: Optional[Type] = None, *args, **kwargs) -> Callable Callable: The decorator function. """ - def decorator(decorated_class: Type) -> Type: + def decorator(decorated_class: Type[C]) -> Type[C]: """ Inner decorator function to process the class. diff --git a/nest/core/decorators/module.py b/nest/core/decorators/module.py index afbf59d..435e737 100644 --- a/nest/core/decorators/module.py +++ b/nest/core/decorators/module.py @@ -1,5 +1,7 @@ +from typing import Type, TypeVar from nest.common.constants import ModuleMetadata +C = TypeVar('C', bound=object) class Module: def __init__( @@ -16,7 +18,7 @@ def __init__( self.exports = exports self.is_global = is_global - def __call__(self, cls): + def __call__(self, cls: Type[C]) -> Type[C]: setattr(cls, ModuleMetadata.CONTROLLERS, self.controllers) setattr(cls, ModuleMetadata.PROVIDERS, self.providers) setattr(cls, ModuleMetadata.IMPORTS, self.imports)