Merge branch 'issue-137-timezones' into 'main'

datetime-local input fields are transformed in UTC server-side

See merge request yaal/canaille!130
This commit is contained in:
Éloi Rivard 2023-06-01 11:56:29 +00:00
commit 7816b0e82f
12 changed files with 316 additions and 168 deletions

View file

@ -8,6 +8,7 @@ Added
- Implemented account expiration based on OpenLDAP ppolicy overlay. Needs OpenLDAP 2.5+ - Implemented account expiration based on OpenLDAP ppolicy overlay. Needs OpenLDAP 2.5+
:issue:`13` :pr:`118` :issue:`13` :pr:`118`
- Timezone configuration entry. :issue:`137` :pr:`130`
Fixed Fixed
***** *****

View file

@ -1,8 +1,11 @@
import datetime
import math import math
import pytz
import wtforms import wtforms
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 flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import make_response from flask import make_response
@ -81,3 +84,31 @@ class TableForm(I18NFormMixin, FlaskForm):
def validate_page(self, field): def validate_page(self, field):
if field.data < 1 or field.data > self.page_max: if field.data < 1 or field.data > self.page_max:
raise wtforms.validators.ValidationError(_("The page number is not valid")) raise wtforms.validators.ValidationError(_("The page number is not valid"))
class DateTimeUTCField(wtforms.DateTimeLocalField):
def _value(self):
if not self.data:
return ""
user_timezone = timezone_selector()
locale_dt = self.data.astimezone(user_timezone)
return locale_dt.strftime(self.format[0])
def process_formdata(self, valuelist):
if not valuelist:
return
date_str = " ".join(valuelist)
user_timezone = timezone_selector()
for format in self.strptime_format:
try:
unaware_dt = datetime.datetime.strptime(date_str, format)
locale_dt = user_timezone.localize(unaware_dt)
utc_dt = locale_dt.astimezone(pytz.utc)
self.data = utc_dt
return
except ValueError:
self.data = None
raise ValueError(self.gettext("Not a valid datetime value."))

View file

@ -1,6 +1,8 @@
import gettext import gettext
import pycountry import pycountry
import pytz
from babel.dates import LOCALTZ
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
@ -13,7 +15,9 @@ babel = Babel()
def setup_i18n(app): def setup_i18n(app):
babel.init_app(app, locale_selector=locale_selector) babel.init_app(
app, locale_selector=locale_selector, timezone_selector=timezone_selector
)
@app.before_request @app.before_request
def before_request(): def before_request():
@ -40,6 +44,13 @@ def locale_selector():
return request.accept_languages.best_match(available_language_codes) return request.accept_languages.best_match(available_language_codes)
def timezone_selector():
try:
return pytz.timezone(current_app.config.get("TIMEZONE"))
except pytz.exceptions.UnknownTimeZoneError:
return LOCALTZ
def native_language_name_from_code(code): def native_language_name_from_code(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:

View file

@ -25,6 +25,10 @@ SECRET_KEY = "change me before you go in production"
# If unset, language is detected # If unset, language is detected
# LANGUAGE = "en" # LANGUAGE = "en"
# The timezone in which datetimes will be displayed to the users.
# If unset, the server timezone will be used.
# TIMEZONE = UTC
# If you have a sentry instance, you can set its dsn here: # If you have a sentry instance, you can set its dsn here:
# SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0" # SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"

View file

@ -1,5 +1,6 @@
import wtforms.form import wtforms.form
from canaille.app import models from canaille.app import models
from canaille.app.forms import DateTimeUTCField
from canaille.app.forms import HTMXBaseForm from canaille.app.forms import HTMXBaseForm
from canaille.app.forms import HTMXForm from canaille.app.forms import HTMXForm
from canaille.app.forms import is_uri from canaille.app.forms import is_uri
@ -286,7 +287,7 @@ def profile_form(write_field_names, readonly_field_names, user=None):
del fields["groups"] del fields["groups"]
if current_app.backend.get().has_account_lockability(): # pragma: no branch if current_app.backend.get().has_account_lockability(): # pragma: no branch
fields["lock_date"] = wtforms.DateTimeLocalField( fields["lock_date"] = DateTimeUTCField(
_("Account expiration"), _("Account expiration"),
validators=[wtforms.validators.Optional()], validators=[wtforms.validators.Optional()],
format=[ format=[

View file

@ -25,6 +25,10 @@ FAVICON = "/static/img/canaille-c.png"
# If unset, language is detected # If unset, language is detected
# LANGUAGE = "en" # LANGUAGE = "en"
# The timezone in which datetimes will be displayed to the users.
# If unset, the server timezone will be used.
# TIMEZONE = UTC
# If you have a sentry instance, you can set its dsn here: # If you have a sentry instance, you can set its dsn here:
# SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0" # SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"

View file

@ -25,6 +25,10 @@ FAVICON = "/static/img/canaille-c.png"
# If unset, language is detected # If unset, language is detected
# LANGUAGE = "en" # LANGUAGE = "en"
# The timezone in which datetimes will be displayed to the users.
# If unset, the server timezone will be used.
# TIMEZONE = UTC
# If you have a sentry instance, you can set its dsn here: # If you have a sentry instance, you can set its dsn here:
# SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0" # SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"

View file

@ -34,6 +34,9 @@ Canaille is based on Flask, so any `flask configuration <https://flask.palletspr
:LANGUAGE: :LANGUAGE:
*Optional.* The locale code of the language to use. If not set, the language of the browser will be used. *Optional.* The locale code of the language to use. If not set, the language of the browser will be used.
:TIMEZONE:
*Optional.* The timezone in which datetimes will be displayed to the users. If unset, the server timezone will be used.
:SENTRY_DSN: :SENTRY_DSN:
*Optional.* A DSN to a sentry instance. *Optional.* A DSN to a sentry instance.
This needs the ``sentry_sdk`` python package to be installed. This needs the ``sentry_sdk`` python package to be installed.

233
poetry.lock generated
View file

@ -1,10 +1,9 @@
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand.
[[package]] [[package]]
name = "aiosmtpd" name = "aiosmtpd"
version = "1.4.4.post2" version = "1.4.4.post2"
description = "aiosmtpd - asyncio based SMTP server" description = "aiosmtpd - asyncio based SMTP server"
category = "dev"
optional = false optional = false
python-versions = "~=3.7" python-versions = "~=3.7"
files = [ files = [
@ -21,7 +20,6 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
name = "alabaster" name = "alabaster"
version = "0.7.13" version = "0.7.13"
description = "A configurable sidebar-enabled Sphinx theme" description = "A configurable sidebar-enabled Sphinx theme"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -33,7 +31,6 @@ files = [
name = "atpublic" name = "atpublic"
version = "3.1.1" version = "3.1.1"
description = "Keep all y'all's __all__'s in sync" description = "Keep all y'all's __all__'s in sync"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -45,7 +42,6 @@ files = [
name = "attrs" name = "attrs"
version = "23.1.0" version = "23.1.0"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -67,7 +63,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
name = "authlib" name = "authlib"
version = "1.2.0" version = "1.2.0"
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."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -82,7 +77,6 @@ cryptography = ">=3.2"
name = "babel" name = "babel"
version = "2.12.1" version = "2.12.1"
description = "Internationalization utilities" description = "Internationalization utilities"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -97,7 +91,6 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""}
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.12.2" version = "4.12.2"
description = "Screen-scraping library" description = "Screen-scraping library"
category = "dev"
optional = false optional = false
python-versions = ">=3.6.0" python-versions = ">=3.6.0"
files = [ files = [
@ -116,7 +109,6 @@ lxml = ["lxml"]
name = "blinker" name = "blinker"
version = "1.6.2" version = "1.6.2"
description = "Fast, simple object-to-object and broadcast signaling" description = "Fast, simple object-to-object and broadcast signaling"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -128,7 +120,6 @@ files = [
name = "certifi" name = "certifi"
version = "2023.5.7" version = "2023.5.7"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -140,7 +131,6 @@ files = [
name = "cffi" name = "cffi"
version = "1.15.1" version = "1.15.1"
description = "Foreign Function Interface for Python calling C code." description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -217,7 +207,6 @@ pycparser = "*"
name = "cfgv" name = "cfgv"
version = "3.3.1" version = "3.3.1"
description = "Validate configuration and produce human readable error messages." description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false optional = false
python-versions = ">=3.6.1" python-versions = ">=3.6.1"
files = [ files = [
@ -229,7 +218,6 @@ files = [
name = "charset-normalizer" name = "charset-normalizer"
version = "3.1.0" version = "3.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
@ -314,7 +302,6 @@ files = [
name = "click" name = "click"
version = "8.1.3" version = "8.1.3"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -330,7 +317,6 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [ files = [
@ -340,63 +326,62 @@ files = [
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.2.5" version = "7.2.6"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "coverage-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c"}, {file = "coverage-7.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:496b86f1fc9c81a1cd53d8842ef712e950a4611bba0c42d33366a7b91ba969ec"},
{file = "coverage-7.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a"}, {file = "coverage-7.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbe6e8c0a9a7193ba10ee52977d4d5e7652957c1f56ccefed0701db8801a2a3b"},
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f"}, {file = "coverage-7.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d06b721c2550c01a60e5d3093f417168658fb454e5dfd9a23570e9bffe39a1"},
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a"}, {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77a04b84d01f0e12c66f16e69e92616442dc675bbe51b90bfb074b1e5d1c7fbd"},
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a"}, {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35db06450272473eab4449e9c2ad9bc6a0a68dab8e81a0eae6b50d9c2838767e"},
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11"}, {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6727a0d929ff0028b1ed8b3e7f8701670b1d7032f219110b55476bb60c390bfb"},
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5"}, {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aac1d5fdc5378f6bac2c0c7ebe7635a6809f5b4376f6cf5d43243c1917a67087"},
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c"}, {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c9e4a5eb1bbc3675ee57bc31f8eea4cd7fb0cbcbe4912cf1cb2bf3b754f4a80"},
{file = "coverage-7.2.5-cp310-cp310-win32.whl", hash = "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5"}, {file = "coverage-7.2.6-cp310-cp310-win32.whl", hash = "sha256:71f739f97f5f80627f1fee2331e63261355fd1e9a9cce0016394b6707ac3f4ec"},
{file = "coverage-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c"}, {file = "coverage-7.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:fde5c7a9d9864d3e07992f66767a9817f24324f354caa3d8129735a3dc74f126"},
{file = "coverage-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce"}, {file = "coverage-7.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc7b667f8654376e9353dd93e55e12ce2a59fb6d8e29fce40de682273425e044"},
{file = "coverage-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88"}, {file = "coverage-7.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:697f4742aa3f26c107ddcb2b1784a74fe40180014edbd9adaa574eac0529914c"},
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e"}, {file = "coverage-7.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:541280dde49ce74a4262c5e395b48ea1207e78454788887118c421cb4ffbfcac"},
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2"}, {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7f1a8328eeec34c54f1d5968a708b50fc38d31e62ca8b0560e84a968fbf9a9"},
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139"}, {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbd58eb5a2371bf160590f4262109f66b6043b0b991930693134cb617bc0169"},
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8"}, {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae82c5f168d2a39a5d69a12a69d4dc23837a43cf2ca99be60dfe59996ea6b113"},
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed"}, {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f5440cdaf3099e7ab17a5a7065aed59aff8c8b079597b61c1f8be6f32fe60636"},
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6"}, {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6f03f87fea579d55e0b690d28f5042ec1368650466520fbc400e7aeaf09e995"},
{file = "coverage-7.2.5-cp311-cp311-win32.whl", hash = "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b"}, {file = "coverage-7.2.6-cp311-cp311-win32.whl", hash = "sha256:dc4d5187ef4d53e0d4c8eaf530233685667844c5fb0b855fea71ae659017854b"},
{file = "coverage-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068"}, {file = "coverage-7.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:c93d52c3dc7b9c65e39473704988602300e3cc1bad08b5ab5b03ca98bbbc68c1"},
{file = "coverage-7.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1"}, {file = "coverage-7.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42c692b55a647a832025a4c048007034fe77b162b566ad537ce65ad824b12a84"},
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33"}, {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7786b2fa7809bf835f830779ad285215a04da76293164bb6745796873f0942d"},
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade"}, {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25bad4196104761bc26b1dae9b57383826542ec689ff0042f7f4f4dd7a815cba"},
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5"}, {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2692306d3d4cb32d2cceed1e47cebd6b1d2565c993d6d2eda8e6e6adf53301e6"},
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47"}, {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:392154d09bd4473b9d11351ab5d63391f3d5d24d752f27b3be7498b0ee2b5226"},
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd"}, {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fa079995432037b5e2ef5ddbb270bcd2ded9f52b8e191a5de11fe59a00ea30d8"},
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969"}, {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d712cefff15c712329113b01088ba71bbcef0f7ea58478ca0bbec63a824844cb"},
{file = "coverage-7.2.5-cp37-cp37m-win32.whl", hash = "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718"}, {file = "coverage-7.2.6-cp37-cp37m-win32.whl", hash = "sha256:004948e296149644d208964300cb3d98affc5211e9e490e9979af4030b0d6473"},
{file = "coverage-7.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0"}, {file = "coverage-7.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:c1d7a31603c3483ac49c1726723b0934f88f2c011c660e6471e7bd735c2fa110"},
{file = "coverage-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84"}, {file = "coverage-7.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3436927d1794fa6763b89b60c896f9e3bd53212001026ebc9080d23f0c2733c1"},
{file = "coverage-7.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790"}, {file = "coverage-7.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44c9b9f1a245f3d0d202b1a8fa666a80b5ecbe4ad5d0859c0fb16a52d9763224"},
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771"}, {file = "coverage-7.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e3783a286d5a93a2921396d50ce45a909aa8f13eee964465012f110f0cbb611"},
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045"}, {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cff6980fe7100242170092bb40d2b1cdad79502cd532fd26b12a2b8a5f9aee0"},
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614"}, {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c534431153caffc7c495c3eddf7e6a6033e7f81d78385b4e41611b51e8870446"},
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3"}, {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3062fd5c62df988cea9f2972c593f77fed1182bfddc5a3b12b1e606cb7aba99e"},
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd"}, {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6284a2005e4f8061c58c814b1600ad0074ccb0289fe61ea709655c5969877b70"},
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1"}, {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:97729e6828643f168a2a3f07848e1b1b94a366b13a9f5aba5484c2215724edc8"},
{file = "coverage-7.2.5-cp38-cp38-win32.whl", hash = "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813"}, {file = "coverage-7.2.6-cp38-cp38-win32.whl", hash = "sha256:dc11b42fa61ff1e788dd095726a0aed6aad9c03d5c5984b54cb9e1e67b276aa5"},
{file = "coverage-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212"}, {file = "coverage-7.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:cbcc874f454ee51f158afd604a315f30c0e31dff1d5d5bf499fc529229d964dd"},
{file = "coverage-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b"}, {file = "coverage-7.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d3cacc6a665221108ecdf90517a8028d07a2783df3417d12dcfef1c517e67478"},
{file = "coverage-7.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200"}, {file = "coverage-7.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:272ab31228a9df857ab5df5d67936d8861464dc89c5d3fab35132626e9369379"},
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5"}, {file = "coverage-7.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a8723ccec4e564d4b9a79923246f7b9a8de4ec55fa03ec4ec804459dade3c4f"},
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e"}, {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5906f6a84b47f995cd1bf0aca1c72d591c55ee955f98074e93660d64dfc66eb9"},
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303"}, {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c139b7ab3f0b15f9aad0a3fedef5a1f8c0b2bdc291d88639ca2c97d3682416"},
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3"}, {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a5ffd45c6b93c23a8507e2f436983015c6457aa832496b6a095505ca2f63e8f1"},
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a"}, {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4f3c7c19581d471af0e9cb49d928172cd8492cd78a2b7a4e82345d33662929bb"},
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1"}, {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e8c0e79820cdd67978e1120983786422d279e07a381dbf89d03bbb23ec670a6"},
{file = "coverage-7.2.5-cp39-cp39-win32.whl", hash = "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31"}, {file = "coverage-7.2.6-cp39-cp39-win32.whl", hash = "sha256:13cde6bb0e58fb67d09e2f373de3899d1d1e866c5a9ff05d93615f2f54fbd2bb"},
{file = "coverage-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252"}, {file = "coverage-7.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:6b9f64526286255735847aed0221b189486e0b9ed943446936e41b7e44b08783"},
{file = "coverage-7.2.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3"}, {file = "coverage-7.2.6-pp37.pp38.pp39-none-any.whl", hash = "sha256:6babcbf1e66e46052442f10833cfc4a0d3554d8276aa37af8531a83ed3c1a01d"},
{file = "coverage-7.2.5.tar.gz", hash = "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"}, {file = "coverage-7.2.6.tar.gz", hash = "sha256:2025f913f2edb0272ef15d00b1f335ff8908c921c8eb2013536fcaf61f5a683d"},
] ]
[package.dependencies] [package.dependencies]
@ -409,7 +394,6 @@ toml = ["tomli"]
name = "cryptography" name = "cryptography"
version = "39.0.2" version = "39.0.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -455,7 +439,6 @@ tox = ["tox"]
name = "cryptography" name = "cryptography"
version = "40.0.2" version = "40.0.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -497,7 +480,6 @@ tox = ["tox"]
name = "cssselect" name = "cssselect"
version = "1.2.0" version = "1.2.0"
description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -509,7 +491,6 @@ files = [
name = "distlib" name = "distlib"
version = "0.3.6" version = "0.3.6"
description = "Distribution utilities" description = "Distribution utilities"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -521,7 +502,6 @@ files = [
name = "dnspython" name = "dnspython"
version = "2.3.0" version = "2.3.0"
description = "DNS toolkit" description = "DNS toolkit"
category = "main"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -542,7 +522,6 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"]
name = "docutils" name = "docutils"
version = "0.18.1" version = "0.18.1"
description = "Docutils -- Python Documentation Utilities" description = "Docutils -- Python Documentation Utilities"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [ files = [
@ -554,7 +533,6 @@ 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."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -570,7 +548,6 @@ idna = ">=2.0.0"
name = "exceptiongroup" name = "exceptiongroup"
version = "1.1.1" version = "1.1.1"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -585,7 +562,6 @@ test = ["pytest (>=6)"]
name = "faker" name = "faker"
version = "18.9.0" version = "18.9.0"
description = "Faker is a Python package that generates fake data for you." description = "Faker is a Python package that generates fake data for you."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -601,7 +577,6 @@ typing-extensions = {version = ">=3.10.0.1", markers = "python_version < \"3.8\"
name = "fancycompleter" name = "fancycompleter"
version = "0.9.1" version = "0.9.1"
description = "colorful TAB completion for Python prompt" description = "colorful TAB completion for Python prompt"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -617,7 +592,6 @@ pyrepl = ">=0.8.2"
name = "filelock" name = "filelock"
version = "3.12.0" version = "3.12.0"
description = "A platform independent file lock." description = "A platform independent file lock."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -633,7 +607,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "p
name = "flask" name = "flask"
version = "2.2.5" version = "2.2.5"
description = "A simple framework for building complex web applications." description = "A simple framework for building complex web applications."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -656,7 +629,6 @@ dotenv = ["python-dotenv"]
name = "flask-babel" name = "flask-babel"
version = "3.1.0" version = "3.1.0"
description = "Adds i18n/l10n support for Flask applications." description = "Adds i18n/l10n support for Flask applications."
category = "main"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -674,7 +646,6 @@ pytz = ">=2022.7"
name = "flask-themer" name = "flask-themer"
version = "1.4.3" version = "1.4.3"
description = "Simple theme mechanism for Flask" description = "Simple theme mechanism for Flask"
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -689,7 +660,6 @@ flask = "*"
name = "flask-webtest" name = "flask-webtest"
version = "0.1.3" version = "0.1.3"
description = "Utilities for testing Flask applications with WebTest." description = "Utilities for testing Flask applications with WebTest."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -709,7 +679,6 @@ tests = ["flask-sqlalchemy"]
name = "flask-wtf" name = "flask-wtf"
version = "1.1.1" version = "1.1.1"
description = "Form rendering, validation, and CSRF protection for Flask with WTForms." description = "Form rendering, validation, and CSRF protection for Flask with WTForms."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -729,7 +698,6 @@ email = ["email-validator"]
name = "freezegun" name = "freezegun"
version = "1.2.2" version = "1.2.2"
description = "Let your Python tests travel through time" description = "Let your Python tests travel through time"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -744,7 +712,6 @@ python-dateutil = ">=2.7"
name = "honcho" name = "honcho"
version = "1.1.0" version = "1.1.0"
description = "Honcho: a Python clone of Foreman. For managing Procfile-based applications." description = "Honcho: a Python clone of Foreman. For managing Procfile-based applications."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -762,7 +729,6 @@ export = ["jinja2 (>=2.7,<3)"]
name = "identify" name = "identify"
version = "2.5.24" version = "2.5.24"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -777,7 +743,6 @@ license = ["ukkonen"]
name = "idna" name = "idna"
version = "3.4" version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -789,7 +754,6 @@ files = [
name = "imagesize" name = "imagesize"
version = "1.4.1" version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file" description = "Getting image size from png/jpeg/jpeg2000/gif file"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
@ -801,7 +765,6 @@ files = [
name = "importlib-metadata" name = "importlib-metadata"
version = "6.6.0" version = "6.6.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -822,7 +785,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -834,7 +796,6 @@ files = [
name = "itsdangerous" name = "itsdangerous"
version = "2.1.2" version = "2.1.2"
description = "Safely pass data to untrusted environments and back." description = "Safely pass data to untrusted environments and back."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -846,7 +807,6 @@ files = [
name = "jinja2" name = "jinja2"
version = "3.1.2" version = "3.1.2"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -864,7 +824,6 @@ i18n = ["Babel (>=2.7)"]
name = "lxml" name = "lxml"
version = "4.9.2" version = "4.9.2"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
files = [ files = [
@ -957,7 +916,6 @@ source = ["Cython (>=0.29.7)"]
name = "markupsafe" name = "markupsafe"
version = "2.1.2" version = "2.1.2"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1017,7 +975,6 @@ files = [
name = "mock" name = "mock"
version = "5.0.2" version = "5.0.2"
description = "Rolling backport of unittest.mock for all Pythons" description = "Rolling backport of unittest.mock for all Pythons"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1034,7 +991,6 @@ test = ["pytest", "pytest-cov"]
name = "nodeenv" name = "nodeenv"
version = "1.8.0" version = "1.8.0"
description = "Node.js virtual environment builder" description = "Node.js virtual environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [ files = [
@ -1049,7 +1005,6 @@ setuptools = "*"
name = "packaging" name = "packaging"
version = "23.1" version = "23.1"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1061,7 +1016,6 @@ files = [
name = "pdbpp" name = "pdbpp"
version = "0.10.3" version = "0.10.3"
description = "pdb++, a drop-in replacement for pdb" description = "pdb++, a drop-in replacement for pdb"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1082,7 +1036,6 @@ testing = ["funcsigs", "pytest"]
name = "platformdirs" name = "platformdirs"
version = "3.5.1" version = "3.5.1"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1101,7 +1054,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-
name = "pluggy" name = "pluggy"
version = "1.0.0" version = "1.0.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1120,7 +1072,6 @@ testing = ["pytest", "pytest-benchmark"]
name = "portpicker" name = "portpicker"
version = "1.5.2" version = "1.5.2"
description = "A library to choose unique available network ports." description = "A library to choose unique available network ports."
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1135,7 +1086,6 @@ psutil = "*"
name = "pre-commit" name = "pre-commit"
version = "2.21.0" version = "2.21.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1155,7 +1105,6 @@ virtualenv = ">=20.10.0"
name = "psutil" name = "psutil"
version = "5.9.5" version = "5.9.5"
description = "Cross-platform lib for process and system monitoring in Python." description = "Cross-platform lib for process and system monitoring in Python."
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
@ -1182,7 +1131,6 @@ 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)"
category = "main"
optional = false optional = false
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 = [
@ -1194,7 +1142,6 @@ 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"
category = "main"
optional = false optional = false
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 = [
@ -1209,7 +1156,6 @@ 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"
category = "main"
optional = false optional = false
python-versions = ">=3.6, <4" python-versions = ">=3.6, <4"
files = [ files = [
@ -1223,7 +1169,6 @@ setuptools = "*"
name = "pycparser" name = "pycparser"
version = "2.21" version = "2.21"
description = "C parser in Python" description = "C parser in Python"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
@ -1235,7 +1180,6 @@ files = [
name = "pygments" name = "pygments"
version = "2.15.1" version = "2.15.1"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1250,7 +1194,6 @@ plugins = ["importlib-metadata"]
name = "pyquery" name = "pyquery"
version = "2.0.0" version = "2.0.0"
description = "A jquery-like library for python" description = "A jquery-like library for python"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1269,7 +1212,6 @@ test = ["pytest", "pytest-cov", "requests", "webob", "webtest"]
name = "pyreadline" name = "pyreadline"
version = "2.1" version = "2.1"
description = "A python implmementation of GNU readline." description = "A python implmementation of GNU readline."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1280,7 +1222,6 @@ files = [
name = "pyrepl" name = "pyrepl"
version = "0.9.0" version = "0.9.0"
description = "A library for building flexible command line interfaces" description = "A library for building flexible command line interfaces"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1291,7 +1232,6 @@ files = [
name = "pytest" name = "pytest"
version = "7.3.1" version = "7.3.1"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1313,14 +1253,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
[[package]] [[package]]
name = "pytest-cov" name = "pytest-cov"
version = "4.0.0" version = "4.1.0"
description = "Pytest plugin for measuring coverage." description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
files = [ files = [
{file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
{file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
] ]
[package.dependencies] [package.dependencies]
@ -1334,7 +1273,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
name = "pytest-cover" name = "pytest-cover"
version = "3.0.0" version = "3.0.0"
description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`." description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1349,7 +1287,6 @@ pytest-cov = ">=2.0"
name = "pytest-coverage" name = "pytest-coverage"
version = "0.0" version = "0.0"
description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`." description = "Pytest plugin for measuring coverage. Forked from `pytest-cov`."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1364,7 +1301,6 @@ pytest-cover = "*"
name = "pytest-flask" name = "pytest-flask"
version = "1.2.0" version = "1.2.0"
description = "A set of py.test fixtures to test Flask applications." description = "A set of py.test fixtures to test Flask applications."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1384,7 +1320,6 @@ docs = ["Sphinx", "sphinx-rtd-theme"]
name = "pytest-httpserver" name = "pytest-httpserver"
version = "1.0.6" version = "1.0.6"
description = "pytest-httpserver is a httpserver for pytest" description = "pytest-httpserver is a httpserver for pytest"
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -1399,7 +1334,6 @@ Werkzeug = ">=2.0.0"
name = "python-dateutil" name = "python-dateutil"
version = "2.8.2" version = "2.8.2"
description = "Extensions to the standard Python datetime module" description = "Extensions to the standard Python datetime module"
category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [ files = [
@ -1414,7 +1348,6 @@ 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"
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1429,7 +1362,6 @@ pyasn1_modules = ">=0.1.5"
name = "pytz" name = "pytz"
version = "2023.3" version = "2023.3"
description = "World timezone definitions, modern and historical" description = "World timezone definitions, modern and historical"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1441,7 +1373,6 @@ files = [
name = "pyyaml" name = "pyyaml"
version = "6.0" version = "6.0"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1489,14 +1420,13 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.30.0" version = "2.31.0"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
] ]
[package.dependencies] [package.dependencies]
@ -1513,7 +1443,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "sentry-sdk" name = "sentry-sdk"
version = "1.21.1" version = "1.21.1"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1557,7 +1486,6 @@ tornado = ["tornado (>=5)"]
name = "setuptools" name = "setuptools"
version = "67.8.0" version = "67.8.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1574,7 +1502,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
@ -1586,7 +1513,6 @@ files = [
name = "slapd" name = "slapd"
version = "0.1.3" version = "0.1.3"
description = "Controls a slapd process in a pythonic way" description = "Controls a slapd process in a pythonic way"
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4" python-versions = ">=3.7,<4"
files = [ files = [
@ -1598,7 +1524,6 @@ files = [
name = "smtpdfix" name = "smtpdfix"
version = "0.5.0" version = "0.5.0"
description = "A SMTP server for use as a pytest fixture that implements encryption and authentication for testing." description = "A SMTP server for use as a pytest fixture that implements encryption and authentication for testing."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1623,7 +1548,6 @@ typing = ["mypy"]
name = "snowballstemmer" name = "snowballstemmer"
version = "2.2.0" version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1635,7 +1559,6 @@ files = [
name = "soupsieve" name = "soupsieve"
version = "2.4.1" version = "2.4.1"
description = "A modern CSS selector implementation for Beautiful Soup." description = "A modern CSS selector implementation for Beautiful Soup."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1647,7 +1570,6 @@ files = [
name = "sphinx" name = "sphinx"
version = "5.3.0" version = "5.3.0"
description = "Python documentation generator" description = "Python documentation generator"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1683,7 +1605,6 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"]
name = "sphinx-issues" name = "sphinx-issues"
version = "3.0.1" version = "3.0.1"
description = "A Sphinx extension for linking to your project's issue tracker" description = "A Sphinx extension for linking to your project's issue tracker"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1701,14 +1622,13 @@ tests = ["pytest (>=6.2.0)"]
[[package]] [[package]]
name = "sphinx-rtd-theme" name = "sphinx-rtd-theme"
version = "1.2.0" version = "1.2.1"
description = "Read the Docs theme for Sphinx" description = "Read the Docs theme for Sphinx"
category = "dev"
optional = false optional = false
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 = "sphinx_rtd_theme-1.2.0-py2.py3-none-any.whl", hash = "sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2"}, {file = "sphinx_rtd_theme-1.2.1-py2.py3-none-any.whl", hash = "sha256:2cc9351176cbf91944ce44cefd4fab6c3b76ac53aa9e15d6db45a3229ad7f866"},
{file = "sphinx_rtd_theme-1.2.0.tar.gz", hash = "sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8"}, {file = "sphinx_rtd_theme-1.2.1.tar.gz", hash = "sha256:cf9a7dc0352cf179c538891cb28d6fad6391117d4e21c891776ab41dd6c8ff70"},
] ]
[package.dependencies] [package.dependencies]
@ -1723,7 +1643,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"]
name = "sphinxcontrib-applehelp" name = "sphinxcontrib-applehelp"
version = "1.0.2" version = "1.0.2"
description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1739,7 +1658,6 @@ test = ["pytest"]
name = "sphinxcontrib-devhelp" name = "sphinxcontrib-devhelp"
version = "1.0.2" version = "1.0.2"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1755,7 +1673,6 @@ test = ["pytest"]
name = "sphinxcontrib-htmlhelp" name = "sphinxcontrib-htmlhelp"
version = "2.0.0" version = "2.0.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -1771,7 +1688,6 @@ test = ["html5lib", "pytest"]
name = "sphinxcontrib-jquery" name = "sphinxcontrib-jquery"
version = "4.1" version = "4.1"
description = "Extension to include jQuery on newer Sphinx releases" description = "Extension to include jQuery on newer Sphinx releases"
category = "dev"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
files = [ files = [
@ -1786,7 +1702,6 @@ Sphinx = ">=1.8"
name = "sphinxcontrib-jsmath" name = "sphinxcontrib-jsmath"
version = "1.0.1" version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript" description = "A sphinx extension which renders display math in HTML via JavaScript"
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1801,7 +1716,6 @@ test = ["flake8", "mypy", "pytest"]
name = "sphinxcontrib-qthelp" name = "sphinxcontrib-qthelp"
version = "1.0.3" version = "1.0.3"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1817,7 +1731,6 @@ test = ["pytest"]
name = "sphinxcontrib-serializinghtml" name = "sphinxcontrib-serializinghtml"
version = "1.1.5" version = "1.1.5"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -1833,7 +1746,6 @@ 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"
category = "main"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
@ -1845,7 +1757,6 @@ files = [
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1855,21 +1766,19 @@ files = [
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.5.0" version = "4.6.2"
description = "Backported and Experimental Type Hints for Python 3.7+" description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, {file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"},
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, {file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"},
] ]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.0.2" version = "2.0.2"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1887,7 +1796,6 @@ zstd = ["zstandard (>=0.18.0)"]
name = "virtualenv" name = "virtualenv"
version = "20.23.0" version = "20.23.0"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1909,7 +1817,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess
name = "waitress" name = "waitress"
version = "2.1.2" version = "2.1.2"
description = "Waitress WSGI server" description = "Waitress WSGI server"
category = "dev"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
@ -1925,7 +1832,6 @@ testing = ["coverage (>=5.0)", "pytest", "pytest-cover"]
name = "webob" name = "webob"
version = "1.8.7" version = "1.8.7"
description = "WSGI request and response object" description = "WSGI request and response object"
category = "dev"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
files = [ files = [
@ -1941,7 +1847,6 @@ testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"]
name = "webtest" name = "webtest"
version = "3.0.0" version = "3.0.0"
description = "Helper to test WSGI applications" description = "Helper to test WSGI applications"
category = "dev"
optional = false optional = false
python-versions = ">=3.6, <4" python-versions = ">=3.6, <4"
files = [ files = [
@ -1962,7 +1867,6 @@ tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-c
name = "werkzeug" name = "werkzeug"
version = "2.2.3" version = "2.2.3"
description = "The comprehensive WSGI web application library." description = "The comprehensive WSGI web application library."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -1980,7 +1884,6 @@ watchdog = ["watchdog"]
name = "wmctrl" name = "wmctrl"
version = "0.4" version = "0.4"
description = "A tool to programmatically control windows inside X" description = "A tool to programmatically control windows inside X"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -1991,7 +1894,6 @@ files = [
name = "wtforms" name = "wtforms"
version = "3.0.1" version = "3.0.1"
description = "Form validation and rendering for Python web development." description = "Form validation and rendering for Python web development."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2009,7 +1911,6 @@ email = ["email-validator"]
name = "zipp" name = "zipp"
version = "3.15.0" version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -2027,4 +1928,4 @@ sentry = ["sentry-sdk"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.7, <4" python-versions = ">=3.7, <4"
content-hash = "ba916dfc7340b0327fba0582d2153de589525f77820e411afc10b2fd19060c22" content-hash = "ea6edd79777591d8c6232b5cd2d712a4de68e72475aca7b7c6417328eac5ea61"

View file

@ -46,6 +46,7 @@ flask-themer = "<2"
flask-wtf = "<2" flask-wtf = "<2"
pycountry = "^22.3.5" pycountry = "^22.3.5"
python-ldap = "<4" python-ldap = "<4"
pytz = "^2023.3"
toml = "<1" toml = "<1"
wtforms = "<4" wtforms = "<4"

186
tests/app/test_forms.py Normal file
View file

@ -0,0 +1,186 @@
import datetime
import wtforms
from babel.dates import LOCALTZ
from canaille.app.forms import DateTimeUTCField
from flask import current_app
from werkzeug.datastructures import ImmutableMultiDict
def test_datetime_utc_field_no_timezone_is_local_timezone(testclient):
del current_app.config["TIMEZONE"]
offset = LOCALTZ.utcoffset(datetime.datetime.utcnow())
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 12) + offset
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
def test_datetime_utc_field_utc(testclient):
current_app.config["TIMEZONE"] = "UTC"
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
rendered_date = date.strftime("%Y-%m-%d %H:%M:%S")
rendered_date_form = date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date_form}">'
)
form = TestForm(data={"dt": date})
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date}">'
)
class Foobar:
dt = date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date}">'
)
def test_datetime_utc_field_japan_timezone(testclient):
current_app.config["TIMEZONE"] = "Japan"
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 21)
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
def test_datetime_utc_field_invalid_timezone(testclient):
current_app.config["TIMEZONE"] = "invalid"
offset = LOCALTZ.utcoffset(datetime.datetime.utcnow())
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 12) + offset
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)

View file

@ -76,6 +76,7 @@ def configuration(slapd_server, smtpd):
conf = { conf = {
"SECRET_KEY": gen_salt(24), "SECRET_KEY": gen_salt(24),
"LOGO": "/static/img/canaille-head.png", "LOGO": "/static/img/canaille-head.png",
"TIMEZONE": "UTC",
"BACKENDS": { "BACKENDS": {
"LDAP": { "LDAP": {
"ROOT_DN": slapd_server.suffix, "ROOT_DN": slapd_server.suffix,