diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aef7f70..e93da95 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,6 +23,6 @@ jobs: - name: Build distribution run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.8.14 + uses: pypa/gh-action-pypi-publish@v1.11.0 with: password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04168ed..2ac916d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,4 +22,4 @@ jobs: - name: Test run: make test-cov - name: Coverage - uses: codecov/codecov-action@v4.4.1 + uses: codecov/codecov-action@v4.6.0 diff --git a/docs/migrations/env.md b/docs/migrations/env.md index c437eb7..1641876 100644 --- a/docs/migrations/env.md +++ b/docs/migrations/env.md @@ -8,8 +8,8 @@ but it also introduces a certain level of complexity. from logging.config import fileConfig from alembic import context -from ellar.app import current_injector -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.threading import run_as_sync from ellar_sql.migrations import SingleDatabaseAlembicEnvMigration from ellar_sql.services import EllarSQLService @@ -28,7 +28,7 @@ fileConfig(config.config_file_name) # type:ignore[arg-type] # my_important_option = config.get_main_option("my_important_option") # ... etc. -@run_as_async +@run_as_sync async def main() -> None: db_service: EllarSQLService = current_injector.get(EllarSQLService) @@ -91,8 +91,8 @@ from logging.config import fileConfig from alembic import context from ellar_sql.migrations import AlembicEnvMigrationBase from ellar_sql.model.database_binds import get_metadata -from ellar.app import current_injector -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.threading import run_as_sync from ellar_sql.services import EllarSQLService # This is the Alembic Config object, which provides @@ -156,7 +156,7 @@ class MyCustomMigrationEnv(AlembicEnvMigrationBase): context.run_migrations() -@run_as_async +@run_as_sync async def main() -> None: db_service: EllarSQLService = current_injector.get(EllarSQLService) diff --git a/docs/models/file-fields.md b/docs/models/file-fields.md index 16c1eea..1dc416e 100644 --- a/docs/models/file-fields.md +++ b/docs/models/file-fields.md @@ -247,4 +247,4 @@ class ArticlesController(ecm.ControllerBase): - [Validators](https://jowilf.github.io/sqlalchemy-file/tutorial/using-files-in-models/#validators) - [Processors](https://jowilf.github.io/sqlalchemy-file/tutorial/using-files-in-models/#processors) -For a more comprehensive hands-on experience, check out the [file-field-example](https://github.com/python-ellar/ellar-sql/tree/main/samples/file-field-example) project. +For a more comprehensive hands-on experience, check out the [file-field-example](https://github.com/python-ellar/ellar-sql/tree/master/samples/file-field-example) project. diff --git a/docs/models/index.md b/docs/models/index.md index 9ef1f6a..2e6f409 100644 --- a/docs/models/index.md +++ b/docs/models/index.md @@ -176,7 +176,7 @@ class User(model.Model): We have created a `User` model but the data does not exist. Let's fix that ```python -from ellar.app import current_injector +from ellar.core import current_injector from ellar_sql import EllarSQLService db_service = current_injector.get(EllarSQLService) @@ -285,7 +285,7 @@ Although with `EllarSQLService` you can get the `engine` and `session`. It's the ```python import sqlalchemy as sa import sqlalchemy.orm as sa_orm -from ellar.app import current_injector +from ellar.core import current_injector from ellar_sql import EllarSQLService db_service = current_injector.get(EllarSQLService) @@ -303,7 +303,7 @@ assert isinstance(db_service.session_factory(), sa_orm.Session) ```python import sqlalchemy as sa import sqlalchemy.orm as sa_orm -from ellar.app import current_injector +from ellar.core import current_injector # get engine from DI default_engine = current_injector.get(sa.Engine) @@ -317,7 +317,7 @@ assert isinstance(session, sa_orm.Session) For Async Database options ```python from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine -from ellar.app import current_injector +from ellar.core import current_injector # get engine from DI default_engine = current_injector.get(AsyncEngine) diff --git a/docs/multiple/index.md b/docs/multiple/index.md index 3235689..af90099 100644 --- a/docs/multiple/index.md +++ b/docs/multiple/index.md @@ -75,7 +75,7 @@ It also requires the `database` argument to target a specific database. ```python # Create tables for all binds -from ellar.app import current_injector +from ellar.core import current_injector from ellar_sql import EllarSQLService db_service = current_injector.get(EllarSQLService) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 5699dcb..44c78d3 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -21,6 +21,20 @@ nav.md-nav--primary ul.md-nav__list li.md-nav__item--active.md-nav__item--nested max-width: 80%; } +/* Styles for devices with a screen width of 768px or less (e.g., tablets and mobile phones) */ +@media only screen and (max-width: 768px) { + .md-grid { + max-width: 90%; + } +} + +/* Styles for devices with a screen width of 480px or less (e.g., mobile phones) */ +@media only screen and (max-width: 480px) { + .md-grid { + max-width: 95%; + } +} + :root { --md-primary-fg-color: #00b4cc; --md-primary-fg-color--light: #2568a7; diff --git a/docs/testing/index.md b/docs/testing/index.md index ae470db..112e3a1 100644 --- a/docs/testing/index.md +++ b/docs/testing/index.md @@ -216,47 +216,52 @@ Now, let's create a factory for our user model in `tests/factories.py`: ```python title="tests/factories.py" import factory -from ellar_sql.factory import EllarSQLFactory, SESSION_PERSISTENCE_FLUSH from db_learning.models import User -from . import common +from ellar.app import current_injector +from sqlalchemy.orm import Session + +from ellar_sql.factory import SESSION_PERSISTENCE_FLUSH, EllarSQLFactory + + +def _get_session(): + session = current_injector.get(Session) + return session class UserFactory(EllarSQLFactory): class Meta: model = User sqlalchemy_session_persistence = SESSION_PERSISTENCE_FLUSH - sqlalchemy_session_factory = lambda: common.Session() + sqlalchemy_session_factory = _get_session username = factory.Faker('username') email = factory.Faker('email') ``` -The `UserFactory` depends on a database session. Since the pytest fixture we created applies to it, -we also need a session factory in `tests/common.py`: - -```python title="tests/common.py" -from sqlalchemy import orm - -Session = orm.scoped_session(orm.sessionmaker()) -``` +The `UserFactory` depends on a database Session as you see from `_get_session()` function. +We need to ensure that test fixture provides `ApplicationContext` for `current_injector` to work. -Additionally, we require a fixture responsible for configuring the Factory session in `tests/conftest.py`: +So in `tests/conftest.py`, we make `tm` test fixture to run application context: ```python title="tests/conftest.py" import os + import pytest -import sqlalchemy as sa +from db_learning.root_module import ApplicationModule from ellar.common.constants import ELLAR_CONFIG_MODULE from ellar.testing import Test +from ellar.threading.sync_worker import execute_async_context_manager + from ellar_sql import EllarSQLService -from db_learning.root_module import ApplicationModule -from . import common os.environ.setdefault(ELLAR_CONFIG_MODULE, "db_learning.config:TestConfig") @pytest.fixture(scope='session') def tm(): test_module = Test.create_test_module(modules=[ApplicationModule]) - yield test_module + app = test_module.create_application() + + with execute_async_context_manager(app.application_context()): + yield test_module # Fixture for creating a database session for testing @pytest.fixture(scope='session') @@ -270,29 +275,8 @@ def db(tm): # Dropping all tables after the tests db_service.drop_all() - -# Fixture for creating a database session for testing -@pytest.fixture(scope='session') -def db_session(db, tm): - db_service = tm.get(EllarSQLService) - - yield db_service.session_factory() - - # Removing the session factory - db_service.session_factory.remove() - -@pytest.fixture -def factory_session(db, tm): - engine = tm.get(sa.Engine) - common.Session.configure(bind=engine) - yield - common.Session.remove() ``` -In the `factory_session` fixture, we retrieve the `Engine` registered in the DI container by **EllarSQLModule**. -Using this engine, we configure the common `Session`. It's important to note that if you are using an -async database driver, **EllarSQLModule** will register `AsyncEngine`. - With this setup, we can rewrite our `test_username_must_be_unique` test using `UserFactory` and `factory_session`: ```python title="tests/test_user_model.py" diff --git a/ellar_sql/__init__.py b/ellar_sql/__init__.py index 82da1b8..03e3aba 100644 --- a/ellar_sql/__init__.py +++ b/ellar_sql/__init__.py @@ -1,6 +1,6 @@ """EllarSQL Module adds support for SQLAlchemy and Alembic package to your Ellar application""" -__version__ = "0.1.0" +__version__ = "0.1.2" from .model.database_binds import get_all_metadata, get_metadata from .module import EllarSQLModule diff --git a/ellar_sql/cli/commands.py b/ellar_sql/cli/commands.py index d374806..1218e02 100644 --- a/ellar_sql/cli/commands.py +++ b/ellar_sql/cli/commands.py @@ -1,5 +1,5 @@ import ellar_cli.click as click -from ellar.app import current_injector +from ellar.core import current_injector from ellar_sql.services import EllarSQLService diff --git a/ellar_sql/factory/base.py b/ellar_sql/factory/base.py index 86734ef..b1f143d 100644 --- a/ellar_sql/factory/base.py +++ b/ellar_sql/factory/base.py @@ -2,7 +2,7 @@ import sqlalchemy as sa import sqlalchemy.orm as sa_orm -from ellar.threading import run_as_async +from ellar.threading import run_as_sync from factory.alchemy import ( SESSION_PERSISTENCE_COMMIT, SESSION_PERSISTENCE_FLUSH, @@ -36,7 +36,7 @@ class Meta: abstract = True @classmethod - @run_as_async + @run_as_sync async def _session_execute( cls, session_func: t.Callable, *args: t.Any, **kwargs: t.Any ) -> t.Union[sa.Result, sa.CursorResult, t.Any]: diff --git a/ellar_sql/model/base.py b/ellar_sql/model/base.py index 9812c9c..8e9c0b0 100644 --- a/ellar_sql/model/base.py +++ b/ellar_sql/model/base.py @@ -3,7 +3,7 @@ import sqlalchemy as sa import sqlalchemy.orm as sa_orm -from ellar.app import current_injector +from ellar.core import current_injector from sqlalchemy.ext.asyncio import AsyncSession from ellar_sql.constant import DATABASE_BIND_KEY, DATABASE_KEY, DEFAULT_KEY diff --git a/ellar_sql/model/typeDecorator/file/exceptions.py b/ellar_sql/model/typeDecorator/file/exceptions.py index 334d38a..67808b7 100644 --- a/ellar_sql/model/typeDecorator/file/exceptions.py +++ b/ellar_sql/model/typeDecorator/file/exceptions.py @@ -6,19 +6,14 @@ from sqlalchemy_file.exceptions import ValidationError from ellar.common import IExecutionContext -from ellar.common.exceptions import CallableExceptionHandler +from ellar.core.exceptions import as_exception_handler -def _exception_handlers(ctx: IExecutionContext, exc: ValidationError): +# Register to application config.EXCEPTION_HANDLERS to add exception handler for sqlalchemy-file +@as_exception_handler(ValidationError) +async def FileExceptionHandler(ctx: IExecutionContext, exc: ValidationError): app_config = ctx.get_app().config return app_config.DEFAULT_JSON_CLASS( {"message": exc.msg, "key": exc.key}, status_code=400, ) - - -# Register to application config.EXCEPTION_HANDLERS to add exception handler for sqlalchemy-file -FileExceptionHandler = CallableExceptionHandler( - exc_class_or_status_code=ValidationError, - callable_exception_handler=_exception_handlers, -) diff --git a/ellar_sql/model/typeDecorator/file/file.py b/ellar_sql/model/typeDecorator/file/file.py index 2197455..f1d56ef 100644 --- a/ellar_sql/model/typeDecorator/file/file.py +++ b/ellar_sql/model/typeDecorator/file/file.py @@ -3,8 +3,8 @@ import warnings from datetime import datetime -from ellar.app import current_injector from ellar.common.compatible import AttributeDictAccessMixin +from ellar.core import current_injector from ellar_storage import StorageService, StoredFile from sqlalchemy_file.file import File as BaseFile from starlette.datastructures import UploadFile diff --git a/ellar_sql/model/typeDecorator/file/file_tracker.py b/ellar_sql/model/typeDecorator/file/file_tracker.py index 4ab59f7..21f4326 100644 --- a/ellar_sql/model/typeDecorator/file/file_tracker.py +++ b/ellar_sql/model/typeDecorator/file/file_tracker.py @@ -1,6 +1,6 @@ import typing as t -from ellar.app import current_injector +from ellar.core import current_injector from ellar_storage import StorageService from sqlalchemy import event, orm from sqlalchemy_file.types import FileFieldSessionTracker diff --git a/ellar_sql/module.py b/ellar_sql/module.py index 04361d6..f0167fd 100644 --- a/ellar_sql/module.py +++ b/ellar_sql/module.py @@ -2,10 +2,11 @@ import typing as t import sqlalchemy as sa -from ellar.common import IExecutionContext, IModuleSetup, Module, middleware +from ellar.common import IHostContext, IModuleSetup, Module from ellar.core import Config, DynamicModule, ModuleBase, ModuleSetup +from ellar.core.middleware import as_middleware +from ellar.core.modules import ModuleRefBase from ellar.di import ProviderConfig, request_or_transient_scope -from ellar.events import app_context_teardown from ellar.utils.importer import get_main_directory_by_stack from sqlalchemy.ext.asyncio import ( AsyncEngine, @@ -26,32 +27,49 @@ def _raise_exception(): return _raise_exception -@Module(commands=[DBCommands]) -class EllarSQLModule(ModuleBase, IModuleSetup): - @middleware() - async def session_middleware( - cls, context: IExecutionContext, call_next: t.Callable[..., t.Coroutine] - ): - connection = context.switch_to_http_connection().get_client() - - db_session = connection.service_provider.get(EllarSQLService) - session = db_session.session_factory() +@as_middleware +async def session_middleware( + context: IHostContext, call_next: t.Callable[..., t.Coroutine] +): + connection = context.switch_to_http_connection().get_client() - connection.state.session = session + db_service = context.get_service_provider().get(EllarSQLService) + session = db_service.session_factory() - try: - await call_next() - except Exception as ex: - res = session.rollback() - if isinstance(res, t.Coroutine): - await res - raise ex + connection.state.session = session - @classmethod - async def _on_application_tear_down(cls, db_service: EllarSQLService) -> None: - res = db_service.session_factory.remove() + try: + await call_next() + except Exception as ex: + res = session.rollback() if isinstance(res, t.Coroutine): await res + raise ex + + res = db_service.session_factory.remove() + if isinstance(res, t.Coroutine): + await res + + +@Module( + commands=[DBCommands], + exports=[ + EllarSQLService, + Session, + AsyncSession, + AsyncEngine, + sa.Engine, + MigrationOption, + ], + providers=[EllarSQLService], + name="EllarSQL", +) +class EllarSQLModule(ModuleBase, IModuleSetup): + @classmethod + def post_build(cls, module_ref: "ModuleRefBase") -> None: + module_ref.config.MIDDLEWARE = list(module_ref.config.MIDDLEWARE) + [ + session_middleware + ] @classmethod def setup( @@ -155,8 +173,10 @@ def __setup_module(cls, sql_alchemy_config: SQLAlchemyConfig) -> DynamicModule: ) providers.append(ProviderConfig(EllarSQLService, use_value=db_service)) - app_context_teardown.connect( - functools.partial(cls._on_application_tear_down, db_service=db_service) + providers.append( + ProviderConfig( + MigrationOption, use_value=lambda: db_service.migration_options + ) ) return DynamicModule( @@ -182,7 +202,7 @@ def register_setup(cls, **override_config: t.Any) -> ModuleSetup: @staticmethod def __register_setup_factory( - module: t.Type["EllarSQLModule"], + module_ref: ModuleRefBase, config: Config, root_path: str, override_config: t.Dict[str, t.Any], @@ -201,6 +221,7 @@ def __register_setup_factory( stack_level=0, from_dir=defined_config["root_path"], ) + module = t.cast(t.Type["EllarSQLModule"], module_ref.module) return module.__setup_module(schema) raise RuntimeError("Could not find `ELLAR_SQL` in application config.") diff --git a/ellar_sql/pagination/base.py b/ellar_sql/pagination/base.py index 75ef012..e66fc86 100644 --- a/ellar_sql/pagination/base.py +++ b/ellar_sql/pagination/base.py @@ -5,8 +5,8 @@ import ellar.common as ecm import sqlalchemy as sa import sqlalchemy.orm as sa_orm -from ellar.app import current_injector -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.threading import run_as_sync from sqlalchemy.ext.asyncio import AsyncSession from ellar_sql.model.base import ModelBase @@ -277,7 +277,7 @@ def __init__( if self._created_session: self._close_session() # session usage is done but only if Paginator created the session - @run_as_async + @run_as_sync async def _close_session(self) -> None: res = self._session.close() if isinstance(res, t.Coroutine): @@ -298,7 +298,7 @@ def _query_items_sync(self) -> t.List[t.Any]: select = self._select.limit(self.per_page).offset(self._query_offset) return list(self._session.execute(select).unique().scalars()) - @run_as_async + @run_as_sync async def _query_items_async(self) -> t.List[t.Any]: session = t.cast(AsyncSession, self._session) @@ -320,7 +320,7 @@ def _query_count_sync(self) -> int: ).scalar() return out # type:ignore[return-value] - @run_as_async + @run_as_sync async def _query_count_async(self) -> int: session = t.cast(AsyncSession, self._session) diff --git a/ellar_sql/query/utils.py b/ellar_sql/query/utils.py index b5e3ad6..7df73e0 100644 --- a/ellar_sql/query/utils.py +++ b/ellar_sql/query/utils.py @@ -3,7 +3,7 @@ import ellar.common as ecm import sqlalchemy as sa import sqlalchemy.exc as sa_exc -from ellar.app import current_injector +from ellar.core import current_injector from ellar_sql.services import EllarSQLService diff --git a/ellar_sql/templates/multiple/env.py b/ellar_sql/templates/multiple/env.py index cf84f50..245b75f 100644 --- a/ellar_sql/templates/multiple/env.py +++ b/ellar_sql/templates/multiple/env.py @@ -1,8 +1,8 @@ from logging.config import fileConfig from alembic import context -from ellar.app import current_injector -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.threading import run_as_sync from ellar_sql.migrations import MultipleDatabaseAlembicEnvMigration from ellar_sql.services import EllarSQLService @@ -22,7 +22,7 @@ # ... etc. -@run_as_async +@run_as_sync async def main() -> None: db_service: EllarSQLService = current_injector.get(EllarSQLService) diff --git a/ellar_sql/templates/multiple/script.py.mako b/ellar_sql/templates/multiple/script.py.mako index e15eb70..b669c35 100644 --- a/ellar_sql/templates/multiple/script.py.mako +++ b/ellar_sql/templates/multiple/script.py.mako @@ -19,7 +19,7 @@ branch_labels = ${repr(branch_labels)} depends_on = ${repr(depends_on)} <%! - from ellar.app import current_injector + from ellar.core import current_injector from ellar_sql.services import EllarSQLService db_service = current_injector.get(EllarSQLService) diff --git a/ellar_sql/templates/single/env.py b/ellar_sql/templates/single/env.py index 9d270b8..9164960 100644 --- a/ellar_sql/templates/single/env.py +++ b/ellar_sql/templates/single/env.py @@ -1,8 +1,8 @@ from logging.config import fileConfig from alembic import context -from ellar.app import current_injector -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.threading import run_as_sync from ellar_sql.migrations import SingleDatabaseAlembicEnvMigration from ellar_sql.services import EllarSQLService @@ -22,7 +22,7 @@ # ... etc. -@run_as_async +@run_as_sync async def main() -> None: db_service: EllarSQLService = current_injector.get(EllarSQLService) diff --git a/pyproject.toml b/pyproject.toml index 3c17603..0d6ce94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,10 +32,10 @@ classifiers = [ ] dependencies = [ - "ellar-cli >= 0.3.7", + "ellar-cli >= 0.4.3", "sqlalchemy >= 2.0.23", "alembic >= 1.10.0", - "ellar-storage >= 0.1.4", + "ellar-storage >= 0.1.7", "sqlalchemy-file >= 0.6.0", ] diff --git a/requirements-docs.txt b/requirements-docs.txt index 5790e4a..8a450ea 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -5,4 +5,4 @@ mkdocs-git-revision-date-localized-plugin mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 mkdocs-material >=7.1.9,<10.0.0 mkdocs-minify-plugin -mkdocstrings[python] >=0.19.0, <0.26.0 +mkdocstrings[python] >=0.19.0, <0.27.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index 6ed5859..3e51350 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -4,9 +4,9 @@ autoflake ellar-cli >= 0.3.7 factory-boy >= 3.3.0 httpx -mypy == 1.10.0 -Pillow >=9.4.0, <10.1.0 +Pillow >=10.4.0, <11.1.0 +mypy == 1.13.0 pytest >= 7.1.3,< 9.0.0 pytest-asyncio -pytest-cov >= 2.12.0,< 6.0.0 -ruff ==0.4.7 +pytest-cov >= 2.12.0,< 7.0.0 +ruff ==0.8.4 diff --git a/requirements.txt b/requirements.txt index f939878..925858e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-docs.txt -r requirements-tests.txt -pre-commit >=2.17.0,<4.0.0 +pre-commit >=2.17.0,<5.0.0 diff --git a/samples/db-learning/db_learning/command.py b/samples/db-learning/db_learning/command.py index adc7ae0..ee43d0b 100644 --- a/samples/db-learning/db_learning/command.py +++ b/samples/db-learning/db_learning/command.py @@ -6,7 +6,7 @@ @click.command("seed") -@click.with_app_context +@click.with_injector_context def seed_user(): db_service = current_injector.get(EllarSQLService) session = db_service.session_factory() diff --git a/samples/db-learning/db_learning/config.py b/samples/db-learning/db_learning/config.py index f453d54..669e22b 100644 --- a/samples/db-learning/db_learning/config.py +++ b/samples/db-learning/db_learning/config.py @@ -13,6 +13,7 @@ from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning from ellar.pydantic import ENCODERS_BY_TYPE as encoders_by_type from starlette.middleware import Middleware +from starlette.requests import Request class BaseConfig(ConfigDefaultTypesMixin): @@ -29,6 +30,15 @@ class BaseConfig(ConfigDefaultTypesMixin): # https://jinja.palletsprojects.com/en/3.0.x/api/#high-level-api JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {} + # Injects context to jinja templating context values + TEMPLATES_CONTEXT_PROCESSORS: t.List[ + t.Union[str, t.Callable[[t.Union[Request]], t.Dict[str, t.Any]]] + ] = [ + "ellar.core.templating.context_processors:request_context", + "ellar.core.templating.context_processors:user", + "ellar.core.templating.context_processors:request_state", + ] + # Application route versioning scheme VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning() @@ -51,14 +61,24 @@ class BaseConfig(ConfigDefaultTypesMixin): ALLOWED_HOSTS: t.List[str] = ["*"] # Application middlewares - MIDDLEWARE: t.Sequence[Middleware] = [] + MIDDLEWARE: t.Union[str, Middleware] = [ + "ellar.core.middleware.trusted_host:trusted_host_middleware", + "ellar.core.middleware.cors:cors_middleware", + "ellar.core.middleware.errors:server_error_middleware", + "ellar.core.middleware.versioning:versioning_middleware", + "ellar.auth.middleware.session:session_middleware", + "ellar.auth.middleware.auth:identity_middleware", + "ellar.core.middleware.exceptions:exception_middleware", + ] # A dictionary mapping either integer status codes, # or exception class types onto callables which handle the exceptions. # Exception handler callables should be of the form # `handler(context:IExecutionContext, exc: Exception) -> response` # and may be either standard functions, or async functions. - EXCEPTION_HANDLERS: t.List[IExceptionHandler] = [] + EXCEPTION_HANDLERS: t.Union[str, IExceptionHandler] = [ + "ellar.core.exceptions:error_404_handler" + ] # Object Serializer custom encoders SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]] = ( diff --git a/samples/db-learning/db_learning/sqlite/test.db b/samples/db-learning/db_learning/sqlite/test.db new file mode 100644 index 0000000..bb4a56a Binary files /dev/null and b/samples/db-learning/db_learning/sqlite/test.db differ diff --git a/samples/db-learning/tests/common.py b/samples/db-learning/tests/common.py deleted file mode 100644 index e4a5010..0000000 --- a/samples/db-learning/tests/common.py +++ /dev/null @@ -1,3 +0,0 @@ -from sqlalchemy import orm - -Session = orm.scoped_session(orm.sessionmaker()) diff --git a/samples/db-learning/tests/conftest.py b/samples/db-learning/tests/conftest.py index 1794c62..6d49b3f 100644 --- a/samples/db-learning/tests/conftest.py +++ b/samples/db-learning/tests/conftest.py @@ -1,22 +1,23 @@ import os import pytest -import sqlalchemy as sa from db_learning.root_module import ApplicationModule from ellar.common.constants import ELLAR_CONFIG_MODULE from ellar.testing import Test +from ellar.threading.sync_worker import execute_async_context_manager from ellar_sql import EllarSQLService -from . import common - os.environ.setdefault(ELLAR_CONFIG_MODULE, "db_learning.config:TestConfig") @pytest.fixture(scope="session") def tm(): test_module = Test.create_test_module(modules=[ApplicationModule]) - yield test_module + app = test_module.create_application() + + with execute_async_context_manager(app.application_context()): + yield test_module @pytest.fixture(scope="session") @@ -26,23 +27,5 @@ def db(tm): yield - db_service.drop_all() - - -@pytest.fixture(scope="session") -def db_session(db, tm): - db_service = tm.get(EllarSQLService) - - yield db_service.session_factory() - db_service.session_factory.remove() - - -@pytest.fixture -def factory_session(db, tm): - engine = tm.get(sa.Engine) - common.Session.configure(bind=engine) - - yield - - common.Session.remove() + db_service.drop_all() diff --git a/samples/db-learning/tests/factories.py b/samples/db-learning/tests/factories.py index 28b0324..d8fe7de 100644 --- a/samples/db-learning/tests/factories.py +++ b/samples/db-learning/tests/factories.py @@ -1,16 +1,21 @@ import factory from db_learning.models import User +from ellar.app import current_injector +from sqlalchemy.orm import Session from ellar_sql.factory import SESSION_PERSISTENCE_FLUSH, EllarSQLFactory -from . import common + +def _get_session(): + session = current_injector.get(Session) + return session class UserFactory(EllarSQLFactory): class Meta: model = User sqlalchemy_session_persistence = SESSION_PERSISTENCE_FLUSH - sqlalchemy_session_factory = common.Session + sqlalchemy_session_factory = _get_session username = factory.Faker("user_name") email = factory.Faker("email") diff --git a/samples/db-learning/tests/test_user_model.py b/samples/db-learning/tests/test_user_model.py index 84d6254..f1a21b5 100644 --- a/samples/db-learning/tests/test_user_model.py +++ b/samples/db-learning/tests/test_user_model.py @@ -20,7 +20,7 @@ # db_session.commit() -def test_username_must_be_unique(factory_session): +def test_username_must_be_unique(db): user1 = UserFactory() with pytest.raises(sa_exc.IntegrityError): UserFactory(username=user1.username) diff --git a/samples/file-field-example/file_field_example/config.py b/samples/file-field-example/file_field_example/config.py index 19bac0d..bb63484 100644 --- a/samples/file-field-example/file_field_example/config.py +++ b/samples/file-field-example/file_field_example/config.py @@ -13,8 +13,7 @@ from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning from ellar.pydantic import ENCODERS_BY_TYPE as encoders_by_type from starlette.middleware import Middleware - -from ellar_sql.model.typeDecorator.file import FileExceptionHandler +from starlette.requests import Request class BaseConfig(ConfigDefaultTypesMixin): @@ -31,6 +30,15 @@ class BaseConfig(ConfigDefaultTypesMixin): # https://jinja.palletsprojects.com/en/3.0.x/api/#high-level-api JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {} + # Injects context to jinja templating context values + TEMPLATES_CONTEXT_PROCESSORS: t.List[ + t.Union[str, t.Callable[[t.Union[Request]], t.Dict[str, t.Any]]] + ] = [ + "ellar.core.templating.context_processors:request_context", + "ellar.core.templating.context_processors:user", + "ellar.core.templating.context_processors:request_state", + ] + # Application route versioning scheme VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning() @@ -53,14 +61,24 @@ class BaseConfig(ConfigDefaultTypesMixin): ALLOWED_HOSTS: t.List[str] = ["*"] # Application middlewares - MIDDLEWARE: t.Sequence[Middleware] = [] + MIDDLEWARE: t.Union[str, Middleware] = [ + "ellar.core.middleware.trusted_host:trusted_host_middleware", + "ellar.core.middleware.cors:cors_middleware", + "ellar.core.middleware.errors:server_error_middleware", + "ellar.core.middleware.versioning:versioning_middleware", + "ellar.auth.middleware.session:session_middleware", + "ellar.auth.middleware.auth:identity_middleware", + "ellar.core.middleware.exceptions:exception_middleware", + ] # A dictionary mapping either integer status codes, # or exception class types onto callables which handle the exceptions. # Exception handler callables should be of the form # `handler(context:IExecutionContext, exc: Exception) -> response` # and may be either standard functions, or async functions. - EXCEPTION_HANDLERS: t.List[IExceptionHandler] = [FileExceptionHandler] + EXCEPTION_HANDLERS: t.Union[str, IExceptionHandler] = [ + "ellar.core.exceptions:error_404_handler" + ] # Object Serializer custom encoders SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]] = ( diff --git a/samples/file-field-example/file_field_example/controllers/schema.py b/samples/file-field-example/file_field_example/controllers/schema.py index 8125e9f..3005e58 100644 --- a/samples/file-field-example/file_field_example/controllers/schema.py +++ b/samples/file-field-example/file_field_example/controllers/schema.py @@ -1,8 +1,7 @@ import typing as t import ellar.common as ecm -from ellar.app import current_injector -from ellar.core import Request +from ellar.core import Request, current_injector from ellar.pydantic import model_validator from pydantic import HttpUrl diff --git a/samples/file-field-example/file_field_example/root_module.py b/samples/file-field-example/file_field_example/root_module.py index 078be66..71e2318 100644 --- a/samples/file-field-example/file_field_example/root_module.py +++ b/samples/file-field-example/file_field_example/root_module.py @@ -4,11 +4,7 @@ from ellar.app import App from ellar.common import ( IApplicationStartup, - IExecutionContext, - JSONResponse, Module, - Response, - exception_handler, ) from ellar.core import ModuleBase from ellar.samples.modules import HomeModule @@ -53,7 +49,3 @@ class ApplicationModule(ModuleBase, IApplicationStartup): async def on_startup(self, app: App) -> None: db_service = app.injector.get(EllarSQLService) db_service.create_all() - - @exception_handler(404) - def exception_404_handler(cls, ctx: IExecutionContext, exc: Exception) -> Response: - return JSONResponse({"detail": "Resource not found."}, status_code=404) diff --git a/samples/single-db/db/tests/__init__.py b/samples/single-db/db/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/samples/single-db/db/tests/test_controllers.py b/samples/single-db/db/tests/test_controllers.py deleted file mode 100644 index e69de29..0000000 diff --git a/samples/single-db/db/tests/test_routers.py b/samples/single-db/db/tests/test_routers.py deleted file mode 100644 index e69de29..0000000 diff --git a/samples/single-db/db/tests/test_services.py b/samples/single-db/db/tests/test_services.py deleted file mode 100644 index e69de29..0000000 diff --git a/samples/single-db/single_db/config.py b/samples/single-db/single_db/config.py index c9805cd..a12a882 100644 --- a/samples/single-db/single_db/config.py +++ b/samples/single-db/single_db/config.py @@ -13,6 +13,7 @@ from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning from ellar.pydantic import ENCODERS_BY_TYPE as encoders_by_type from starlette.middleware import Middleware +from starlette.requests import Request class BaseConfig(ConfigDefaultTypesMixin): @@ -29,6 +30,15 @@ class BaseConfig(ConfigDefaultTypesMixin): # https://jinja.palletsprojects.com/en/3.0.x/api/#high-level-api JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {} + # Injects context to jinja templating context values + TEMPLATES_CONTEXT_PROCESSORS: t.List[ + t.Union[str, t.Callable[[t.Union[Request]], t.Dict[str, t.Any]]] + ] = [ + "ellar.core.templating.context_processors:request_context", + "ellar.core.templating.context_processors:user", + "ellar.core.templating.context_processors:request_state", + ] + # Application route versioning scheme VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning() @@ -51,14 +61,24 @@ class BaseConfig(ConfigDefaultTypesMixin): ALLOWED_HOSTS: t.List[str] = ["*"] # Application middlewares - MIDDLEWARE: t.Sequence[Middleware] = [] + MIDDLEWARE: t.Union[str, Middleware] = [ + "ellar.core.middleware.trusted_host:trusted_host_middleware", + "ellar.core.middleware.cors:cors_middleware", + "ellar.core.middleware.errors:server_error_middleware", + "ellar.core.middleware.versioning:versioning_middleware", + "ellar.auth.middleware.session:session_middleware", + "ellar.auth.middleware.auth:identity_middleware", + "ellar.core.middleware.exceptions:exception_middleware", + ] # A dictionary mapping either integer status codes, # or exception class types onto callables which handle the exceptions. # Exception handler callables should be of the form # `handler(context:IExecutionContext, exc: Exception) -> response` # and may be either standard functions, or async functions. - EXCEPTION_HANDLERS: t.List[IExceptionHandler] = [] + EXCEPTION_HANDLERS: t.Union[str, IExceptionHandler] = [ + "ellar.core.exceptions:error_404_handler" + ] # Object Serializer custom encoders SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]] = ( diff --git a/samples/single-db/single_db/root_module.py b/samples/single-db/single_db/root_module.py index 99841bd..60baf0f 100644 --- a/samples/single-db/single_db/root_module.py +++ b/samples/single-db/single_db/root_module.py @@ -1,9 +1,5 @@ from ellar.common import ( - IExecutionContext, - JSONResponse, Module, - Response, - exception_handler, ) from ellar.core import LazyModuleImport as lazyLoad from ellar.core import ModuleBase @@ -12,6 +8,4 @@ @Module(modules=[HomeModule, lazyLoad("db.module:DbModule")]) class ApplicationModule(ModuleBase): - @exception_handler(404) - def exception_404_handler(cls, ctx: IExecutionContext, exc: Exception) -> Response: - return JSONResponse({"detail": "Resource not found."}, status_code=404) + pass diff --git a/tests/conftest.py b/tests/conftest.py index 5ce252d..3694ce2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import typing as t import pytest +from ellar.core import injector_context from ellar.testing import Test from ellar_storage import Provider, StorageModule, get_driver @@ -152,7 +153,7 @@ def _setup(**kwargs): async def app_ctx(app_setup): app = app_setup() - async with app.application_context(): + async with injector_context(app.injector): yield app @@ -160,5 +161,5 @@ async def app_ctx(app_setup): async def app_ctx_async(app_setup_async): app = app_setup_async() - async with app.application_context(): + async with injector_context(app.injector): yield app diff --git a/tests/test_migrations/samples/custom_directory.py b/tests/test_migrations/samples/custom_directory.py index 44173c8..c009806 100644 --- a/tests/test_migrations/samples/custom_directory.py +++ b/tests/test_migrations/samples/custom_directory.py @@ -1,7 +1,8 @@ #!/bin/env python import click -from ellar.app import AppFactory, current_injector +from ellar.app import AppFactory +from ellar.core import current_injector from ellar.utils.importer import get_main_directory_by_stack from ellar_cli.main import create_ellar_cli from models import User diff --git a/tests/test_migrations/samples/default.py b/tests/test_migrations/samples/default.py index a95f544..3da17e3 100644 --- a/tests/test_migrations/samples/default.py +++ b/tests/test_migrations/samples/default.py @@ -1,6 +1,7 @@ #!/bin/env python import click -from ellar.app import AppFactory, current_injector +from ellar.app import AppFactory +from ellar.core import current_injector from ellar.utils.importer import get_main_directory_by_stack from ellar_cli.main import create_ellar_cli from models import User diff --git a/tests/test_migrations/samples/default_async.py b/tests/test_migrations/samples/default_async.py index 015c2c6..b1ffe92 100644 --- a/tests/test_migrations/samples/default_async.py +++ b/tests/test_migrations/samples/default_async.py @@ -1,6 +1,7 @@ #!/bin/env python import ellar_cli.click as click -from ellar.app import AppFactory, current_injector +from ellar.app import AppFactory +from ellar.core import current_injector from ellar.utils.importer import get_main_directory_by_stack from ellar_cli.main import create_ellar_cli from models import User @@ -29,7 +30,7 @@ def bootstrap(): @cli.command() -@click.run_as_async +@click.run_as_sync async def add_user(): session = current_injector.get(AsyncSession) user = User(name="default App Ellar") diff --git a/tests/test_migrations/samples/multiple_database.py b/tests/test_migrations/samples/multiple_database.py index b6005ba..5806c14 100644 --- a/tests/test_migrations/samples/multiple_database.py +++ b/tests/test_migrations/samples/multiple_database.py @@ -1,6 +1,7 @@ #!/bin/env python import click -from ellar.app import AppFactory, current_injector +from ellar.app import AppFactory +from ellar.core import current_injector from ellar.utils.importer import get_main_directory_by_stack from ellar_cli.main import create_ellar_cli from models import Group, User @@ -31,7 +32,7 @@ def bootstrap(): @cli.command() -def add_user(): +async def add_user(): session = current_injector.get(model.Session) user = User(name="Multiple Database App Ellar") group = Group(name="group") diff --git a/tests/test_migrations/samples/multiple_database_async.py b/tests/test_migrations/samples/multiple_database_async.py index 57f1e4a..b2b2cff 100644 --- a/tests/test_migrations/samples/multiple_database_async.py +++ b/tests/test_migrations/samples/multiple_database_async.py @@ -1,6 +1,7 @@ #!/bin/env python import ellar_cli.click as click -from ellar.app import AppFactory, current_injector +from ellar.app import AppFactory +from ellar.core import current_injector from ellar.utils.importer import get_main_directory_by_stack from ellar_cli.main import create_ellar_cli from models import Group, User @@ -32,7 +33,7 @@ def bootstrap(): @cli.command() -@click.run_as_async +@click.run_as_sync async def add_user(): session = current_injector.get(AsyncSession) user = User(name="Multiple Database App Ellar") diff --git a/tests/test_pagination/seed.py b/tests/test_pagination/seed.py index f182549..6841a6f 100644 --- a/tests/test_pagination/seed.py +++ b/tests/test_pagination/seed.py @@ -1,7 +1,7 @@ import typing as t -from ellar.app import App -from ellar.threading import run_as_async +from ellar.core import current_injector +from ellar.events import ensure_build_context from ellar_sql import EllarSQLService, model @@ -14,20 +14,24 @@ class User(model.Model): return User -@run_as_async -async def seed_100_users(app: App): +def seed_100_users(): user_model = create_model() - db_service = app.injector.get(EllarSQLService) - session = db_service.session_factory() + @ensure_build_context(app_ready=True) + async def _on_context(): + db_service = current_injector.get(EllarSQLService) - db_service.create_all() + session = db_service.session_factory() - for i in range(100): - session.add(user_model(name=f"User Number {i+1}")) + db_service.create_all() - res = session.commit() - if isinstance(res, t.Coroutine): - await res + for i in range(100): + session.add(user_model(name=f"User Number {i + 1}")) + + res = session.commit() + if isinstance(res, t.Coroutine): + await res + + _on_context() return user_model diff --git a/tests/test_pagination/test_pagination_view.py b/tests/test_pagination/test_pagination_view.py index 836a5fb..cafbf86 100644 --- a/tests/test_pagination/test_pagination_view.py +++ b/tests/test_pagination/test_pagination_view.py @@ -51,10 +51,14 @@ def html_pagination(): [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_1(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) + user_model = seed_100_users() + + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[_get_route_test_route(user_model, pagination_class, **kw)], + ) - app.router.append(_get_route_test_route(user_model, pagination_class, **kw)) client = TestClient(app) res = client.get("/list") @@ -72,12 +76,15 @@ def test_paginate_template_case_1(ignore_base, app_setup, pagination_class, kw): [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_2(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, case_2=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, case_2=True, **kw) + ], ) + client = TestClient(app) res = client.get("/list") @@ -95,12 +102,15 @@ def test_paginate_template_case_2(ignore_base, app_setup, pagination_class, kw): [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_3(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, case_3=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, case_3=True, **kw) + ], ) + client = TestClient(app) res = client.get("/list") @@ -118,12 +128,15 @@ def test_paginate_template_case_3(ignore_base, app_setup, pagination_class, kw): [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_invalid(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, invalid=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, invalid=True, **kw) + ], ) + client = TestClient(app, raise_server_exceptions=False) res = client.get("/list") @@ -131,15 +144,14 @@ def test_paginate_template_case_invalid(ignore_base, app_setup, pagination_class def test_api_paginate_case_1(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate(item_schema=UserSerializer, per_page=5) def paginated_user(): return model.select(user_model) - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list") @@ -160,15 +172,14 @@ def paginated_user(): def test_api_paginate_case_2(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate(item_schema=UserSerializer, per_page=10) def paginated_user(): return user_model - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list?page=10") @@ -185,15 +196,14 @@ def paginated_user(): def test_api_paginate_case_3(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate(model=user_model, item_schema=UserSerializer, per_page=5) def paginated_user(): pass - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list") @@ -212,8 +222,7 @@ def paginated_user(): def test_api_paginate_with_limit_offset_case_1(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate( @@ -225,7 +234,7 @@ def test_api_paginate_with_limit_offset_case_1(ignore_base, app_setup): def paginated_user(): return user_model - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list") @@ -254,8 +263,7 @@ def paginated_user(): def test_api_paginate_with_limit_offset_case_2(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate( @@ -267,7 +275,7 @@ def test_api_paginate_with_limit_offset_case_2(ignore_base, app_setup): def paginated_user(): return model.select(user_model) - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list?limit=5&offset=2") @@ -278,8 +286,7 @@ def paginated_user(): def test_api_paginate_with_limit_offset_case_3(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate( @@ -292,7 +299,7 @@ def test_api_paginate_with_limit_offset_case_3(ignore_base, app_setup): def paginated_user(): pass - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list?limit=5&offset=2") diff --git a/tests/test_pagination/test_pagination_view_async.py b/tests/test_pagination/test_pagination_view_async.py index ab1ebbb..e3b03df 100644 --- a/tests/test_pagination/test_pagination_view_async.py +++ b/tests/test_pagination/test_pagination_view_async.py @@ -51,10 +51,12 @@ async def html_pagination(): [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_1_async(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append(_get_route_test_route(user_model, pagination_class, **kw)) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[_get_route_test_route(user_model, pagination_class, **kw)], + ) client = TestClient(app) res = client.get("/list") @@ -72,12 +74,15 @@ def test_paginate_template_case_1_async(ignore_base, app_setup, pagination_class [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_2_async(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, case_2=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, case_2=True, **kw) + ], ) + client = TestClient(app) res = client.get("/list") @@ -95,12 +100,15 @@ def test_paginate_template_case_2_async(ignore_base, app_setup, pagination_class [(LimitOffsetPagination, {"limit": 5}), (PageNumberPagination, {"per_page": 5})], ) def test_paginate_template_case_3_async(ignore_base, app_setup, pagination_class, kw): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, case_3=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, case_3=True, **kw) + ], ) + client = TestClient(app) res = client.get("/list") @@ -120,12 +128,15 @@ def test_paginate_template_case_3_async(ignore_base, app_setup, pagination_class def test_paginate_template_case_invalid_async( ignore_base, app_setup, pagination_class, kw ): - app = app_setup(base_directory=base, template_folder="templates") - user_model = seed_100_users(app) - - app.router.append( - _get_route_test_route(user_model, pagination_class, invalid=True, **kw) + user_model = seed_100_users() + app = app_setup( + base_directory=base, + template_folder="templates", + routers=[ + _get_route_test_route(user_model, pagination_class, invalid=True, **kw) + ], ) + client = TestClient(app, raise_server_exceptions=False) res = client.get("/list") @@ -133,15 +144,14 @@ def test_paginate_template_case_invalid_async( def test_api_paginate_case_1_async(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate(item_schema=UserSerializer, per_page=5) async def paginated_user(): return model.select(user_model) - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list") @@ -162,8 +172,7 @@ async def paginated_user(): def test_api_paginate_with_limit_offset_case_1_async(ignore_base, app_setup): - app = app_setup() - user_model = seed_100_users(app) + user_model = seed_100_users() @ecm.get("/list") @paginate( @@ -175,7 +184,7 @@ def test_api_paginate_with_limit_offset_case_1_async(ignore_base, app_setup): async def paginated_user(): return user_model - app.router.append(paginated_user) + app = app_setup(routers=[paginated_user]) client = TestClient(app) res = client.get("/list") diff --git a/tests/test_pagination/test_paginator.py b/tests/test_pagination/test_paginator.py index f37a651..a4a8134 100644 --- a/tests/test_pagination/test_paginator.py +++ b/tests/test_pagination/test_paginator.py @@ -8,7 +8,7 @@ async def test_user_model_paginator(ignore_base, app_ctx, anyio_backend): - user_model = seed_100_users(app_ctx) + user_model = seed_100_users() page1 = Paginator(model=user_model, per_page=25, page=1, count=True) assert page1.page == 1 @@ -20,7 +20,7 @@ async def test_user_model_paginator(ignore_base, app_ctx, anyio_backend): async def test_user_model_paginator_async(ignore_base, app_ctx_async, anyio_backend): - user_model = seed_100_users(app_ctx_async) + user_model = seed_100_users() page2 = Paginator(model=user_model, per_page=25, page=2, count=True) assert page2.page == 2 @@ -35,7 +35,7 @@ async def test_user_model_paginator_async(ignore_base, app_ctx_async, anyio_back async def test_paginate_qs(ignore_base, app_ctx, anyio_backend): - user_model = seed_100_users(app_ctx) + user_model = seed_100_users() p = Paginator(model=user_model, page=2, per_page=10) assert p.page == 2 @@ -43,14 +43,14 @@ async def test_paginate_qs(ignore_base, app_ctx, anyio_backend): async def test_paginate_max(ignore_base, app_ctx, anyio_backend): - user_model = seed_100_users(app_ctx) + user_model = seed_100_users() p = Paginator(model=user_model, per_page=100, max_per_page=50) assert p.per_page == 50 async def test_next_page_size(ignore_base, app_ctx, anyio_backend): - user_model = seed_100_users(app_ctx) + user_model = seed_100_users() p = Paginator(model=user_model, per_page=25, max_per_page=50) assert p.page == 1 @@ -62,7 +62,7 @@ async def test_next_page_size(ignore_base, app_ctx, anyio_backend): async def test_no_count(ignore_base, app_ctx, anyio_backend): - user_model = seed_100_users(app_ctx) + user_model = seed_100_users() p = Paginator(model=user_model, count=False) assert p.total is None diff --git a/tests/test_session.py b/tests/test_session.py index 27b0ca8..082686c 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,18 +1,20 @@ +from ellar.core import injector_context + from ellar_sql import EllarSQLService, model from ellar_sql.schemas import ModelBaseConfig async def test_scope(anyio_backend, ignore_base, app_setup) -> None: app = app_setup() - async with app.application_context(): + async with app.request_context({}): first = app.injector.get(model.Session) second = app.injector.get(model.Session) assert first is second assert isinstance(first, model.Session) - async with app.application_context(): - third = app.injector.get(model.Session) - assert first is not third + # async with app.request_context({}): + # third = app.injector.get(model.Session) + # assert first is not third async def test_custom_scope(ignore_base, app_setup, anyio_backend): @@ -25,7 +27,7 @@ def scope() -> int: app = app_setup(sql_module={"session_options": {"scopefunc": scope}}) - async with app.application_context(): + async with injector_context(app.injector): first = app.injector.get(model.Session) second = app.injector.get(model.Session) assert first is not second # a new scope is generated on each call diff --git a/tests/test_type_decorators/test_files/test_file_upload.py b/tests/test_type_decorators/test_files/test_file_upload.py index 26a8986..662d11c 100644 --- a/tests/test_type_decorators/test_files/test_file_upload.py +++ b/tests/test_type_decorators/test_files/test_file_upload.py @@ -3,6 +3,7 @@ import pytest from ellar.common.datastructures import ContentFile +from ellar.core import injector_context from ellar_storage import StorageService from libcloud.storage.types import ObjectDoesNotExistError @@ -63,7 +64,7 @@ async def init_app(self, app_setup): db_service.create_all("default") session = db_service.session_factory() - async with app.application_context(): + async with injector_context(app.injector): yield app, db_service, session db_service.drop_all("default") diff --git a/tests/test_type_decorators/test_files/test_image_field.py b/tests/test_type_decorators/test_files/test_image_field.py index 03d0330..4b3260d 100644 --- a/tests/test_type_decorators/test_files/test_image_field.py +++ b/tests/test_type_decorators/test_files/test_image_field.py @@ -3,6 +3,7 @@ from contextlib import asynccontextmanager import pytest +from ellar.core import injector_context from ellar_sql import EllarSQLService, model from ellar_sql.model.typeDecorator import ImageField @@ -65,7 +66,7 @@ async def init_app(self, app_setup): db_service.create_all("default") session = db_service.session_factory() - async with app.application_context(): + async with injector_context(app.injector): yield app, db_service, session db_service.drop_all("default") diff --git a/tests/test_type_decorators/test_files/test_multiple_field.py b/tests/test_type_decorators/test_files/test_multiple_field.py index 106ccb8..3910477 100644 --- a/tests/test_type_decorators/test_files/test_multiple_field.py +++ b/tests/test_type_decorators/test_files/test_multiple_field.py @@ -2,6 +2,7 @@ from contextlib import asynccontextmanager import pytest +from ellar.core import injector_context from ellar_storage import StorageService from libcloud.storage.types import ObjectDoesNotExistError @@ -44,7 +45,7 @@ async def init_app(self, app_setup): db_service.create_all("default") session = db_service.session_factory() - async with app.application_context(): + async with injector_context(app.injector): yield app, db_service, session db_service.drop_all("default")
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: