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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -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"),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

101
poetry.lock generated
View file

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

View file

@ -25,7 +25,7 @@ classifiers = [
"Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
]
authors = ["Yaal team <contact@yaal.coop>"]
authors = ["Yaal Coop <contact@yaal.coop>"]
maintainers = [
"Éloi Rivard <eloi@yaal.coop>",
]
@ -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
"""