forked from Github-Mirrors/canaille
chore: configure ruff
This commit is contained in:
parent
256566df94
commit
dc89a20b11
13 changed files with 63 additions and 54 deletions
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: 'v0.3.7'
|
rev: 'v0.4.1'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
@ -15,21 +15,10 @@ repos:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: "\\.svg$|\\.map$|\\.min\\.css$|\\.min\\.js$|\\.po$|\\.pot$"
|
exclude: "\\.svg$|\\.map$|\\.min\\.css$|\\.min\\.js$|\\.po$|\\.pot$"
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- repo: https://github.com/pycqa/isort
|
|
||||||
rev: "5.13.2"
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
name: isort (python)
|
|
||||||
args: ["--force-single-line-imports", "--profile", "black"]
|
|
||||||
- repo: https://github.com/PyCQA/docformatter
|
- repo: https://github.com/PyCQA/docformatter
|
||||||
rev: v1.7.5
|
rev: v1.7.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: docformatter
|
- id: docformatter
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.15.2
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: ["--py38-plus"]
|
|
||||||
- repo: https://github.com/rtts/djhtml
|
- repo: https://github.com/rtts/djhtml
|
||||||
rev: 3.0.6
|
rev: 3.0.6
|
||||||
hooks:
|
hooks:
|
||||||
|
|
|
@ -17,8 +17,9 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
class RootSettings(BaseSettings):
|
class RootSettings(BaseSettings):
|
||||||
"""The top-level namespace contains holds the configuration settings
|
"""The top-level namespace contains holds the configuration settings
|
||||||
unrelated to Canaille. The configuration paramateres from the following
|
unrelated to Canaille.
|
||||||
libraries can be used:
|
|
||||||
|
The configuration paramateres from the following libraries can be used:
|
||||||
|
|
||||||
- :doc:`Flask <flask:config>`
|
- :doc:`Flask <flask:config>`
|
||||||
- :doc:`Flask-WTF <flask-wtf:config>`
|
- :doc:`Flask-WTF <flask-wtf:config>`
|
||||||
|
|
|
@ -36,7 +36,7 @@ def current_user():
|
||||||
|
|
||||||
|
|
||||||
def login_user(user):
|
def login_user(user):
|
||||||
"""Opens a session for the user."""
|
"""Open a session for the user."""
|
||||||
g.user = user
|
g.user = user
|
||||||
try:
|
try:
|
||||||
previous = (
|
previous = (
|
||||||
|
@ -50,7 +50,7 @@ def login_user(user):
|
||||||
|
|
||||||
|
|
||||||
def logout_user():
|
def logout_user():
|
||||||
"""Closes the user session."""
|
"""Close the user session."""
|
||||||
try:
|
try:
|
||||||
session["user_id"].pop()
|
session["user_id"].pop()
|
||||||
del g.user
|
del g.user
|
||||||
|
|
|
@ -72,7 +72,7 @@ class HTMXFormMixin:
|
||||||
render_field_extra_context = {}
|
render_field_extra_context = {}
|
||||||
|
|
||||||
def field_from_name(self, field_name):
|
def field_from_name(self, field_name):
|
||||||
"""Returns a tuple containing a field and its rendering context."""
|
"""Return a tuple containing a field and its rendering context."""
|
||||||
if self.SEPARATOR not in field_name:
|
if self.SEPARATOR not in field_name:
|
||||||
field = self[field_name] if field_name in self else None
|
field = self[field_name] if field_name in self else None
|
||||||
return field, {}
|
return field, {}
|
||||||
|
@ -120,7 +120,7 @@ class HTMXFormMixin:
|
||||||
abort(response)
|
abort(response)
|
||||||
|
|
||||||
def form_control(self):
|
def form_control(self):
|
||||||
"""Checks wether the current request is the result of the users adding
|
"""Check wether the current request is the result of the users adding
|
||||||
or removing a field from a FieldList."""
|
or removing a field from a FieldList."""
|
||||||
FIELDLIST_ADD_BUTTON = "fieldlist_add"
|
FIELDLIST_ADD_BUTTON = "fieldlist_add"
|
||||||
FIELDLIST_REMOVE_BUTTON = "fieldlist_remove"
|
FIELDLIST_REMOVE_BUTTON = "fieldlist_remove"
|
||||||
|
|
|
@ -34,21 +34,20 @@ class BaseBackend:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def install(self, config):
|
def install(self, config):
|
||||||
"""This methods prepares the database to host canaille data."""
|
"""Prepare the database to host canaille data."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""This method will be called before each http request, it should open
|
"""Is called before each http request, it should open the connection to
|
||||||
the connection to the backend."""
|
the backend."""
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
"""This method will be called after each http request, it should close
|
"""Is called after each http request, it should close the connections
|
||||||
the connections to the backend."""
|
to the backend."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, config):
|
def validate(cls, config):
|
||||||
"""This method should validate the config part dedicated to the
|
"""Validate the config part dedicated to the backend.
|
||||||
backend.
|
|
||||||
|
|
||||||
It should raise :class:`~canaille.configuration.ConfigurationError` when
|
It should raise :class:`~canaille.configuration.ConfigurationError` when
|
||||||
errors are met.
|
errors are met.
|
||||||
|
@ -56,15 +55,15 @@ class BaseBackend:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def check_user_password(self, user, password: str) -> bool:
|
def check_user_password(self, user, password: str) -> bool:
|
||||||
"""Checks if the password matches the user password in the database."""
|
"""Check if the password matches the user password in the database."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_user_password(self, user, password: str):
|
def set_user_password(self, user, password: str):
|
||||||
"""Sets a password for the user."""
|
"""Set a password for the user."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def has_account_lockability(self):
|
def has_account_lockability(self):
|
||||||
"""Indicates wether the backend supports locking user accounts."""
|
"""Indicate wether the backend supports locking user accounts."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def register_models(self):
|
def register_models(self):
|
||||||
|
|
|
@ -89,8 +89,9 @@ class BackendModel:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query(cls, **kwargs):
|
def query(cls, **kwargs):
|
||||||
"""
|
"""Perform a query on the database and return a collection of
|
||||||
Performs a query on the database and return a collection of instances.
|
instances.
|
||||||
|
|
||||||
Parameters can be any valid attribute with the expected value:
|
Parameters can be any valid attribute with the expected value:
|
||||||
|
|
||||||
>>> User.query(first_name="George")
|
>>> User.query(first_name="George")
|
||||||
|
@ -120,11 +121,11 @@ class BackendModel:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Validates the current modifications in the database."""
|
"""Validate the current modifications in the database."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Removes the current instance from the database."""
|
"""Remove the current instance from the database."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs):
|
||||||
|
@ -145,7 +146,7 @@ class BackendModel:
|
||||||
setattr(self, attribute, value)
|
setattr(self, attribute, value)
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
"""Cancels the unsaved modifications.
|
"""Cancel the unsaved modifications.
|
||||||
|
|
||||||
>>> user = User.get(user_name="george")
|
>>> user = User.get(user_name="george")
|
||||||
>>> user.display_name
|
>>> user.display_name
|
||||||
|
|
|
@ -26,7 +26,6 @@ def populate(ctx, nb):
|
||||||
@with_backendcontext
|
@with_backendcontext
|
||||||
def users(ctx):
|
def users(ctx):
|
||||||
"""Populate the database with generated random users."""
|
"""Populate the database with generated random users."""
|
||||||
|
|
||||||
from canaille.core.populate import fake_users
|
from canaille.core.populate import fake_users
|
||||||
|
|
||||||
fake_users(ctx.obj["number"])
|
fake_users(ctx.obj["number"])
|
||||||
|
@ -43,7 +42,6 @@ def users(ctx):
|
||||||
@with_backendcontext
|
@with_backendcontext
|
||||||
def groups(ctx, nb_users_max):
|
def groups(ctx, nb_users_max):
|
||||||
"""Populate the database with generated random groups."""
|
"""Populate the database with generated random groups."""
|
||||||
|
|
||||||
from canaille.core.populate import fake_groups
|
from canaille.core.populate import fake_groups
|
||||||
|
|
||||||
fake_groups(ctx.obj["number"], nb_users_max)
|
fake_groups(ctx.obj["number"], nb_users_max)
|
||||||
|
|
|
@ -11,8 +11,7 @@ from canaille.core.configuration import Permission
|
||||||
|
|
||||||
|
|
||||||
class User(Model):
|
class User(Model):
|
||||||
"""
|
"""User model, based on the `SCIM User schema
|
||||||
User model, based on the `SCIM User schema
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.1>`_,
|
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.1>`_,
|
||||||
`Entreprise User Schema Extension
|
`Entreprise User Schema Extension
|
||||||
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.3>`_
|
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.3>`_
|
||||||
|
@ -249,7 +248,7 @@ class User(Model):
|
||||||
_permissions = None
|
_permissions = None
|
||||||
|
|
||||||
def has_password(self) -> bool:
|
def has_password(self) -> bool:
|
||||||
"""Checks wether a password has been set for the user."""
|
"""Check wether a password has been set for the user."""
|
||||||
return self.password is not None
|
return self.password is not None
|
||||||
|
|
||||||
def can_read(self, field: str):
|
def can_read(self, field: str):
|
||||||
|
@ -323,8 +322,7 @@ class User(Model):
|
||||||
|
|
||||||
|
|
||||||
class Group(Model):
|
class Group(Model):
|
||||||
"""
|
"""User model, based on the `SCIM Group schema
|
||||||
User model, based on the `SCIM Group schema
|
|
||||||
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.2>`_.
|
<https://datatracker.ietf.org/doc/html/rfc7643#section-4.2>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,7 @@ from canaille.core.models import User
|
||||||
|
|
||||||
|
|
||||||
class Client(Model):
|
class Client(Model):
|
||||||
"""
|
"""OpenID Connect client definition, based on the
|
||||||
OpenID Connect client definition, based on the
|
|
||||||
`OAuth 2.0 Dynamic Client Registration protocols
|
`OAuth 2.0 Dynamic Client Registration protocols
|
||||||
<https://datatracker.ietf.org/doc/html/rfc7591.html>`_
|
<https://datatracker.ietf.org/doc/html/rfc7591.html>`_
|
||||||
and the `OpenID Connect RP-Initiated Logout
|
and the `OpenID Connect RP-Initiated Logout
|
||||||
|
|
|
@ -90,7 +90,8 @@ class OIDCSettings(BaseModel):
|
||||||
"""Wether a token is needed for the RFC7591 dynamical client registration.
|
"""Wether a token is needed for the RFC7591 dynamical client registration.
|
||||||
|
|
||||||
If :py:data:`True`, no token is needed to register a client.
|
If :py:data:`True`, no token is needed to register a client.
|
||||||
If :py:data:`False`, dynamical client registration needs a token defined in :attr:`DYNAMIC_CLIENT_REGISTRATION_TOKENS`.
|
If :py:data:`False`, dynamical client registration needs a token defined in
|
||||||
|
:attr:`DYNAMIC_CLIENT_REGISTRATION_TOKENS`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DYNAMIC_CLIENT_REGISTRATION_TOKENS: Optional[List[str]] = None
|
DYNAMIC_CLIENT_REGISTRATION_TOKENS: Optional[List[str]] = None
|
||||||
|
|
|
@ -179,7 +179,22 @@ exclude_lines = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
ignore = ["E501", "E722"]
|
select = [
|
||||||
|
"E", # pycodestyle
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"E501", # line-too-long
|
||||||
|
"E722", # bare-except
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
force-single-line = true
|
||||||
|
|
||||||
|
[too.ruff.format]
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
[tool.tox]
|
[tool.tox]
|
||||||
legacy_tox_ini = """
|
legacy_tox_ini = """
|
||||||
|
|
|
@ -67,7 +67,6 @@ def test_keep_old_object_classes(backend, testclient, slapd_server):
|
||||||
In such a case Canaille should keep the unmanaged objectClass and
|
In such a case Canaille should keep the unmanaged objectClass and
|
||||||
attributes.
|
attributes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.User(cn="foo", sn="bar", user_name="baz")
|
user = models.User(cn="foo", sn="bar", user_name="baz")
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ def test_password_forgotten(smtpd, testclient, user):
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. You should receive "
|
||||||
|
"it within a few minutes.",
|
||||||
) in res.flashes
|
) in res.flashes
|
||||||
res.mustcontain("Send again")
|
res.mustcontain("Send again")
|
||||||
|
|
||||||
|
@ -35,7 +36,8 @@ def test_password_forgotten_multiple_mails(smtpd, testclient, user):
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. You should receive "
|
||||||
|
"it within a few minutes.",
|
||||||
) in res.flashes
|
) in res.flashes
|
||||||
res.mustcontain("Send again")
|
res.mustcontain("Send again")
|
||||||
|
|
||||||
|
@ -61,7 +63,8 @@ def test_password_forgotten_invalid(smtpd, testclient, user):
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. "
|
||||||
|
"You should receive it within a few minutes.",
|
||||||
) in res.flashes
|
) in res.flashes
|
||||||
res.mustcontain(no="The login 'i-dont-really-exist' does not exist")
|
res.mustcontain(no="The login 'i-dont-really-exist' does not exist")
|
||||||
|
|
||||||
|
@ -72,7 +75,8 @@ def test_password_forgotten_invalid(smtpd, testclient, user):
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. "
|
||||||
|
"You should receive it within a few minutes.",
|
||||||
) not in res.flashes
|
) not in res.flashes
|
||||||
res.mustcontain("The login 'i-dont-really-exist' does not exist")
|
res.mustcontain("The login 'i-dont-really-exist' does not exist")
|
||||||
|
|
||||||
|
@ -90,11 +94,13 @@ def test_password_forgotten_invalid_when_user_cannot_self_edit(smtpd, testclient
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. "
|
||||||
|
"You should receive it within a few minutes.",
|
||||||
) not in res.flashes
|
) not in res.flashes
|
||||||
assert (
|
assert (
|
||||||
"error",
|
"error",
|
||||||
"The user 'John (johnny) Doe' does not have permissions to update their password. We cannot send a password reset email.",
|
"The user 'John (johnny) Doe' does not have permissions to update their "
|
||||||
|
"password. We cannot send a password reset email.",
|
||||||
) in res.flashes
|
) in res.flashes
|
||||||
|
|
||||||
testclient.app.config["CANAILLE"]["HIDE_INVALID_LOGINS"] = True
|
testclient.app.config["CANAILLE"]["HIDE_INVALID_LOGINS"] = True
|
||||||
|
@ -105,11 +111,13 @@ def test_password_forgotten_invalid_when_user_cannot_self_edit(smtpd, testclient
|
||||||
res = res.form.submit(status=200)
|
res = res.form.submit(status=200)
|
||||||
assert (
|
assert (
|
||||||
"error",
|
"error",
|
||||||
"The user 'John (johnny) Doe' does not have permissions to update their password. We cannot send a password reset email.",
|
"The user 'John (johnny) Doe' does not have permissions to update their "
|
||||||
|
"password. We cannot send a password reset email.",
|
||||||
) not in res.flashes
|
) not in res.flashes
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. "
|
||||||
|
"You should receive it within a few minutes.",
|
||||||
) in res.flashes
|
) in res.flashes
|
||||||
|
|
||||||
assert len(smtpd.messages) == 0
|
assert len(smtpd.messages) == 0
|
||||||
|
@ -124,7 +132,8 @@ def test_password_forgotten_mail_error(SMTP, smtpd, testclient, user):
|
||||||
res = res.form.submit(status=200, expect_errors=True)
|
res = res.form.submit(status=200, expect_errors=True)
|
||||||
assert (
|
assert (
|
||||||
"success",
|
"success",
|
||||||
"A password reset link has been sent at your email address. You should receive it within a few minutes.",
|
"A password reset link has been sent at your email address. "
|
||||||
|
"You should receive it within a few minutes.",
|
||||||
) not in res.flashes
|
) not in res.flashes
|
||||||
assert (
|
assert (
|
||||||
"error",
|
"error",
|
||||||
|
|
Loading…
Reference in a new issue