From 96ccf12ad9d5ba4de3da0952ba23507217dd138d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 16 Aug 2023 17:14:11 +0200 Subject: [PATCH] feat: split installation in different extras packages --- .github/workflows/tests.yaml | 4 +- .gitlab-ci.yml | 12 ++-- CHANGES.rst | 1 + build.py | 5 +- canaille/__init__.py | 32 +++------- canaille/app/configuration.py | 8 ++- canaille/app/flask.py | 2 +- canaille/app/forms.py | 11 +++- canaille/app/i18n.py | 6 +- canaille/app/themes.py | 36 +++++++++++ canaille/backends/ldap/backend.py | 2 +- canaille/core/account.py | 2 +- canaille/core/admin.py | 6 +- canaille/core/auth.py | 2 +- canaille/core/forms.py | 9 +-- canaille/core/groups.py | 2 +- canaille/core/mails.py | 2 +- canaille/oidc/authorizations.py | 2 +- canaille/oidc/clients.py | 2 +- canaille/oidc/consents.py | 2 +- canaille/oidc/endpoints.py | 2 +- canaille/oidc/forms.py | 3 +- canaille/oidc/tokens.py | 2 +- demo/Dockerfile-canaille | 2 +- demo/run.sh | 4 +- doc/install.rst | 13 +++- poetry.lock | 101 ++++++------------------------ pyproject.toml | 62 +++++++++++++----- 28 files changed, 178 insertions(+), 159 deletions(-) create mode 100644 canaille/app/themes.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ed229664..d9479cc4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,7 +36,7 @@ jobs: - name: App armor configuration for slapd run: sudo aa-complain /usr/sbin/slapd - run: poetry --version - - run: poetry install + - run: poetry install --extras all - run: poetry run pytest minversions: name: minimum dependency versions @@ -59,7 +59,7 @@ jobs: - run: sed -i -E 's/python = "==/python = "^/' pyproject.toml - run: poetry --version - run: poetry lock - - run: poetry install + - run: poetry install --extras all - run: poetry run pytest style: runs-on: ubuntu-latest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8cf316a1..ee7e74d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,28 +26,28 @@ python38: image: python:3.8 stage: test script: - - poetry install + - poetry install --extras all - poetry run pytest python39: image: python:3.9 stage: test script: - - poetry install + - poetry install --extras all - poetry run pytest python310: image: python:3.10 stage: test script: - - poetry install + - poetry install --extras all - poetry run pytest python311: image: python:3.11 stage: test script: - - poetry install + - poetry install --extras all - poetry run pytest minversions: @@ -57,7 +57,7 @@ minversions: - sed -i -E 's/"(\^|>=)([0-9\.]+)(.*)"/"==\2"/' pyproject.toml - sed -i -E 's/python = "==/python = "^/' pyproject.toml - poetry lock - - poetry install + - poetry install --extras all - poetry run pytest doc: @@ -73,6 +73,6 @@ coverage: allow_failure: true script: - pip install coveralls pyyaml tomli - - poetry install + - poetry install --extras all - poetry run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered -n auto - coveralls diff --git a/CHANGES.rst b/CHANGES.rst index 5923b0f4..79e4b42c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Added ***** - Additional inmemory backend :issue:`30` :pr:`149` +- Installation extras :issue:`167` :pr:`150` [0.0.31] - 2023-08-15 ===================== diff --git a/build.py b/build.py index 7a45edba..eba4878e 100644 --- a/build.py +++ b/build.py @@ -1,11 +1,8 @@ -import os - - def create_mo_files(setup_kwargs): from babel.messages.frontend import compile_catalog cmd = compile_catalog() - cmd.directory = os.path.dirname(__file__) + "/canaille/translations" + cmd.directory = "canaille/translations" cmd.quiet = True cmd.statistics = True cmd.finalize_options() diff --git a/canaille/__init__.py b/canaille/__init__.py index 47293a04..fb6593ea 100644 --- a/canaille/__init__.py +++ b/canaille/__init__.py @@ -1,13 +1,9 @@ import datetime -import os from logging.config import dictConfig from flask import Flask from flask import request from flask import session -from flask_themer import FileSystemThemeLoader -from flask_themer import render_template -from flask_themer import Themer from flask_wtf.csrf import CSRFProtect @@ -64,25 +60,6 @@ def setup_jinja(app): app.jinja_env.policies["ext.i18n.trimmed"] = True -def setup_themer(app): - additional_themes_dir = ( - os.path.dirname(app.config["THEME"]) - if app.config.get("THEME") and os.path.exists(app.config["THEME"]) - else None - ) - themer = Themer( - app, - loaders=[FileSystemThemeLoader(additional_themes_dir)] - if additional_themes_dir - else None, - ) - - @themer.current_theme_loader - def get_current_theme(): - # if config['THEME'] may be a theme name or an absolute path - return app.config.get("THEME", "default").split("/")[-1] - - def setup_blueprints(app): import canaille.core.blueprints import canaille.oidc.blueprints @@ -123,18 +100,26 @@ def setup_flask(app): @app.errorhandler(400) def bad_request(error): + from canaille.app.themes import render_template + return render_template("error.html", description=error, error_code=400), 400 @app.errorhandler(403) def unauthorized(error): + from canaille.app.themes import render_template + return render_template("error.html", description=error, error_code=403), 403 @app.errorhandler(404) def page_not_found(error): + from canaille.app.themes import render_template + return render_template("error.html", description=error, error_code=404), 404 @app.errorhandler(500) def server_error(error): # pragma: no cover + from canaille.app.themes import render_template + return render_template("error.html", description=error, error_code=500), 500 @@ -150,6 +135,7 @@ def create_app(config=None, validate=True, backend=None): from .oidc.oauth import setup_oauth from .app.i18n import setup_i18n from .app.configuration import setup_config + from .app.themes import setup_themer from .backends import setup_backend app = Flask(__name__) diff --git a/canaille/app/configuration.py b/canaille/app/configuration.py index 26a3abfb..b070b158 100644 --- a/canaille/app/configuration.py +++ b/canaille/app/configuration.py @@ -3,7 +3,6 @@ import smtplib import socket from collections.abc import Mapping -import toml from canaille.app.mails import DEFAULT_SMTP_HOST from canaille.app.mails import DEFAULT_SMTP_PORT from flask import current_app @@ -43,6 +42,11 @@ def parse_file_keys(config): def setup_config(app, config=None, validate_config=True): from canaille.oidc.installation import install + try: + import toml + except ImportError: # pragma: no cover + toml = None + app.config.from_mapping( { "SESSION_COOKIE_NAME": "canaille", @@ -52,7 +56,7 @@ def setup_config(app, config=None, validate_config=True): ) if config: app.config.from_mapping(parse_file_keys(config)) - elif "CONFIG" in os.environ: + elif toml and "CONFIG" in os.environ: app.config.from_mapping(parse_file_keys(toml.load(os.environ.get("CONFIG")))) else: raise Exception( diff --git a/canaille/app/flask.py b/canaille/app/flask.py index d16cc26f..2134bec2 100644 --- a/canaille/app/flask.py +++ b/canaille/app/flask.py @@ -4,13 +4,13 @@ from urllib.parse import urlsplit from urllib.parse import urlunsplit from canaille.app import models +from canaille.app.themes import render_template from flask import abort from flask import current_app from flask import g from flask import request from flask import session from flask_babel import gettext as _ -from flask_themer import render_template from werkzeug.routing import BaseConverter diff --git a/canaille/app/forms.py b/canaille/app/forms.py index 10fd0146..b827b76c 100644 --- a/canaille/app/forms.py +++ b/canaille/app/forms.py @@ -3,7 +3,7 @@ import math import re import pytz -import wtforms +import wtforms.validators from canaille.app.i18n import DEFAULT_LANGUAGE_CODE from canaille.app.i18n import locale_selector from canaille.app.i18n import timezone_selector @@ -44,6 +44,15 @@ def phone_number(form, field): raise wtforms.ValidationError(_("Not a valid phone number")) +def email_validator(form, field): + try: + import email_validator # noqa: F401 + except ImportError: # pragma: no cover + pass + + wtforms.validators.Email()(form, field) + + meta = DefaultMeta() diff --git a/canaille/app/i18n.py b/canaille/app/i18n.py index 3d1d68bd..9b54bd55 100644 --- a/canaille/app/i18n.py +++ b/canaille/app/i18n.py @@ -1,6 +1,5 @@ import gettext -import pycountry import pytz from babel.dates import LOCALTZ from flask import current_app @@ -52,6 +51,11 @@ def timezone_selector(): def native_language_name_from_code(code): + try: + import pycountry + except ImportError: # pragma: no cover + return code + language = pycountry.languages.get(alpha_2=code[:2]) if code == DEFAULT_LANGUAGE_CODE: return language.name diff --git a/canaille/app/themes.py b/canaille/app/themes.py new file mode 100644 index 00000000..6ed2d821 --- /dev/null +++ b/canaille/app/themes.py @@ -0,0 +1,36 @@ +import os + +import flask + +try: + import flask_themer +except ImportError: # pragma: no cover + flask_themer = None + + +if flask_themer: + render_template = flask_themer.render_template + + def setup_themer(app): + additional_themes_dir = ( + os.path.dirname(app.config["THEME"]) + if app.config.get("THEME") and os.path.exists(app.config["THEME"]) + else None + ) + themer = flask_themer.Themer( + app, + loaders=[flask_themer.FileSystemThemeLoader(additional_themes_dir)] + if additional_themes_dir + else None, + ) + + @themer.current_theme_loader + def get_current_theme(): + # if config['THEME'] may be a theme name or an absolute path + return app.config.get("THEME", "default").split("/")[-1] + +else: # pragma: no cover + render_template = flask.render_template + + def setup_themer(app): + return diff --git a/canaille/backends/ldap/backend.py b/canaille/backends/ldap/backend.py index 23d1f652..5e03030c 100644 --- a/canaille/backends/ldap/backend.py +++ b/canaille/backends/ldap/backend.py @@ -7,11 +7,11 @@ import ldap.modlist import ldif from canaille.app import models from canaille.app.configuration import ConfigurationException +from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import current_app from flask import request from flask_babel import gettext as _ -from flask_themer import render_template from .utils import listify diff --git a/canaille/core/account.py b/canaille/core/account.py index fa548f1d..cd8e46b5 100644 --- a/canaille/core/account.py +++ b/canaille/core/account.py @@ -22,6 +22,7 @@ from canaille.app.forms import is_readonly from canaille.app.forms import set_readonly from canaille.app.forms import set_writable from canaille.app.forms import TableForm +from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import abort from flask import Blueprint @@ -34,7 +35,6 @@ from flask import send_file from flask import url_for from flask_babel import gettext as _ from flask_babel import refresh -from flask_themer import render_template from werkzeug.datastructures import CombinedMultiDict from werkzeug.datastructures import FileStorage diff --git a/canaille/core/admin.py b/canaille/core/admin.py index 26f41b8d..d03c2c32 100644 --- a/canaille/core/admin.py +++ b/canaille/core/admin.py @@ -1,6 +1,8 @@ from canaille.app import obj_to_b64 from canaille.app.flask import permissions_needed +from canaille.app.forms import email_validator from canaille.app.forms import Form +from canaille.app.themes import render_template from canaille.core.mails import build_hash from canaille.core.mails import send_test_mail from flask import Blueprint @@ -9,10 +11,8 @@ from flask import flash from flask import request from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from wtforms import StringField from wtforms.validators import DataRequired -from wtforms.validators import Email bp = Blueprint("admin", __name__, url_prefix="/admin") @@ -23,7 +23,7 @@ class MailTestForm(Form): _("Email"), validators=[ DataRequired(), - Email(), + email_validator, ], render_kw={ "placeholder": _("jane@doe.com"), diff --git a/canaille/core/auth.py b/canaille/core/auth.py index 62956750..c51320f7 100644 --- a/canaille/core/auth.py +++ b/canaille/core/auth.py @@ -2,6 +2,7 @@ from canaille.app import build_hash from canaille.app import models from canaille.app.flask import current_user from canaille.app.flask import smtp_needed +from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import abort from flask import Blueprint @@ -12,7 +13,6 @@ from flask import request from flask import session from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from .forms import FirstLoginForm from .forms import ForgottenPasswordForm diff --git a/canaille/core/forms.py b/canaille/core/forms.py index f64f7614..6bc21b42 100644 --- a/canaille/core/forms.py +++ b/canaille/core/forms.py @@ -2,6 +2,7 @@ import wtforms.form from canaille.app import models from canaille.app.forms import BaseForm from canaille.app.forms import DateTimeUTCField +from canaille.app.forms import email_validator from canaille.app.forms import Form from canaille.app.forms import is_uri from canaille.app.forms import phone_number @@ -164,7 +165,7 @@ PROFILE_FORM_FIELDS = dict( _("Email addresses"), validators=[ wtforms.validators.DataRequired(), - wtforms.validators.Email(), + email_validator, unique_email, ], description=_( @@ -357,7 +358,7 @@ class JoinForm(Form): _("Email address"), validators=[ wtforms.validators.DataRequired(), - wtforms.validators.Email(), + email_validator, ], render_kw={ "placeholder": _("jane@doe.com"), @@ -382,7 +383,7 @@ class InvitationForm(Form): _("Email address"), validators=[ wtforms.validators.DataRequired(), - wtforms.validators.Email(), + email_validator, unique_email, ], render_kw={ @@ -420,7 +421,7 @@ class EmailConfirmationForm(Form): _("New email address"), validators=[ wtforms.validators.DataRequired(), - wtforms.validators.Email(), + email_validator, unique_email, ], render_kw={ diff --git a/canaille/core/groups.py b/canaille/core/groups.py index a7fbdaa9..788ca47e 100644 --- a/canaille/core/groups.py +++ b/canaille/core/groups.py @@ -2,6 +2,7 @@ from canaille.app import models from canaille.app.flask import permissions_needed from canaille.app.flask import render_htmx_template from canaille.app.forms import TableForm +from canaille.app.themes import render_template from flask import abort from flask import Blueprint from flask import flash @@ -9,7 +10,6 @@ from flask import redirect from flask import request from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from .forms import CreateGroupForm from .forms import EditGroupForm diff --git a/canaille/core/mails.py b/canaille/core/mails.py index 78265788..6f5a11e1 100644 --- a/canaille/core/mails.py +++ b/canaille/core/mails.py @@ -1,10 +1,10 @@ from canaille.app import build_hash from canaille.app.mails import logo from canaille.app.mails import send_email +from canaille.app.themes import render_template from flask import current_app from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template def send_test_mail(email): diff --git a/canaille/oidc/authorizations.py b/canaille/oidc/authorizations.py index 7fa9e147..50e68791 100644 --- a/canaille/oidc/authorizations.py +++ b/canaille/oidc/authorizations.py @@ -2,10 +2,10 @@ from canaille.app import models from canaille.app.flask import permissions_needed from canaille.app.flask import render_htmx_template from canaille.app.forms import TableForm +from canaille.app.themes import render_template from flask import abort from flask import Blueprint from flask import request -from flask_themer import render_template bp = Blueprint("authorizations", __name__, url_prefix="/admin/authorization") diff --git a/canaille/oidc/clients.py b/canaille/oidc/clients.py index f86def6b..7ef82195 100644 --- a/canaille/oidc/clients.py +++ b/canaille/oidc/clients.py @@ -4,6 +4,7 @@ from canaille.app import models from canaille.app.flask import permissions_needed from canaille.app.flask import render_htmx_template from canaille.app.forms import TableForm +from canaille.app.themes import render_template from canaille.oidc.forms import ClientAddForm from flask import abort from flask import Blueprint @@ -12,7 +13,6 @@ from flask import redirect from flask import request from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from werkzeug.security import gen_salt diff --git a/canaille/oidc/consents.py b/canaille/oidc/consents.py index 790cd197..54d71a88 100644 --- a/canaille/oidc/consents.py +++ b/canaille/oidc/consents.py @@ -3,12 +3,12 @@ import uuid from canaille.app import models from canaille.app.flask import user_needed +from canaille.app.themes import render_template from flask import Blueprint from flask import flash from flask import redirect from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from .utils import SCOPE_DETAILS diff --git a/canaille/oidc/endpoints.py b/canaille/oidc/endpoints.py index 16cd7178..6a8cffbb 100644 --- a/canaille/oidc/endpoints.py +++ b/canaille/oidc/endpoints.py @@ -9,6 +9,7 @@ from canaille import csrf from canaille.app import models from canaille.app.flask import current_user from canaille.app.flask import set_parameter_in_url_query +from canaille.app.themes import render_template from canaille.core.forms import FullLoginForm from flask import abort from flask import Blueprint @@ -20,7 +21,6 @@ from flask import request from flask import session from flask import url_for from flask_babel import gettext as _ -from flask_themer import render_template from werkzeug.datastructures import CombinedMultiDict from .forms import AuthorizeForm diff --git a/canaille/oidc/forms.py b/canaille/oidc/forms.py index 72c1be18..7060ab59 100644 --- a/canaille/oidc/forms.py +++ b/canaille/oidc/forms.py @@ -1,5 +1,6 @@ import wtforms from canaille.app import models +from canaille.app.forms import email_validator from canaille.app.forms import Form from canaille.app.forms import is_uri from canaille.app.forms import unique_values @@ -27,7 +28,7 @@ class ClientAddForm(Form): contacts = wtforms.FieldList( wtforms.EmailField( _("Contacts"), - validators=[wtforms.validators.Optional(), wtforms.validators.Email()], + validators=[wtforms.validators.Optional(), email_validator], render_kw={"placeholder": "admin@mydomain.tld"}, ), min_entries=1, diff --git a/canaille/oidc/tokens.py b/canaille/oidc/tokens.py index 99ad34bf..0732ee80 100644 --- a/canaille/oidc/tokens.py +++ b/canaille/oidc/tokens.py @@ -4,13 +4,13 @@ from canaille.app import models from canaille.app.flask import permissions_needed from canaille.app.flask import render_htmx_template from canaille.app.forms import TableForm +from canaille.app.themes import render_template from canaille.oidc.forms import TokenRevokationForm from flask import abort from flask import Blueprint from flask import flash from flask import request from flask_babel import gettext as _ -from flask_themer import render_template bp = Blueprint("tokens", __name__, url_prefix="/admin/token") diff --git a/demo/Dockerfile-canaille b/demo/Dockerfile-canaille index dcb1cff0..7fbc7f22 100644 --- a/demo/Dockerfile-canaille +++ b/demo/Dockerfile-canaille @@ -12,6 +12,6 @@ RUN \ COPY poetry.lock pyproject.toml demo/demoapp.py /opt/canaille/ RUN pip install poetry WORKDIR /opt/canaille -RUN poetry install --with demo --without dev +RUN poetry install --with demo --without dev --extras all ENTRYPOINT ["poetry", "run", "flask", "run", "--host=0.0.0.0", "--extra-files", "/opt/canaille/conf/canaille-memory.toml", "/opt/canaille/conf/canaille-ldap.toml"] diff --git a/demo/run.sh b/demo/run.sh index d437bfbb..f04c22ce 100755 --- a/demo/run.sh +++ b/demo/run.sh @@ -20,12 +20,11 @@ if ! type poetry > /dev/null 2>&1; then exit 1 fi -poetry install --with demo --without dev - pushd "$DIR" > /dev/null 2>&1 || exit if [ "$BACKEND" = "memory" ]; then + poetry install --with demo --without dev --extras front --extras oidc env poetry run honcho --procfile Procfile-memory start elif [ "$BACKEND" = "ldap" ]; then @@ -36,6 +35,7 @@ elif [ "$BACKEND" = "ldap" ]; then exit 1 fi + poetry install --with demo --without dev --extras front --extras oidc --extras ldap env poetry run honcho --procfile Procfile-ldap start else diff --git a/doc/install.rst b/doc/install.rst index 7b8b2bcf..a8fa22e2 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -21,7 +21,18 @@ Let us choose a place for the canaille environment, like ``/opt/canaille/env``. export CANAILLE_INSTALL_DIR=/opt/canaille sudo mkdir --parents "$CANAILLE_INSTALL_DIR" sudo virtualenv --python=python3 "$CANAILLE_INSTALL_DIR/env" - sudo "$CANAILLE_INSTALL_DIR/env/bin/pip" install canaille + sudo "$CANAILLE_INSTALL_DIR/env/bin/pip" install "canaille[all]" + +Extras +------ + +Canaille provides different package options: + +- `front` provides all the things needed to produce the user interface; +- `oidc` provides the dependencies to perform OAuth2/OIDC authentication; +- `ldap` provides the dependencies to enable the LDAP backend; +- `sentry` provides sentry integration to watch Canaille exceptions; +- `all` provides all the extras above. Configuration ============= diff --git a/poetry.lock b/poetry.lock index 1cbe122d..423efa9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -59,7 +59,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "authlib" version = "1.2.1" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." -optional = false +optional = true python-versions = "*" files = [ {file = "Authlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:c88984ea00149a90e3537c964327da930779afa4564e354edfd98410bea01911"}, @@ -457,7 +457,7 @@ files = [ name = "dnspython" version = "2.4.2" description = "DNS toolkit" -optional = false +optional = true python-versions = ">=3.8,<4.0" files = [ {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, @@ -487,7 +487,7 @@ files = [ name = "email-validator" version = "2.0.0.post2" description = "A robust email address syntax and deliverability validation library." -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, @@ -541,21 +541,6 @@ files = [ python-dateutil = ">=2.4" typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\""} -[[package]] -name = "fancycompleter" -version = "0.9.1" -description = "colorful TAB completion for Python prompt" -optional = false -python-versions = "*" -files = [ - {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, - {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, -] - -[package.dependencies] -pyreadline = {version = "*", markers = "platform_system == \"Windows\""} -pyrepl = ">=0.8.2" - [[package]] name = "filelock" version = "3.12.2" @@ -614,7 +599,7 @@ pytz = ">=2022.7" name = "flask-themer" version = "2.0.0" description = "Simple theme mechanism for Flask" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "flask-themer-2.0.0.tar.gz", hash = "sha256:c8dbea370ad88d9d13e5d0c360f9678bb219bfbab7a7d0cb4f00ff89ac09a275"}, @@ -978,26 +963,6 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] -[[package]] -name = "pdbpp" -version = "0.10.3" -description = "pdb++, a drop-in replacement for pdb" -optional = false -python-versions = "*" -files = [ - {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, - {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, -] - -[package.dependencies] -fancycompleter = ">=0.8" -pygments = "*" -wmctrl = "*" - -[package.extras] -funcsigs = ["funcsigs"] -testing = ["funcsigs", "pytest"] - [[package]] name = "platformdirs" version = "3.10.0" @@ -1090,7 +1055,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, @@ -1101,7 +1066,7 @@ files = [ name = "pyasn1-modules" version = "0.3.0" description = "A collection of ASN.1-based protocols modules" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, @@ -1115,7 +1080,7 @@ pyasn1 = ">=0.4.6,<0.6.0" name = "pycountry" version = "22.3.5" description = "ISO country, subdivision, language, currency and script definitions and their translations" -optional = false +optional = true python-versions = ">=3.6, <4" files = [ {file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"}, @@ -1180,26 +1145,6 @@ lxml = ">=2.1" [package.extras] test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] -[[package]] -name = "pyreadline" -version = "2.1" -description = "A python implmementation of GNU readline." -optional = false -python-versions = "*" -files = [ - {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, -] - -[[package]] -name = "pyrepl" -version = "0.9.0" -description = "A library for building flexible command line interfaces" -optional = false -python-versions = "*" -files = [ - {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, -] - [[package]] name = "pytest" version = "7.4.0" @@ -1353,7 +1298,7 @@ six = ">=1.5" name = "python-ldap" version = "3.4.3" description = "Python modules for implementing LDAP clients" -optional = false +optional = true python-versions = ">=3.6" files = [ {file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"}, @@ -1491,18 +1436,18 @@ tornado = ["tornado (>=5)"] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"}, + {file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1750,7 +1695,7 @@ test = ["pytest"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -optional = false +optional = true python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, @@ -1883,16 +1828,6 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] -[[package]] -name = "wmctrl" -version = "0.4" -description = "A tool to programmatically control windows inside X" -optional = false -python-versions = "*" -files = [ - {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, -] - [[package]] name = "wtforms" version = "3.0.1" @@ -1926,9 +1861,13 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] +all = ["authlib", "email_validator", "flask-themer", "pycountry", "python-ldap", "sentry-sdk", "toml"] +front = ["email_validator", "flask-themer", "pycountry", "toml"] +ldap = ["python-ldap"] +oidc = ["authlib"] sentry = ["sentry-sdk"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "992ada3ebda103757cfda97ccc5775e21b11d8275d0ceeeeb1f29ecb1c5f9a45" +content-hash = "739e40a2b7ee9549652e6616743dbc2c9a25b7f56456c2e0a4691b1e25bf5094" diff --git a/pyproject.toml b/pyproject.toml index b550aa81..316fc688 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP", ] -authors = ["Yaal team "] +authors = ["Yaal Coop "] maintainers = [ "Éloi Rivard ", ] @@ -37,29 +37,35 @@ include = ["canaille/translations/*/LC_MESSAGES/*.mo"] [tool.poetry.dependencies] python = "^3.8" -authlib = "^1.2.1" -click = ">=8.0.0" -email_validator = "^2.0.0" flask = ">=2.2.2 <2.3" flask-babel = "^3.0.0" -flask-themer = "^2.0.0" flask-wtf = "^1.1.1" -pycountry = ">=22.1.10" -python-ldap = "^3.4.0" pytz = ">=2022.7" -toml = "^0.10.0" wtforms = "^3.0.1" werkzeug = ">=2.2.2 <2.3" -"sentry-sdk" = {version = "<2", optional=true, extras=["flask"]} +# extra : front +email_validator = {version = "^2.0.0", optional=true} +flask-themer = {version = "^2.0.0", optional=true} +pycountry = {version = ">=22.1.10", optional=true} +toml = {version = "^0.10.0", optional=true} + +# extra : oidc +authlib = {version = "^1.2.1", optional=true} + +# extra : ldap +python-ldap = {version = "^3.4.0", optional=true} + +# extra : sentry +sentry-sdk = {version = "<2", optional=true, extras=["flask"]} [tool.poetry.group.doc] optional = true [tool.poetry.group.doc.dependencies] -"sphinx" = "*" -"sphinx-rtd-theme" = "*" -"sphinx-issues" = "*" +sphinx = "*" +sphinx-rtd-theme = "*" +sphinx-issues = "*" pygments-ldif = "==1.0.1" [tool.poetry.group.dev.dependencies] @@ -67,7 +73,6 @@ coverage = {version = "*", extras=["toml"]} faker = "*" flask-webtest = "*" freezegun = "*" -pdbpp = "*" pre-commit = "*" pyquery = "*" pytest = "*" @@ -89,7 +94,32 @@ slapd = "*" requests = "*" [tool.poetry.extras] -sentry = ["sentry-sdk"] +front = [ + "click", + "email_validator", + "flask-themer", + "pycountry", + "toml", +] +ldap = [ + "python-ldap", +] +oidc = [ + "authlib", +] +sentry = [ + "sentry-sdk", +] +all = [ + "click", + "email_validator", + "flask-themer", + "pycountry", + "toml", + "python-ldap", + "authlib", + "sentry-sdk", +] [tool.poetry.scripts] canaille = "canaille.commands:cli" @@ -134,7 +164,7 @@ envlist = [testenv] allowlist_externals = poetry commands = - poetry install + poetry install --extras all poetry run pytest --showlocals --full-trace {posargs:-n auto} [testenv:style] @@ -149,7 +179,7 @@ commands = [testenv:coverage] commands = - poetry install + poetry install --extras all poetry run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered {posargs:-n auto} poetry run coverage html """