diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ca605d..82445ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-toml @@ -12,6 +12,7 @@ repos: args: - --unsafe - id: end-of-file-fixer + - id: debug-statements - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v2.37.3 @@ -21,29 +22,15 @@ repos: - --py3-plus - --keep-runtime-typing - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.254 + rev: v0.3.0 hooks: - id: ruff args: ["--fix", "--line-length=99"] - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 24.3.0 hooks: - id: black args: ["--line-length=99"] - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort (python) - args: ["--project=esmerald_admin", "--line-length=99"] - - id: isort - name: isort (cython) - types: [cython] - args: ["--project=esmerald_admin", "--line-length=99"] - - id: isort - name: isort (pyi) - types: [pyi] - args: ["--project=esmerald_admin", "--line-length=99"] ci: autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/docs/authentication.md b/docs/authentication.md index 0569271..bf3a715 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -18,13 +18,13 @@ and apply logins using `email` or `username`, your choice. * `secret_key` - The secret to be used with your auth. * `auth_model` - The class object of your `User`, usually derived from the [User model][user_model] for Saffier or [User model][user_model_edgy] for Edgy. -* `config` - The application settings object. It can be the [settings config][settings_config] or +* `config` - The application settings object. It can be the [settings config][settings_module] or the application `settings` from `esmerald.conf`. It all depends of which one you use. ### How to use This is how you could use the backends in your Esmerald application. This example is very simple -and it will use the [settings_config][settings_config] from Esmerald to simplify. This is not +and it will use the [settings_module][settings_module] from Esmerald to simplify. This is not mandatory and you can use your preferred way. === "Saffier" @@ -103,5 +103,5 @@ Like SQLAdmin, both methods implement the same signature and should return a `bo [user_model]: https://esmerald.dev/databases/saffier/models/#user [user_model_edgy]: https://esmerald.dev/databases/edgy/models/#user [sqladmin_models]: https://aminalaee.dev/sqladmin/authentication/ -[settings_config]: https://esmerald.dev/application/settings/#the-settings_config +[settings_module]: https://esmerald.dev/application/settings/#the-settings_module [permissions]: https://esmerald.dymmond.com/permissions/ diff --git a/docs_src/auth/edgy.py b/docs_src/auth/edgy.py index 46a840a..73c3828 100644 --- a/docs_src/auth/edgy.py +++ b/docs_src/auth/edgy.py @@ -23,7 +23,7 @@ class Meta: registry = registry -# You can use the `settings_config` directly or ESMERALD_SETTINGS_MODULE +# You can use the `settings_module` directly or ESMERALD_SETTINGS_MODULE settings = AppSettings() @@ -36,7 +36,7 @@ def get_application(): routes=[Include(namespace="linezap.urls")], on_startup=[database.connect], on_shutdown=[database.disconnect], - settings_config=settings, + settings_module=settings, ) # EmailAdminAuth or UsernameAdminAuth diff --git a/docs_src/auth/example.py b/docs_src/auth/example.py index eadf529..2600862 100644 --- a/docs_src/auth/example.py +++ b/docs_src/auth/example.py @@ -23,7 +23,7 @@ class Meta: registry = registry -# You can use the `settings_config` directly or ESMERALD_SETTINGS_MODULE +# You can use the `settings_module` directly or ESMERALD_SETTINGS_MODULE settings = AppSettings() @@ -36,7 +36,7 @@ def get_application(): routes=[Include(namespace="linezap.urls")], on_startup=[database.connect], on_shutdown=[database.disconnect], - settings_config=settings, + settings_module=settings, ) # EmailAdminAuth or UsernameAdminAuth diff --git a/esmerald_admin/application.py b/esmerald_admin/application.py index 50259c4..8341f35 100644 --- a/esmerald_admin/application.py +++ b/esmerald_admin/application.py @@ -1,14 +1,19 @@ -from typing import Optional, Sequence +from typing import Awaitable, Optional, Sequence, Union from esmerald import Esmerald, HTTPException, Request, status from esmerald.exceptions import ImproperlyConfigured +from lilya.apps import Lilya +from lilya.middleware import Middleware +from lilya.responses import Response +from lilya.routing import Include, Path +from lilya.staticfiles import StaticFiles from sqladmin import Admin as SQLAdmin # noqa from sqladmin import ModelView as SQLAModelView # noqa from sqladmin._types import ENGINE_TYPE +from sqladmin.application import BaseAdminView from sqladmin.authentication import AuthenticationBackend from sqladmin.models import BaseView as SQLAdminBaseView # noqa from sqlalchemy.orm.session import sessionmaker -from starlette.middleware import Middleware class EsmeraldBase: @@ -28,15 +33,15 @@ def has_permission(self, request: Request) -> bool: return True -class BaseView(SQLAdminBaseView, EsmeraldBase): +class BaseView(SQLAdminBaseView, EsmeraldBase): # type: ignore ... -class ModelView(SQLAModelView, BaseView): +class ModelView(SQLAModelView, BaseView): # type: ignore ... -class Admin(SQLAdmin): +class Admin(SQLAdmin, BaseAdminView): """ Tha base inherited for Saffier which inherits from the base of sqladmin package. """ @@ -54,19 +59,65 @@ def __init__( templates_dir: str = "templates", authentication_backend: Optional[AuthenticationBackend] = None, ) -> None: - super().__init__( - app=app, + super(BaseAdminView, self).__init__( + app=app, # type: ignore engine=engine, session_maker=session_maker, base_url=base_url, title=title, logo_url=logo_url, - middlewares=middlewares, - debug=debug, templates_dir=templates_dir, + middlewares=middlewares, # type: ignore authentication_backend=authentication_backend, ) + statics = StaticFiles(packages=["sqladmin"]) + + async def http_exception( + request: Request, exc: Exception + ) -> Union[Response, Awaitable[Response]]: + assert isinstance(exc, HTTPException) + context = { + "status_code": exc.status_code, + "message": exc.detail, + } + return await self.templates.TemplateResponse( # type: ignore + request, "error.html", context, status_code=exc.status_code # type: ignore + ) + + routes = [ + Include("/statics", app=statics, name="statics"), + Path("/", handler=self.index, name="index"), + Path("/{identity}/list", handler=self.list, name="list"), + Path("/{identity}/details/{pk:path}", handler=self.details, name="details"), + Path( + "/{identity}/delete", + handler=self.delete, + name="delete", + methods=["DELETE"], + ), + Path( + "/{identity}/create", + handler=self.create, + name="create", + methods=["GET", "POST"], + ), + Path( + "/{identity}/edit/{pk:path}", + handler=self.edit, + name="edit", + methods=["GET", "POST"], + ), + Path("/{identity}/export/{export_type}", handler=self.export, name="export"), + Path("/{identity}/ajax/lookup", handler=self.ajax_lookup, name="ajax_lookup"), + Path("/login", handler=self.login, name="login", methods=["GET", "POST"]), + Path("/logout", handler=self.logout, name="logout", methods=["GET"]), + ] + + self.admin = Lilya(middleware=middlewares, routes=routes) # type: ignore + self.admin.exception_handlers = {HTTPException: http_exception} + self.admin.debug = debug + self.app.include(base_url, app=self.admin, name="admin") self.app.router.activate() # type: ignore def _find_model_view(self, identity: str) -> ModelView: diff --git a/esmerald_admin/backends/base.py b/esmerald_admin/backends/base.py index 8adf6ac..e615ac4 100644 --- a/esmerald_admin/backends/base.py +++ b/esmerald_admin/backends/base.py @@ -5,8 +5,8 @@ from esmerald.exceptions import AuthenticationError from esmerald.security.jwt.token import Token from jose import JWSError, JWTError +from lilya.responses import RedirectResponse from sqladmin.authentication import AuthenticationBackend -from starlette.responses import RedirectResponse DEFAULT_HEADER = "Bearer" diff --git a/pyproject.toml b/pyproject.toml index a84db97..eaee053 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP", ] -dependencies = ["esmerald[jwt]>=2.2.0", "sqladmin>=0.15.2,<1.0"] +dependencies = ["esmerald[jwt]>=3.1.0", "sqladmin>=0.15.2,<1.0"] keywords = ["esmerald_admin"] [project.urls] @@ -59,6 +59,7 @@ test = [ "pytest-cov>=4.0.0,<5.0.0", "requests>=2.28.2", "ruff>=0.0.256,<1.0.0", + "httpx", "saffier[postgres,testing]>=1.0.0", "edgy[postgres,testing]>=0.5.1", ] @@ -102,19 +103,10 @@ disallow_untyped_defs = true ignore_missing_imports = true no_implicit_optional = true -[tool.ruff] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "C", # flake8-comprehensions - "B", # flake8-bugbear -] -ignore = [ - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "C901", # too complex -] +[tool.ruff.lint] +select = ["E", "W", "F", "C", "B", "I"] +ignore = ["E501", "B008", "C901", "B026"] + [[tool.mypy.overrides]] module = "esmerald_admin.tests.*" diff --git a/scripts/build b/scripts/build index fd5b226..957d9e4 100755 --- a/scripts/build +++ b/scripts/build @@ -12,4 +12,3 @@ set -x ${PREFIX}python -m build ${PREFIX}twine check dist/* -${PREFIX}mkdocs build diff --git a/scripts/check b/scripts/check index 8507c26..3535592 100755 --- a/scripts/check +++ b/scripts/check @@ -12,7 +12,6 @@ export MAIN="esmerald_admin" set -x -${PREFIX}mypy $MAIN -${PREFIX}ruff $SOURCE_FILES --line-length 99 -${PREFIX}black $SOURCE_FILES --check --diff --check --line-length 99 -${PREFIX}isort $SOURCE_FILES --check --diff --project=esmerald_admin --line-length 99 +${PREFIX}ruff check $SOURCE_FILES --fix --line-length 99 +${PREFIX}black $SOURCE_FILES --line-length 99 +${PREFIX}mypy esmerald_admin diff --git a/scripts/lint b/scripts/lint index b30dfbc..61a45d5 100755 --- a/scripts/lint +++ b/scripts/lint @@ -10,7 +10,6 @@ fi export SOURCE_FILES="esmerald_admin tests" set -x -${PREFIX}ruff $SOURCE_FILES --fix --line-length 99 +${PREFIX}ruff check $SOURCE_FILES --fix --line-length 99 ${PREFIX}black $SOURCE_FILES --line-length 99 -${PREFIX}isort $SOURCE_FILES --project=esmerald_admin --line-length 99 ${PREFIX}mypy esmerald_admin diff --git a/tests/test_integrations/test_edgy_authentication.py b/tests/test_integrations/test_edgy_authentication.py index b78a109..f9bc6a2 100644 --- a/tests/test_integrations/test_edgy_authentication.py +++ b/tests/test_integrations/test_edgy_authentication.py @@ -9,10 +9,10 @@ from esmerald.contrib.auth.edgy.base_user import AbstractUser from esmerald.testclient import EsmeraldTestClient from httpx import AsyncClient -from tests.settings import DATABASE_URL from esmerald_admin import Admin from esmerald_admin.backends.edgy import EmailAdminAuth, UsernameAdminAuth +from tests.settings import DATABASE_URL pytestmark = pytest.mark.anyio @@ -60,7 +60,7 @@ async def rollback_transactions(): def get_email_backend_admin() -> Admin: app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) email_authentication_backend = EmailAdminAuth( @@ -74,7 +74,7 @@ def get_email_backend_admin() -> Admin: def get_username_backend_admin() -> Admin: app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) user_authentication_backend = UsernameAdminAuth( diff --git a/tests/test_integrations/test_saffier_authentication.py b/tests/test_integrations/test_saffier_authentication.py index 80a78ea..d35800a 100644 --- a/tests/test_integrations/test_saffier_authentication.py +++ b/tests/test_integrations/test_saffier_authentication.py @@ -9,10 +9,10 @@ from esmerald.testclient import EsmeraldTestClient from httpx import AsyncClient from saffier.testclient import DatabaseTestClient as Database -from tests.settings import DATABASE_URL from esmerald_admin import Admin from esmerald_admin.backends.saffier import EmailAdminAuth, UsernameAdminAuth +from tests.settings import DATABASE_URL pytestmark = pytest.mark.anyio @@ -60,7 +60,7 @@ async def rollback_transactions(): def get_email_backend_admin() -> Admin: app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) email_authentication_backend = EmailAdminAuth( @@ -74,7 +74,7 @@ def get_email_backend_admin() -> Admin: def get_username_backend_admin() -> Admin: app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) user_authentication_backend = UsernameAdminAuth( diff --git a/tests/test_views/test_edgy_views_async.py b/tests/test_views/test_edgy_views_async.py index 8224ebe..e50e944 100644 --- a/tests/test_views/test_edgy_views_async.py +++ b/tests/test_views/test_edgy_views_async.py @@ -8,9 +8,9 @@ from esmerald.config.jwt import JWTConfig from esmerald.contrib.auth.edgy.base_user import AbstractUser from httpx import AsyncClient -from tests.settings import DATABASE_URL from esmerald_admin import Admin, ModelView +from tests.settings import DATABASE_URL pytestmark = pytest.mark.anyio @@ -80,7 +80,7 @@ def has_permission(self, request: Request) -> bool: settings = TestSettings() app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) admin = Admin(app=app, engine=models.engine) diff --git a/tests/test_views/test_saffier_views_async.py b/tests/test_views/test_saffier_views_async.py index 1ba7ca8..91cb216 100644 --- a/tests/test_views/test_saffier_views_async.py +++ b/tests/test_views/test_saffier_views_async.py @@ -8,9 +8,9 @@ from esmerald.contrib.auth.saffier.base_user import AbstractUser from httpx import AsyncClient from saffier.testclient import DatabaseTestClient as Database -from tests.settings import DATABASE_URL from esmerald_admin import Admin, ModelView +from tests.settings import DATABASE_URL pytestmark = pytest.mark.anyio @@ -80,7 +80,7 @@ def has_permission(self, request: Request) -> bool: settings = TestSettings() app = Esmerald( - settings_config=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] + settings_module=settings, on_startup=[database.connect], on_shutdown=[database.disconnect] ) admin = Admin(app=app, engine=models.engine)