From 50917f73d84fed00253179085fabe1ca42e040b5 Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 1 Feb 2025 19:00:28 +0200 Subject: [PATCH 1/7] create protocol --- nest/engine/fastapi_engine/__init__.py | 2 ++ nest/engine/proto/__init__.py | 3 +++ nest/engine/proto/app.py | 20 ++++++++++++++++++++ nest/engine/proto/route.py | 12 ++++++++++++ nest/engine/proto/router.py | 14 ++++++++++++++ nest/engine/types.py | 3 +++ 6 files changed, 54 insertions(+) create mode 100644 nest/engine/fastapi_engine/__init__.py create mode 100644 nest/engine/proto/__init__.py create mode 100644 nest/engine/proto/app.py create mode 100644 nest/engine/proto/route.py create mode 100644 nest/engine/proto/router.py create mode 100644 nest/engine/types.py diff --git a/nest/engine/fastapi_engine/__init__.py b/nest/engine/fastapi_engine/__init__.py new file mode 100644 index 0000000..08d4cb9 --- /dev/null +++ b/nest/engine/fastapi_engine/__init__.py @@ -0,0 +1,2 @@ +from fastapi import APIRouter as FastAPIRouter +from fastapi import FastAPI as FastAPIApp \ No newline at end of file diff --git a/nest/engine/proto/__init__.py b/nest/engine/proto/__init__.py new file mode 100644 index 0000000..8c39bb3 --- /dev/null +++ b/nest/engine/proto/__init__.py @@ -0,0 +1,3 @@ +from .app import App +from .router import Router +from .route import Route, RouteProtocol \ No newline at end of file diff --git a/nest/engine/proto/app.py b/nest/engine/proto/app.py new file mode 100644 index 0000000..4a1929e --- /dev/null +++ b/nest/engine/proto/app.py @@ -0,0 +1,20 @@ +from typing import Protocol, TypeVar, List, Optional, Iterable + +from nest.engine.proto.router import Router +from nest.engine.proto.route import Route +from nest.engine.types import Endpoint + +Middleware = TypeVar('Middleware') + + +class AppProtocol(Protocol): + + def __init__(self, **kwargs) -> None: ... + def add_middleware(self, middleware: Middleware, **options) -> None: ... + def include_router(self, router: Router, *, prefix: str = "", tags: Optional[List[str]] = None, **options) -> None: ... + def add_api_route(self, *, path: str, endpoint: Endpoint, methods: Optional[Iterable[str]]) -> None: ... + @property + def routes(self) -> List[Route]: ... + + +App = TypeVar('App', bound=AppProtocol) diff --git a/nest/engine/proto/route.py b/nest/engine/proto/route.py new file mode 100644 index 0000000..d586b74 --- /dev/null +++ b/nest/engine/proto/route.py @@ -0,0 +1,12 @@ +from typing import Protocol, TypeVar, runtime_checkable + +from nest.engine.types import Endpoint + + +@runtime_checkable +class RouteProtocol(Protocol): + endpoint: Endpoint + """Function representing the route endpoint logic.""" + + +Route = TypeVar('Route', bound=RouteProtocol) diff --git a/nest/engine/proto/router.py b/nest/engine/proto/router.py new file mode 100644 index 0000000..07147ec --- /dev/null +++ b/nest/engine/proto/router.py @@ -0,0 +1,14 @@ +from typing import Protocol, List, Optional, TypeVar, Iterable, runtime_checkable +from .route import Route +from ..types import Endpoint + + +@runtime_checkable +class RouterProtocol(Protocol): + routes: List[Route] + def __init__(self, tags: Optional[List[str]] = None, **kwargs) -> None: ... + def include_router(self, router: 'RouterProtocol', **kwargs) -> None: ... + def add_api_route(self, *, path: str, endpoint: Endpoint, methods: Optional[Iterable[str]]) -> None: ... + + +Router = TypeVar('Router', bound=RouterProtocol) diff --git a/nest/engine/types.py b/nest/engine/types.py new file mode 100644 index 0000000..d477ae7 --- /dev/null +++ b/nest/engine/types.py @@ -0,0 +1,3 @@ +from typing import Callable, TypeAlias, Any + +Endpoint: TypeAlias = Callable[..., Any] From 9e9da0332e33481708dba7bcdd1bedad0fb6d414 Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 1 Feb 2025 19:02:07 +0200 Subject: [PATCH 2/7] change concreate type to protocol --- nest/common/route_resolver.py | 7 ++++--- nest/core/decorators/class_based_view.py | 17 +++++++++-------- nest/core/decorators/controller.py | 12 +++++++----- nest/core/pynest_application.py | 14 ++++++-------- nest/core/pynest_factory.py | 9 +++++---- 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/nest/common/route_resolver.py b/nest/common/route_resolver.py index 9d545c4..0fda4a1 100644 --- a/nest/common/route_resolver.py +++ b/nest/common/route_resolver.py @@ -1,8 +1,8 @@ -from fastapi import APIRouter, FastAPI +from nest.engine.proto import App, Router class RoutesResolver: - def __init__(self, container, app_ref: FastAPI): + def __init__(self, container, app_ref: App): self.container = container self.app_ref = app_ref @@ -12,5 +12,6 @@ def register_routes(self): self.register_route(controller) def register_route(self, controller): - router: APIRouter = controller.get_router() + router: Router = controller.get_router() self.app_ref.include_router(router) + diff --git a/nest/core/decorators/class_based_view.py b/nest/core/decorators/class_based_view.py index 18cf0b8..aadf378 100644 --- a/nest/core/decorators/class_based_view.py +++ b/nest/core/decorators/class_based_view.py @@ -11,13 +11,14 @@ List, Type, TypeVar, - Union, get_origin, get_type_hints, ) -from fastapi import APIRouter, Depends -from starlette.routing import Route, WebSocketRoute + +from nest.engine.proto import Router, RouteProtocol, Route +from fastapi import Depends # TODO: remove in future release + T = TypeVar("T") K = TypeVar("K", bound=Callable[..., Any]) @@ -25,19 +26,19 @@ CBV_CLASS_KEY = "__cbv_class__" -def class_based_view(router: APIRouter, cls: Type[T]) -> Type[T]: +def class_based_view(router: Router, cls: Type[T]) -> Type[T]: """ 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`. """ _init_cbv(cls) - cbv_router = APIRouter() + cbv_router = router.__class__() function_members = inspect.getmembers(cls, inspect.isfunction) functions_set = set(func for _, func in function_members) cbv_routes = [ route for route in router.routes - if isinstance(route, (Route, WebSocketRoute)) + if isinstance(route, RouteProtocol) and route.endpoint in functions_set ] for route in cbv_routes: @@ -95,7 +96,7 @@ def new_init(self: Any, *args: Any, **kwargs: Any) -> None: def _update_cbv_route_endpoint_signature( - cls: Type[Any], route: Union[Route, WebSocketRoute] + cls: Type[Any], route: Route ) -> None: """ Fixes the endpoint signature for a cbv route to ensure FastAPI performs dependency injection properly. @@ -110,4 +111,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..341d2a7 100644 --- a/nest/core/decorators/controller.py +++ b/nest/core/decorators/controller.py @@ -1,7 +1,7 @@ from typing import Optional, Type -from fastapi.routing import APIRouter - +from nest.engine.proto import Router +from nest.engine.fastapi_engine import FastAPIRouter from nest.core.decorators.class_based_view import class_based_view as ClassBasedView from nest.core.decorators.http_method import HTTPMethod from nest.core.decorators.utils import get_instance_variables, parse_dependencies @@ -24,7 +24,9 @@ def Controller(prefix: Optional[str] = None, tag: Optional[str] = None): route_prefix = process_prefix(prefix, tag) def wrapper(cls: Type) -> Type[ClassBasedView]: - router = APIRouter(tags=[tag] if tag else None) + tags = [tag] if tag else None + # TODO: replace with factory + router: Router = FastAPIRouter(tags=tags) # Process class dependencies process_dependencies(cls) @@ -86,7 +88,7 @@ def ensure_init_method(cls: Type) -> None: pass -def add_routes(cls: Type, router: APIRouter, route_prefix: str) -> None: +def add_routes(cls: Type, router: Router, route_prefix: str) -> None: """Add routes from class methods to the router.""" for method_name, method_function in cls.__dict__.items(): if callable(method_function) and hasattr(method_function, "__http_method__"): @@ -127,7 +129,7 @@ def configure_method_route(method_function: callable, route_prefix: str) -> None method_function.__route_path__ = method_function.__route_path__.rstrip("/") -def add_route_to_router(router: APIRouter, method_function: callable) -> None: +def add_route_to_router(router: Router, method_function: callable) -> None: """Add the configured route to the router.""" route_kwargs = { "path": method_function.__route_path__, diff --git a/nest/core/pynest_application.py b/nest/core/pynest_application.py index 22e2669..ae70ee4 100644 --- a/nest/core/pynest_application.py +++ b/nest/core/pynest_application.py @@ -1,10 +1,8 @@ from typing import Any - -from fastapi import FastAPI - from nest.common.route_resolver import RoutesResolver from nest.core.pynest_app_context import PyNestApplicationContext from nest.core.pynest_container import PyNestContainer +from nest.engine.proto import App class PyNestApp(PyNestApplicationContext): @@ -19,13 +17,13 @@ class PyNestApp(PyNestApplicationContext): def is_listening(self) -> bool: return self._is_listening - def __init__(self, container: PyNestContainer, http_server: FastAPI): + def __init__(self, container: PyNestContainer, http_server: App): """ Initialize the PyNestApp with the given container and HTTP server. Args: container (PyNestContainer): The PyNestContainer container instance. - http_server (FastAPI): The FastAPI server instance. + http_server (App): The App server instance. """ self.container = container self.http_server = http_server @@ -48,12 +46,12 @@ def use(self, middleware: type, **options: Any) -> "PyNestApp": self.http_server.add_middleware(middleware, **options) return self - def get_server(self) -> FastAPI: + def get_server(self) -> App: """ - Get the FastAPI server instance. + Get the App server instance. Returns: - FastAPI: The FastAPI server instance. + App: The App server instance. """ return self.http_server diff --git a/nest/core/pynest_factory.py b/nest/core/pynest_factory.py index 2d988ae..77327c0 100644 --- a/nest/core/pynest_factory.py +++ b/nest/core/pynest_factory.py @@ -1,14 +1,15 @@ from abc import ABC, abstractmethod from typing import Type, TypeVar -from fastapi import FastAPI - from nest.core.pynest_application import PyNestApp from nest.core.pynest_container import PyNestContainer +from nest.engine.proto import App +from nest.engine.fastapi_engine import FastAPIApp ModuleType = TypeVar("ModuleType") +# TODO: move default fastapi to here and add future implementation class AbstractPyNestFactory(ABC): @abstractmethod def create(self, main_module: Type[ModuleType], **kwargs): @@ -36,7 +37,7 @@ def create(main_module: Type[ModuleType], **kwargs) -> PyNestApp: return PyNestApp(container, http_server) @staticmethod - def _create_server(**kwargs) -> FastAPI: + def _create_server(app_cls: Type[App] = FastAPIApp, **kwargs) -> App: """ Create a FastAPI server. @@ -46,4 +47,4 @@ def _create_server(**kwargs) -> FastAPI: Returns: FastAPI: The created FastAPI server. """ - return FastAPI(**kwargs) + return app_cls(**kwargs) From 417821fa3494b8ffaf87b9a57b1b32baf537323d Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 1 Feb 2025 19:06:13 +0200 Subject: [PATCH 3/7] remove TypeAlias for version lowerr than python 3.10 --- nest/engine/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nest/engine/types.py b/nest/engine/types.py index d477ae7..ffde65e 100644 --- a/nest/engine/types.py +++ b/nest/engine/types.py @@ -1,3 +1,4 @@ -from typing import Callable, TypeAlias, Any +from typing import Callable, Any +from typing_extensions import TypeAlias Endpoint: TypeAlias = Callable[..., Any] From 92edba782ce2e2fbbb786fbf4a139cdb1a73db01 Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:23:58 +0200 Subject: [PATCH 4/7] rename from fastapi_engine to fastapi --- nest/core/decorators/controller.py | 2 +- nest/core/pynest_factory.py | 2 +- nest/engine/{fastapi_engine => fastapi}/__init__.py | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename nest/engine/{fastapi_engine => fastapi}/__init__.py (100%) diff --git a/nest/core/decorators/controller.py b/nest/core/decorators/controller.py index 341d2a7..edb2da0 100644 --- a/nest/core/decorators/controller.py +++ b/nest/core/decorators/controller.py @@ -1,7 +1,7 @@ from typing import Optional, Type from nest.engine.proto import Router -from nest.engine.fastapi_engine import FastAPIRouter +from nest.engine.fastapi import FastAPIRouter from nest.core.decorators.class_based_view import class_based_view as ClassBasedView from nest.core.decorators.http_method import HTTPMethod from nest.core.decorators.utils import get_instance_variables, parse_dependencies diff --git a/nest/core/pynest_factory.py b/nest/core/pynest_factory.py index 77327c0..00c6a80 100644 --- a/nest/core/pynest_factory.py +++ b/nest/core/pynest_factory.py @@ -4,7 +4,7 @@ from nest.core.pynest_application import PyNestApp from nest.core.pynest_container import PyNestContainer from nest.engine.proto import App -from nest.engine.fastapi_engine import FastAPIApp +from nest.engine.fastapi import FastAPIApp ModuleType = TypeVar("ModuleType") diff --git a/nest/engine/fastapi_engine/__init__.py b/nest/engine/fastapi/__init__.py similarity index 100% rename from nest/engine/fastapi_engine/__init__.py rename to nest/engine/fastapi/__init__.py From 921db669e0ffc777ebbb6bb97e3c363f2a8fd2e9 Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:29:36 +0200 Subject: [PATCH 5/7] remove the use of fastapi in create_server function --- nest/core/pynest_factory.py | 6 +++--- tests/test_core/__init__.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nest/core/pynest_factory.py b/nest/core/pynest_factory.py index 00c6a80..6f23fec 100644 --- a/nest/core/pynest_factory.py +++ b/nest/core/pynest_factory.py @@ -20,7 +20,7 @@ class PyNestFactory(AbstractPyNestFactory): """Factory class for creating PyNest applications.""" @staticmethod - def create(main_module: Type[ModuleType], **kwargs) -> PyNestApp: + def create(main_module: Type[ModuleType], app_cls: Type[App] = FastAPIApp, **kwargs) -> PyNestApp: """ Create a PyNest application with the specified main module class. @@ -33,11 +33,11 @@ def create(main_module: Type[ModuleType], **kwargs) -> PyNestApp: """ container = PyNestContainer() container.add_module(main_module) - http_server = PyNestFactory._create_server(**kwargs) + http_server = PyNestFactory._create_server(app_cls, **kwargs) return PyNestApp(container, http_server) @staticmethod - def _create_server(app_cls: Type[App] = FastAPIApp, **kwargs) -> App: + def _create_server(app_cls: Type[App], **kwargs) -> App: """ Create a FastAPI server. diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py index f5303b4..e0d78e1 100644 --- a/tests/test_core/__init__.py +++ b/tests/test_core/__init__.py @@ -46,6 +46,7 @@ def test_module(): @pytest.fixture def test_server() -> FastAPI: server = PyNestFactory._create_server( + app_cls=FastAPI, title="Test Server", description="This is a test server", version="1.0.0", From fd38152eb40fa26494866d5d1c0c81d831e9a9ee Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:21:49 +0200 Subject: [PATCH 6/7] overloading according to given type --- nest/core/pynest_factory.py | 41 ++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/nest/core/pynest_factory.py b/nest/core/pynest_factory.py index 6f23fec..1271edf 100644 --- a/nest/core/pynest_factory.py +++ b/nest/core/pynest_factory.py @@ -1,26 +1,47 @@ -from abc import ABC, abstractmethod -from typing import Type, TypeVar +from typing import Type, TypeVar, overload, Union, Optional from nest.core.pynest_application import PyNestApp from nest.core.pynest_container import PyNestContainer from nest.engine.proto import App from nest.engine.fastapi import FastAPIApp + ModuleType = TypeVar("ModuleType") -# TODO: move default fastapi to here and add future implementation -class AbstractPyNestFactory(ABC): - @abstractmethod - def create(self, main_module: Type[ModuleType], **kwargs): - raise NotImplementedError +class PyNestFactory: + """Factory class for creating PyNest applications.""" + @staticmethod + @overload + def create( + main_module: Type[ModuleType], + app_cls: Type[FastAPIApp], + title: str = "", + description: str = "", + version: Optional[Union[str, int, float]] = None, + debug: bool = False, + ) -> PyNestApp: + """ + Create a PyNest application of FastAPIApp kind. + """ -class PyNestFactory(AbstractPyNestFactory): - """Factory class for creating PyNest applications.""" + @staticmethod + @overload + def create( + main_module: Type[ModuleType], + app_cls: Type[App], + ) -> PyNestApp: + """ + Create a PyNest application of FastAPIApp kind. + """ @staticmethod - def create(main_module: Type[ModuleType], app_cls: Type[App] = FastAPIApp, **kwargs) -> PyNestApp: + def create( + main_module: Type[ModuleType], + app_cls: Type[App] = FastAPIApp, + **kwargs + ) -> PyNestApp: """ Create a PyNest application with the specified main module class. From a7da700fe255762f3fc2e7cf672e484550f0bc0d Mon Sep 17 00:00:00 2001 From: dev0Guy <97923827+dev0Guy@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:30:51 +0200 Subject: [PATCH 7/7] overloading according to given type --- nest/core/cli_factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nest/core/cli_factory.py b/nest/core/cli_factory.py index 99284be..e0338ee 100644 --- a/nest/core/cli_factory.py +++ b/nest/core/cli_factory.py @@ -3,10 +3,10 @@ import click from nest.core.pynest_container import PyNestContainer -from nest.core.pynest_factory import AbstractPyNestFactory, ModuleType +from nest.core.pynest_factory import PyNestFactory, ModuleType -class CLIAppFactory(AbstractPyNestFactory): +class CLIAppFactory(PyNestFactory): def __init__(self): super().__init__()