Skip to content
This repository was archived by the owner on May 3, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ 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
- id: check-yaml
args:
- --unsafe
- id: end-of-file-fixer
- id: debug-statements
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
Expand All @@ -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
6 changes: 3 additions & 3 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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/
4 changes: 2 additions & 2 deletions docs_src/auth/edgy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs_src/auth/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand All @@ -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
Expand Down
69 changes: 60 additions & 9 deletions esmerald_admin/application.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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.
"""
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion esmerald_admin/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
20 changes: 6 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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",
]
Expand Down Expand Up @@ -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.*"
Expand Down
1 change: 0 additions & 1 deletion scripts/build
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ set -x

${PREFIX}python -m build
${PREFIX}twine check dist/*
${PREFIX}mkdocs build
7 changes: 3 additions & 4 deletions scripts/check
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 1 addition & 2 deletions scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions tests/test_integrations/test_edgy_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions tests/test_integrations/test_saffier_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_views/test_edgy_views_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_views/test_saffier_views_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down