forked from Github-Mirrors/canaille
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:
commit
6e9f1ac0ad
28 changed files with 178 additions and 159 deletions
4
.github/workflows/tests.yaml
vendored
4
.github/workflows/tests.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,6 +7,7 @@ Added
|
|||
*****
|
||||
|
||||
- Additional inmemory backend :issue:`30` :pr:`149`
|
||||
- Installation extras :issue:`167` :pr:`150`
|
||||
|
||||
[0.0.31] - 2023-08-15
|
||||
=====================
|
||||
|
|
5
build.py
5
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()
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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
36
canaille/app/themes.py
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
101
poetry.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue