Merge branch 'issue-167-extra-dependencies' into 'main'

split installation in different extras packages

Closes #167

See merge request yaal/canaille!150
This commit is contained in:
Éloi Rivard 2023-08-17 12:40:38 +00:00
commit 6e9f1ac0ad
28 changed files with 178 additions and 159 deletions

View file

@ -36,7 +36,7 @@ jobs:
- name: App armor configuration for slapd - name: App armor configuration for slapd
run: sudo aa-complain /usr/sbin/slapd run: sudo aa-complain /usr/sbin/slapd
- run: poetry --version - run: poetry --version
- run: poetry install - run: poetry install --extras all
- run: poetry run pytest - run: poetry run pytest
minversions: minversions:
name: minimum dependency versions name: minimum dependency versions
@ -59,7 +59,7 @@ jobs:
- run: sed -i -E 's/python = "==/python = "^/' pyproject.toml - run: sed -i -E 's/python = "==/python = "^/' pyproject.toml
- run: poetry --version - run: poetry --version
- run: poetry lock - run: poetry lock
- run: poetry install - run: poetry install --extras all
- run: poetry run pytest - run: poetry run pytest
style: style:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -26,28 +26,28 @@ python38:
image: python:3.8 image: python:3.8
stage: test stage: test
script: script:
- poetry install - poetry install --extras all
- poetry run pytest - poetry run pytest
python39: python39:
image: python:3.9 image: python:3.9
stage: test stage: test
script: script:
- poetry install - poetry install --extras all
- poetry run pytest - poetry run pytest
python310: python310:
image: python:3.10 image: python:3.10
stage: test stage: test
script: script:
- poetry install - poetry install --extras all
- poetry run pytest - poetry run pytest
python311: python311:
image: python:3.11 image: python:3.11
stage: test stage: test
script: script:
- poetry install - poetry install --extras all
- poetry run pytest - poetry run pytest
minversions: minversions:
@ -57,7 +57,7 @@ minversions:
- sed -i -E 's/"(\^|>=)([0-9\.]+)(.*)"/"==\2"/' pyproject.toml - sed -i -E 's/"(\^|>=)([0-9\.]+)(.*)"/"==\2"/' pyproject.toml
- sed -i -E 's/python = "==/python = "^/' pyproject.toml - sed -i -E 's/python = "==/python = "^/' pyproject.toml
- poetry lock - poetry lock
- poetry install - poetry install --extras all
- poetry run pytest - poetry run pytest
doc: doc:
@ -73,6 +73,6 @@ coverage:
allow_failure: true allow_failure: true
script: script:
- pip install coveralls pyyaml tomli - 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 - poetry run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered -n auto
- coveralls - coveralls

View file

@ -7,6 +7,7 @@ Added
***** *****
- Additional inmemory backend :issue:`30` :pr:`149` - Additional inmemory backend :issue:`30` :pr:`149`
- Installation extras :issue:`167` :pr:`150`
[0.0.31] - 2023-08-15 [0.0.31] - 2023-08-15
===================== =====================

View file

@ -1,11 +1,8 @@
import os
def create_mo_files(setup_kwargs): def create_mo_files(setup_kwargs):
from babel.messages.frontend import compile_catalog from babel.messages.frontend import compile_catalog
cmd = compile_catalog() cmd = compile_catalog()
cmd.directory = os.path.dirname(__file__) + "/canaille/translations" cmd.directory = "canaille/translations"
cmd.quiet = True cmd.quiet = True
cmd.statistics = True cmd.statistics = True
cmd.finalize_options() cmd.finalize_options()

View file

@ -1,13 +1,9 @@
import datetime import datetime
import os
from logging.config import dictConfig from logging.config import dictConfig
from flask import Flask from flask import Flask
from flask import request from flask import request
from flask import session 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 from flask_wtf.csrf import CSRFProtect
@ -64,25 +60,6 @@ def setup_jinja(app):
app.jinja_env.policies["ext.i18n.trimmed"] = True 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): def setup_blueprints(app):
import canaille.core.blueprints import canaille.core.blueprints
import canaille.oidc.blueprints import canaille.oidc.blueprints
@ -123,18 +100,26 @@ def setup_flask(app):
@app.errorhandler(400) @app.errorhandler(400)
def bad_request(error): def bad_request(error):
from canaille.app.themes import render_template
return render_template("error.html", description=error, error_code=400), 400 return render_template("error.html", description=error, error_code=400), 400
@app.errorhandler(403) @app.errorhandler(403)
def unauthorized(error): def unauthorized(error):
from canaille.app.themes import render_template
return render_template("error.html", description=error, error_code=403), 403 return render_template("error.html", description=error, error_code=403), 403
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(error): def page_not_found(error):
from canaille.app.themes import render_template
return render_template("error.html", description=error, error_code=404), 404 return render_template("error.html", description=error, error_code=404), 404
@app.errorhandler(500) @app.errorhandler(500)
def server_error(error): # pragma: no cover 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 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 .oidc.oauth import setup_oauth
from .app.i18n import setup_i18n from .app.i18n import setup_i18n
from .app.configuration import setup_config from .app.configuration import setup_config
from .app.themes import setup_themer
from .backends import setup_backend from .backends import setup_backend
app = Flask(__name__) app = Flask(__name__)

View file

@ -3,7 +3,6 @@ import smtplib
import socket import socket
from collections.abc import Mapping from collections.abc import Mapping
import toml
from canaille.app.mails import DEFAULT_SMTP_HOST from canaille.app.mails import DEFAULT_SMTP_HOST
from canaille.app.mails import DEFAULT_SMTP_PORT from canaille.app.mails import DEFAULT_SMTP_PORT
from flask import current_app from flask import current_app
@ -43,6 +42,11 @@ def parse_file_keys(config):
def setup_config(app, config=None, validate_config=True): def setup_config(app, config=None, validate_config=True):
from canaille.oidc.installation import install from canaille.oidc.installation import install
try:
import toml
except ImportError: # pragma: no cover
toml = None
app.config.from_mapping( app.config.from_mapping(
{ {
"SESSION_COOKIE_NAME": "canaille", "SESSION_COOKIE_NAME": "canaille",
@ -52,7 +56,7 @@ def setup_config(app, config=None, validate_config=True):
) )
if config: if config:
app.config.from_mapping(parse_file_keys(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")))) app.config.from_mapping(parse_file_keys(toml.load(os.environ.get("CONFIG"))))
else: else:
raise Exception( raise Exception(

View file

@ -4,13 +4,13 @@ from urllib.parse import urlsplit
from urllib.parse import urlunsplit from urllib.parse import urlunsplit
from canaille.app import models from canaille.app import models
from canaille.app.themes import render_template
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import g from flask import g
from flask import request from flask import request
from flask import session from flask import session
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter

View file

@ -3,7 +3,7 @@ import math
import re import re
import pytz import pytz
import wtforms import wtforms.validators
from canaille.app.i18n import DEFAULT_LANGUAGE_CODE from canaille.app.i18n import DEFAULT_LANGUAGE_CODE
from canaille.app.i18n import locale_selector from canaille.app.i18n import locale_selector
from canaille.app.i18n import timezone_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")) 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() meta = DefaultMeta()

View file

@ -1,6 +1,5 @@
import gettext import gettext
import pycountry
import pytz import pytz
from babel.dates import LOCALTZ from babel.dates import LOCALTZ
from flask import current_app from flask import current_app
@ -52,6 +51,11 @@ def timezone_selector():
def native_language_name_from_code(code): 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]) language = pycountry.languages.get(alpha_2=code[:2])
if code == DEFAULT_LANGUAGE_CODE: if code == DEFAULT_LANGUAGE_CODE:
return language.name return language.name

36
canaille/app/themes.py Normal file
View file

@ -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

View file

@ -7,11 +7,11 @@ import ldap.modlist
import ldif import ldif
from canaille.app import models from canaille.app import models
from canaille.app.configuration import ConfigurationException from canaille.app.configuration import ConfigurationException
from canaille.app.themes import render_template
from canaille.backends import BaseBackend from canaille.backends import BaseBackend
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from .utils import listify from .utils import listify

View file

@ -22,6 +22,7 @@ from canaille.app.forms import is_readonly
from canaille.app.forms import set_readonly from canaille.app.forms import set_readonly
from canaille.app.forms import set_writable from canaille.app.forms import set_writable
from canaille.app.forms import TableForm from canaille.app.forms import TableForm
from canaille.app.themes import render_template
from canaille.backends import BaseBackend from canaille.backends import BaseBackend
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
@ -34,7 +35,6 @@ from flask import send_file
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_babel import refresh from flask_babel import refresh
from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict from werkzeug.datastructures import CombinedMultiDict
from werkzeug.datastructures import FileStorage from werkzeug.datastructures import FileStorage

View file

@ -1,6 +1,8 @@
from canaille.app import obj_to_b64 from canaille.app import obj_to_b64
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.forms import email_validator
from canaille.app.forms import Form 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 build_hash
from canaille.core.mails import send_test_mail from canaille.core.mails import send_test_mail
from flask import Blueprint from flask import Blueprint
@ -9,10 +11,8 @@ from flask import flash
from flask import request from flask import request
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from wtforms import StringField from wtforms import StringField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from wtforms.validators import Email
bp = Blueprint("admin", __name__, url_prefix="/admin") bp = Blueprint("admin", __name__, url_prefix="/admin")
@ -23,7 +23,7 @@ class MailTestForm(Form):
_("Email"), _("Email"),
validators=[ validators=[
DataRequired(), DataRequired(),
Email(), email_validator,
], ],
render_kw={ render_kw={
"placeholder": _("jane@doe.com"), "placeholder": _("jane@doe.com"),

View file

@ -2,6 +2,7 @@ from canaille.app import build_hash
from canaille.app import models from canaille.app import models
from canaille.app.flask import current_user from canaille.app.flask import current_user
from canaille.app.flask import smtp_needed from canaille.app.flask import smtp_needed
from canaille.app.themes import render_template
from canaille.backends import BaseBackend from canaille.backends import BaseBackend
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
@ -12,7 +13,6 @@ from flask import request
from flask import session from flask import session
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from .forms import FirstLoginForm from .forms import FirstLoginForm
from .forms import ForgottenPasswordForm from .forms import ForgottenPasswordForm

View file

@ -2,6 +2,7 @@ import wtforms.form
from canaille.app import models from canaille.app import models
from canaille.app.forms import BaseForm from canaille.app.forms import BaseForm
from canaille.app.forms import DateTimeUTCField from canaille.app.forms import DateTimeUTCField
from canaille.app.forms import email_validator
from canaille.app.forms import Form from canaille.app.forms import Form
from canaille.app.forms import is_uri from canaille.app.forms import is_uri
from canaille.app.forms import phone_number from canaille.app.forms import phone_number
@ -164,7 +165,7 @@ PROFILE_FORM_FIELDS = dict(
_("Email addresses"), _("Email addresses"),
validators=[ validators=[
wtforms.validators.DataRequired(), wtforms.validators.DataRequired(),
wtforms.validators.Email(), email_validator,
unique_email, unique_email,
], ],
description=_( description=_(
@ -357,7 +358,7 @@ class JoinForm(Form):
_("Email address"), _("Email address"),
validators=[ validators=[
wtforms.validators.DataRequired(), wtforms.validators.DataRequired(),
wtforms.validators.Email(), email_validator,
], ],
render_kw={ render_kw={
"placeholder": _("jane@doe.com"), "placeholder": _("jane@doe.com"),
@ -382,7 +383,7 @@ class InvitationForm(Form):
_("Email address"), _("Email address"),
validators=[ validators=[
wtforms.validators.DataRequired(), wtforms.validators.DataRequired(),
wtforms.validators.Email(), email_validator,
unique_email, unique_email,
], ],
render_kw={ render_kw={
@ -420,7 +421,7 @@ class EmailConfirmationForm(Form):
_("New email address"), _("New email address"),
validators=[ validators=[
wtforms.validators.DataRequired(), wtforms.validators.DataRequired(),
wtforms.validators.Email(), email_validator,
unique_email, unique_email,
], ],
render_kw={ render_kw={

View file

@ -2,6 +2,7 @@ from canaille.app import models
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template from canaille.app.flask import render_htmx_template
from canaille.app.forms import TableForm from canaille.app.forms import TableForm
from canaille.app.themes import render_template
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
from flask import flash from flask import flash
@ -9,7 +10,6 @@ from flask import redirect
from flask import request from flask import request
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from .forms import CreateGroupForm from .forms import CreateGroupForm
from .forms import EditGroupForm from .forms import EditGroupForm

View file

@ -1,10 +1,10 @@
from canaille.app import build_hash from canaille.app import build_hash
from canaille.app.mails import logo from canaille.app.mails import logo
from canaille.app.mails import send_email from canaille.app.mails import send_email
from canaille.app.themes import render_template
from flask import current_app from flask import current_app
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
def send_test_mail(email): def send_test_mail(email):

View file

@ -2,10 +2,10 @@ from canaille.app import models
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template from canaille.app.flask import render_htmx_template
from canaille.app.forms import TableForm from canaille.app.forms import TableForm
from canaille.app.themes import render_template
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
from flask import request from flask import request
from flask_themer import render_template
bp = Blueprint("authorizations", __name__, url_prefix="/admin/authorization") bp = Blueprint("authorizations", __name__, url_prefix="/admin/authorization")

View file

@ -4,6 +4,7 @@ from canaille.app import models
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template from canaille.app.flask import render_htmx_template
from canaille.app.forms import TableForm from canaille.app.forms import TableForm
from canaille.app.themes import render_template
from canaille.oidc.forms import ClientAddForm from canaille.oidc.forms import ClientAddForm
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
@ -12,7 +13,6 @@ from flask import redirect
from flask import request from flask import request
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from werkzeug.security import gen_salt from werkzeug.security import gen_salt

View file

@ -3,12 +3,12 @@ import uuid
from canaille.app import models from canaille.app import models
from canaille.app.flask import user_needed from canaille.app.flask import user_needed
from canaille.app.themes import render_template
from flask import Blueprint from flask import Blueprint
from flask import flash from flask import flash
from flask import redirect from flask import redirect
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from .utils import SCOPE_DETAILS from .utils import SCOPE_DETAILS

View file

@ -9,6 +9,7 @@ from canaille import csrf
from canaille.app import models from canaille.app import models
from canaille.app.flask import current_user from canaille.app.flask import current_user
from canaille.app.flask import set_parameter_in_url_query from canaille.app.flask import set_parameter_in_url_query
from canaille.app.themes import render_template
from canaille.core.forms import FullLoginForm from canaille.core.forms import FullLoginForm
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
@ -20,7 +21,6 @@ from flask import request
from flask import session from flask import session
from flask import url_for from flask import url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict from werkzeug.datastructures import CombinedMultiDict
from .forms import AuthorizeForm from .forms import AuthorizeForm

View file

@ -1,5 +1,6 @@
import wtforms import wtforms
from canaille.app import models from canaille.app import models
from canaille.app.forms import email_validator
from canaille.app.forms import Form from canaille.app.forms import Form
from canaille.app.forms import is_uri from canaille.app.forms import is_uri
from canaille.app.forms import unique_values from canaille.app.forms import unique_values
@ -27,7 +28,7 @@ class ClientAddForm(Form):
contacts = wtforms.FieldList( contacts = wtforms.FieldList(
wtforms.EmailField( wtforms.EmailField(
_("Contacts"), _("Contacts"),
validators=[wtforms.validators.Optional(), wtforms.validators.Email()], validators=[wtforms.validators.Optional(), email_validator],
render_kw={"placeholder": "admin@mydomain.tld"}, render_kw={"placeholder": "admin@mydomain.tld"},
), ),
min_entries=1, min_entries=1,

View file

@ -4,13 +4,13 @@ from canaille.app import models
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template from canaille.app.flask import render_htmx_template
from canaille.app.forms import TableForm from canaille.app.forms import TableForm
from canaille.app.themes import render_template
from canaille.oidc.forms import TokenRevokationForm from canaille.oidc.forms import TokenRevokationForm
from flask import abort from flask import abort
from flask import Blueprint from flask import Blueprint
from flask import flash from flask import flash
from flask import request from flask import request
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template
bp = Blueprint("tokens", __name__, url_prefix="/admin/token") bp = Blueprint("tokens", __name__, url_prefix="/admin/token")

View file

@ -12,6 +12,6 @@ RUN \
COPY poetry.lock pyproject.toml demo/demoapp.py /opt/canaille/ COPY poetry.lock pyproject.toml demo/demoapp.py /opt/canaille/
RUN pip install poetry RUN pip install poetry
WORKDIR /opt/canaille 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"] ENTRYPOINT ["poetry", "run", "flask", "run", "--host=0.0.0.0", "--extra-files", "/opt/canaille/conf/canaille-memory.toml", "/opt/canaille/conf/canaille-ldap.toml"]

View file

@ -20,12 +20,11 @@ if ! type poetry > /dev/null 2>&1; then
exit 1 exit 1
fi fi
poetry install --with demo --without dev
pushd "$DIR" > /dev/null 2>&1 || exit pushd "$DIR" > /dev/null 2>&1 || exit
if [ "$BACKEND" = "memory" ]; then if [ "$BACKEND" = "memory" ]; then
poetry install --with demo --without dev --extras front --extras oidc
env poetry run honcho --procfile Procfile-memory start env poetry run honcho --procfile Procfile-memory start
elif [ "$BACKEND" = "ldap" ]; then elif [ "$BACKEND" = "ldap" ]; then
@ -36,6 +35,7 @@ elif [ "$BACKEND" = "ldap" ]; then
exit 1 exit 1
fi fi
poetry install --with demo --without dev --extras front --extras oidc --extras ldap
env poetry run honcho --procfile Procfile-ldap start env poetry run honcho --procfile Procfile-ldap start
else else

View file

@ -21,7 +21,18 @@ Let us choose a place for the canaille environment, like ``/opt/canaille/env``.
export CANAILLE_INSTALL_DIR=/opt/canaille export CANAILLE_INSTALL_DIR=/opt/canaille
sudo mkdir --parents "$CANAILLE_INSTALL_DIR" sudo mkdir --parents "$CANAILLE_INSTALL_DIR"
sudo virtualenv --python=python3 "$CANAILLE_INSTALL_DIR/env" 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 Configuration
============= =============

101
poetry.lock generated
View file

@ -59,7 +59,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
name = "authlib" name = "authlib"
version = "1.2.1" version = "1.2.1"
description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients."
optional = false optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "Authlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:c88984ea00149a90e3537c964327da930779afa4564e354edfd98410bea01911"}, {file = "Authlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:c88984ea00149a90e3537c964327da930779afa4564e354edfd98410bea01911"},
@ -457,7 +457,7 @@ files = [
name = "dnspython" name = "dnspython"
version = "2.4.2" version = "2.4.2"
description = "DNS toolkit" description = "DNS toolkit"
optional = false optional = true
python-versions = ">=3.8,<4.0" python-versions = ">=3.8,<4.0"
files = [ files = [
{file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"},
@ -487,7 +487,7 @@ files = [
name = "email-validator" name = "email-validator"
version = "2.0.0.post2" version = "2.0.0.post2"
description = "A robust email address syntax and deliverability validation library." description = "A robust email address syntax and deliverability validation library."
optional = false optional = true
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"},
@ -541,21 +541,6 @@ files = [
python-dateutil = ">=2.4" python-dateutil = ">=2.4"
typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\""} 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]] [[package]]
name = "filelock" name = "filelock"
version = "3.12.2" version = "3.12.2"
@ -614,7 +599,7 @@ pytz = ">=2022.7"
name = "flask-themer" name = "flask-themer"
version = "2.0.0" version = "2.0.0"
description = "Simple theme mechanism for Flask" description = "Simple theme mechanism for Flask"
optional = false optional = true
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "flask-themer-2.0.0.tar.gz", hash = "sha256:c8dbea370ad88d9d13e5d0c360f9678bb219bfbab7a7d0cb4f00ff89ac09a275"}, {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"}, {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]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "3.10.0" version = "3.10.0"
@ -1090,7 +1055,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
name = "pyasn1" name = "pyasn1"
version = "0.5.0" version = "0.5.0"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" 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" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [ files = [
{file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"},
@ -1101,7 +1066,7 @@ files = [
name = "pyasn1-modules" name = "pyasn1-modules"
version = "0.3.0" version = "0.3.0"
description = "A collection of ASN.1-based protocols modules" 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" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [ files = [
{file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, {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" name = "pycountry"
version = "22.3.5" version = "22.3.5"
description = "ISO country, subdivision, language, currency and script definitions and their translations" description = "ISO country, subdivision, language, currency and script definitions and their translations"
optional = false optional = true
python-versions = ">=3.6, <4" python-versions = ">=3.6, <4"
files = [ files = [
{file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"}, {file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"},
@ -1180,26 +1145,6 @@ lxml = ">=2.1"
[package.extras] [package.extras]
test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] 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]] [[package]]
name = "pytest" name = "pytest"
version = "7.4.0" version = "7.4.0"
@ -1353,7 +1298,7 @@ six = ">=1.5"
name = "python-ldap" name = "python-ldap"
version = "3.4.3" version = "3.4.3"
description = "Python modules for implementing LDAP clients" description = "Python modules for implementing LDAP clients"
optional = false optional = true
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"}, {file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"},
@ -1491,18 +1436,18 @@ tornado = ["tornado (>=5)"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "68.0.0" version = "68.1.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, {file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"},
{file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, {file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"},
] ]
[package.extras] [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"] 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.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"] 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"] 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]] [[package]]
@ -1750,7 +1695,7 @@ test = ["pytest"]
name = "toml" name = "toml"
version = "0.10.2" version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language" description = "Python Library for Tom's Obvious, Minimal Language"
optional = false optional = true
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
@ -1883,16 +1828,6 @@ MarkupSafe = ">=2.1.1"
[package.extras] [package.extras]
watchdog = ["watchdog"] 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]] [[package]]
name = "wtforms" name = "wtforms"
version = "3.0.1" 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"] 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] [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"] sentry = ["sentry-sdk"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "992ada3ebda103757cfda97ccc5775e21b11d8275d0ceeeeb1f29ecb1c5f9a45" content-hash = "739e40a2b7ee9549652e6616743dbc2c9a25b7f56456c2e0a4691b1e25bf5094"

View file

@ -25,7 +25,7 @@ classifiers = [
"Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP", "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
] ]
authors = ["Yaal team <contact@yaal.coop>"] authors = ["Yaal Coop <contact@yaal.coop>"]
maintainers = [ maintainers = [
"Éloi Rivard <eloi@yaal.coop>", "Éloi Rivard <eloi@yaal.coop>",
] ]
@ -37,29 +37,35 @@ include = ["canaille/translations/*/LC_MESSAGES/*.mo"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
authlib = "^1.2.1"
click = ">=8.0.0"
email_validator = "^2.0.0"
flask = ">=2.2.2 <2.3" flask = ">=2.2.2 <2.3"
flask-babel = "^3.0.0" flask-babel = "^3.0.0"
flask-themer = "^2.0.0"
flask-wtf = "^1.1.1" flask-wtf = "^1.1.1"
pycountry = ">=22.1.10"
python-ldap = "^3.4.0"
pytz = ">=2022.7" pytz = ">=2022.7"
toml = "^0.10.0"
wtforms = "^3.0.1" wtforms = "^3.0.1"
werkzeug = ">=2.2.2 <2.3" 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] [tool.poetry.group.doc]
optional = true optional = true
[tool.poetry.group.doc.dependencies] [tool.poetry.group.doc.dependencies]
"sphinx" = "*" sphinx = "*"
"sphinx-rtd-theme" = "*" sphinx-rtd-theme = "*"
"sphinx-issues" = "*" sphinx-issues = "*"
pygments-ldif = "==1.0.1" pygments-ldif = "==1.0.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
@ -67,7 +73,6 @@ coverage = {version = "*", extras=["toml"]}
faker = "*" faker = "*"
flask-webtest = "*" flask-webtest = "*"
freezegun = "*" freezegun = "*"
pdbpp = "*"
pre-commit = "*" pre-commit = "*"
pyquery = "*" pyquery = "*"
pytest = "*" pytest = "*"
@ -89,7 +94,32 @@ slapd = "*"
requests = "*" requests = "*"
[tool.poetry.extras] [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] [tool.poetry.scripts]
canaille = "canaille.commands:cli" canaille = "canaille.commands:cli"
@ -134,7 +164,7 @@ envlist =
[testenv] [testenv]
allowlist_externals = poetry allowlist_externals = poetry
commands = commands =
poetry install poetry install --extras all
poetry run pytest --showlocals --full-trace {posargs:-n auto} poetry run pytest --showlocals --full-trace {posargs:-n auto}
[testenv:style] [testenv:style]
@ -149,7 +179,7 @@ commands =
[testenv:coverage] [testenv:coverage]
commands = 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 pytest --cov --cov-fail-under=100 --cov-report term:skip-covered {posargs:-n auto}
poetry run coverage html poetry run coverage html
""" """