From 8685c87e371a358b0e14be66ba6832be57e9f364 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Wed, 24 Jul 2024 14:20:51 +0300 Subject: [PATCH 01/21] add echo as an extra param to allow disabling logs from db --- docs/async_orm.md | 25 +++++++++++++++---------- nest/core/database/orm_provider.py | 3 ++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/async_orm.md b/docs/async_orm.md index d5da712..63a570b 100644 --- a/docs/async_orm.md +++ b/docs/async_orm.md @@ -94,9 +94,11 @@ config = AsyncOrmProvider( ) ``` -Note: you can add any parameters that needed in order to configure the database connection. +Disable asyncpg logging by set the parameter of `echo=False` in the `AsyncOrmProvider` object. +more on engine parameters [here](https://docs.sqlalchemy.org/en/20/core/engines.html) `app_service.py` + ```python from nest.core import Injectable @@ -104,7 +106,7 @@ from nest.core import Injectable @Injectable class AppService: def __init__(self): - self.app_name = "MongoApp" + self.app_name = "AsyncOrmApp" self.app_version = "1.0.0" async def get_app_info(self): @@ -112,6 +114,7 @@ class AppService: ``` `app_controller.py` + ```python from nest.core import Controller, Get @@ -168,8 +171,10 @@ async def startup(): await config.create_all() ``` -`@Module(...)`: This is a decorator that defines a module. In PyNest, a module is a class annotated with a `@Module()` decorator. -The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. +`@Module(...)`: This is a decorator that defines a module. In PyNest, a module is a class annotated with a `@Module()` +decorator. +The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers +and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. `PyNestFactory.create()` is a command to create an instance of the application. The AppModule is passed as an argument, which acts as the root module of the application. @@ -189,8 +194,6 @@ other parameters for efficient database access. AsyncSession from sqlalchemy.ext.asyncio is used for executing asynchronous database operations. It is essential for leveraging the full capabilities of SQLAlchemy 2.0 in an async environment. - - ## Implementing Async Features ### Creating Entities @@ -305,11 +308,12 @@ from sqlalchemy.ext.asyncio import AsyncSession @Controller("examples") class ExamplesController: - - def __init__(self, service: ExamplesService): + + def __init__(self, service: ExamplesService): self.service = service @Get("/") + async def get_examples(self, session: AsyncSession = Depends(config.get_db)): return await self.service.get_examples(session) @@ -330,11 +334,12 @@ from .examples_model import Examples @Controller("examples") class ExamplesController: - - def __init__(self, service: ExamplesService): + + def __init__(self, service: ExamplesService): self.service = service @Get("/") + async def get_examples(self): return await self.service.get_examples() diff --git a/nest/core/database/orm_provider.py b/nest/core/database/orm_provider.py index 6a2b40e..5fa8700 100644 --- a/nest/core/database/orm_provider.py +++ b/nest/core/database/orm_provider.py @@ -99,7 +99,8 @@ class AsyncOrmProvider(BaseOrmProvider): def __init__( self, db_type: str = "postgresql", config_params: dict = None, **kwargs ): - kwargs["engine_params"] = dict(echo=True) + echo = kwargs.get("echo", True) + kwargs["engine_params"] = dict(echo=echo) kwargs["session_params"] = dict(expire_on_commit=False, class_=AsyncSession) super().__init__( db_type=db_type, config_params=config_params, async_mode=True, **kwargs From d9469bb3eb7f254787e6332232842e9839939409 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:41:09 +0200 Subject: [PATCH 02/21] refactor project structure. apply decoupling between pynest and fastapi as a starting point for multi framework support. --- nest/__init__.py | 2 +- nest/cli/__init__.py | 2 + .../decorators => }/cli/cli_decorators.py | 8 +-- nest/cli/click_handlers.py | 70 ------------------- .../pynest_cli_factory.py} | 2 +- nest/cli/src/utils.py | 17 +++++ nest/cli/templates/abstract_empty_template.py | 35 ---------- nest/core/__init__.py | 17 +---- nest/core/decorators/__init__.py | 5 -- nest/core/decorators/cli/__init__.py | 0 nest/core/{decorators => }/injectable.py | 5 +- nest/core/{decorators => }/module.py | 0 nest/core/{decorators => }/utils.py | 0 nest/{core => }/database/__init__.py | 0 nest/{core => }/database/base_config.py | 0 nest/{core => }/database/odm_config.py | 46 ++++++++++-- nest/{core => }/database/odm_provider.py | 23 +++--- nest/{core => }/database/orm_config.py | 3 +- nest/{core => }/database/orm_provider.py | 5 +- .../database.py => database/utils.py} | 0 nest/plugins/__init__.py | 0 nest/plugins/controllers/__init__.py | 0 nest/plugins/modules/__init__.py | 0 nest/plugins/modules/auth/__init__.py | 0 nest/plugins/modules/redis/__init__.py | 4 -- nest/plugins/services/__init__.py | 0 nest/web/__init__.py | 3 + .../decorators => web}/class_based_view.py | 7 +- nest/{core/decorators => web}/controller.py | 6 +- nest/{core/decorators => web}/http_code.py | 0 nest/{core/decorators => web}/http_method.py | 0 .../pynest_fastapi_application.py} | 4 +- nest/web/pynset_web_factory.py | 42 +++++++++++ nest/{common => web}/route_resolver.py | 0 tests/test_core/__init__.py | 15 ++-- 35 files changed, 142 insertions(+), 179 deletions(-) rename nest/{core/decorators => }/cli/cli_decorators.py (90%) delete mode 100644 nest/cli/click_handlers.py rename nest/{core/cli_factory.py => cli/pynest_cli_factory.py} (95%) create mode 100644 nest/cli/src/utils.py delete mode 100644 nest/cli/templates/abstract_empty_template.py delete mode 100644 nest/core/decorators/__init__.py delete mode 100644 nest/core/decorators/cli/__init__.py rename nest/core/{decorators => }/injectable.py (88%) rename nest/core/{decorators => }/module.py (100%) rename nest/core/{decorators => }/utils.py (100%) rename nest/{core => }/database/__init__.py (100%) rename nest/{core => }/database/base_config.py (100%) rename nest/{core => }/database/odm_config.py (61%) rename nest/{core => }/database/odm_provider.py (75%) rename nest/{core => }/database/orm_config.py (98%) rename nest/{core => }/database/orm_provider.py (94%) rename nest/{core/decorators/database.py => database/utils.py} (100%) delete mode 100644 nest/plugins/__init__.py delete mode 100644 nest/plugins/controllers/__init__.py delete mode 100644 nest/plugins/modules/__init__.py delete mode 100644 nest/plugins/modules/auth/__init__.py delete mode 100644 nest/plugins/modules/redis/__init__.py delete mode 100644 nest/plugins/services/__init__.py create mode 100644 nest/web/__init__.py rename nest/{core/decorators => web}/class_based_view.py (95%) rename nest/{core/decorators => web}/controller.py (95%) rename nest/{core/decorators => web}/http_code.py (100%) rename nest/{core/decorators => web}/http_method.py (100%) rename nest/{core/pynest_application.py => web/pynest_fastapi_application.py} (94%) create mode 100644 nest/web/pynset_web_factory.py rename nest/{common => web}/route_resolver.py (100%) diff --git a/nest/__init__.py b/nest/__init__.py index 493f741..260c070 100644 --- a/nest/__init__.py +++ b/nest/__init__.py @@ -1 +1 @@ -__version__ = "0.3.0" +__version__ = "0.3.1" diff --git a/nest/cli/__init__.py b/nest/cli/__init__.py index e69de29..29cd4c0 100644 --- a/nest/cli/__init__.py +++ b/nest/cli/__init__.py @@ -0,0 +1,2 @@ +from nest.cli.cli_decorators import CliCommand, CliController +from nest.cli.pynest_cli_factory import PyNestCLIFactory diff --git a/nest/core/decorators/cli/cli_decorators.py b/nest/cli/cli_decorators.py similarity index 90% rename from nest/core/decorators/cli/cli_decorators.py rename to nest/cli/cli_decorators.py index 3d82011..800fc7a 100644 --- a/nest/core/decorators/cli/cli_decorators.py +++ b/nest/cli/cli_decorators.py @@ -2,12 +2,8 @@ import click -from nest.core import Controller -from nest.core.decorators.utils import ( - get_instance_variables, - parse_dependencies, - parse_params, -) +from nest.core.utils import (get_instance_variables, parse_dependencies, + parse_params) def CliController(name: str, **kwargs): diff --git a/nest/cli/click_handlers.py b/nest/cli/click_handlers.py deleted file mode 100644 index 0f9ed63..0000000 --- a/nest/cli/click_handlers.py +++ /dev/null @@ -1,70 +0,0 @@ -from pathlib import Path - -import yaml - -from nest.common.templates.templates_factory import TemplateFactory - - -def get_metadata(): - setting_path = Path(__file__).parent.parent / "settings.yaml" - assert setting_path.exists(), "settings.yaml file not found" - with open(setting_path, "r") as file: - file = yaml.load(file, Loader=yaml.FullLoader) - - config = file["config"] - db_type = config["db_type"] - is_async = config["is_async"] - return db_type, is_async - - -def create_nest_app(app_name: str = ".", db_type: str = None, is_async: bool = False): - """ - Create a new nest app - - :param app_name: The name of the app - :param db_type: The type of the database (sqlite, mysql, postgresql) - :param is_async: whether the project should be async or not (only for relational databases) - - The files structure are: - - ├── app_module.py - ├── config.py (only for databases) - ├── main.py - ├── requirements.txt - ├── .gitignore - ├── src - │ ├── __init__.py - - in addition to those files, a setting.yaml file will be created in the package level that will help managed configurations - """ - template_factory = TemplateFactory() - template = template_factory.get_template( - module_name="example", db_type=db_type, is_async=is_async - ) - template.generate_project(app_name) - - -def create_nest_module(name: str): - """ - Create a new nest module - - :param name: The name of the module - - The files structure are: - ├── ... - ├── src - │ ├── __init__.py - │ ├── module_name - ├── __init__.py - ├── module_name_controller.py - ├── module_name_service.py - ├── module_name_model.py - ├── module_name_entity.py (only for databases) - ├── module_name_module.py - """ - db_type, is_async = get_metadata() - template_factory = TemplateFactory() - template = template_factory.get_template( - module_name=name, db_type=db_type, is_async=is_async - ) - template.generate_module(name) diff --git a/nest/core/cli_factory.py b/nest/cli/pynest_cli_factory.py similarity index 95% rename from nest/core/cli_factory.py rename to nest/cli/pynest_cli_factory.py index 99284be..c575db2 100644 --- a/nest/core/cli_factory.py +++ b/nest/cli/pynest_cli_factory.py @@ -6,7 +6,7 @@ from nest.core.pynest_factory import AbstractPyNestFactory, ModuleType -class CLIAppFactory(AbstractPyNestFactory): +class PyNestCLIFactory(AbstractPyNestFactory): def __init__(self): super().__init__() diff --git a/nest/cli/src/utils.py b/nest/cli/src/utils.py new file mode 100644 index 0000000..c013cd3 --- /dev/null +++ b/nest/cli/src/utils.py @@ -0,0 +1,17 @@ +from pathlib import Path + +import yaml + +from nest.common.templates.templates_factory import TemplateFactory + + +def get_metadata(): + setting_path = Path(__file__).parent.parent / "settings.yaml" + assert setting_path.exists(), "settings.yaml file not found" + with open(setting_path, "r") as file: + file = yaml.load(file, Loader=yaml.FullLoader) + + config = file["config"] + db_type = config["db_type"] + is_async = config["is_async"] + return db_type, is_async diff --git a/nest/cli/templates/abstract_empty_template.py b/nest/cli/templates/abstract_empty_template.py deleted file mode 100644 index 702464e..0000000 --- a/nest/cli/templates/abstract_empty_template.py +++ /dev/null @@ -1,35 +0,0 @@ -from nest.core.templates.abstract_base_template import AbstractBaseTemplate - - -class AbstractEmptyTemplate(AbstractBaseTemplate): - def __init__(self): - super().__init__(is_empty=True) - - def generate_app_file(self) -> str: - return f"""from nest.core.app import App - from src.examples.examples_module import ExamplesModule - - app = App( - description="Blank PyNest service", - modules=[ - ExamplesModule, - ] - ) - """ - - def generate_controller_file(self, name) -> str: - return f"""from nest.core import Controller, Get, Post, Depends, Delete, Put - - from src.{name}.{name}_service import {self.capitalized_name}Service - from src.{name}.{name}_model import {self.capitalized_name} - - - @Controller("{name}") - class {self.capitalized_name}Controller: - - service: {self.capitalized_name}Service = Depends({self.capitalized_name}Service) - - @Get("/get_{name}") - {self.is_async}def get_{name}(self): - return {self.is_await}self.service.get_{name}() -""" diff --git a/nest/core/__init__.py b/nest/core/__init__.py index 50a4cc8..e1e8a40 100644 --- a/nest/core/__init__.py +++ b/nest/core/__init__.py @@ -1,16 +1,3 @@ -from fastapi import Depends - -from nest.core.decorators import ( - Controller, - Delete, - Get, - HttpCode, - Injectable, - Module, - Patch, - Post, - Put, -) -from nest.core.pynest_application import PyNestApp +from nest.core.injectable import Injectable +from nest.core.module import Module from nest.core.pynest_container import PyNestContainer -from nest.core.pynest_factory import PyNestFactory diff --git a/nest/core/decorators/__init__.py b/nest/core/decorators/__init__.py deleted file mode 100644 index 1ed7489..0000000 --- a/nest/core/decorators/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from nest.core.decorators.controller import Controller -from nest.core.decorators.http_code import HttpCode -from nest.core.decorators.http_method import Delete, Get, Patch, Post, Put -from nest.core.decorators.injectable import Injectable -from nest.core.decorators.module import Module diff --git a/nest/core/decorators/cli/__init__.py b/nest/core/decorators/cli/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/core/decorators/injectable.py b/nest/core/injectable.py similarity index 88% rename from nest/core/decorators/injectable.py rename to nest/core/injectable.py index 2e00aea..8cc8e7a 100644 --- a/nest/core/decorators/injectable.py +++ b/nest/core/injectable.py @@ -2,8 +2,9 @@ from injector import inject -from nest.common.constants import DEPENDENCIES, INJECTABLE_NAME, INJECTABLE_TOKEN -from nest.core.decorators.utils import parse_dependencies +from nest.common.constants import (DEPENDENCIES, INJECTABLE_NAME, + INJECTABLE_TOKEN) +from nest.core.utils import parse_dependencies def Injectable(target_class: Optional[Type] = None, *args, **kwargs) -> Callable: diff --git a/nest/core/decorators/module.py b/nest/core/module.py similarity index 100% rename from nest/core/decorators/module.py rename to nest/core/module.py diff --git a/nest/core/decorators/utils.py b/nest/core/utils.py similarity index 100% rename from nest/core/decorators/utils.py rename to nest/core/utils.py diff --git a/nest/core/database/__init__.py b/nest/database/__init__.py similarity index 100% rename from nest/core/database/__init__.py rename to nest/database/__init__.py diff --git a/nest/core/database/base_config.py b/nest/database/base_config.py similarity index 100% rename from nest/core/database/base_config.py rename to nest/database/base_config.py diff --git a/nest/core/database/odm_config.py b/nest/database/odm_config.py similarity index 61% rename from nest/core/database/odm_config.py rename to nest/database/odm_config.py index 8dc13b2..f3151fd 100644 --- a/nest/core/database/odm_config.py +++ b/nest/database/odm_config.py @@ -1,4 +1,5 @@ -from nest.core.database.base_config import BaseProvider, ConfigFactoryBase +from nest.database.base_config import BaseProvider +from urllib.parse import urlencode class MongoDBConfig(BaseProvider): @@ -12,17 +13,21 @@ class MongoDBConfig(BaseProvider): password (str): The password for database authentication. port (int): The database port number. srv (bool): Whether to use the SRV connection string. + uri (str): Optional pre-built MongoDB URI. + **kwargs: Additional keyword arguments to include in the URI query parameters. """ def __init__( self, - host: str, - db_name: str, + host: str = None, + db_name: str = None, user: str = None, password: str = None, port: int = 27017, srv: bool = False, + uri: str = None, + **kwargs, ): """ Initializes the MongoDBConfig instance. @@ -34,22 +39,50 @@ def __init__( password (str): The password for database authentication. port (int): The database port number. srv (bool): Whether to use the SRV connection string. + uri (str): Optional pre-built MongoDB URI. + **kwargs: Additional keyword arguments to include in the URI query parameters. """ self.srv = srv + self.uri = uri + self.kwargs = kwargs or {} super().__init__(host, db_name, user, password, port) def get_engine_url(self) -> str: """ - Returns the engine URL for the ORM. + Returns the engine URL for the ODM. Returns: str: The engine URL. """ + if self.uri: + return self.uri + + # Build the base URI + protocol = "mongodb+srv" if self.srv else "mongodb" + credentials = "" if self.user and self.password: - return f"mongodb{'+srv' if self.srv else ''}://{self.user}:{self.password}@{self.host}:{self.port}" - return f"mongodb{'+srv' if self.srv else ''}://{self.host}:{self.port}" + credentials = f"{self.user}:{self.password}@" + elif self.user: + credentials = f"{self.user}@" + + host_port = self.host or "" + if not self.srv and self.port: + host_port = f"{host_port}:{self.port}" + + db_name = f"/{self.db_name}" if self.db_name else "" + + # Build the query string from kwargs + query = "" + if self.kwargs: + query = "?" + urlencode(self.kwargs) + + # Build the full URI + uri = f"{protocol}://{credentials}{host_port}{db_name}{query}" + return uri + + class ConfigFactory(ConfigFactoryBase): @@ -86,3 +119,4 @@ def get_config(self): return MongoDBConfig else: raise Exception(f"Database type {self.db_type} is not supported") + diff --git a/nest/core/database/odm_provider.py b/nest/database/odm_provider.py similarity index 75% rename from nest/core/database/odm_provider.py rename to nest/database/odm_provider.py index ead8d06..b082909 100644 --- a/nest/core/database/odm_provider.py +++ b/nest/database/odm_provider.py @@ -1,9 +1,9 @@ -from typing import List +from typing import List, Type from beanie import Document, init_beanie from motor.motor_asyncio import AsyncIOMotorClient -from nest.core.database.odm_config import ConfigFactory +from nest.database.odm_config import ConfigFactory class OdmProvider: @@ -14,7 +14,7 @@ class OdmProvider: db_type (str, optional): The type of database. Defaults to "mongodb". config_params (dict, optional): Configuration parameters specific to the chosen database type. Defaults to None. - document_models (beanie.Document): a list of beanie.Document instances + document_models (List[Type[Document]]): A list of beanie.Document subclasses. Attributes: config: The configuration factory for the chosen database type. @@ -26,34 +26,35 @@ def __init__( self, db_type="mongodb", config_params: dict = None, - document_models: List[Document] = None, + document_models: list[Type[Document]] = None, ): """ - Initializes the OrmService instance. + Initializes the OdmProvider instance. Args: db_type (str, optional): The type of database. Defaults to "mongodb". config_params (dict, optional): Configuration parameters specific to the chosen database type. Defaults to None. - document_models (beanie.Document): a list of beanie.Document instances + document_models (List[Type[Document]]): A list of beanie.Document subclasses. """ - self.config_object = ConfigFactory(db_type=db_type).get_config() self.config = self.config_object(**config_params) self.config_url = self.config.get_engine_url() - self.document_models = document_models + self.document_models = document_models or [] async def create_all(self): + """ + Initializes the Beanie ODM with the provided document models. + """ self.check_document_models() client = AsyncIOMotorClient(self.config_url) await init_beanie( - database=client[self.config.db_name], document_models=self.document_models + database=client.get_default_database(), document_models=self.document_models ) def check_document_models(self): """ - Checks that the document_models argument is a list of beanie.Document instances. - + Checks that the document_models argument is a list of beanie.Document subclasses. """ if not isinstance(self.document_models, list): raise Exception("document_models should be a list") diff --git a/nest/core/database/orm_config.py b/nest/database/orm_config.py similarity index 98% rename from nest/core/database/orm_config.py rename to nest/database/orm_config.py index f681d86..515be35 100644 --- a/nest/core/database/orm_config.py +++ b/nest/database/orm_config.py @@ -1,4 +1,5 @@ -from nest.core.database.base_config import BaseConfig, BaseProvider, ConfigFactoryBase +from nest.database.base_config import (BaseConfig, BaseProvider, + ConfigFactoryBase) class PostgresConfig(BaseProvider): diff --git a/nest/core/database/orm_provider.py b/nest/database/orm_provider.py similarity index 94% rename from nest/core/database/orm_provider.py rename to nest/database/orm_provider.py index 21b0df3..0ac9d0c 100644 --- a/nest/core/database/orm_provider.py +++ b/nest/database/orm_provider.py @@ -3,10 +3,11 @@ from typing import Any, Dict from sqlalchemy import create_engine -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker, + create_async_engine) from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker -from nest.core.database.orm_config import AsyncConfigFactory, ConfigFactory +from nest.database.orm_config import AsyncConfigFactory, ConfigFactory class Base(DeclarativeBase): diff --git a/nest/core/decorators/database.py b/nest/database/utils.py similarity index 100% rename from nest/core/decorators/database.py rename to nest/database/utils.py diff --git a/nest/plugins/__init__.py b/nest/plugins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/plugins/controllers/__init__.py b/nest/plugins/controllers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/plugins/modules/__init__.py b/nest/plugins/modules/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/plugins/modules/auth/__init__.py b/nest/plugins/modules/auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/plugins/modules/redis/__init__.py b/nest/plugins/modules/redis/__init__.py deleted file mode 100644 index 013a218..0000000 --- a/nest/plugins/modules/redis/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from nest.plugins.modules.redis.redis_controller import RedisController -from nest.plugins.modules.redis.redis_model import RedisInput -from nest.plugins.modules.redis.redis_module import RedisModule -from nest.plugins.modules.redis.redis_service import RedisService diff --git a/nest/plugins/services/__init__.py b/nest/plugins/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nest/web/__init__.py b/nest/web/__init__.py new file mode 100644 index 0000000..9da0344 --- /dev/null +++ b/nest/web/__init__.py @@ -0,0 +1,3 @@ +from nest.web.controller import Controller +from nest.web.http_method import Delete, Get, Patch, Post, Put +from nest.web.pynset_web_factory import PyNestWebFactory diff --git a/nest/core/decorators/class_based_view.py b/nest/web/class_based_view.py similarity index 95% rename from nest/core/decorators/class_based_view.py rename to nest/web/class_based_view.py index a465542..5450c34 100644 --- a/nest/core/decorators/class_based_view.py +++ b/nest/web/class_based_view.py @@ -4,16 +4,15 @@ """ import inspect -from typing import Any, Callable, List, Type, TypeVar, Union, get_type_hints +from typing import (Any, Callable, ClassVar, List, Type, TypeVar, Union, + get_origin, get_type_hints) from fastapi import APIRouter, Depends -from pydantic.typing import is_classvar from starlette.routing import Route, WebSocketRoute T = TypeVar("T") K = TypeVar("K", bound=Callable[..., Any]) - CBV_CLASS_KEY = "__cbv_class__" @@ -61,7 +60,7 @@ def _init_cbv(cls: Type[Any]) -> None: ] dependency_names: List[str] = [] for name, hint in get_type_hints(cls).items(): - if is_classvar(hint): + if get_origin(hint) is ClassVar: continue parameter_kwargs = {"default": getattr(cls, name, Ellipsis)} dependency_names.append(name) diff --git a/nest/core/decorators/controller.py b/nest/web/controller.py similarity index 95% rename from nest/core/decorators/controller.py rename to nest/web/controller.py index 8073083..d5c2d41 100644 --- a/nest/core/decorators/controller.py +++ b/nest/web/controller.py @@ -2,9 +2,9 @@ from fastapi.routing import APIRouter -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 +from nest.core.utils import get_instance_variables, parse_dependencies +from nest.web.class_based_view import class_based_view as ClassBasedView +from nest.web.http_method import HTTPMethod def Controller(prefix: Optional[str] = None, tag: Optional[str] = None): diff --git a/nest/core/decorators/http_code.py b/nest/web/http_code.py similarity index 100% rename from nest/core/decorators/http_code.py rename to nest/web/http_code.py diff --git a/nest/core/decorators/http_method.py b/nest/web/http_method.py similarity index 100% rename from nest/core/decorators/http_method.py rename to nest/web/http_method.py diff --git a/nest/core/pynest_application.py b/nest/web/pynest_fastapi_application.py similarity index 94% rename from nest/core/pynest_application.py rename to nest/web/pynest_fastapi_application.py index 22e2669..8db6f0e 100644 --- a/nest/core/pynest_application.py +++ b/nest/web/pynest_fastapi_application.py @@ -2,12 +2,12 @@ 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.web.route_resolver import RoutesResolver -class PyNestApp(PyNestApplicationContext): +class PyNestFastapiApp(PyNestApplicationContext): """ PyNestApp is the main application class for the PyNest framework, managing the container and HTTP server. diff --git a/nest/web/pynset_web_factory.py b/nest/web/pynset_web_factory.py new file mode 100644 index 0000000..dbf4f65 --- /dev/null +++ b/nest/web/pynset_web_factory.py @@ -0,0 +1,42 @@ +from abc import ABC, abstractmethod +from typing import Type, TypeVar + +from fastapi import FastAPI + +from nest.core.pynest_container import PyNestContainer +from nest.core.pynest_factory import AbstractPyNestFactory, ModuleType +from nest.web.pynest_fastapi_application import PyNestFastapiApp + + +class PyNestWebFactory(AbstractPyNestFactory): + """Factory class for creating PyNest applications.""" + + @staticmethod + def create(main_module: Type[ModuleType], **kwargs) -> PyNestFastapiApp: + """ + Create a PyNest application with the specified main module class. + + Args: + main_module (ModuleType): The main module for the PyNest application. + **kwargs: Additional keyword arguments for the FastAPI server. + + Returns: + PyNestApp: The created PyNest application. + """ + container = PyNestContainer() + container.add_module(main_module) + http_server = PyNestWebFactory._create_server(**kwargs) + return PyNestFastapiApp(container, http_server) + + @staticmethod + def _create_server(**kwargs) -> FastAPI: + """ + Create a FastAPI server. + + Args: + **kwargs: Additional keyword arguments for the FastAPI server. + + Returns: + FastAPI: The created FastAPI server. + """ + return FastAPI(**kwargs) diff --git a/nest/common/route_resolver.py b/nest/web/route_resolver.py similarity index 100% rename from nest/common/route_resolver.py rename to nest/web/route_resolver.py diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py index f5303b4..a1ba98a 100644 --- a/tests/test_core/__init__.py +++ b/tests/test_core/__init__.py @@ -1,16 +1,9 @@ import pytest from fastapi import FastAPI -from nest.common.route_resolver import RoutesResolver -from nest.core import ( - Controller, - Get, - Injectable, - Module, - PyNestContainer, - PyNestFactory, -) -from nest.core.pynest_application import PyNestApp +from nest.core import Injectable, Module, PyNestContainer +from nest.web import (Controller, Get, PyNestFastapiApp, PyNestWebFactory, + RoutesResolver) @Injectable @@ -45,7 +38,7 @@ def test_module(): @pytest.fixture def test_server() -> FastAPI: - server = PyNestFactory._create_server( + server = PyNestWebFactory._create_server( title="Test Server", description="This is a test server", version="1.0.0", From 070dd7db82a22c59214bfcd6e57ef919a7a9c437 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:42:28 +0200 Subject: [PATCH 03/21] fix examples according to new project structure --- examples/BlankApp/src/app_module.py | 3 ++- examples/BlankApp/src/example/example_controller.py | 3 +-- examples/BlankApp/src/example/example_service.py | 2 +- examples/BlankApp/src/user/user_service.py | 4 +--- examples/CommandLineApp/src/app_controller.py | 2 +- examples/CommandLineApp/src/app_module.py | 3 +-- examples/CommandLineApp/src/user/user_controller.py | 2 +- examples/MongoApp/src/config.py | 2 +- examples/MongoApp/src/example/example_service.py | 2 +- examples/MongoApp/src/product/product_service.py | 2 +- examples/MongoApp/src/user/user_service.py | 2 +- examples/OrmAsyncApp/src/config.py | 2 +- examples/OrmAsyncApp/src/example/example_service.py | 2 +- examples/OrmAsyncApp/src/product/product_service.py | 2 +- examples/OrmAsyncApp/src/user/user_service.py | 2 +- examples/OrmSyncApp/src/config.py | 2 +- examples/OrmSyncApp/src/example/example_service.py | 2 +- examples/OrmSyncApp/src/product/product_service.py | 2 +- examples/OrmSyncApp/src/user/user_service.py | 2 +- 19 files changed, 20 insertions(+), 23 deletions(-) diff --git a/examples/BlankApp/src/app_module.py b/examples/BlankApp/src/app_module.py index a84edbd..05bc4b1 100644 --- a/examples/BlankApp/src/app_module.py +++ b/examples/BlankApp/src/app_module.py @@ -1,6 +1,7 @@ from fastapi import FastAPI -from nest.core import Module, PyNestFactory +from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService diff --git a/examples/BlankApp/src/example/example_controller.py b/examples/BlankApp/src/example/example_controller.py index fcfd0d8..62138e4 100644 --- a/examples/BlankApp/src/example/example_controller.py +++ b/examples/BlankApp/src/example/example_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get, HttpCode, Post +from nest.web import Controller, Get, Post from .example_model import Example from .example_service import ExampleService @@ -14,6 +14,5 @@ def get_example(self): return self.service.get_example() @Post("/") - @HttpCode(201) def add_example(self, example: Example): return self.service.add_example(example) diff --git a/examples/BlankApp/src/example/example_service.py b/examples/BlankApp/src/example/example_service.py index dd7cfb2..14c0073 100644 --- a/examples/BlankApp/src/example/example_service.py +++ b/examples/BlankApp/src/example/example_service.py @@ -1,4 +1,4 @@ -from nest.core.decorators import Injectable +from nest.core import Injectable from ..user.user_service import UserService from .example_model import Example diff --git a/examples/BlankApp/src/user/user_service.py b/examples/BlankApp/src/user/user_service.py index 90354ba..2d92805 100644 --- a/examples/BlankApp/src/user/user_service.py +++ b/examples/BlankApp/src/user/user_service.py @@ -1,6 +1,6 @@ import time -from nest.core.decorators import Injectable +from nest.core import Injectable from .user_model import User @@ -9,8 +9,6 @@ class UserService: def __init__(self): self.database = [] - time.sleep(5) - print("UserService initialized") def get_user(self): return self.database diff --git a/examples/CommandLineApp/src/app_controller.py b/examples/CommandLineApp/src/app_controller.py index f509a6c..8563cd6 100644 --- a/examples/CommandLineApp/src/app_controller.py +++ b/examples/CommandLineApp/src/app_controller.py @@ -1,4 +1,4 @@ -from nest.core.decorators.cli.cli_decorators import CliCommand, CliController +from nest.cli.cli_decorators import CliCommand, CliController from .app_service import AppService diff --git a/examples/CommandLineApp/src/app_module.py b/examples/CommandLineApp/src/app_module.py index 0c5b7fb..91d740b 100644 --- a/examples/CommandLineApp/src/app_module.py +++ b/examples/CommandLineApp/src/app_module.py @@ -1,6 +1,5 @@ +from nest.cli.cli_factory import CLIAppFactory from nest.core import Module -from nest.core.cli_factory import CLIAppFactory -from nest.core.pynest_factory import PyNestFactory from .app_controller import AppController from .app_service import AppService diff --git a/examples/CommandLineApp/src/user/user_controller.py b/examples/CommandLineApp/src/user/user_controller.py index 08d2547..70ce440 100644 --- a/examples/CommandLineApp/src/user/user_controller.py +++ b/examples/CommandLineApp/src/user/user_controller.py @@ -1,7 +1,7 @@ import click from examples.CommandLineApp.src.user.user_service import UserService -from nest.core.decorators.cli.cli_decorators import CliCommand, CliController +from nest.cli.cli_decorators import CliCommand, CliController class ListOptions: diff --git a/examples/MongoApp/src/config.py b/examples/MongoApp/src/config.py index 78256ad..e786903 100644 --- a/examples/MongoApp/src/config.py +++ b/examples/MongoApp/src/config.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv -from nest.core.database.odm_provider import OdmProvider +from nest.database.odm_provider import OdmProvider from .example.example_entity import Example from .product.product_entity import Product diff --git a/examples/MongoApp/src/example/example_service.py b/examples/MongoApp/src/example/example_service.py index cc08a52..e196d83 100644 --- a/examples/MongoApp/src/example/example_service.py +++ b/examples/MongoApp/src/example/example_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from .example_entity import Example as ExampleEntity from .example_model import Example diff --git a/examples/MongoApp/src/product/product_service.py b/examples/MongoApp/src/product/product_service.py index f0f72a9..3606c0c 100644 --- a/examples/MongoApp/src/product/product_service.py +++ b/examples/MongoApp/src/product/product_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from .product_entity import Product as ProductEntity from .product_model import Product diff --git a/examples/MongoApp/src/user/user_service.py b/examples/MongoApp/src/user/user_service.py index 90bed1b..f0e0773 100644 --- a/examples/MongoApp/src/user/user_service.py +++ b/examples/MongoApp/src/user/user_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from .user_entity import User as UserEntity from .user_model import User diff --git a/examples/OrmAsyncApp/src/config.py b/examples/OrmAsyncApp/src/config.py index deaa2d2..2814f6d 100644 --- a/examples/OrmAsyncApp/src/config.py +++ b/examples/OrmAsyncApp/src/config.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv -from nest.core.database.orm_provider import AsyncOrmProvider +from nest.database.orm_provider import AsyncOrmProvider load_dotenv() diff --git a/examples/OrmAsyncApp/src/example/example_service.py b/examples/OrmAsyncApp/src/example/example_service.py index 18a6813..85d2630 100644 --- a/examples/OrmAsyncApp/src/example/example_service.py +++ b/examples/OrmAsyncApp/src/example/example_service.py @@ -2,7 +2,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from nest.core import Injectable -from nest.core.decorators.database import async_db_request_handler +from nest.database import async_db_request_handler from .example_entity import Example as ExampleEntity from .example_model import Example diff --git a/examples/OrmAsyncApp/src/product/product_service.py b/examples/OrmAsyncApp/src/product/product_service.py index 18e54d5..0baf88a 100644 --- a/examples/OrmAsyncApp/src/product/product_service.py +++ b/examples/OrmAsyncApp/src/product/product_service.py @@ -2,7 +2,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from nest.core import Injectable -from nest.core.decorators.database import async_db_request_handler +from nest.database import async_db_request_handler from .product_entity import Product as ProductEntity from .product_model import Product diff --git a/examples/OrmAsyncApp/src/user/user_service.py b/examples/OrmAsyncApp/src/user/user_service.py index dc9866a..306139e 100644 --- a/examples/OrmAsyncApp/src/user/user_service.py +++ b/examples/OrmAsyncApp/src/user/user_service.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from nest.core import Injectable -from nest.core.decorators.database import async_db_request_handler +from nest.database import async_db_request_handler from .user_entity import User as UserEntity from .user_model import User diff --git a/examples/OrmSyncApp/src/config.py b/examples/OrmSyncApp/src/config.py index 822f72b..06f6632 100644 --- a/examples/OrmSyncApp/src/config.py +++ b/examples/OrmSyncApp/src/config.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv -from nest.core.database.orm_provider import OrmProvider +from nest.database.orm_provider import OrmProvider load_dotenv() diff --git a/examples/OrmSyncApp/src/example/example_service.py b/examples/OrmSyncApp/src/example/example_service.py index 076c24d..c0df60b 100644 --- a/examples/OrmSyncApp/src/example/example_service.py +++ b/examples/OrmSyncApp/src/example/example_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from ..config import config from .example_entity import Example as ExampleEntity diff --git a/examples/OrmSyncApp/src/product/product_service.py b/examples/OrmSyncApp/src/product/product_service.py index 69d0f76..e89ac25 100644 --- a/examples/OrmSyncApp/src/product/product_service.py +++ b/examples/OrmSyncApp/src/product/product_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from ..config import config from .product_entity import Product as ProductEntity diff --git a/examples/OrmSyncApp/src/user/user_service.py b/examples/OrmSyncApp/src/user/user_service.py index 798f42d..00de753 100644 --- a/examples/OrmSyncApp/src/user/user_service.py +++ b/examples/OrmSyncApp/src/user/user_service.py @@ -1,5 +1,5 @@ from nest.core import Injectable -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from ..config import config from .user_entity import User as UserEntity From 2cd4f6e178769e4f02e9197a9c22c4539ea850f5 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:42:53 +0200 Subject: [PATCH 04/21] fix unit tests to match new project structure --- tests/test_core/test_database/test_odm.py | 6 ++---- tests/test_core/test_database/test_orm.py | 4 ++-- tests/test_core/test_decorators/test_controller.py | 3 ++- tests/test_core/test_pynest_application.py | 4 ++-- tests/test_core/test_pynest_factory.py | 5 +++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_core/test_database/test_odm.py b/tests/test_core/test_database/test_odm.py index 7b2ffb2..d45b345 100644 --- a/tests/test_core/test_database/test_odm.py +++ b/tests/test_core/test_database/test_odm.py @@ -1,9 +1,7 @@ -import os - import pytest -from nest.core.database.odm_config import ConfigFactory, MongoDBConfig -from nest.core.database.odm_provider import OdmProvider +from nest.database.odm_config import MongoDBConfig +from nest.database.odm_provider import OdmProvider @pytest.fixture(scope="module") diff --git a/tests/test_core/test_database/test_orm.py b/tests/test_core/test_database/test_orm.py index 82ddf35..e4ea55e 100644 --- a/tests/test_core/test_database/test_orm.py +++ b/tests/test_core/test_database/test_orm.py @@ -2,8 +2,8 @@ import pytest -from nest.core.database.orm_config import ConfigFactory -from nest.core.database.orm_provider import OrmProvider +from nest.database.orm_config import ConfigFactory +from nest.database.orm_provider import OrmProvider @pytest.fixture(scope="module") diff --git a/tests/test_core/test_decorators/test_controller.py b/tests/test_core/test_decorators/test_controller.py index 0985014..dd594e7 100644 --- a/tests/test_core/test_decorators/test_controller.py +++ b/tests/test_core/test_decorators/test_controller.py @@ -1,6 +1,7 @@ import pytest -from nest.core import Controller, Delete, Get, Injectable, Patch, Post, Put +from nest.core import Injectable +from nest.web import Controller, Delete, Get, Patch, Post, Put @Controller(prefix="api/v1/user", tag="test") diff --git a/tests/test_core/test_pynest_application.py b/tests/test_core/test_pynest_application.py index df364b7..4f9d6fe 100644 --- a/tests/test_core/test_pynest_application.py +++ b/tests/test_core/test_pynest_application.py @@ -1,13 +1,13 @@ import pytest -from nest.core import PyNestApp +from nest.web import PyNestFastapiApp from tests.test_core import test_container, test_resolver from tests.test_core.test_pynest_factory import test_server @pytest.fixture def pynest_app(test_container, test_server): - return PyNestApp(container=test_container, http_server=test_server) + return PyNestFastapiApp(container=test_container, http_server=test_server) def test_is_listening_property(pynest_app): diff --git a/tests/test_core/test_pynest_factory.py b/tests/test_core/test_pynest_factory.py index 51ecb0f..a597d75 100644 --- a/tests/test_core/test_pynest_factory.py +++ b/tests/test_core/test_pynest_factory.py @@ -1,7 +1,8 @@ import pytest from fastapi import FastAPI -from nest.core import PyNestFactory # Replace 'your_module' with the actual module name +from nest.web import \ + PyNestWebFactory # Replace 'your_module' with the actual module name from tests.test_core import test_container, test_module, test_server @@ -14,7 +15,7 @@ def test_create_server(test_server): def test_e2e(test_module): - app = PyNestFactory.create( + app = PyNestWebFactory.create( test_module, title="Test Server", description="This is a test server", From 64b6cd6586aebcbda1667eda2f0683c92d58e44b Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:43:30 +0200 Subject: [PATCH 05/21] fix cli templates to work with the new project structure --- nest/cli/src/generate/generate_controller.py | 3 +- nest/cli/templates/base_template.py | 14 +-- nest/cli/templates/blank_template.py | 9 +- nest/cli/templates/cli_templates.py | 16 ++-- nest/cli/templates/mongo_db_template.py | 95 -------------------- nest/cli/templates/mongo_template.py | 15 ++-- nest/cli/templates/orm_template.py | 24 ++--- nest/cli/templates/postgres_template.py | 4 +- nest/cli/templates/templates_factory.py | 9 +- 9 files changed, 47 insertions(+), 142 deletions(-) delete mode 100644 nest/cli/templates/mongo_db_template.py diff --git a/nest/cli/src/generate/generate_controller.py b/nest/cli/src/generate/generate_controller.py index 381dc84..6d84eb5 100644 --- a/nest/cli/src/generate/generate_controller.py +++ b/nest/cli/src/generate/generate_controller.py @@ -1,9 +1,8 @@ import click -from click import Option +from nest.cli.cli_decorators import CliCommand, CliController from nest.cli.src.generate.generate_model import SharedOptions from nest.cli.src.generate.generate_service import GenerateService -from nest.core.decorators.cli.cli_decorators import CliCommand, CliController @CliController("generate") diff --git a/nest/cli/templates/base_template.py b/nest/cli/templates/base_template.py index 7d490a3..3682d42 100644 --- a/nest/cli/templates/base_template.py +++ b/nest/cli/templates/base_template.py @@ -123,7 +123,7 @@ class {self.capitalized_module_name}Module: @staticmethod def app_controller_file(): - return f"""from nest.core import Controller, Get, Post + return f"""from nest.web import Controller, Get, Post from .app_service import AppService @@ -284,7 +284,11 @@ def get_ast_tree(file_path: Union[str, Path]) -> ast.Module: return ast.parse(source) def append_import( - self, file_path: str, module_path: str, class_name: str, import_exception: str + self, + file_path: Union[str, Path], + module_path: str, + class_name: str, + import_exception: str, ) -> ast.Module: tree = self.get_ast_tree(file_path) import_node = ast.ImportFrom( @@ -304,7 +308,7 @@ def append_import( return tree - def append_module_to_app(self, path_to_app_py: str): + def append_module_to_app(self, path_to_app_py: Union[str, Path]): tree = self.append_import( file_path=path_to_app_py, module_path=f"src.{self.module_name}.{self.module_name}_module", @@ -366,9 +370,9 @@ def generate_module(self, module_name: str, path: str = None): raise NotImplementedError def generate_empty_controller_file(self) -> str: - return f"""from nest.core import Controller + return f"""from nest.web import Controller -@Controller("{self.module_name}") +@Controller("{self.module_name}", tag="{self.module_name}") class {self.capitalized_module_name}Controller: ... """ diff --git a/nest/cli/templates/blank_template.py b/nest/cli/templates/blank_template.py index 50b1fd2..d47ad43 100644 --- a/nest/cli/templates/blank_template.py +++ b/nest/cli/templates/blank_template.py @@ -9,7 +9,8 @@ def __init__(self, module_name: str): super().__init__(module_name) def app_file(self): - return f"""from nest.core import PyNestFactory, Module + return f"""from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService @@ -20,7 +21,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my PyNest app.", title="PyNest Application", @@ -73,12 +74,12 @@ def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_na """ def controller_file(self): - return f"""from nest.core import Controller, Get, Post + return f"""from nest.web import Controller, Get, Post from .{self.module_name}_service import {self.capitalized_module_name}Service from .{self.module_name}_model import {self.capitalized_module_name} -@Controller("{self.module_name}") +@Controller("{self.module_name}", tag="{self.module_name}") class {self.capitalized_module_name}Controller: def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service): diff --git a/nest/cli/templates/cli_templates.py b/nest/cli/templates/cli_templates.py index 2720fcc..87980e9 100644 --- a/nest/cli/templates/cli_templates.py +++ b/nest/cli/templates/cli_templates.py @@ -1,7 +1,6 @@ from pathlib import Path from nest.cli.templates.base_template import BaseTemplate -from nest.core.decorators.cli.cli_decorators import CliCommand, CliController class ClickTemplate(BaseTemplate): @@ -9,8 +8,7 @@ def __init__(self, module_name: str): super().__init__(module_name) def app_file(self): - return f"""from nest.core.pynest_factory import PyNestFactory -from nest.core.cli_factory import CLIAppFactory + return f"""from nest.cli import PyNestCLIFactory from nest.core import Module from src.app_service import AppService from src.app_controller import AppController @@ -25,10 +23,10 @@ class AppModule: pass -cli_app = CLIAppFactory().create(AppModule) +nest_app = PyNestCLIFactory().create(AppModule) if __name__ == "__main__": - cli_app() + nest_app() """ def module_file(self): @@ -46,7 +44,7 @@ class {self.module_name.capitalize()}Module: """ def controller_file(self): - return f"""from nest.core.decorators.cli.cli_decorators import CliCommand, CliController + return f"""from nest.cli import CliCommand, CliController from src.{self.module_name}.{self.module_name}_service import {self.module_name.capitalize()}Service @CliController("{self.module_name}") @@ -78,7 +76,7 @@ def settings_file(self): """ def app_controller_file(self): - return f"""from nest.core.decorators.cli.cli_decorators import CliCommand, CliController + return f"""from nest.cli import CliCommand, CliController from src.app_service import AppService @@ -105,10 +103,10 @@ def app_service_file(self): class AppService: def version(self): - print(click.style("1.0.0", fg="blue")) + click.echo(click.style("1.0.0", fg="blue")) def info(self): - print(click.style("This is a cli nest app!", fg="green")) + click.echo(click.style("This is a cli nest app!", fg="green")) """ def create_module(self, module_name: str, src_path: Path): diff --git a/nest/cli/templates/mongo_db_template.py b/nest/cli/templates/mongo_db_template.py deleted file mode 100644 index eb1338c..0000000 --- a/nest/cli/templates/mongo_db_template.py +++ /dev/null @@ -1,95 +0,0 @@ -from abc import ABC - -from nest.cli.templates.abstract_base_template import AbstractBaseTemplate - - -class MongoDbTemplate(AbstractBaseTemplate, ABC): - def __init__(self, name: str): - self.name = name - self.db_type = "mongodb" - super().__init__(self.name, self.db_type) - - def generate_service_file(self) -> str: - return f"""from src.{self.name}.{self.name}_model import {self.capitalized_name} -from src.{self.name}.{self.name}_entity import {self.capitalized_name} as {self.capitalized_name}Entity -from nest.core.decorators import db_request_handler -from functools import lru_cache - - -@lru_cache() -class {self.capitalized_name}Service: - - @db_request_handler - async def get_{self.name}(self): - return await {self.capitalized_name}Entity.find_all().to_list() - - @db_request_handler - async def add_{self.name}(self, {self.name}: {self.capitalized_name}): - new_{self.name} = {self.capitalized_name}Entity( - **{self.name}.dict() - ) - await new_{self.name}.save() - return new_{self.name}.id - - - @db_request_handler - async def update_{self.name}(self, {self.name}: {self.capitalized_name}): - return await {self.capitalized_name}Entity.find_one_and_update( - {{"id": {self.name}.id}}, {self.name}.dict() - ) - - @db_request_handler - async def delete_{self.name}(self, {self.name}: {self.capitalized_name}): - return await {self.capitalized_name}Entity.find_one_and_delete( - {{"id": {self.name}.id}} - ) -""" - - def generate_orm_config_file(self) -> str: - return f"""from nest.core.database.base_odm import OdmService -from src.examples.examples_entity import Examples -import os -from dotenv import load_dotenv - -load_dotenv() - -config = OdmService( - db_type="{self.db_type}", - config_params={{ - "db_name": os.getenv("DB_NAME"), - "host": os.getenv("DB_HOST"), - "user": os.getenv("DB_USER"), - "password": os.getenv("DB_PASSWORD"), - "port": os.getenv("DB_PORT"), - }}, - document_models=[Examples] -) -""" - - def generate_entity_file(self) -> str: - return f"""from beanie import Document - - -class {self.capitalized_name}(Document): - name: str - - class Config: - schema_extra = {{ - "example": {{ - "name": "Example Name", - }} - }} -""" - - def generate_requirements_file(self) -> str: - return f"""click==8.1.6 -fastapi==0.95.1 -python-dotenv==1.0.0 -uvicorn==0.23.1 -motor==3.2.0 -beanie==1.20.0 -pynest-api=={self.version} - """ - - def generate_dockerfile(self) -> str: - pass diff --git a/nest/cli/templates/mongo_template.py b/nest/cli/templates/mongo_template.py index c36bdab..4be314a 100644 --- a/nest/cli/templates/mongo_template.py +++ b/nest/cli/templates/mongo_template.py @@ -16,7 +16,7 @@ def __init__(self, module_name: str): def config_file(self): return f"""import os from dotenv import load_dotenv -from nest.core.database.odm_provider import OdmProvider +from nest.database.odm_provider import OdmProvider load_dotenv() @@ -47,7 +47,7 @@ class {self.capitalized_module_name}(Document): name: str class Config: - schema_extra = {{ + json_schema_extra = {{ "example": {{ "name": "Example Name", }} @@ -55,13 +55,13 @@ class Config: """ def controller_file(self): - return f"""from nest.core import Controller, Get, Post + return f"""from nest.web import Controller, Get, Post from .{self.module_name}_service import {self.capitalized_module_name}Service from .{self.module_name}_model import {self.capitalized_module_name} -@Controller("{self.module_name}") +@Controller("{self.module_name}", tag="{self.module_name}") class {self.capitalized_module_name}Controller: def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service): @@ -79,22 +79,19 @@ async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_mod def service_file(self): return f"""from .{self.module_name}_model import {self.capitalized_module_name} from .{self.module_name}_entity import {self.capitalized_module_name} as {self.capitalized_module_name}Entity -from nest.core.decorators.database import db_request_handler from nest.core import Injectable -@Injectable +@Injectable() class {self.capitalized_module_name}Service: - @db_request_handler async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}): new_{self.module_name} = {self.capitalized_module_name}Entity( - **{self.module_name}.dict() + **{self.module_name}.model_dump() ) await new_{self.module_name}.save() return new_{self.module_name}.id - @db_request_handler async def get_{self.module_name}(self): return await {self.capitalized_module_name}Entity.find_all().to_list() """ diff --git a/nest/cli/templates/orm_template.py b/nest/cli/templates/orm_template.py index 5230e4a..b7ed8e6 100644 --- a/nest/cli/templates/orm_template.py +++ b/nest/cli/templates/orm_template.py @@ -13,7 +13,8 @@ def __init__(self, module_name: str, db_type: Database): self.db_type = db_type def app_file(self): - return f"""from nest.core import PyNestFactory, Module + return f"""from nest.core import Module +from nest.web import PyNestWebFactory from .config import config from .app_controller import AppController from .app_service import AppService @@ -24,7 +25,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my PyNest app.", title="PyNest Application", @@ -94,11 +95,11 @@ def service_file(self): return f"""from .{self.module_name}_model import {self.capitalized_module_name} from .{self.module_name}_entity import {self.capitalized_module_name} as {self.capitalized_module_name}Entity from src.config import config -from nest.core.decorators.database import db_request_handler +from nest.database.utils import db_request_handler from nest.core import Injectable -@Injectable +@Injectable() class {self.capitalized_module_name}Service: def __init__(self): @@ -108,7 +109,7 @@ def __init__(self): @db_request_handler def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}): new_{self.module_name} = {self.capitalized_module_name}Entity( - **{self.module_name}.dict() + **{self.module_name}.model_dump() ) self.session.add(new_{self.module_name}) self.session.commit() @@ -121,13 +122,13 @@ def get_{self.module_name}(self): """ def controller_file(self): - return f"""from nest.core import Controller, Get, Post + return f"""from nest.web import Controller, Get, Post from .{self.module_name}_service import {self.capitalized_module_name}Service from .{self.module_name}_model import {self.capitalized_module_name} -@Controller("{self.module_name}") +@Controller("{self.module_name}", tag="{self.module_name}") class {self.capitalized_module_name}Controller: def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service): @@ -206,7 +207,8 @@ def generate_project(self, project_name: str): class AsyncORMTemplate(ORMTemplate, ABC): def app_file(self): - return f"""from nest.core import PyNestFactory, Module + return f"""from nest.web import PyNestWebFactory +from nest.core import Module from .config import config from .app_controller import AppController from .app_service import AppService @@ -217,7 +219,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my Async PyNest app.", title="PyNest Application", @@ -270,7 +272,7 @@ class {self.capitalized_module_name}Service: @async_db_request_handler async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}, session: AsyncSession): new_{self.module_name} = {self.capitalized_module_name}Entity( - **{self.module_name}.dict() + **{self.module_name}.model_dump() ) session.add(new_{self.module_name}) await session.commit() @@ -293,7 +295,7 @@ def controller_file(self): from .{self.module_name}_model import {self.capitalized_module_name} -@Controller("{self.module_name}") +@Controller("{self.module_name}", tag="{self.module_name}") class {self.capitalized_module_name}Controller: def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service): diff --git a/nest/cli/templates/postgres_template.py b/nest/cli/templates/postgres_template.py index ced9957..a427173 100644 --- a/nest/cli/templates/postgres_template.py +++ b/nest/cli/templates/postgres_template.py @@ -12,7 +12,7 @@ def __init__(self, module_name: str): ) def config_file(self): - return """from nest.core.database.orm_provider import OrmProvider + return """from nest.database.orm_provider import OrmProvider import os from dotenv import load_dotenv @@ -44,7 +44,7 @@ def __init__(self, module_name: str): ) def config_file(self): - return """from nest.core.database.orm_provider import AsyncOrmProvider + return """from nest.database.orm_provider import AsyncOrmProvider import os from dotenv import load_dotenv diff --git a/nest/cli/templates/templates_factory.py b/nest/cli/templates/templates_factory.py index b79f970..e846a66 100644 --- a/nest/cli/templates/templates_factory.py +++ b/nest/cli/templates/templates_factory.py @@ -6,11 +6,10 @@ from nest.cli.templates.cli_templates import ClickTemplate from nest.cli.templates.mongo_template import MongoTemplate from nest.cli.templates.mysql_template import AsyncMySQLTemplate, MySQLTemplate -from nest.cli.templates.postgres_template import ( - AsyncPostgresqlTemplate, - PostgresqlTemplate, -) -from nest.cli.templates.sqlite_template import AsyncSQLiteTemplate, SQLiteTemplate +from nest.cli.templates.postgres_template import (AsyncPostgresqlTemplate, + PostgresqlTemplate) +from nest.cli.templates.sqlite_template import (AsyncSQLiteTemplate, + SQLiteTemplate) class TemplateFactory: From fd4e76485c5ce6a026333896bba0ffa44bf90639 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:44:00 +0200 Subject: [PATCH 06/21] fix cli ro work with new file structure --- nest/cli/src/app_controller.py | 2 +- nest/cli/src/app_module.py | 4 ++-- nest/cli/templates/sqlite_template.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nest/cli/src/app_controller.py b/nest/cli/src/app_controller.py index 55d3163..a162163 100644 --- a/nest/cli/src/app_controller.py +++ b/nest/cli/src/app_controller.py @@ -1,5 +1,5 @@ +from nest.cli import CliCommand, CliController from nest.cli.src.app_service import AppService -from nest.core.decorators.cli.cli_decorators import CliCommand, CliController @CliController("app") diff --git a/nest/cli/src/app_module.py b/nest/cli/src/app_module.py index b16e234..92c99c4 100644 --- a/nest/cli/src/app_module.py +++ b/nest/cli/src/app_module.py @@ -1,8 +1,8 @@ +from nest.cli import PyNestCLIFactory from nest.cli.src.app_controller import AppController from nest.cli.src.app_service import AppService from nest.cli.src.generate.generate_module import GenerateModule from nest.core import Module -from nest.core.cli_factory import CLIAppFactory @Module( @@ -14,4 +14,4 @@ class AppModule: pass -nest_cli = CLIAppFactory().create(AppModule) +nest_cli = PyNestCLIFactory().create(AppModule) diff --git a/nest/cli/templates/sqlite_template.py b/nest/cli/templates/sqlite_template.py index 28e29fe..6724479 100644 --- a/nest/cli/templates/sqlite_template.py +++ b/nest/cli/templates/sqlite_template.py @@ -12,7 +12,7 @@ def __init__(self, module_name: str): ) def config_file(self): - return """from nest.core.database.orm_provider import OrmProvider + return """from nest.database.orm_provider import OrmProvider import os from dotenv import load_dotenv From f0980ea128fd9e7f1e96d85ac1d27285b5ea39f6 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:44:26 +0200 Subject: [PATCH 07/21] fix docs --- docs/async_orm.md | 86 +++++++++++++++++++++++------------------------ docs/mongodb.md | 4 +-- docs/sync_orm.md | 4 +-- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/async_orm.md b/docs/async_orm.md index 6a6d512..8d706ae 100644 --- a/docs/async_orm.md +++ b/docs/async_orm.md @@ -76,21 +76,21 @@ once you have created your app, this is the code that support the asynchronous f `config.py` ```python -from nest.core.database.orm_provider import AsyncOrmProvider +from nest.database.orm_provider import AsyncOrmProvider import os from dotenv import load_dotenv load_dotenv() config = AsyncOrmProvider( - db_type="postgresql", - config_params=dict( - host=os.getenv("POSTGRESQL_HOST", "localhost"), - db_name=os.getenv("POSTGRESQL_DB_NAME", "default_nest_db"), - user=os.getenv("POSTGRESQL_USER", "postgres"), - password=os.getenv("POSTGRESQL_PASSWORD", "postgres"), - port=int(os.getenv("POSTGRESQL_PORT", 5432)), - ) + db_type="postgresql", + config_params=dict( + host=os.getenv("POSTGRESQL_HOST", "localhost"), + db_name=os.getenv("POSTGRESQL_DB_NAME", "default_nest_db"), + user=os.getenv("POSTGRESQL_USER", "postgres"), + password=os.getenv("POSTGRESQL_PASSWORD", "postgres"), + port=int(os.getenv("POSTGRESQL_PORT", 5432)), + ) ) ``` @@ -225,7 +225,7 @@ There are two ways of creating service. from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from nest.core.decorators.database import async_db_request_handler +from nest.database import async_db_request_handler from nest.core import Injectable from .example_entity import Example as ExampleEntity @@ -234,18 +234,18 @@ from .example_model import Example @Injectable class ExampleService: - @async_db_request_handler - async def add_example(self, example: Example, session: AsyncSession): - new_example = ExampleEntity(**example.dict()) - session.add(new_example) - await session.commit() - return new_example.id - - @async_db_request_handler - async def get_example(self, session: AsyncSession): - query = select(ExampleEntity) - result = await session.execute(query) - return result.scalars().all() + @async_db_request_handler + async def add_example(self, example: Example, session: AsyncSession): + new_example = ExampleEntity(**example.dict()) + session.add(new_example) + await session.commit() + return new_example.id + + @async_db_request_handler + async def get_example(self, session: AsyncSession): + query = select(ExampleEntity) + result = await session.execute(query) + return result.scalars().all() ``` 2. In that way, the service init the async session in the constructor, and each function that depends on the database is @@ -255,7 +255,7 @@ class ExampleService: from .examples_model import Examples from .examples_entity import Examples as ExamplesEntity from src.config import config -from nest.core.decorators.database import async_db_request_handler +from nest.database import async_db_request_handler from nest.core import Injectable from sqlalchemy import select @@ -263,26 +263,26 @@ from sqlalchemy import select @Injectable class ExamplesService: - def __init__(self): - self.orm_config = config - self.session = self.orm_config.get_session - - @async_db_request_handler - async def add_examples(self, examples: Examples): - examples_entity = ExamplesEntity( - **examples.dict() - ) - async with self.session() as session: - session.add(examples_entity) - await session.commit() - return examples_entity.id - - @async_db_request_handler - async def get_examples(self): - query = select(ExamplesEntity) - async with self.session() as session: - result = await session.execute(query) - return result.scalars().all() + def __init__(self): + self.orm_config = config + self.session = self.orm_config.get_session + + @async_db_request_handler + async def add_examples(self, examples: Examples): + examples_entity = ExamplesEntity( + **examples.dict() + ) + async with self.session() as session: + session.add(examples_entity) + await session.commit() + return examples_entity.id + + @async_db_request_handler + async def get_examples(self): + query = select(ExamplesEntity) + async with self.session() as session: + result = await session.execute(query) + return result.scalars().all() ``` create a controller to handle the requests and responses. The controller should call the service to execute business diff --git a/docs/mongodb.md b/docs/mongodb.md index 85cd40b..52694ae 100644 --- a/docs/mongodb.md +++ b/docs/mongodb.md @@ -87,7 +87,7 @@ Let's go over the boilerplate code that support the mongo integration: `config.py` ```python -from nest.core.database.odm_provider import OdmProvider +from nest.database.odm_provider import OdmProvider from src.examples.examples_entity import Examples import os from dotenv import load_dotenv @@ -231,7 +231,7 @@ Implement services to handle business logic. ```python from .examples_model import Examples from .examples_entity import Examples as ExamplesEntity -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from nest.core import Injectable diff --git a/docs/sync_orm.md b/docs/sync_orm.md index b69b251..887c028 100644 --- a/docs/sync_orm.md +++ b/docs/sync_orm.md @@ -66,7 +66,7 @@ Let's go over the boilerplate code that generated by the cli: `config.py` ```python -from nest.core.database.orm_provider import OrmProvider +from nest.database.orm_provider import OrmProvider import os from dotenv import load_dotenv @@ -185,7 +185,7 @@ Implement services to handle business logic. from src.config import config from .examples_model import Examples from .examples_entity import Examples as ExamplesEntity -from nest.core.decorators.database import db_request_handler +from nest.database import db_request_handler from nest.core import Injectable From 517f6a7a89f545e3a4394fd043a68c7c6355e9d4 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:44:58 +0200 Subject: [PATCH 08/21] remove unused files --- nest/cli/templates/relational_db_template.py | 123 ------------------ .../plugins/modules/redis/redis_controller.py | 26 ---- nest/plugins/modules/redis/redis_model.py | 14 -- nest/plugins/modules/redis/redis_module.py | 8 -- nest/plugins/modules/redis/redis_service.py | 30 ----- 5 files changed, 201 deletions(-) delete mode 100644 nest/cli/templates/relational_db_template.py delete mode 100644 nest/plugins/modules/redis/redis_controller.py delete mode 100644 nest/plugins/modules/redis/redis_model.py delete mode 100644 nest/plugins/modules/redis/redis_module.py delete mode 100644 nest/plugins/modules/redis/redis_service.py diff --git a/nest/cli/templates/relational_db_template.py b/nest/cli/templates/relational_db_template.py deleted file mode 100644 index f915076..0000000 --- a/nest/cli/templates/relational_db_template.py +++ /dev/null @@ -1,123 +0,0 @@ -from abc import ABC - -from nest import __version__ as version -from nest.cli.templates.abstract_base_template import AbstractBaseTemplate - - -class RelationalDBTemplate(AbstractBaseTemplate, ABC): - def __init__(self, name, db_type): - super().__init__(name, db_type) - - def generate_service_file(self) -> str: - return f"""from src.{self.name}.{self.name}_model import {self.capitalized_name} -from src.{self.name}.{self.name}_entity import {self.capitalized_name} as {self.capitalized_name}Entity -from orm_config import config -from nest.core.decorators import db_request_handler -from functools import lru_cache - - -@lru_cache() -class {self.capitalized_name}Service: - - def __init__(self): - self.orm_config = config - self.session = self.orm_config.get_db() - - @db_request_handler - def add_{self.name}(self, {self.name}: {self.capitalized_name}): - new_{self.name} = {self.capitalized_name}Entity( - **{self.name}.dict() - ) - self.session.add(new_{self.name}) - self.session.commit() - return new_{self.name}.id - - @db_request_handler - def get_{self.name}(self): - return self.session.query({self.capitalized_name}Entity).all() - - @db_request_handler - def delete_{self.name}(self, {self.name}_id: int): - self.session.query({self.capitalized_name}Entity).filter_by(id={self.name}_id).delete() - self.session.commit() - return {self.name}_id - - @db_request_handler - def update_{self.name}(self, {self.name}_id: int, {self.name}: {self.capitalized_name}): - self.session.query({self.capitalized_name}Entity).filter_by(id={self.name}_id).update( - {self.name}.dict() - ) - self.session.commit() - return {self.name}_id - """ - - def generate_entity_file(self) -> str: - return f"""from orm_config import config -from sqlalchemy import Column, Integer, String, Float - - -class {self.capitalized_name}(config.Base): - __tablename__ = "{self.name}" - - id = Column(Integer, primary_key=True, autoincrement=True) - name = Column(String, unique=True) - """ - - def generate_requirements_file(self) -> str: - return f"""anyio==3.6.2 -click==8.1.3 -fastapi==0.95.1 -fastapi-utils==0.2.1 -greenlet==2.0.2 -h11==0.14.0 -idna==3.4 -pydantic==1.10.7 -python-dotenv==1.0.0 -sniffio==1.3.0 -SQLAlchemy==1.4.48 -starlette==0.26.1 -typing_extensions==4.5.0 -uvicorn==0.22.0 -pynest-api=={version} - """ - - def generate_dockerfile(self) -> str: - pass - - def generate_orm_config_file(self) -> str: - base_template = f"""from nest.core.database.base_orm import OrmService -import os -from dotenv import load_dotenv - -load_dotenv() - - - """ - - if self.db_type == "sqlite": - return f"""{base_template} - config = OrmService( - db_type="{self.db_type}", - config_params=dict( - db_name=os.getenv("SQLITE_DB_NAME", "{self.name}_db"), - ) - ) - """ - else: - return f"""{base_template} - config = OrmService( - db_type="{self.db_type}", - config_params=dict( - host=os.getenv("{self.db_type.upper()}_HOST"), - db_name=os.getenv("{self.db_type.upper()}_DB_NAME"), - user=os.getenv("{self.db_type.upper()}_USER"), - password=os.getenv("{self.db_type.upper()}_PASSWORD"), - port=int(os.getenv("{self.db_type.upper()}_PORT")), - ) - ) - """ - - -if __name__ == "__main__": - relational_db_template = RelationalDBTemplate("users", "mysql") - print(relational_db_template.generate_orm_config_file()) diff --git a/nest/plugins/modules/redis/redis_controller.py b/nest/plugins/modules/redis/redis_controller.py deleted file mode 100644 index c5c4954..0000000 --- a/nest/plugins/modules/redis/redis_controller.py +++ /dev/null @@ -1,26 +0,0 @@ -from nest.core import Controller, Delete, Depends, Get, Post -from nest.plugins.modules.redis.redis_model import RedisInput -from nest.plugins.modules.redis.redis_service import RedisService - - -@Controller("redis") -class RedisController: - - def __init__(self, redis_service: RedisService): - self.redis_service = redis_service - - @Get("/{key}") - def get(self, key: str): - return self.redis_service.get(key) - - @Post("/") - def set(self, redis_input: RedisInput): - return self.redis_service.set(redis_input) - - @Delete("/{key}") - def delete(self, key: str): - return self.redis_service.delete(key) - - @Get("/exists/{key}") - def exists(self, key: str): - return self.redis_service.exists(key) diff --git a/nest/plugins/modules/redis/redis_model.py b/nest/plugins/modules/redis/redis_model.py deleted file mode 100644 index d852228..0000000 --- a/nest/plugins/modules/redis/redis_model.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Any - -from pydantic import BaseModel, BaseSettings - - -class RedisInput(BaseModel): - key: str - value: Any - - -class RedisConfig(BaseSettings): - REDIS_HOST: str = "localhost" - REDIS_PORT: int = 6379 - REDIS_DB: int = 0 diff --git a/nest/plugins/modules/redis/redis_module.py b/nest/plugins/modules/redis/redis_module.py deleted file mode 100644 index 95ff6ab..0000000 --- a/nest/plugins/modules/redis/redis_module.py +++ /dev/null @@ -1,8 +0,0 @@ -from nest.core import Module -from nest.plugins.modules.redis.redis_controller import RedisController -from nest.plugins.modules.redis.redis_service import RedisService - - -@Module(controllers=[RedisController], providers=[RedisService], imports=[]) -class RedisModule: - pass diff --git a/nest/plugins/modules/redis/redis_service.py b/nest/plugins/modules/redis/redis_service.py deleted file mode 100644 index 8464873..0000000 --- a/nest/plugins/modules/redis/redis_service.py +++ /dev/null @@ -1,30 +0,0 @@ -import redis -from fastapi import HTTPException - -from nest.core import Injectable -from nest.plugins.modules.redis.redis_model import RedisConfig, RedisInput - - -@Injectable() -class RedisService: - def __init__(self): - self.redis_config = RedisConfig() - self.redis_client = redis.StrictRedis( - host=self.redis_config.REDIS_HOST, - port=self.redis_config.REDIS_PORT, - db=self.redis_config.REDIS_DB, - ) - - def set(self, redis_input: RedisInput): - if self.exists(redis_input.key): - raise HTTPException(status_code=400, detail="Key already exists") - self.redis_client.set(redis_input.key, redis_input.value) - - def get(self, redis_key: str): - return self.redis_client.get(redis_key) - - def exists(self, redis_key: str): - return self.redis_client.exists(redis_key) - - def delete(self, redis_key: str): - self.redis_client.delete(redis_key) From ccc24623ac6f4405009d5c40bbc2f95089e2a9f4 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:46:46 +0200 Subject: [PATCH 09/21] fix core components --- nest/common/module.py | 2 +- nest/core/pynest_container.py | 17 +++++---------- nest/core/pynest_factory.py | 39 ----------------------------------- 3 files changed, 6 insertions(+), 52 deletions(-) diff --git a/nest/common/module.py b/nest/common/module.py index a877eab..2999f18 100644 --- a/nest/common/module.py +++ b/nest/common/module.py @@ -5,7 +5,7 @@ from typing import Any, List, Type from uuid import uuid4 -from nest.core import Module +from nest.core.module import Module class ModulesContainer(dict): diff --git a/nest/core/pynest_container.py b/nest/core/pynest_container.py index 9e524f8..c590e93 100644 --- a/nest/core/pynest_container.py +++ b/nest/core/pynest_container.py @@ -5,18 +5,11 @@ from injector import Injector, UnknownProvider, singleton from nest.common.constants import DEPENDENCIES, INJECTABLE_TOKEN -from nest.common.exceptions import ( - CircularDependencyException, - NoneInjectableException, - UnknownModuleException, -) -from nest.common.module import ( - Module, - ModuleCompiler, - ModuleFactory, - ModulesContainer, - ModuleTokenFactory, -) +from nest.common.exceptions import (CircularDependencyException, + NoneInjectableException, + UnknownModuleException) +from nest.common.module import (Module, ModuleCompiler, ModuleFactory, + ModulesContainer, ModuleTokenFactory) TController = type("TController", (), {}) TProvider = type("TProvider", (), {}) diff --git a/nest/core/pynest_factory.py b/nest/core/pynest_factory.py index 2d988ae..5015fc1 100644 --- a/nest/core/pynest_factory.py +++ b/nest/core/pynest_factory.py @@ -1,11 +1,6 @@ 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 - ModuleType = TypeVar("ModuleType") @@ -13,37 +8,3 @@ class AbstractPyNestFactory(ABC): @abstractmethod def create(self, main_module: Type[ModuleType], **kwargs): raise NotImplementedError - - -class PyNestFactory(AbstractPyNestFactory): - """Factory class for creating PyNest applications.""" - - @staticmethod - def create(main_module: Type[ModuleType], **kwargs) -> PyNestApp: - """ - Create a PyNest application with the specified main module class. - - Args: - main_module (ModuleType): The main module for the PyNest application. - **kwargs: Additional keyword arguments for the FastAPI server. - - Returns: - PyNestApp: The created PyNest application. - """ - container = PyNestContainer() - container.add_module(main_module) - http_server = PyNestFactory._create_server(**kwargs) - return PyNestApp(container, http_server) - - @staticmethod - def _create_server(**kwargs) -> FastAPI: - """ - Create a FastAPI server. - - Args: - **kwargs: Additional keyword arguments for the FastAPI server. - - Returns: - FastAPI: The created FastAPI server. - """ - return FastAPI(**kwargs) From f9e5cc365be84b082552b6511399195ecd440d24 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 14:47:09 +0200 Subject: [PATCH 10/21] migrate project from setuptools and pip to poetry! --- pyproject.toml | 106 +++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 60 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9acd99e..73b5d30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,48 +1,52 @@ [build-system] -requires = ["setuptools>=61.0", "wheel>=0.37.0"] -build-backend = "setuptools.build_meta" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" -[project] +[tool.poetry] name = "pynest-api" +version = "0.3.1" description = "PyNest is a FastAPI Abstraction for building microservices, influenced by NestJS." +authors = ["itay.dar "] readme = "README.md" -requires-python = ">=3.8.1" -license = { file = "LICENSE" } -authors = [ - { name = "Itay Dar", email = "itay2803@gmail.com" }, -] -dynamic = ["version"] -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", -] -dependencies = [ - "click>=8.1.6", - "fastapi>=0.88.0,<1.0.0", - "python-dotenv>=1.0.0", - "uvicorn>=0.23.1", - "PyYAML>=6.0.1", - "astor>=0.8.1", - "black>=23.11.0", - "injector>=0.20.1", - "pydantic<2.0.0", - "sqlalchemy == 2.0.19", - "alembic == 1.7.5", +homepage = "https://github.com/PythonNest/PyNest" +documentation = "https://pythonnest.github.io/PyNest/" +packages = [ + { include = "nest" } ] -[tool.setuptools.dynamic] -version = { attr = "nest.__init__.__version__" } -[tool.pip] -index-url = "https://pypi.org/simple" -trusted-host = ["pypi.org", "files.pythonhosted.org"] +[tool.poetry.dependencies] +python = "^3.10" +# Core dependencies +click = "^8.1.7" +injector = "^0.22.0" +astor = "^0.8.1" +pyyaml = "^6.0.2" +black = "^24.10.0" + + +# Optional dependencies +fastapi = { version = "^0.115.4", optional = true } +pydantic = { version = "^2.9.2", optional = true } +uvicorn = { version = "^0.32.0", optional = true } +sqlalchemy = { version = "^2.0.36", optional = true } +asyncpg = { version = "^0.30.0", optional = true } +psycopg2 = { version = "^2.9.3", optional = true } +alembic = { version = "^1.13.3", optional = true } +beanie = { version = "^1.27.0", optional = true } +pytest = { version = "6.2.5", optional = true } +python-dotenv = { version = "^1.0.1", optional = true } +greenlet = { version = "^3.1.1", optional = true } + +[tool.poetry.extras] +web = ["fastapi", "pydantic", "uvicorn"] +postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"] +mongo = ["beanie", "python-dotenv"] +test = ["pytest"] -[tools.black] + +[tool.black] force-exclude = ''' /( | /*venv* @@ -58,34 +62,16 @@ force-exclude = ''' )/ ''' -[project.optional-dependencies] -test = [ - "pytest == 6.2.5", -] - -orm = [ - "sqlalchemy == 2.0.19", - "alembic == 1.7.4", -] -mongo = [ - "pymongo == 3.12.0", - "motor == 3.2.0", - "beanie == 1.20.0", -] - -[project.scripts] -pynest = "nest.cli.cli:nest_cli" - -[tool.setuptools.packages.find] -include = ["nest*"] -namespaces = false - [tool.mypy] exclude = [ "/*venv*" ] ignore_missing_imports = true -[project.urls] -"Homepage" = "https://github.com/PythonNest/PyNest" -"Documentation" = "https://pythonnest.github.io/PyNest/" +[tool.poetry.urls] +Homepage = "https://github.com/PythonNest/PyNest" +Documentation = "https://pythonnest.github.io/PyNest/" + +[tool.poetry.scripts] +pynest = "nest.cli.cli:nest_cli" + From 01071d9b986cf28b688c89d7f2edf92fb46ef5a9 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 15:30:07 +0200 Subject: [PATCH 11/21] refactor Duplicate Module class name for better maintainabillity --- nest/{common/module.py => core/nest_module.py} | 2 +- nest/core/pynest_app_context.py | 6 +++--- nest/core/pynest_container.py | 17 ++++++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) rename nest/{common/module.py => core/nest_module.py} (99%) diff --git a/nest/common/module.py b/nest/core/nest_module.py similarity index 99% rename from nest/common/module.py rename to nest/core/nest_module.py index 2999f18..de4ad05 100644 --- a/nest/common/module.py +++ b/nest/core/nest_module.py @@ -26,7 +26,7 @@ def has(self, token: str): return True if self.get(token) is not None else False -class Module: +class NestModule: def __init__(self, metatype: Type[object], container): self._id = str(uuid.uuid4()) self._metatype = metatype diff --git a/nest/core/pynest_app_context.py b/nest/core/pynest_app_context.py index 030d627..035a815 100644 --- a/nest/core/pynest_app_context.py +++ b/nest/core/pynest_app_context.py @@ -2,7 +2,7 @@ from typing import TypeVar, Union from nest.common.exceptions import UnknownModuleException -from nest.common.module import Module, ModuleCompiler +from nest.common.nest_module import NestModule, ModuleCompiler from nest.core.pynest_container import PyNestContainer T = TypeVar("T") @@ -60,7 +60,7 @@ def init(self): return self def __init__( - self, container: PyNestContainer, context_module: Union[Module, None] = None + self, container: PyNestContainer, context_module: Union[NestModule, None] = None ): """ Constructor for the PyNestApplicationContext. @@ -70,7 +70,7 @@ def __init__( context_module (Union[Module, None], optional): The initial context module of the application. """ self.container = container - self.context_module: Module = context_module + self.context_module: NestModule = context_module self.logger = logging.getLogger(PyNestApplicationContext.__name__) def select_context_module(self): diff --git a/nest/core/pynest_container.py b/nest/core/pynest_container.py index c590e93..396870f 100644 --- a/nest/core/pynest_container.py +++ b/nest/core/pynest_container.py @@ -5,11 +5,18 @@ from injector import Injector, UnknownProvider, singleton from nest.common.constants import DEPENDENCIES, INJECTABLE_TOKEN -from nest.common.exceptions import (CircularDependencyException, - NoneInjectableException, - UnknownModuleException) -from nest.common.module import (Module, ModuleCompiler, ModuleFactory, - ModulesContainer, ModuleTokenFactory) +from nest.common.exceptions import ( + CircularDependencyException, + NoneInjectableException, + UnknownModuleException, +) +from nest.core.nest_module import ( + Module, + ModuleCompiler, + ModuleFactory, + ModulesContainer, + ModuleTokenFactory, +) TController = type("TController", (), {}) TProvider = type("TProvider", (), {}) From 7f689e77aef742ba96eadbfa6b87990b6c818442 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 15:30:15 +0200 Subject: [PATCH 12/21] run black --- nest/cli/cli_decorators.py | 3 +-- nest/cli/src/app_controller.py | 6 ++++-- nest/cli/templates/templates_factory.py | 9 +++++---- nest/core/injectable.py | 3 +-- nest/database/odm_config.py | 5 +---- nest/database/orm_config.py | 3 +-- nest/database/orm_provider.py | 3 +-- nest/web/class_based_view.py | 13 +++++++++++-- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/nest/cli/cli_decorators.py b/nest/cli/cli_decorators.py index 800fc7a..0d1854b 100644 --- a/nest/cli/cli_decorators.py +++ b/nest/cli/cli_decorators.py @@ -2,8 +2,7 @@ import click -from nest.core.utils import (get_instance_variables, parse_dependencies, - parse_params) +from nest.core.utils import get_instance_variables, parse_dependencies, parse_params def CliController(name: str, **kwargs): diff --git a/nest/cli/src/app_controller.py b/nest/cli/src/app_controller.py index a162163..73d2522 100644 --- a/nest/cli/src/app_controller.py +++ b/nest/cli/src/app_controller.py @@ -1,3 +1,5 @@ +import click + from nest.cli import CliCommand, CliController from nest.cli.src.app_service import AppService @@ -10,9 +12,9 @@ def __init__(self, app_service: AppService): @CliCommand("info") def get_app_info(self): app_info = self.app_service.get_app_info() - print(app_info) + click.echo(app_info) @CliCommand("version", help="Get the version of the app") def get_app_version(self): app_info = self.app_service.get_app_info() - print(app_info["app_version"]) + click.echo(app_info["app_version"]) diff --git a/nest/cli/templates/templates_factory.py b/nest/cli/templates/templates_factory.py index e846a66..b79f970 100644 --- a/nest/cli/templates/templates_factory.py +++ b/nest/cli/templates/templates_factory.py @@ -6,10 +6,11 @@ from nest.cli.templates.cli_templates import ClickTemplate from nest.cli.templates.mongo_template import MongoTemplate from nest.cli.templates.mysql_template import AsyncMySQLTemplate, MySQLTemplate -from nest.cli.templates.postgres_template import (AsyncPostgresqlTemplate, - PostgresqlTemplate) -from nest.cli.templates.sqlite_template import (AsyncSQLiteTemplate, - SQLiteTemplate) +from nest.cli.templates.postgres_template import ( + AsyncPostgresqlTemplate, + PostgresqlTemplate, +) +from nest.cli.templates.sqlite_template import AsyncSQLiteTemplate, SQLiteTemplate class TemplateFactory: diff --git a/nest/core/injectable.py b/nest/core/injectable.py index 8cc8e7a..2eff7d8 100644 --- a/nest/core/injectable.py +++ b/nest/core/injectable.py @@ -2,8 +2,7 @@ from injector import inject -from nest.common.constants import (DEPENDENCIES, INJECTABLE_NAME, - INJECTABLE_TOKEN) +from nest.common.constants import DEPENDENCIES, INJECTABLE_NAME, INJECTABLE_TOKEN from nest.core.utils import parse_dependencies diff --git a/nest/database/odm_config.py b/nest/database/odm_config.py index f3151fd..908e8b6 100644 --- a/nest/database/odm_config.py +++ b/nest/database/odm_config.py @@ -1,4 +1,4 @@ -from nest.database.base_config import BaseProvider +from nest.database.base_config import BaseProvider, ConfigFactoryBase from urllib.parse import urlencode @@ -83,8 +83,6 @@ def get_engine_url(self) -> str: return uri - - class ConfigFactory(ConfigFactoryBase): """ Factory class for retrieving the appropriate ORM configuration based on the database type. @@ -119,4 +117,3 @@ def get_config(self): return MongoDBConfig else: raise Exception(f"Database type {self.db_type} is not supported") - diff --git a/nest/database/orm_config.py b/nest/database/orm_config.py index 515be35..88a1151 100644 --- a/nest/database/orm_config.py +++ b/nest/database/orm_config.py @@ -1,5 +1,4 @@ -from nest.database.base_config import (BaseConfig, BaseProvider, - ConfigFactoryBase) +from nest.database.base_config import BaseConfig, BaseProvider, ConfigFactoryBase class PostgresConfig(BaseProvider): diff --git a/nest/database/orm_provider.py b/nest/database/orm_provider.py index 0ac9d0c..d8cc81c 100644 --- a/nest/database/orm_provider.py +++ b/nest/database/orm_provider.py @@ -3,8 +3,7 @@ from typing import Any, Dict from sqlalchemy import create_engine -from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker, - create_async_engine) +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from nest.database.orm_config import AsyncConfigFactory, ConfigFactory diff --git a/nest/web/class_based_view.py b/nest/web/class_based_view.py index 5450c34..e73c3cd 100644 --- a/nest/web/class_based_view.py +++ b/nest/web/class_based_view.py @@ -4,8 +4,17 @@ """ import inspect -from typing import (Any, Callable, ClassVar, List, Type, TypeVar, Union, - get_origin, get_type_hints) +from typing import ( + Any, + Callable, + ClassVar, + List, + Type, + TypeVar, + Union, + get_origin, + get_type_hints, +) from fastapi import APIRouter, Depends from starlette.routing import Route, WebSocketRoute From 2dec5b3b922b81a30ba2ab7172892ae18f4d4f18 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 15:52:21 +0200 Subject: [PATCH 13/21] fix imports error change web dependency to fastapi --- nest/core/pynest_app_context.py | 2 +- nest/core/pynest_container.py | 14 +++++++------- pyproject.toml | 2 +- tests/test_core/__init__.py | 4 ++-- tests/test_core/test_database/test_odm.py | 2 +- tests/test_core/test_pynest_application.py | 2 +- tests/test_core/test_pynest_factory.py | 5 +++-- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/nest/core/pynest_app_context.py b/nest/core/pynest_app_context.py index 035a815..1f289f4 100644 --- a/nest/core/pynest_app_context.py +++ b/nest/core/pynest_app_context.py @@ -2,7 +2,7 @@ from typing import TypeVar, Union from nest.common.exceptions import UnknownModuleException -from nest.common.nest_module import NestModule, ModuleCompiler +from nest.core.nest_module import NestModule, ModuleCompiler from nest.core.pynest_container import PyNestContainer T = TypeVar("T") diff --git a/nest/core/pynest_container.py b/nest/core/pynest_container.py index 396870f..275bba3 100644 --- a/nest/core/pynest_container.py +++ b/nest/core/pynest_container.py @@ -11,7 +11,7 @@ UnknownModuleException, ) from nest.core.nest_module import ( - Module, + NestModule, ModuleCompiler, ModuleFactory, ModulesContainer, @@ -95,7 +95,7 @@ def add_module(self, metaclass) -> dict: return {"module_ref": self.modules.get(token), "inserted": False} return {"module_ref": self.register_module(module_factory), "inserted": True} - def register_module(self, module_factory: ModuleFactory) -> Module: + def register_module(self, module_factory: ModuleFactory) -> NestModule: """ Register a module in the container. @@ -111,7 +111,7 @@ def register_module(self, module_factory: ModuleFactory) -> Module: Module: The module reference that has been registered in the container. """ - module_ref = Module(module_factory.type, self) + module_ref = NestModule(module_factory.type, self) module_ref.token = module_factory.token self._modules[module_factory.token] = module_ref @@ -140,7 +140,7 @@ def add_import(self, token: str): if not self.modules.has(token): return module_metadata = self._modules_metadata.get(token) - module_ref: Module = self.modules.get(token) + module_ref: NestModule = self.modules.get(token) imports_mod: List[Any] = module_metadata.get("imports") self.add_modules(imports_mod) module_ref.add_imports(imports_mod) @@ -158,7 +158,7 @@ def add_providers(self, providers: List[Any], module_token: str) -> None: def add_provider(self, token: str, provider): """Add a provider to a module.""" - module_ref: Module = self.modules[token] + module_ref: NestModule = self.modules[token] if not provider: raise CircularDependencyException(module_ref.metatype) @@ -200,7 +200,7 @@ def _add_controller(self, token: str, controller: TController) -> None: """Add a controller to a module.""" if not self.modules.has(token): raise UnknownModuleException() - module_ref: Module = self.modules[token] + module_ref: NestModule = self.modules[token] module_ref.add_controller(controller) if hasattr(controller, DEPENDENCIES): for provider_name, provider_type in getattr( @@ -228,5 +228,5 @@ def add_related_module(self, related_module, token: str) -> None: # UNUSED: This function is currently not used but retained for potential future use. # It retrieves a module from the container by its key. - def get_module_by_key(self, module_key: str) -> Module: + def get_module_by_key(self, module_key: str) -> NestModule: return self._modules[module_key] diff --git a/pyproject.toml b/pyproject.toml index 73b5d30..b4e3ec8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ python-dotenv = { version = "^1.0.1", optional = true } greenlet = { version = "^3.1.1", optional = true } [tool.poetry.extras] -web = ["fastapi", "pydantic", "uvicorn"] +fastapi = ["fastapi", "pydantic", "uvicorn"] postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"] mongo = ["beanie", "python-dotenv"] test = ["pytest"] diff --git a/tests/test_core/__init__.py b/tests/test_core/__init__.py index a1ba98a..40e79ec 100644 --- a/tests/test_core/__init__.py +++ b/tests/test_core/__init__.py @@ -2,8 +2,8 @@ from fastapi import FastAPI from nest.core import Injectable, Module, PyNestContainer -from nest.web import (Controller, Get, PyNestFastapiApp, PyNestWebFactory, - RoutesResolver) +from nest.web import Controller, Get, PyNestWebFactory +from nest.web.route_resolver import RoutesResolver @Injectable diff --git a/tests/test_core/test_database/test_odm.py b/tests/test_core/test_database/test_odm.py index d45b345..a935c47 100644 --- a/tests/test_core/test_database/test_odm.py +++ b/tests/test_core/test_database/test_odm.py @@ -34,7 +34,7 @@ def test_odm_service_definition(odm_service): def test_odm_service_config_url(odm_service): config_url = odm_service.config_url - assert config_url == "mongodb://user:password@host:port" + assert config_url == "mongodb://user:password@host:port/db_name" def test_mongo_config_definition(mongodb_config): diff --git a/tests/test_core/test_pynest_application.py b/tests/test_core/test_pynest_application.py index 4f9d6fe..e46467a 100644 --- a/tests/test_core/test_pynest_application.py +++ b/tests/test_core/test_pynest_application.py @@ -1,6 +1,6 @@ import pytest -from nest.web import PyNestFastapiApp +from nest.web.pynest_fastapi_application import PyNestFastapiApp from tests.test_core import test_container, test_resolver from tests.test_core.test_pynest_factory import test_server diff --git a/tests/test_core/test_pynest_factory.py b/tests/test_core/test_pynest_factory.py index a597d75..c7cb627 100644 --- a/tests/test_core/test_pynest_factory.py +++ b/tests/test_core/test_pynest_factory.py @@ -1,8 +1,9 @@ import pytest from fastapi import FastAPI -from nest.web import \ - PyNestWebFactory # Replace 'your_module' with the actual module name +from nest.web import ( + PyNestWebFactory, +) from tests.test_core import test_container, test_module, test_server From caa39af7a42816f6353789c7d0ce80af4b1e306b Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 17:46:52 +0200 Subject: [PATCH 14/21] fix github actions --- .github/workflows/cli_test.yaml | 16 +++++++--------- .github/workflows/integration_test.yaml | 12 ++++++------ pyproject.toml | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cli_test.yaml b/.github/workflows/cli_test.yaml index 7feef03..48e2543 100644 --- a/.github/workflows/cli_test.yaml +++ b/.github/workflows/cli_test.yaml @@ -1,6 +1,6 @@ name: CLI Test -on: [ push, workflow_call, pull_request ] +on: [push, workflow_call, pull_request] jobs: test: @@ -8,21 +8,21 @@ jobs: strategy: matrix: - app_type: [ "Blank", "SyncORM", "AsyncORM", "MongoDB", "PostgresSync", "PostgresAsync", "MySQLSync", "MySQLAsync" ] + app_type: ["Blank", "SyncORM", "AsyncORM", "MongoDB", "PostgresSync", "PostgresAsync", "MySQLSync", "MySQLAsync"] steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.10' - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install . + pip install poetry + poetry install - name: Test CLI Commands run: | @@ -74,7 +74,6 @@ jobs: exit 1 fi - # List of expected files declare -a files=("main.py" "requirements.txt" "README.md") declare -a src_level_files=("app_module.py" "app_service.py" "app_controller.py") @@ -100,7 +99,6 @@ jobs: fi done - # Check each file in the list of module_files for file in "${module_files[@]}"; do if [ -f "$app_name/src/user/$file" ]; then diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index c82f624..5b6a465 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,17 +12,17 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.10' - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install . + pip install poetry + poetry install - name: Start Application run: | @@ -41,7 +41,7 @@ jobs: pynest generate application -n "$app_name" else pynest generate application -n "$app_name" -db sqlite $is_async - pip install aiosqlite + poetry add aiosqlite fi cd "$app_name" diff --git a/pyproject.toml b/pyproject.toml index b4e3ec8..7f41ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,27 @@ postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python- mongo = ["beanie", "python-dotenv"] test = ["pytest"] +[tool.poetry.group.build.dependencies] +setuptools = "^68.0.0" +wheel = "^0.41.0" +build = "^0.10.0" +twine = "^4.0.2" +git-changelog = "^0.7.0" + +[tool.poetry.group.test.dependencies] +pytest = "^7.0.1" +fastapi = "^0.115.4" +sqlalchemy = "^2.0.36" +motor = "^3.2.0" +beanie = "^1.27.0" +pydantic = "^2.9.2" +python-dotenv = "^1.0.1" +uvicorn = "^0.32.0" + +[tool.poetry.group.docs.dependencies] +mkdocs-material = "^9.1.20" +mkdocstrings-python = "^0.9.1" + [tool.black] force-exclude = ''' From 0386af566cb1ba1d7c493c0b93612f29e87fd182 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 17:51:17 +0200 Subject: [PATCH 15/21] fix github actions --- .github/workflows/tests.yaml | 32 +++++++++++++++++--------------- pyproject.toml | 1 - 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b9c3ae7..4847479 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,21 +5,23 @@ on: [push, workflow_call, pull_request] jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.10, 3.11] + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - - name: Install Python - id: setup_python - uses: actions/setup-python@v4 - with: - python-version: | - 3.7 - 3.8 - 3.9 - 3.10 - 3.11 + - name: Install dependencies + run: | + pip install poetry + poetry install --with test - - name: Run tests - run: | - pip install -r requirements-tests.txt - pytest tests \ No newline at end of file + - name: Run tests + run: | + pytest tests diff --git a/pyproject.toml b/pyproject.toml index 7f41ad1..c37ee3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ asyncpg = { version = "^0.30.0", optional = true } psycopg2 = { version = "^2.9.3", optional = true } alembic = { version = "^1.13.3", optional = true } beanie = { version = "^1.27.0", optional = true } -pytest = { version = "6.2.5", optional = true } python-dotenv = { version = "^1.0.1", optional = true } greenlet = { version = "^3.1.1", optional = true } From 6d5bc740b30fdfda9bc9a557ab225630a36b7ec0 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 18:02:27 +0200 Subject: [PATCH 16/21] fix github actions --- .github/workflows/tests.yaml | 2 +- pyproject.toml | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4847479..2dfbbb7 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.10, 3.11] + python-version: ["3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index c37ee3f..5cf7dd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,8 @@ beanie = { version = "^1.27.0", optional = true } python-dotenv = { version = "^1.0.1", optional = true } greenlet = { version = "^3.1.1", optional = true } + + [tool.poetry.extras] fastapi = ["fastapi", "pydantic", "uvicorn"] postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"] @@ -45,11 +47,11 @@ mongo = ["beanie", "python-dotenv"] test = ["pytest"] [tool.poetry.group.build.dependencies] -setuptools = "^68.0.0" -wheel = "^0.41.0" -build = "^0.10.0" -twine = "^4.0.2" -git-changelog = "^0.7.0" +setuptools = "^75.3.0" +wheel = "^0.44.0" +build = "^1.2.2.post1" +twine = "^5.1.1" +git-changelog = "^2.5.2" [tool.poetry.group.test.dependencies] pytest = "^7.0.1" @@ -62,8 +64,8 @@ python-dotenv = "^1.0.1" uvicorn = "^0.32.0" [tool.poetry.group.docs.dependencies] -mkdocs-material = "^9.1.20" -mkdocstrings-python = "^0.9.1" +mkdocs-material = "^9.5.43" +mkdocstrings-python = "^1.12.2" [tool.black] From 663da03da4b26c9e1cb09e3d8cb83f2a81e30135 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 18:05:43 +0200 Subject: [PATCH 17/21] fix github actions --- .github/workflows/cli_test.yaml | 18 +++++++++--------- .github/workflows/integration_test.yaml | 8 ++++---- .github/workflows/tests.yaml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cli_test.yaml b/.github/workflows/cli_test.yaml index 48e2543..e40df67 100644 --- a/.github/workflows/cli_test.yaml +++ b/.github/workflows/cli_test.yaml @@ -29,33 +29,33 @@ jobs: app_name="${{ matrix.app_type }}App" case "${{ matrix.app_type }}" in "Blank") - pynest generate application -n "$app_name" + poetry run pynest generate application -n "$app_name" ;; "SyncORM") - pynest generate application -n "$app_name" -db sqlite + poetry run pynest generate application -n "$app_name" -db sqlite ;; "AsyncORM") - pynest generate application -n "$app_name" -db sqlite --is-async + poetry run pynest generate application -n "$app_name" -db sqlite --is-async ;; "MongoDB") - pynest generate application -n "$app_name" -db mongodb + poetry run pynest generate application -n "$app_name" -db mongodb ;; "PostgresSync") - pynest generate application -n "$app_name" -db postgresql + poetry run pynest generate application -n "$app_name" -db postgresql ;; "PostgresAsync") - pynest generate application -n "$app_name" -db postgresql --is-async + poetry run pynest generate application -n "$app_name" -db postgresql --is-async ;; "MySQLSync") - pynest generate application -n "$app_name" -db mysql + poetry run pynest generate application -n "$app_name" -db mysql ;; "MySQLAsync") - pynest generate application -n "$app_name" -db mysql --is-async + poetry run pynest generate application -n "$app_name" -db mysql --is-async ;; esac cd "$app_name" - pynest generate resource -n user + poetry run pynest generate resource -n user - name: Verify Boilerplate run: | diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 5b6a465..ab7a9e6 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -38,15 +38,15 @@ jobs: fi if [ "${{ matrix.app_type }}" == "Blank" ]; then - pynest generate application -n "$app_name" + poetry run pynest generate application -n "$app_name" else - pynest generate application -n "$app_name" -db sqlite $is_async + poetry run pynest generate application -n "$app_name" -db sqlite $is_async poetry add aiosqlite fi cd "$app_name" - pynest generate resource -n user - uvicorn "src.app_module:http_server" --host "0.0.0.0" --port 8000 --reload & + poetry run pynest generate resource -n user + poetry run uvicorn "src.app_module:http_server" --host "0.0.0.0" --port 8000 --reload & - name: Wait for the server to start run: sleep 10 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2dfbbb7..89ce472 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,4 +24,4 @@ jobs: - name: Run tests run: | - pytest tests + poetry run pytest tests From 155189e4755426cc66e83b8ad5bde6fe0729c192 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 18:08:21 +0200 Subject: [PATCH 18/21] fix github actions --- nest/cli/templates/mysql_template.py | 2 +- nest/cli/templates/sqlite_template.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nest/cli/templates/mysql_template.py b/nest/cli/templates/mysql_template.py index 83a22f7..b40feca 100644 --- a/nest/cli/templates/mysql_template.py +++ b/nest/cli/templates/mysql_template.py @@ -44,7 +44,7 @@ def __init__(self, module_name: str): ) def config_file(self): - return """from nest.core.database.orm_provider import AsyncOrmProvider + return """from nest.database.orm_provider import AsyncOrmProvider import os from dotenv import load_dotenv diff --git a/nest/cli/templates/sqlite_template.py b/nest/cli/templates/sqlite_template.py index 6724479..449022c 100644 --- a/nest/cli/templates/sqlite_template.py +++ b/nest/cli/templates/sqlite_template.py @@ -45,7 +45,7 @@ def __init__(self, module_name: str): ) def config_file(self): - return """from nest.core.database.orm_provider import AsyncOrmProvider + return """from nest.database.orm_provider import AsyncOrmProvider import os from dotenv import load_dotenv From 108909345fd7ae62590187bb1eda3a0054ffde7f Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Thu, 31 Oct 2024 18:14:06 +0200 Subject: [PATCH 19/21] fix async orm template --- nest/cli/templates/orm_template.py | 46 +++++++++++++++++------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/nest/cli/templates/orm_template.py b/nest/cli/templates/orm_template.py index b7ed8e6..f7c0780 100644 --- a/nest/cli/templates/orm_template.py +++ b/nest/cli/templates/orm_template.py @@ -258,37 +258,43 @@ class {self.capitalized_module_name}(config.Base): """ def service_file(self): - return f"""from .{self.module_name}_model import {self.capitalized_module_name} -from .{self.module_name}_entity import {self.capitalized_module_name} as {self.capitalized_module_name}Entity -from nest.core.decorators.database import async_db_request_handler + return f"""from nest.database.utils import async_db_request_handler from nest.core import Injectable +from .{self.module_name}_model import {self.capitalized_module_name} +from .{self.module_name}_entity import {self.capitalized_module_name} as {self.capitalized_module_name}Entity +from src.config import config + from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -@Injectable +@Injectable() class {self.capitalized_module_name}Service: + def __init__(self): + self.session: AsyncSession = config.get_session + @async_db_request_handler - async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}, session: AsyncSession): - new_{self.module_name} = {self.capitalized_module_name}Entity( - **{self.module_name}.model_dump() - ) - session.add(new_{self.module_name}) - await session.commit() - return new_{self.module_name}.id + async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}): + async with self.session() as session: + new_{self.module_name} = {self.capitalized_module_name}Entity( + **{self.module_name}.model_dump() + ) + session.add(new_{self.module_name}) + await session.commit() + return new_{self.module_name}.id @async_db_request_handler - async def get_{self.module_name}(self, session: AsyncSession): - query = select({self.capitalized_module_name}Entity) - result = await session.execute(query) - return result.scalars().all() + async def get_{self.module_name}(self): + async with self.session() as session: + query = select({self.capitalized_module_name}Entity) + result = await session.execute(query) + return result.scalars().all() """ def controller_file(self): - return f"""from nest.core import Controller, Get, Post, Depends + return f"""from nest.web import Controller, Get, Post from sqlalchemy.ext.asyncio import AsyncSession -from src.config import config from .{self.module_name}_service import {self.capitalized_module_name}Service @@ -302,12 +308,12 @@ def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Ser self.{self.module_name}_service = {self.module_name}_service @Get("/") - async def get_{self.module_name}(self, session: AsyncSession = Depends(config.get_db)): + async def get_{self.module_name}(self): return await self.{self.module_name}_service.get_{self.module_name}(session) @Post("/") - async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}, session: AsyncSession = Depends(config.get_db)): - return await self.{self.module_name}_service.add_{self.module_name}({self.module_name}, session) + async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}): + return await self.{self.module_name}_service.add_{self.module_name}({self.module_name}) """ def settings_file(self): From 55328231359f414d6aec10e7a6683ce53b66b437 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Fri, 1 Nov 2024 11:15:27 +0200 Subject: [PATCH 20/21] make deploy docs work with poetry --- .github/workflows/deploy_docs.yaml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy_docs.yaml b/.github/workflows/deploy_docs.yaml index ffe9bdb..6b59796 100644 --- a/.github/workflows/deploy_docs.yaml +++ b/.github/workflows/deploy_docs.yaml @@ -2,13 +2,11 @@ name: Deploy Docs on: [workflow_call, workflow_dispatch] -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write -# Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true @@ -27,15 +25,15 @@ jobs: - name: Copy License File run: cp LICENSE docs/license.md - - name: Set up Python 3.10 + - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - - name: Install MKDocs + - name: Install dependencies run: | - pip install --upgrade pip - pip install mkdocs-material mkdocstrings-python + pip install poetry + poetry install --with docs - name: Build docs run: mkdocs build --clean @@ -50,4 +48,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 \ No newline at end of file + uses: actions/deploy-pages@v1 From 17cabb80f6a231e8ecea781e06b8a3477b6d89d9 Mon Sep 17 00:00:00 2001 From: "itay.dar" Date: Wed, 4 Dec 2024 12:13:12 +0200 Subject: [PATCH 21/21] fix docs --- README.md | 48 ++++---- docs/async_orm.md | 15 +-- docs/blank.md | 17 +-- docs/cli_app.md | 0 docs/controllers.md | 11 +- docs/dependency_injection.md | 2 +- docs/getting_started.md | 41 +++++-- docs/index.html | 5 +- docs/mongodb.md | 112 +++++++++--------- docs/providers.md | 2 +- docs/sync_orm.md | 13 +- examples/BlankApp/src/app_controller.py | 2 +- examples/BlankApp/src/app_module.py | 2 +- .../src/product/product_controller.py | 2 +- examples/BlankApp/src/user/user_controller.py | 2 +- examples/MongoApp/src/app_controller.py | 2 +- examples/MongoApp/src/app_module.py | 5 +- .../src/example/example_controller.py | 2 +- .../src/product/product_controller.py | 2 +- examples/MongoApp/src/user/user_controller.py | 2 +- examples/OrmAsyncApp/src/app_controller.py | 2 +- examples/OrmAsyncApp/src/app_module.py | 6 +- .../src/example/example_controller.py | 2 +- .../src/product/product_controller.py | 2 +- .../OrmAsyncApp/src/user/user_controller.py | 2 +- examples/OrmSyncApp/src/app_controller.py | 2 +- examples/OrmSyncApp/src/app_module.py | 5 +- .../src/example/example_controller.py | 2 +- .../src/product/product_controller.py | 2 +- .../OrmSyncApp/src/user/user_controller.py | 2 +- mkdocs.yml | 2 + pyproject.toml | 14 +++ 32 files changed, 190 insertions(+), 140 deletions(-) create mode 100644 docs/cli_app.md diff --git a/README.md b/README.md index 985ac6d..35324d1 100644 --- a/README.md +++ b/README.md @@ -23,20 +23,26 @@ PyNest is designed to help structure your APIs in an intuitive, easy to understand, and enjoyable way. -With PyNest, you can build scalable and maintainable APIs with ease. The framework supports dependency injection, type +`With PyNest, you can build scalable and maintainable APIs with ease. The framework supports dependency injection, type annotations, decorators, and code generation, making it easy to write clean and testable code. This framework is not a direct port of NestJS to Python but rather a re-imagining of the framework specifically for Python developers, including backend engineers and ML engineers. It aims to assist them in building better and faster APIs for their data applications. - +` ## Getting Started To get started with PyNest, you'll need to install it using pip: -```bash -pip install pynest-api -``` +=== "pip" + ```bash + pip install pynest-api + ``` + +=== "Poetry" + ```bash + poetry add pynest-api + ``` ### Start with cli @@ -47,13 +53,15 @@ pynest generate appplication -n my_app_name this command will create a new project with the following structure: ```text -├── app.py ├── main.py ├── requirements.txt ├── .gitignore ├── README.md ├── src │ ├── __init__.py +│ ├── app_module.py +│ ├── app_controller.py +│ ├── app_service.py ``` once you have created your app, get into the folder and run the following command: @@ -65,7 +73,7 @@ cd my_app_name run the server with the following command: ```bash -uvicorn "app:app" --host "0.0.0.0" --port "8000" --reload +uvicorn "src.app_module:http_server" --host "0.0.0.0" --port "8000" --reload ``` Now you can visit [OpenAPI](http://localhost:8000/docs) in your browser to see the default API documentation. @@ -75,20 +83,20 @@ Now you can visit [OpenAPI](http://localhost:8000/docs) in your browser to see t To add a new module to your application, you can use the pynest generate module command: ```bash -pynest generate resource -n users +pynest generate resource -n user ``` -This will create a new resource called ```users``` in your application with the following structure under the ```src``` +This will create a new resource called ```user``` in your application with the following structure under the ```src``` folder: ```text ├── users │ ├── __init__.py -│ ├── users_controller.py -│ ├── users_service.py -│ ├── users_model.py -│ ├── users_entity.py -│ ├── users_module.py +│ ├── user_controller.py +│ ├── user_service.py +│ ├── user_model.py +│ ├── user_entity.py +│ ├── user_module.py ``` The users module will immediately register itself with the application and will be available for use. @@ -113,6 +121,7 @@ and their descriptions: - `--app-name`/`-n`: The name of the nest app (required). - `--db-type`/`-db`: The type of the database (optional). You can specify PostgreSQL, MySQL, SQLite, or MongoDB. - `--is-async`: Whether the project should be asynchronous (optional, default is False). + - `--is-cli`: For creating CLI-based application (optional, default is False). ### `generate` command group @@ -133,7 +142,7 @@ and their descriptions: `pynest generate application -n my_app_name -db postgresql --is-async` * create new module - - `pynest generate resource -n users` + `pynest generate resource -n user` ## Key Features @@ -162,15 +171,6 @@ controllers, services, and providers with types to make your code more robust. PyNest includes a code generation tool that can create boilerplate code for modules, controllers, and other components. This saves you time and helps you focus on writing the code that matters. -## Future Plans - -- [ ] Create plugins Marketplace for modules where developers can share their modules and download modules created by - others. -- [ ] Implement IOC mechanism and introduce Module decorator -- [ ] Add support for new databases -- [ ] Create out-of-the-box authentication module that can be easily integrated into any application. -- [ ] Add support for other testing frameworks and create testing templates. -- [ ] Add support for other web frameworks (Litestar, blackship, etc.) - Same Architecture, different engine. ## Contributing diff --git a/docs/async_orm.md b/docs/async_orm.md index 8d706ae..10541a1 100644 --- a/docs/async_orm.md +++ b/docs/async_orm.md @@ -35,7 +35,7 @@ pip install asyncpg #### Create a new project ```bash -pynest create-nest-app -n my_app_name -db postgresql --is-async +pynest generate application -n my_app_name -db postgresql --is-async ``` this command will create a new project with the following structure: @@ -116,7 +116,7 @@ class AppService: `app_controller.py` ```python -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService @@ -143,7 +143,8 @@ from .example.example_module import ExampleModule from .app_controller import AppController from .app_service import AppService -from nest.core import Module, PyNestFactory +from nest.core import Module +from nest.web import PyNestWebFactory @Module( @@ -155,7 +156,7 @@ class AppModule: pass -app = PyNestFactory.create(AppModule, description="This is my FastAPI app drive by Async ORM Engine", title="My App", +app = PyNestWebFactory.create(AppModule, description="This is my FastAPI app drive by Async ORM Engine", title="My App", version="1.0.0", debug=True) http_server = app.get_server() @@ -171,7 +172,7 @@ decorator. The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. -`PyNestFactory.create()` is a command to create an instance of the application. +`PyNestWebFactory.create()` is a command to create an instance of the application. The AppModule is passed as an argument, which acts as the root module of the application. Additional metadata like description, title, version, and debug flag are also provided @@ -293,7 +294,7 @@ Here we have also two ways of creating the controller. 1. In that way, the controller's functions are getting the async session from the config ```python -from nest.core import Controller, Get, Post, Depends +from nest.web import Controller, Get, Post, Depends from .examples_service import ExamplesService from .examples_model import Examples @@ -321,7 +322,7 @@ class ExamplesController: in his constructor. ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post from .examples_service import ExamplesService from .examples_model import Examples diff --git a/docs/blank.md b/docs/blank.md index c6ea9aa..9ce4284 100644 --- a/docs/blank.md +++ b/docs/blank.md @@ -1,4 +1,4 @@ -# Blank Application with PyNest +___# Blank Application with PyNest ## Introduction @@ -24,8 +24,8 @@ pip install pynest-api #### Create a new project ```bash -pynest create-nest-app -n my_app_name -``` +pynest generate application -n my_app_name +```___ this command will create a new project with the following structure: @@ -77,7 +77,7 @@ class AppService: `app_controller.py` ```python -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService @@ -96,7 +96,8 @@ class AppController: `app_module.py` ```python -from nest.core import PyNestFactory, Module +from nest.core import Module +from nest.web import PyNestWebFactory from src.example.example_module import ExampleModule from .app_controller import AppController from .app_service import AppService @@ -112,7 +113,7 @@ class AppModule: pass -app = PyNestFactory.create(AppModule, description="This is my FastAPI app", title="My App", version="1.0.0", debug=True) +app = PyNestWebFactory.create(AppModule, description="This is my FastAPI app", title="My App", version="1.0.0", debug=True) http_server: FastAPI = app.get_server() ``` @@ -120,7 +121,7 @@ http_server: FastAPI = app.get_server() `@Module(...)`: This is a decorator that defines a module. In PyNest, a module is a class annotated with a `@Module()` decorator. The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. -`PyNestFactory.create()` is a command to create an instance of the application. +`PyNestWebFactory.create()` is a command to create an instance of the application. The AppModule is passed as an argument, which acts as the root module of the application. Additional metadata like description, title, version, and debug flag are also provided @@ -171,7 +172,7 @@ logic. `example_controller.py` ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post from .examples_service import ExamplesService from .examples_model import Examples diff --git a/docs/cli_app.md b/docs/cli_app.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/controllers.md b/docs/controllers.md index d148eb9..2780442 100644 --- a/docs/controllers.md +++ b/docs/controllers.md @@ -9,7 +9,8 @@ This decorator is required to define a controller and specify an optional route which helps group related routes and minimize repetitive code. ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post +from src.book.book_service import BookService @Controller('/books') class BookController: @@ -29,7 +30,7 @@ class BookController: Let's take this step by step: ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post ``` PyNest exposes an api for creating Controllers, a class that is responsible for the module routing and requests handling. @@ -76,7 +77,7 @@ Each controller can have multiple routes, and different routes can perform diffe ### Example ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post @Controller('/book') class BookController: @@ -106,7 +107,7 @@ Since pynest is an abstraction of fastapi, we can use those methods in the same ### Example ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post from .book_service import BookService from typing import List @@ -170,7 +171,7 @@ POST, PUT, DELETE, etc. ### Full CRUD Example ```python -from nest.core import Controller, Get, Post, Put, Delete +from nest.web import Controller, Get, Post, Put, Delete from .book_service import BookService from .book_models import Book diff --git a/docs/dependency_injection.md b/docs/dependency_injection.md index 4014799..2e35d7b 100644 --- a/docs/dependency_injection.md +++ b/docs/dependency_injection.md @@ -43,7 +43,7 @@ class LoggerModule: ### Injecting the Provider into a Controller ```python # app_controller.py -from nest.core import Controller, Get +from nest.web import Controller, Get from src.providers.logger.logger_service import LoggerService @Controller('/') diff --git a/docs/getting_started.md b/docs/getting_started.md index 83fbce6..edde9d8 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -4,11 +4,17 @@ This guide will help you get started with setting up a new PyNest project, creat ## Installation -To install PyNest, ensure you have Python 3.9+ installed. Then, install PyNest using pip: +To install PyNest, ensure you have Python 3.10+ installed. Then, install PyNest using your preferred package manager: -```bash -pip install pynest-api -``` +=== "pip" + ```bash + pip install pynest-api + ``` + +=== "Poetry" + ```bash + poetry add pynest-api + ``` ## Creating a New Project 📂 @@ -19,6 +25,20 @@ mkdir my_pynest_project cd my_pynest_project ``` +You can simply use the pynest-cli to create the project structure: + +=== "pip" + ```bash + pynest generate application -n . + ``` + +=== "Poetry" + ```bash + poetry run pynest generate application -n . + ``` + +Or you can create the file structure manually. + ## Creating the Application Structure 🏗️ We'll create the basic structure of a PyNest application. Here's what it will look like: @@ -42,7 +62,8 @@ The app_module.py file is where we define our main application module. Create th ```python # src/app_module.py -from nest.core import Module, PyNestFactory +from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService @@ -53,12 +74,13 @@ from .app_service import AppService class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my PyNest app", title="My App", version="1.0.0", - debug=True, + # here you can add more of the fastapi kwargs as describe here - + # https://fastapi.tiangolo.com/reference/fastapi/#fastapi.FastAPI ) http_server = app.get_server() @@ -90,7 +112,7 @@ The app_controller.py file will handle the routing and responses. Create the fil ```python # src/app_controller.py -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService @Controller("/") @@ -111,10 +133,9 @@ The main.py file will run our PyNest application. Create the file and add the fo # main.py import uvicorn -from src.app_module import http_server if __name__ == "__main__": - uvicorn.run(http_server, host="0.0.0.0", port=8000, reload=True) + uvicorn.run("src.app_module:http_server", host="0.0.0.0", port=8000, reload=True) ``` ### File Structure 🗂️ diff --git a/docs/index.html b/docs/index.html index 3c0694c..4250eae 100644 --- a/docs/index.html +++ b/docs/index.html @@ -449,7 +449,8 @@

Get Started with PyNest


-from nest.core import PyNestFactory, Module, Controller, Get, Injectable
+from nest.core import Module, Injectable
+from nest.web import Controller, Get
 
 @Injectable()
 class AppService:
@@ -473,7 +474,7 @@ 

Get Started with PyNest

class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my PyNest app.", title="PyNest Application", diff --git a/docs/mongodb.md b/docs/mongodb.md index 52694ae..f292afb 100644 --- a/docs/mongodb.md +++ b/docs/mongodb.md @@ -37,22 +37,27 @@ read more about motor [here](https://motor.readthedocs.io/en/stable/) Ensure you have the latest version of PyNest and SQLAlchemy 2.0 installed. You can install them using pip: -```bash -pip install pynest-api -``` +=== "pip" + ```bash + pip install pynest-api[mongo, fastapi] + ``` + +=== "Poetry" + ```bash + poetry add pynest-api[mongo, fastapi] + ``` ## Start with cli #### Create a new project ```bash -pynest create-nest-app -n my_app_name -db mongodb +pynest generate application -n my_app_name -db mongodb ``` this command will create a new project with the following structure: ```text -├── app.py ├── main.py |── requirements.txt |── README.md @@ -67,19 +72,19 @@ this command will create a new project with the following structure: once you have created your app, you can create a new module: ```bash -pynest g module -n examples +pynest g module -n example ``` -This will create a new module called examples in your application with the following structure under the src folder: +This will create a new module called `example` in your application with the following structure under the src folder: ```text -├── examples +├── example │ ├── __init__.py -│ ├── examples_controller.py -│ ├── examples_service.py -│ ├── examples_model.py -│ ├── examples_entity.py -│ ├── examples_module.py +│ ├── example_controller.py +│ ├── example_service.py +│ ├── example_model.py +│ ├── example_entity.py +│ ├── example_module.py ``` Let's go over the boilerplate code that support the mongo integration: @@ -88,25 +93,25 @@ Let's go over the boilerplate code that support the mongo integration: ```python from nest.database.odm_provider import OdmProvider -from src.examples.examples_entity import Examples +from src.example.example_entity import Example import os from dotenv import load_dotenv load_dotenv() config = OdmProvider( - config_params={{ + config_params={ "db_name": os.getenv("DB_NAME", "default_nest_db"), "host": os.getenv("DB_HOST", "localhost"), "user": os.getenv("DB_USER", "root"), "password": os.getenv("DB_PASSWORD", "root"), "port": os.getenv("DB_PORT", 27017), - }}, - document_models=[Examples] + }, + document_models=[Example] ) ``` -Note: you can add any parameters that needed in order to configure the database connection. +Note: you can add any parameters that needed in order to configure the database connection inside the config params. `app_service.py` @@ -127,9 +132,9 @@ class AppService: `app_controller.py` ```python -from nest.core import Controller, Get +from nest.web import Controller, Get -from .app_service import AppService +from src.app_service import AppService @Controller("/") @@ -148,17 +153,18 @@ Now we need to declare the App object and register the module in `app_module.py` ```python -from .config import config +from src.config import config -from nest.core import Module, PyNestFactory -from src.examples.examples_module import ExamplesModule +from nest.core import Module +from nest.web import PyNestWebFactory +from src.example.example_module import ExampleModule -from .app_controller import AppController -from .app_service import AppService +from src.app_controller import AppController +from src.app_service import AppService @Module( - imports=[ExamplesModule], + imports=[ExampleModule], controllers=[AppController], providers=[AppService], ) @@ -166,7 +172,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my FastAPI app drive by MongoDB Engine", title="My App", @@ -187,11 +193,11 @@ decorator. The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. -`PyNestFactory.create()` is a command to create an instance of the application. +`PyNestWebFactory.create()` is a command to create an instance of the application. The AppModule is passed as an argument, which acts as the root module of the application. Additional metadata like description, title, version, and debug flag are also provided -`http_server: FastAPI = app.get_server()`: Retrieves the HTTP server instance from the application. +`http_server = app.get_server()`: Retrieves the HTTP server instance from the application. ### Creating Entity @@ -220,7 +226,7 @@ Define your model using Pydantic. For example, the Examples model: from pydantic import BaseModel -class Examples(BaseModel): +class Example(BaseModel): name: str ``` @@ -229,8 +235,8 @@ class Examples(BaseModel): Implement services to handle business logic. ```python -from .examples_model import Examples -from .examples_entity import Examples as ExamplesEntity +from .example_model import Examples +from .example_entity import Example as ExamplesEntity from nest.database import db_request_handler from nest.core import Injectable @@ -239,15 +245,15 @@ from nest.core import Injectable class ExamplesService: @db_request_handler - async def add_examples(self, examples: Examples): - new_examples = ExamplesEntity( - **examples.dict() + async def add_example(self, example: Examples): + new_example = ExamplesEntity( + **example.dict() ) - await new_examples.save() - return new_examples.id + await new_example.save() + return new_example.id @db_request_handler - async def get_examples(self): + async def get_example(self): return await ExamplesEntity.find_all().to_list() ``` @@ -255,25 +261,25 @@ create a controller to handle the requests and responses. The controller should logic. ```python -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post -from .examples_service import ExamplesService -from .examples_model import Examples +from .example_service import ExampleService +from .example_model import Example -@Controller("examples") -class ExamplesController: +@Controller("example") +class ExampleController: - def __init__(self, service: ExamplesService): + def __init__(self, service: ExampleService): self.service = service @Get("/") - async def get_examples(self): - return await self.service.get_examples() + async def get_example(self): + return await self.service.get_example() @Post("/") - async def add_examples(self, examples: Examples): - return await self.service.add_examples(examples) + async def add_example(self, example: Example): + return await self.service.add_example(example) ``` ### Creating Module @@ -282,15 +288,15 @@ Create a module to register the controller and service. ```python from nest.core import Module -from .examples_controller import ExamplesController -from .examples_service import ExamplesService +from .example_controller import ExampleController +from .example_service import ExampleService @Module( - controllers=[ExamplesController], - providers=[ExamplesService] + controllers=[ExampleController], + providers=[ExampleService] ) -class ExamplesModule: +class ExampleModule: pass ``` diff --git a/docs/providers.md b/docs/providers.md index a6fb106..d5825dd 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -82,7 +82,7 @@ In this Service, the `LoggerService` is injected into the `S3Service` via the co `s3_controller.py` ```python -from nest.core import Controller, Post +from nest.web import Controller, Post from .s3_service import S3Service @Controller('/s3') diff --git a/docs/sync_orm.md b/docs/sync_orm.md index 887c028..55690b3 100644 --- a/docs/sync_orm.md +++ b/docs/sync_orm.md @@ -25,7 +25,7 @@ pip install pynest-api #### Create a new project ```bash -pynest create-nest-app -n my_app_name -db postgresql +pynest generate application -n my_app_name -db postgresql ``` this command will create a new project with the following structure: @@ -103,7 +103,7 @@ class AppService: `app_controller.py` ```python -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService @@ -123,7 +123,8 @@ class AppController: ```python from src.config import config -from nest.core import PyNestFactory, Module +from nest.core import Module +from nest.web import PyNestWebFactory from src.example.example_module import ExampleModule from fastapi import FastAPI @@ -135,7 +136,7 @@ class AppModule: pass -app = PyNestFactory.create(AppModule, description="This is my FastAPI app drive by ORM Engine", title="My App", +app = PyNestWebFactory.create(AppModule, description="This is my FastAPI app drive by ORM Engine", title="My App", version="1.0.0", debug=True) http_server: FastAPI = app.get_server() @@ -151,7 +152,7 @@ decorator. The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services. -`PyNestFactory.create()` is a command to create an instance of the application. +`PyNestWebFactory.create()` is a command to create an instance of the application. The AppModule is passed as an argument, which acts as the root module of the application. Additional metadata like description, title, version, and debug flag are also provided @@ -218,7 +219,7 @@ business logic. `examples_controller.py` ```python -from nest.core import Controller, Get, Post, Depends +from nest.web import Controller, Get, Post, Depends from .examples_service import ExamplesService from .examples_model import Examples diff --git a/examples/BlankApp/src/app_controller.py b/examples/BlankApp/src/app_controller.py index 286e872..8ee9b09 100644 --- a/examples/BlankApp/src/app_controller.py +++ b/examples/BlankApp/src/app_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService diff --git a/examples/BlankApp/src/app_module.py b/examples/BlankApp/src/app_module.py index 05bc4b1..a437e1a 100644 --- a/examples/BlankApp/src/app_module.py +++ b/examples/BlankApp/src/app_module.py @@ -19,7 +19,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my FastAPI app.", title="My App", diff --git a/examples/BlankApp/src/product/product_controller.py b/examples/BlankApp/src/product/product_controller.py index 063260f..076c97f 100644 --- a/examples/BlankApp/src/product/product_controller.py +++ b/examples/BlankApp/src/product/product_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post from .product_model import Product from .product_service import ProductService diff --git a/examples/BlankApp/src/user/user_controller.py b/examples/BlankApp/src/user/user_controller.py index b59d9f6..5920b97 100644 --- a/examples/BlankApp/src/user/user_controller.py +++ b/examples/BlankApp/src/user/user_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Get, Post from .user_model import User from .user_service import UserService diff --git a/examples/MongoApp/src/app_controller.py b/examples/MongoApp/src/app_controller.py index 286e872..8ee9b09 100644 --- a/examples/MongoApp/src/app_controller.py +++ b/examples/MongoApp/src/app_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService diff --git a/examples/MongoApp/src/app_module.py b/examples/MongoApp/src/app_module.py index f186cce..a16fecc 100644 --- a/examples/MongoApp/src/app_module.py +++ b/examples/MongoApp/src/app_module.py @@ -1,4 +1,5 @@ -from nest.core import Module, PyNestFactory +from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService @@ -17,7 +18,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my FastAPI app drive by MongoDB Engine", title="My App", diff --git a/examples/MongoApp/src/example/example_controller.py b/examples/MongoApp/src/example/example_controller.py index dd5dcea..35036f7 100644 --- a/examples/MongoApp/src/example/example_controller.py +++ b/examples/MongoApp/src/example/example_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get, Post +from nest.web import Controller, Get, Post from .example_model import Example from .example_service import ExampleService diff --git a/examples/MongoApp/src/product/product_controller.py b/examples/MongoApp/src/product/product_controller.py index 0966eb6..85d31a8 100644 --- a/examples/MongoApp/src/product/product_controller.py +++ b/examples/MongoApp/src/product/product_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from .product_model import Product from .product_service import ProductService diff --git a/examples/MongoApp/src/user/user_controller.py b/examples/MongoApp/src/user/user_controller.py index d873f5b..b7b4752 100644 --- a/examples/MongoApp/src/user/user_controller.py +++ b/examples/MongoApp/src/user/user_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from .user_model import User from .user_service import UserService diff --git a/examples/OrmAsyncApp/src/app_controller.py b/examples/OrmAsyncApp/src/app_controller.py index 9c64b53..0ea5b09 100644 --- a/examples/OrmAsyncApp/src/app_controller.py +++ b/examples/OrmAsyncApp/src/app_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService diff --git a/examples/OrmAsyncApp/src/app_module.py b/examples/OrmAsyncApp/src/app_module.py index 1a25138..82d05d8 100644 --- a/examples/OrmAsyncApp/src/app_module.py +++ b/examples/OrmAsyncApp/src/app_module.py @@ -1,5 +1,5 @@ -from nest.core import Module, PyNestFactory - +from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService from .config import config @@ -17,7 +17,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my FastAPI app drive by Async ORM Engine", title="My App", diff --git a/examples/OrmAsyncApp/src/example/example_controller.py b/examples/OrmAsyncApp/src/example/example_controller.py index 4f5f6fa..4caf94d 100644 --- a/examples/OrmAsyncApp/src/example/example_controller.py +++ b/examples/OrmAsyncApp/src/example/example_controller.py @@ -1,6 +1,6 @@ from sqlalchemy.ext.asyncio import AsyncSession -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from ..config import config from .example_model import Example diff --git a/examples/OrmAsyncApp/src/product/product_controller.py b/examples/OrmAsyncApp/src/product/product_controller.py index 432fe7e..c967536 100644 --- a/examples/OrmAsyncApp/src/product/product_controller.py +++ b/examples/OrmAsyncApp/src/product/product_controller.py @@ -1,6 +1,6 @@ from sqlalchemy.ext.asyncio import AsyncSession -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from ..config import config from .product_model import Product diff --git a/examples/OrmAsyncApp/src/user/user_controller.py b/examples/OrmAsyncApp/src/user/user_controller.py index e02408e..b1d344f 100644 --- a/examples/OrmAsyncApp/src/user/user_controller.py +++ b/examples/OrmAsyncApp/src/user/user_controller.py @@ -1,6 +1,6 @@ from sqlalchemy.ext.asyncio import AsyncSession -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from ..config import config from .user_model import User diff --git a/examples/OrmSyncApp/src/app_controller.py b/examples/OrmSyncApp/src/app_controller.py index 286e872..8ee9b09 100644 --- a/examples/OrmSyncApp/src/app_controller.py +++ b/examples/OrmSyncApp/src/app_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Get +from nest.web import Controller, Get from .app_service import AppService diff --git a/examples/OrmSyncApp/src/app_module.py b/examples/OrmSyncApp/src/app_module.py index 9ae3d40..2177333 100644 --- a/examples/OrmSyncApp/src/app_module.py +++ b/examples/OrmSyncApp/src/app_module.py @@ -1,4 +1,5 @@ -from nest.core import Module, PyNestFactory +from nest.core import Module +from nest.web import PyNestWebFactory from .app_controller import AppController from .app_service import AppService @@ -17,7 +18,7 @@ class AppModule: pass -app = PyNestFactory.create( +app = PyNestWebFactory.create( AppModule, description="This is my FastAPI app drive by Sync ORM Engine", title="My App", diff --git a/examples/OrmSyncApp/src/example/example_controller.py b/examples/OrmSyncApp/src/example/example_controller.py index 5fe6378..14dace7 100644 --- a/examples/OrmSyncApp/src/example/example_controller.py +++ b/examples/OrmSyncApp/src/example/example_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from .example_model import Example from .example_service import ExampleService diff --git a/examples/OrmSyncApp/src/product/product_controller.py b/examples/OrmSyncApp/src/product/product_controller.py index 44addac..b241306 100644 --- a/examples/OrmSyncApp/src/product/product_controller.py +++ b/examples/OrmSyncApp/src/product/product_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from .product_model import Product from .product_service import ProductService diff --git a/examples/OrmSyncApp/src/user/user_controller.py b/examples/OrmSyncApp/src/user/user_controller.py index 43f008f..d287d38 100644 --- a/examples/OrmSyncApp/src/user/user_controller.py +++ b/examples/OrmSyncApp/src/user/user_controller.py @@ -1,4 +1,4 @@ -from nest.core import Controller, Depends, Get, Post +from nest.web import Controller, Depends, Get, Post from .user_model import User from .user_service import UserService diff --git a/mkdocs.yml b/mkdocs.yml index f955e53..3e4fde2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,8 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true diff --git a/pyproject.toml b/pyproject.toml index 5cf7dd1..ac5c5f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,18 @@ documentation = "https://pythonnest.github.io/PyNest/" packages = [ { include = "nest" } ] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "License :: OSI Approved :: MIT License", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", +] + [tool.poetry.dependencies] @@ -97,3 +109,5 @@ Documentation = "https://pythonnest.github.io/PyNest/" [tool.poetry.scripts] pynest = "nest.cli.cli:nest_cli" + +