diff options
author | Daniel Schadt <kingdread@gmx.de> | 2022-11-20 00:43:16 +0100 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2022-11-20 00:43:16 +0100 |
commit | b117ddcde172c4a9c2c377ac5aa08f5ede345f2d (patch) | |
tree | 87eb96d960c8df04eed0f4e2191bc6044fa9e773 | |
parent | 97835c471bc10bd20e1253e1bbfd71e5e71b2883 (diff) | |
parent | 20fd6620deb79f214cd2b7c9474b5df0d2fbda5b (diff) | |
download | fietsboek-b117ddcde172c4a9c2c377ac5aa08f5ede345f2d.tar.gz fietsboek-b117ddcde172c4a9c2c377ac5aa08f5ede345f2d.tar.bz2 fietsboek-b117ddcde172c4a9c2c377ac5aa08f5ede345f2d.zip |
Merge branch 'tile-proxy'
-rw-r--r-- | development.ini | 1 | ||||
-rw-r--r-- | doc/administration/configuration.rst | 197 | ||||
-rw-r--r-- | doc/administration/installation.rst | 12 | ||||
-rw-r--r-- | doc/conf.py | 2 | ||||
-rw-r--r-- | fietsboek/__init__.py | 21 | ||||
-rw-r--r-- | fietsboek/jinja2.py | 37 | ||||
-rw-r--r-- | fietsboek/locale/fietslog.pot | 102 | ||||
-rw-r--r-- | fietsboek/routes.py | 3 | ||||
-rw-r--r-- | fietsboek/static/osm-monkeypatch.js | 306 | ||||
-rw-r--r-- | fietsboek/templates/layout.jinja2 | 4 | ||||
-rw-r--r-- | fietsboek/views/tileproxy.py | 313 | ||||
-rw-r--r-- | poetry.lock | 297 | ||||
-rw-r--r-- | pylint.toml | 2 | ||||
-rw-r--r-- | pyproject.toml | 5 |
14 files changed, 1161 insertions, 141 deletions
diff --git a/development.ini b/development.ini index eb33586..3259d3b 100644 --- a/development.ini +++ b/development.ini @@ -18,6 +18,7 @@ pyramid.includes = pyramid_debugtoolbar sqlalchemy.url = sqlite:///%(here)s/fietsboek.sqlite +redis.url = redis://localhost/ fietsboek.data_dir = %(here)s/data retry.attempts = 3 diff --git a/doc/administration/configuration.rst b/doc/administration/configuration.rst index 5268903..feb3e40 100644 --- a/doc/administration/configuration.rst +++ b/doc/administration/configuration.rst @@ -36,51 +36,164 @@ Most of the configuration is in the ``[app:main]`` category and looks like this: sqlalchemy.url = sqlite:///%(here)s/fietsboek.sqlite fietsboek.data_dir = %(here)s/data + redis.url = redis://localhost/ retry.attempts = 3 -* You should leave the ``use``, ``pyramid.reload_templates`` and - ``pyramid.debug_*`` settings as they are. -* ``pyramid.default_locale_name`` can be used to set the default language of - the installation. Note that Fietsboek will try to detect the user's language, - so the ``default_locale_name`` is used as a fallback. -* ``available_locales`` sets the list of available languages. Currently, - Fietsboek ships with English ("en") and German ("de"). Removing a language - from this list will make it unavailable. If you create a custom language - locally, make sure to add it to this list here! -* ``enable_account_registration`` can be used to enable and disable the - creation of new accounts via the web interface, for example if you want to - have a private instance. New accounts can always be created using the CLI - management tool. -* ``session_key`` should be set to a random string of characters. This is the - key used to sign session data, so it should not get into wrong hands! -* ``sqlalchemy.url`` is the URL to the database. See the `SQLAlchemy - documentation - <https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls>`__ for - more information. -* ``fietsboek.data_dir`` sets the directory for data uploads. This directory - must be writable by the Fietsboek process, as Fietsboek will save track - images in there. -* ``fietsboek.pages`` see :doc:`custom-pages`. -* ``email.from`` sets the sender of emails, for example for account verifications. -* ``email.smtp_url`` sets the URL of the SMTP server. The following formats are accepted: - - * ``debug://`` a debug implementation that simply prints emails to the - standard output. Should not be used in production, as no emails would ever - arrive. - * ``smtp://host:port`` use the given SMTP server (without transport encryption!) - * ``smtp+ssl://host:port`` use the given SMTP server over a SSL connection - * ``smtp+starttls://host:port`` use the given SMTP server and the STARTTLS - command to start an encrypted channel. - -* ``email.username`` and ``email.password`` can be used to set the login - information for the SMTP server. -* ``thunderforest.api_key`` can be set to an API key of `Thunderforest - <https://www.thunderforest.com/>`__ to enable support for the OpenCycleMap, - Landscape and Outdoors maps. +General Settings +---------------- +Use ``enable_account_registration`` enable or disable the creation of new +accounts via the web interface, for example if you want to have a private +instance. New accounts can always be created using the CLI management tool. + +Set ``session_key`` to a random string of characters. This is the key used to +sign session data, so it should not get into wrong hands! + +You can set up custom pages using ``fietsboek.pages``. See :doc:`custom-pages` +for more information. + +Pyramid Settings +---------------- + +You should leave the ``use``, ``pyramid.reload_templates`` and +``pyramid.debug_*`` settings as they are. Refer to the `Pyramid documentation +<https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html>`__ +for more information. + +Language Settings +----------------- + +You can set the default language with the ``pyramid.default_locale_name`` +setting. Note that Fietsboek will try to detect the user's language, so the +``default_locale_name`` is used as a fallback. + +You can use ``available_locales`` to set the list of available languages. +Currently, Fietsboek ships with English ("en") and German ("de"). Removing a +language from this list will make it unavailable. If you create a custom +language locally, make sure to add it to this list here! + +Database Settings +----------------- + +Fietsboek uses three different databases: +A SQL database for persistent data (like user accounts), a file storage on the +disk for big files (like images), and a redis server for ephemeral data (like +cached tiles). + +Set ``sqlalchemy.url`` to the URL of the SQL database. See the `SQLAlchemy +documentation +<https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls>`__ for more +information on available URL formats. Make sure to install the driver necessary +to communicate with your database (e.g. ``psycopg2`` for PostreSQL)! + +Set ``fietsboek.data_dir`` to the directory for data uploads. This directory +must be writable by the Fietsboek process, as Fietsboek will save track images +in there. + +Set ``redis.url`` to the URL of the redis instance. See the `redis module +documentation +<https://redis.readthedocs.io/en/latest/connections.html#redis.Redis.from_url>`__ +for information about the possible syntaxes of this URL. Note that the redis +server is only used for caching and temporary data, so don't sweat to make it +persistent. A container running redis is fine. + +.. note:: + + Fietsboek will cache map tiles in the redis server. + To avoid using up too much memory, consider setting a maximum memory size + and policy in redis: + + https://redis.io/docs/management/config/#configuring-redis-as-a-cache + +Email Settings +-------------- + +Use ``email.from`` to set the sender of emails, for example for account verifications. + +Set ``email.smtp_url`` to the URL of the SMTP server. The following formats are +accepted: + +* ``debug://`` a debug implementation that simply prints emails to the + standard output. Should not be used in production, as no emails would ever + arrive. +* ``smtp://host:port`` use the given SMTP server (without transport encryption!) +* ``smtp+ssl://host:port`` use the given SMTP server over a SSL connection +* ``smtp+starttls://host:port`` use the given SMTP server and the STARTTLS + command to start an encrypted channel. + +Use ``email.username`` and ``email.password`` to set the login credentials for +the SMTP server. + +Map Layers & Thunderforest Integration +-------------------------------------- + +By default, Fietsboek offers the following map layers: + +* ``osm``: `OpenStreetMap <https://www.openstreetmap.org>`__ +* ``osmde``: `OpenStreetMap Deutschland <https://www.openstreetmap.de/>`__ +* ``satellite``: Satellite imaging from `Esri <https://www.esri.com>`__ +* ``opentopo``: `OpenTopoMap <https://opentopomap.org/>`__ +* ``topplusopen``: `TopPlus-Open + <https://www.bkg.bund.de/SharedDocs/Produktinformationen/BKG/EN/P-2017/171114-TopPlus-Web-Open.html>`__ + +As well as the following overlay layers: + +* ``opensea``: `OpenSeaMap <https://openseamap.org>`__ +* ``cycling``: `Waymarked Trails: Cycling <https://cycling.waymarkedtrails.org>`__ +* ``hiking``: `Waymarked Trails: Hiking <https://hiking.waymarkedtrails.org/>`__ + +You can use ``fietsboek.default_tile_layers`` to set the list of activated +layers (by default, all of them), for example: + +.. code:: ini + + fietsboek.default_tile_layers = osm osmde cycling + +You can enable `Thunderforest <https://www.thunderforest.com>`__ support by +setting ``thunderforest.api_key``, and ``thunderforest.maps`` to a list of +Thunderforest maps (e.g. "cycle" or "landscape"). By default, only logged in +users will be able to use the Thunderforest maps (to protect your quota), this +can be changed by setting ``thunderforest.access = public`` (default is +"restricted"). + +You can add custom tile layers in the following way: + +.. code:: ini + + fietsboek.tile_layer.ID = My Custom Layer + fietsboek.tile_layer.ID.url = https://tiles.example.com/{z}/{x}/{y}.png + # Optional, set the type (base or overlay), default base + fietsboek.tile_layer.ID.type = base + # Optional, set the maximum zoom factor, default 22 + fietsboek.tile_layer.ID.zoom = 22 + # Optional, set the attribution + fietsboek.tile_layer.ID.attribution = Copyright Example + # Optional, set the access restriction (public or restricted), default + # public + fietsboek.tile_layer.ID.access = public + +``ID`` must be an alphanumerical identifier. + +By default, Fietsboek will proxy all tile requests through the Fietsboek +instance. While this can slow down the user experience, it has the following +benefits: + +* Your users' IPs stay private and protected, as no third party is contacted. + The tile servers will only see the IP from the Fietsboek server. +* If you use private tile servers or servers that require a key, your key is + protected as it will not be given out to the users. +* Fietsboek caches tile requests, which reduces the strain on the providers and + might even make maps faster if many people use them. + +You can disable the tile proxy by setting ``fietsboek.tile_proxy.disable = +true``. This will cause the tiles to be loaded directly by the client. .. warning:: - The API key will be embedded in the source of the website, therefore it is - possible for visitors to "steal" the API key. Keep that in mind when - setting an API key for a publicly accessible site! + If you disable the tile proxy, all tile source URLs will be given to the + user. If you use API keys or other private sources, **those keys will be + leaked to the users**. + + In addition, depending on the jurisdiction, you might be required to tell + your users that third party content is included in your site, and that + their IP will be accessible to the third party. diff --git a/doc/administration/installation.rst b/doc/administration/installation.rst index b8ede06..4af0941 100644 --- a/doc/administration/installation.rst +++ b/doc/administration/installation.rst @@ -3,6 +3,18 @@ Installation This document will outline the installation process of Fietsboek, step-by-step. +Requirements +------------ + +Fietsboek has the following requirements (apart from the Python modules, which +will be installed by ``pip``): + +* Python 3.7 or later +* A `redis <https://redis.com/>`__ server, used for caching and temporary data +* (Optionally) an SQL database server like `PostgreSQL + <https://www.postgresql.org/>`__ or `MariaDB <https://mariadb.org/>`__ (if + SQLite is not enough) + Creating an Environment ----------------------- diff --git a/doc/conf.py b/doc/conf.py index e7f04e0..7769cf1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -36,6 +36,8 @@ intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None), 'sqlalchemy': ('https://docs.sqlalchemy.org/en/14/', None), + 'jinja2': ('https://jinja.palletsprojects.com/en/3.0.x/', None), + 'markupsafe': ('https://markupsafe.palletsprojects.com/en/2.1.x/', None), } # Add any paths that contain templates here, relative to this directory. diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py index a727230..d9077d5 100644 --- a/fietsboek/__init__.py +++ b/fietsboek/__init__.py @@ -4,6 +4,8 @@ For more information, see the README or the included documentation. """ from pathlib import Path +import importlib_metadata +import redis from pyramid.config import Configurator from pyramid.session import SignedCookieSessionFactory from pyramid.csrf import CookieCSRFStoragePolicy @@ -16,6 +18,9 @@ from .pages import Pages from . import jinja2 as fiets_jinja2 +__VERSION__ = importlib_metadata.version('fietsboek') + + def locale_negotiator(request): """Negotiates the right locale to use. @@ -46,7 +51,8 @@ def locale_negotiator(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - # pylint: disable=unused-argument + # pylint: disable=unused-argument, import-outside-toplevel, cyclic-import + from .views import tileproxy if settings.get('session_key', '<EDIT THIS>') == '<EDIT THIS>': raise ValueError("Please set a session signing key (session_key) in your settings!") @@ -57,12 +63,23 @@ def main(global_config, **settings): data_dir = request.registry.settings["fietsboek.data_dir"] return DataManager(Path(data_dir)) + def redis_(request): + return redis.from_url(request.registry.settings["redis.url"]) + settings['enable_account_registration'] = asbool( settings.get('enable_account_registration', 'false')) settings['available_locales'] = aslist( settings.get('available_locales', 'en')) settings['fietsboek.pages'] = aslist( settings.get('fietsboek.pages', '')) + settings['fietsboek.tile_proxy.disable'] = asbool( + settings.get('fietsboek.tile_proxy.disable', 'false')) + settings['thunderforest.maps'] = aslist( + settings.get('thunderforest.maps', '')) + settings['fietsboek.default_tile_layers'] = aslist( + settings.get('fietsboek.default_tile_layers', + 'osm satellite osmde opentopo topplusopen opensea cycling hiking')) + settings['fietsboek.tile_layers'] = tileproxy.extract_tile_layers(settings) # Load the pages page_manager = Pages() @@ -90,10 +107,12 @@ def main(global_config, **settings): config.set_locale_negotiator(locale_negotiator) config.add_request_method(data_manager, reify=True) config.add_request_method(pages, reify=True) + config.add_request_method(redis_, name="redis", reify=True) jinja2_env = config.get_jinja2_environment() jinja2_env.filters['format_decimal'] = fiets_jinja2.filter_format_decimal jinja2_env.filters['format_datetime'] = fiets_jinja2.filter_format_datetime jinja2_env.filters['local_datetime'] = fiets_jinja2.filter_local_datetime + jinja2_env.globals['embed_tile_layers'] = fiets_jinja2.global_embed_tile_layers return config.make_wsgi_app() diff --git a/fietsboek/jinja2.py b/fietsboek/jinja2.py index 6043791..a0b9457 100644 --- a/fietsboek/jinja2.py +++ b/fietsboek/jinja2.py @@ -1,5 +1,6 @@ """Custom filters for Jinja2.""" import datetime +import json import jinja2 from markupsafe import Markup @@ -77,3 +78,39 @@ def filter_local_datetime(ctx, value): return Markup( f'<span class="fietsboek-local-datetime" data-utc-timestamp="{timestamp}">{fallback}</span>' ) + + +def global_embed_tile_layers(request): + """Renders the available tile servers for the current user, as a JSON object. + + The returned value is wrapped as a :class:`~markupsafe.Markup` so that it + won't get escaped by jinja. + + :param request: The Pyramid request. + :type request: pyramid.request.Request + :return: The available tile servers. + :rtype: markupsafe.Markup + """ + # pylint: disable=import-outside-toplevel,cyclic-import + from .views import tileproxy + tile_sources = tileproxy.sources_for(request) + + if request.registry.settings.get("fietsboek.tile_proxy.disable"): + def _url(source): + return source.url_template + else: + def _url(source): + return (request.route_url("tile-proxy", provider=source.key, x="{x}", y="{y}", z="{z}") + .replace("%7Bx%7D", "{x}") + .replace("%7By%7D", "{y}") + .replace("%7Bz%7D", "{z}")) + + return Markup(json.dumps([ + { + "name": source.name, + "url": _url(source), + "attribution": source.attribution, + "type": source.layer_type.value, + } + for source in tile_sources + ])) diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot index 91390c8..abb2bfc 100644 --- a/fietsboek/locale/fietslog.pot +++ b/fietsboek/locale/fietslog.pot @@ -8,14 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-08-10 13:36+0200\n" +"POT-Creation-Date: 2022-11-15 23:42+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.3\n" +"Generated-By: Babel 2.11.0\n" #: fietsboek/util.py:282 msgid "password_constraint.mismatch" @@ -153,43 +153,43 @@ msgstr "" msgid "page.browse.synthetic_tooltip" msgstr "" -#: fietsboek/templates/browse.jinja2:132 fietsboek/templates/details.jinja2:88 +#: fietsboek/templates/browse.jinja2:132 fietsboek/templates/details.jinja2:90 msgid "page.details.date" msgstr "" -#: fietsboek/templates/browse.jinja2:134 fietsboek/templates/details.jinja2:102 +#: fietsboek/templates/browse.jinja2:134 fietsboek/templates/details.jinja2:104 msgid "page.details.length" msgstr "" -#: fietsboek/templates/browse.jinja2:139 fietsboek/templates/details.jinja2:93 +#: fietsboek/templates/browse.jinja2:139 fietsboek/templates/details.jinja2:95 msgid "page.details.start_time" msgstr "" -#: fietsboek/templates/browse.jinja2:141 fietsboek/templates/details.jinja2:97 +#: fietsboek/templates/browse.jinja2:141 fietsboek/templates/details.jinja2:99 msgid "page.details.end_time" msgstr "" -#: fietsboek/templates/browse.jinja2:146 fietsboek/templates/details.jinja2:106 +#: fietsboek/templates/browse.jinja2:146 fietsboek/templates/details.jinja2:108 msgid "page.details.uphill" msgstr "" -#: fietsboek/templates/browse.jinja2:148 fietsboek/templates/details.jinja2:110 +#: fietsboek/templates/browse.jinja2:148 fietsboek/templates/details.jinja2:112 msgid "page.details.downhill" msgstr "" -#: fietsboek/templates/browse.jinja2:153 fietsboek/templates/details.jinja2:115 +#: fietsboek/templates/browse.jinja2:153 fietsboek/templates/details.jinja2:117 msgid "page.details.moving_time" msgstr "" -#: fietsboek/templates/browse.jinja2:155 fietsboek/templates/details.jinja2:119 +#: fietsboek/templates/browse.jinja2:155 fietsboek/templates/details.jinja2:121 msgid "page.details.stopped_time" msgstr "" -#: fietsboek/templates/browse.jinja2:159 fietsboek/templates/details.jinja2:123 +#: fietsboek/templates/browse.jinja2:159 fietsboek/templates/details.jinja2:125 msgid "page.details.max_speed" msgstr "" -#: fietsboek/templates/browse.jinja2:161 fietsboek/templates/details.jinja2:127 +#: fietsboek/templates/browse.jinja2:161 fietsboek/templates/details.jinja2:129 msgid "page.details.avg_speed" msgstr "" @@ -293,40 +293,40 @@ msgstr "" msgid "page.details.delete.close" msgstr "" -#: fietsboek/templates/details.jinja2:69 +#: fietsboek/templates/details.jinja2:70 msgid "page.details.tags" msgstr "" -#: fietsboek/templates/details.jinja2:78 fietsboek/templates/edit.jinja2:10 +#: fietsboek/templates/details.jinja2:80 fietsboek/templates/edit.jinja2:10 #: fietsboek/templates/finish_upload.jinja2:10 msgid "page.noscript" msgstr "" -#: fietsboek/templates/details.jinja2:83 +#: fietsboek/templates/details.jinja2:85 msgid "page.details.download" msgstr "" -#: fietsboek/templates/details.jinja2:172 +#: fietsboek/templates/details.jinja2:174 msgid "page.details.comments" msgstr "" -#: fietsboek/templates/details.jinja2:176 +#: fietsboek/templates/details.jinja2:178 msgid "page.details.comments.author" msgstr "" -#: fietsboek/templates/details.jinja2:193 +#: fietsboek/templates/details.jinja2:195 msgid "page.details.comments.new.title" msgstr "" -#: fietsboek/templates/details.jinja2:196 +#: fietsboek/templates/details.jinja2:198 msgid "page.details.comments.new.input_title" msgstr "" -#: fietsboek/templates/details.jinja2:197 +#: fietsboek/templates/details.jinja2:199 msgid "page.details.comments.new.input_comment" msgstr "" -#: fietsboek/templates/details.jinja2:200 +#: fietsboek/templates/details.jinja2:202 msgid "page.details.comments.new.submit" msgstr "" @@ -394,40 +394,40 @@ msgstr "" msgid "page.track.form.tags" msgstr "" -#: fietsboek/templates/edit_form.jinja2:43 +#: fietsboek/templates/edit_form.jinja2:50 msgid "page.track.form.add_tag" msgstr "" -#: fietsboek/templates/edit_form.jinja2:48 +#: fietsboek/templates/edit_form.jinja2:55 msgid "page.track.form.tagged_people" msgstr "" -#: fietsboek/templates/edit_form.jinja2:63 +#: fietsboek/templates/edit_form.jinja2:70 msgid "page.track.form.add_friend" msgstr "" -#: fietsboek/templates/edit_form.jinja2:83 +#: fietsboek/templates/edit_form.jinja2:90 msgid "page.track.form.badges" msgstr "" -#: fietsboek/templates/edit_form.jinja2:94 +#: fietsboek/templates/edit_form.jinja2:101 msgid "page.track.form.description" msgstr "" -#: fietsboek/templates/edit_form.jinja2:101 -#: fietsboek/templates/edit_form.jinja2:115 +#: fietsboek/templates/edit_form.jinja2:108 +#: fietsboek/templates/edit_form.jinja2:122 msgid "page.track.form.remove_image" msgstr "" -#: fietsboek/templates/edit_form.jinja2:110 +#: fietsboek/templates/edit_form.jinja2:117 msgid "page.track.form.select_images" msgstr "" -#: fietsboek/templates/edit_form.jinja2:126 +#: fietsboek/templates/edit_form.jinja2:133 msgid "page.track.form.image_description_modal" msgstr "" -#: fietsboek/templates/edit_form.jinja2:133 +#: fietsboek/templates/edit_form.jinja2:140 msgid "page.track.form.image_description_modal.save" msgstr "" @@ -457,43 +457,43 @@ msgstr "" msgid "page.navbar.toggle" msgstr "" -#: fietsboek/templates/layout.jinja2:46 +#: fietsboek/templates/layout.jinja2:51 msgid "page.navbar.home" msgstr "" -#: fietsboek/templates/layout.jinja2:49 +#: fietsboek/templates/layout.jinja2:54 msgid "page.navbar.browse" msgstr "" -#: fietsboek/templates/layout.jinja2:53 +#: fietsboek/templates/layout.jinja2:58 msgid "page.navbar.upload" msgstr "" -#: fietsboek/templates/layout.jinja2:57 +#: fietsboek/templates/layout.jinja2:67 msgid "page.navbar.user" msgstr "" -#: fietsboek/templates/layout.jinja2:61 +#: fietsboek/templates/layout.jinja2:71 msgid "page.navbar.welcome_user" msgstr "" -#: fietsboek/templates/layout.jinja2:64 +#: fietsboek/templates/layout.jinja2:74 msgid "page.navbar.logout" msgstr "" -#: fietsboek/templates/layout.jinja2:67 +#: fietsboek/templates/layout.jinja2:77 msgid "page.navbar.profile" msgstr "" -#: fietsboek/templates/layout.jinja2:71 +#: fietsboek/templates/layout.jinja2:81 msgid "page.navbar.admin" msgstr "" -#: fietsboek/templates/layout.jinja2:77 +#: fietsboek/templates/layout.jinja2:87 msgid "page.navbar.login" msgstr "" -#: fietsboek/templates/layout.jinja2:81 +#: fietsboek/templates/layout.jinja2:91 msgid "page.navbar.create_account" msgstr "" @@ -645,43 +645,43 @@ msgstr "" msgid "flash.badge_deleted" msgstr "" -#: fietsboek/views/default.py:75 +#: fietsboek/views/default.py:96 msgid "flash.invalid_credentials" msgstr "" -#: fietsboek/views/default.py:79 +#: fietsboek/views/default.py:100 msgid "flash.account_not_verified" msgstr "" -#: fietsboek/views/default.py:82 +#: fietsboek/views/default.py:103 msgid "flash.logged_in" msgstr "" -#: fietsboek/views/default.py:96 +#: fietsboek/views/default.py:117 msgid "flash.logged_out" msgstr "" -#: fietsboek/views/default.py:127 +#: fietsboek/views/default.py:148 msgid "flash.reset_invalid_email" msgstr "" -#: fietsboek/views/default.py:132 +#: fietsboek/views/default.py:153 msgid "flash.password_token_generated" msgstr "" -#: fietsboek/views/default.py:137 +#: fietsboek/views/default.py:158 msgid "page.password_reset.email.subject" msgstr "" -#: fietsboek/views/default.py:141 +#: fietsboek/views/default.py:162 msgid "page.password_reset.email.body" msgstr "" -#: fietsboek/views/default.py:168 +#: fietsboek/views/default.py:189 msgid "flash.email_verified" msgstr "" -#: fietsboek/views/default.py:182 +#: fietsboek/views/default.py:203 msgid "flash.password_updated" msgstr "" diff --git a/fietsboek/routes.py b/fietsboek/routes.py index ab1eabf..9286f13 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -54,3 +54,6 @@ def includeme(config): config.add_route('delete-friend', '/me/delete-friend') config.add_route('accept-friend', '/me/accept-friend') config.add_route('json-friends', '/me/friends.json') + + config.add_route('tile-proxy', + '/tile/{provider}/{z:\\d+}/{x:\\d+}/{y:\\d+}') diff --git a/fietsboek/static/osm-monkeypatch.js b/fietsboek/static/osm-monkeypatch.js new file mode 100644 index 0000000..a8310a2 --- /dev/null +++ b/fietsboek/static/osm-monkeypatch.js @@ -0,0 +1,306 @@ +/* We want to override JB.Map to add our own maps instead. + * We do this by (ab)using the JS property system to override the setter, so + * that JB.Map won't actually set the new function. This means we don't have to + * source-patch the gmutils.js file. + */ +"use strict"; + +(() => { + let ourMap = function(makemap) { + var dieses = this; + var id = makemap.id; + var mapcanvas = makemap.mapdiv; + dieses.id = id; + dieses.makemap = makemap; + dieses.mapcanvas = mapcanvas; + this.cluster_zoomhistory = []; + + // Map anlegen + + const mycp = '<a href="https://www.j-berkemeier.de/GPXViewer" title="GPX Viewer '+JB.GPX2GM.ver+'">GPXViewer</a> | '; + + this.baseLayers = {}; + this.overlayLayers = {}; + + for (let layer of TILE_LAYERS) { + if (layer.type === "base") { + this.baseLayers[layer.name] = L.tileLayer(layer.url, { + maxZoom: layer.zoom, + attribution: layer.attribution, + }); + } else if (layer.type === "overlay") { + this.overlayLayers[layer.name] = L.tileLayer(layer.url, { + attribution: layer.attribution, + }); + } + } + + // https://tileserver.4umaps.com/${z}/${x}/${y}.png + // zoomlevel 16 + // https://www.4umaps.com/ + + this.baseLayers[JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].noMap]= L.tileLayer(JB.GPX2GM.Path+"Icons/Grau256x256.png", { + maxZoom: 22, + attribution: mycp + }); + + this.layerNameTranslate = { + satellit: "Satellit", + satellite: "Satellit", + osm: "OSM", + osmde: "OSMDE", + opentopo: "Open Topo", + topplusopen: "TopPlusOpen", + cycle: "Cycle", + landscape: "Landscape", + outdoors: "Outdoors", + keinekarte: "Keine Karte", + pasdecarte: "Pas de carte", + nomap: "No Map", + ning\u00FAnmapa: "Ning\u00FAn Mapa", + nessunamappa: "Nessuna mappa", + opensea: "Open Sea", + hiking: "Hiking", + cycling: "Cycling", + } + + // ['hiking', 'cycling', 'mtb', 'skating', 'slopes', 'riding']; + + var genugplatz = JB.platzgenug(makemap.mapdiv); + + this.map = L.map(mapcanvas, { + // layers: osm, + closePopupOnClick: false, + scrollWheelZoom: genugplatz & makemap.parameters.scrollwheelzoom, + tap: genugplatz, + keyboard: genugplatz, + touchZoom: true, + dragging: true, + } ); + + JB.handle_touch_action(dieses,genugplatz); + + if(makemap.parameters.unit=="si") L.control.scale({imperial:false}).addTo(this.map); // Mit Maßstab km + else L.control.scale({metric:false}).addTo(this.map); // Mit Maßstab ml + + var ctrl_layer = null; + var showmaptypecontroll_save = makemap.parameters.showmaptypecontroll; + JB.onresize(mapcanvas,function(w,h) { + makemap.parameters.showmaptypecontroll = (w>200 && h>190 && showmaptypecontroll_save); + if(makemap.parameters.showmaptypecontroll) { + if(!ctrl_layer) ctrl_layer = L.control.layers(dieses.baseLayers, dieses.overlayLayers).addTo(dieses.map); + } + else { + if(ctrl_layer) { + ctrl_layer.remove(); + ctrl_layer = null; + } + } + },true); + + // Button für Full Screen / normale Größe + var fullscreen = false; + if(makemap.parameters.fullscreenbutton) { + var fsb = document.createElement("button"); + fsb.style.backgroundColor = "transparent"; + fsb.style.border = "none"; + fsb.style.padding = "7px 7px 7px 0"; + fsb.style.cursor = "pointer"; + var fsbim = document.createElement("img"); + fsbim.width = 31; + fsbim.height = 31; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_p.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].fullScreen; + fsbim.large = false; + var ele = mapcanvas.parentNode; + fsb.onclick = function() { + this.blur(); + if(fsbim.large) { + document.body.style.overflow = ""; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_p.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].fullScreen; + ele.style.left = ele.oleft + "px"; + ele.style.top = ele.otop + "px"; + ele.style.width = ele.owidth + "px"; + ele.style.height = ele.oheight + "px"; + ele.style.margin = ele.omargin; + ele.style.padding = ele.opadding; + window.setTimeout(function() { + JB.removeClass("JBfull",ele); + ele.style.position = ele.sposition; + ele.style.left = ele.sleft; + ele.style.top = ele.stop; + ele.style.width = ele.swidth; + ele.style.height = ele.sheight; + //ele.style.zIndex = ele.szindex; + },1000); + JB.handle_touch_action(dieses,genugplatz); + fullscreen = false; + } + else { + document.body.style.overflow = "hidden"; + fsbim.src = JB.GPX2GM.Path+"Icons/fullscreen_m.svg"; + fsb.title = fsbim.title = fsbim.alt = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].normalSize; + var scrollY = 0; + if(document.documentElement.scrollTop && document.documentElement.scrollTop!=0) scrollY = document.documentElement.scrollTop; + else if(document.body.scrollTop && document.body.scrollTop!=0) scrollY = document.body.scrollTop; + else if(window.scrollY) scrollY = window.scrollY; + else if(window.pageYOffset) scrollY = window.pageYOffset; + var rect = JB.getRect(ele); + ele.oleft = rect.left; + ele.otop = rect.top - scrollY; + ele.owidth = rect.width; + ele.oheight = rect.height; + //ele.szindex = ele.style.zIndex; + ele.sposition = ele.style.position; + ele.omargin = ele.style.margin; + ele.opadding = ele.style.padding; + ele.sleft = ele.style.left; + ele.stop = ele.style.top; + ele.swidth = ele.style.width; + ele.sheight = ele.style.height; + ele.style.position = "fixed"; + ele.style.left = ele.oleft+"px"; + ele.style.top = ele.otop+"px"; + ele.style.width = ele.owidth+"px"; + ele.style.height = ele.oheight+"px"; + //ele.style.zIndex = "1001"; + window.setTimeout(function() { + JB.addClass("JBfull",ele); + ele.style.width = "100%"; + ele.style.height = "100%"; + ele.style.left = "0px"; + ele.style.top = "0px"; + ele.style.margin = "0px"; + ele.style.padding = "0px"; + },100); + dieses.map.scrollWheelZoom.enable(); + JB.handle_touch_action(dieses,true); + makemap.mapdiv.focus(); + fullscreen = true; + } + fsbim.large = !fsbim.large; + }; + fsb.appendChild(fsbim); + fsb.index = 0; + L.Control.Fsbutton = L.Control.extend({ + onAdd: function(map) { + return fsb; + } + }); + var fsbutton = new L.Control.Fsbutton({ position: 'topright' }); + fsbutton.addTo(this.map); + } // fullscreenbutton + + // Button für Traffic-Layer + if(makemap.parameters.trafficbutton) { + console.warn("Traffic-Layer wird unter Leaflet (noch) nicht unterstützt."); + } + + // Button für Anzeige aktuelle Position + if(makemap.parameters.currentlocationbutton) { + var clb = document.createElement("button"); + clb.style.backgroundColor = "white"; + clb.style.border = "none"; + clb.style.width = "28px"; + clb.style.height = "28px"; + clb.style.margin = "10px 10px 0 0"; + clb.style.borderRadius = "2px"; + clb.style.cursor = "pointer"; + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].showCurrentLocation; + var clbimg = document.createElement("img"); + clbimg.style.position = "absolute"; + clbimg.style.top = "50%"; + clbimg.style.left = "50%"; + clbimg.style.transform = "translate(-50%, -50%)"; + clbimg.src = JB.GPX2GM.Path+"Icons/whereami.svg"; + var wpid = -1, marker = null, first; + clb.onclick = function() { + this.blur(); + if (navigator.geolocation) { + var geolocpos = function(position) { + var lat = position.coords.latitude; + var lon = position.coords.longitude; + marker.setLatLng([lat,lon]); + if(first) { + dieses.map.setView([lat,lon]); + first = false; + } + } + var geolocerror = function(error) { + var errorCodes = ["Permission Denied","Position unavailible","Timeout"]; + var errorString = (error.code<=3)?errorCodes[error.code-1]:"Error code: "+error.code; + JB.Debug_Info("Geolocation-Dienst fehlgeschlagen!",errorString+". "+error.message,true); + } + first = true; + if(!marker) marker = dieses.Marker({lat:0,lon:0},JB.icons.CL)[0]; + if ( wpid == -1 ) { + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].hideCurrentLocation; + wpid = navigator.geolocation.watchPosition(geolocpos,geolocerror,{enableHighAccuracy:true, timeout: 5000, maximumAge: 60000}); + marker.addTo(dieses.map); + JB.Debug_Info("","Geolocation-Dienst wird eingerichtet.",false); + } + else { + clb.title = JB.GPX2GM.strings[JB.GPX2GM.parameters.doclang].showCurrentLocation; + navigator.geolocation.clearWatch(wpid); + wpid = -1; + marker.remove(); + JB.Debug_Info("","Geolocation-Dienst wird abgeschaltet.",false); + } + } + else JB.Debug_Info("geolocation","Geolocation wird nicht unterstützt!",true); + } // click-Handler + clb.appendChild(clbimg); + L.Control.Clbutton = L.Control.extend({ + onAdd: function(map) { + return clb; + } + }); + var clbutton = new L.Control.Clbutton({ position: 'topright' }); + clbutton.addTo(this.map); + } // currentlocationbutton + + // Scalieren nach MAP-Resize + dieses.zoomstatus = {}; + dieses.zoomstatus.iszoomed = false; + dieses.zoomstatus.zoom_changed = function() { + dieses.zoomstatus.iszoomed = true; + dieses.zoomstatus.level = dieses.map.getZoom(); + dieses.zoomstatus.w = mapcanvas.offsetWidth; + dieses.zoomstatus.h = mapcanvas.offsetHeight; + } + dieses.zoomstatus.move_end = function() { + dieses.zoomstatus.iszoomed = true; + dieses.mapcenter = dieses.map.getCenter(); + } + dieses.map.on("moveend", dieses.zoomstatus.move_end); + JB.onresize(mapcanvas,function(w,h) { + if(w*h==0) return; + dieses.map.invalidateSize(); + dieses.map.setView(dieses.mapcenter); + dieses.map.off("zoomend", dieses.zoomstatus.zoom_changed); + if(dieses.zoomstatus.iszoomed) { + var dz = Math.round(Math.min(Math.log(w/dieses.zoomstatus.w)/Math.LN2,Math.log(h/dieses.zoomstatus.h)/Math.LN2)); + dieses.map.setZoom(dieses.zoomstatus.level+dz); + } + else { + if(dieses.bounds) { + dieses.map.fitBounds(dieses.bounds,{padding:[20,20]}); + dieses.map.setView(dieses.mapcenter); + dieses.zoomstatus.level = dieses.map.getZoom(); + dieses.zoomstatus.w = w; + dieses.zoomstatus.h = h; + } + } + if(!fullscreen) { + genugplatz = JB.platzgenug(makemap.mapdiv); + JB.handle_touch_action(dieses,genugplatz); + } + }); + }; + window.JB = window.JB || {}; + Object.defineProperty(window.JB, "Map", { + get() { return ourMap; }, + set(_) {}, + }); +})(); diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 7371052..bf30143 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -20,6 +20,8 @@ <script> const FRIENDS_URL = {{ request.route_url('json-friends') | tojson }}; +const TILE_LAYERS = {{ embed_tile_layers(request) }}; +const BASE_URL = {{ request.route_url('home') | tojson }}; const LOCALE = {{ request.localizer.locale_name.replace('_', '-') | tojson }}; {% if request.registry.settings.get("thunderforest.api_key") %} {% set api_key = request.registry.settings.get("thunderforest.api_key") %} @@ -112,6 +114,8 @@ window.JB.GPX2GM.OSM_Outdoors_Api_Key = {{ api_key | tojson }}; ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="{{request.static_url('fietsboek:static/bootstrap.bundle.min.js')}}"></script> + <!-- Our patch to the GPX viewer, load before the actual GPX viewer --> + <script src="{{request.static_url('fietsboek:static/osm-monkeypatch.js')}}"></script> <!-- Jürgen Berkemeier's GPX viewer --> <script src="{{request.static_url('fietsboek:static/GM_Utils/GPX2GM.js')}}"></script> <script src="{{request.static_url('fietsboek:static/fietsboek.js')}}"></script> diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py new file mode 100644 index 0000000..b9a32c4 --- /dev/null +++ b/fietsboek/views/tileproxy.py @@ -0,0 +1,313 @@ +"""Tile proxying layer. + +While this might slow down the initial load (as we now load everything through +fietsboek), we can cache the OSM tiles per instance, and we can provide better +access control for services like thunderforest.com. + +Additionally, this protects the users' IP, as only fietsboek can see it. +""" +import datetime +import random +import logging +import re +from enum import Enum +from typing import NamedTuple +from itertools import chain + +from pyramid.view import view_config +from pyramid.response import Response +from pyramid.httpexceptions import HTTPBadRequest, HTTPGatewayTimeout + +import requests +from requests.exceptions import ReadTimeout + +from .. import __VERSION__ + + +class LayerType(Enum): + """Enum to distinguish base layers and overlay layers.""" + BASE = "base" + OVERLAY = "overlay" + + +class LayerAccess(Enum): + """Enum discerning whether a layer is publicly accessible or restriced to + logged-in users. + + Note that in the future, a finer-grained distinction might be possible. + """ + PUBLIC = "public" + RESTRICTED = "restricted" + + +class TileSource(NamedTuple): + """Represents a remote server that can provide tiles to us.""" + key: str + """Key to indicate this source in URLs.""" + name: str + """Human-readable name of the source.""" + url_template: str + """URL with placeholders.""" + layer_type: LayerType + """Type of this layer.""" + zoom: int + """Max zoom of this layer.""" + access: LayerAccess + """Access restrictions to use this layer.""" + attribution: str + """Attribution string.""" + + +LOGGER = logging.getLogger(__name__) + + +def _href(url, text): + return f'<a href="{url}" target="_blank">{text}</a>' + + +_jb_copy = _href("https://www.j-berkemeier.de/GPXViewer", "GPXViewer") + + +DEFAULT_TILE_LAYERS = [ + # Main base layers + TileSource( + 'osm', + 'OSM', + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + LayerType.BASE, + 19, + LayerAccess.PUBLIC, + ''.join([ + _jb_copy, ' | Map data © ', + _href("https://www.openstreetmap.org/", "OpenStreetMap"), ' and contributors ', + _href("https://creativecommons.org/licenses/by-sa/2.0/", "CC-BY-SA"), + ]), + ), + TileSource( + 'satellite', + 'Satellit', + 'https://server.arcgisonline.com/ArcGIS/rest/services/' + 'World_Imagery/MapServer/tile/{z}/{y}/{x}', + LayerType.BASE, + 21, + LayerAccess.PUBLIC, + ''.join([ + _jb_copy, ' | Map data © ', _href("https://www.esri.com", "Esri"), + ', i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, ', + 'IGP, UPR-EGP, and the GIS User Community', + ]), + ), + TileSource( + 'osmde', + 'OSMDE', + 'https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', + LayerType.BASE, + 19, + LayerAccess.PUBLIC, + ''.join([ + _jb_copy, ' | Map data © ', + _href("https://www.openstreetmap.org/", "OpenStreetMap"), ' and contributors ', + _href("https://creativecommons.org/licenses/by-sa/2.0/", "CC-BY-SA") + ]), + ), + TileSource( + 'opentopo', + 'Open Topo', + 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', + LayerType.BASE, + 17, + LayerAccess.PUBLIC, + ''.join([ + _jb_copy, + ' | Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | Kartendarstellung: © ', + _href("https://opentopomap.org/about", "OpenTopoMap"), ' (CC-BY-SA)', + ]), + ), + TileSource( + 'topplusopen', + 'TopPlusOpen', + 'https://sgx.geodatenzentrum.de/wmts_topplus_open/tile/' + '1.0.0/web/default/WEBMERCATOR/{z}/{y}/{x}.png', + LayerType.BASE, + 18, + LayerAccess.PUBLIC, + ''.join([ + _jb_copy, ' | Kartendaten: © ', + _href("https://www.bkg.bund.de/SharedDocs/Produktinformationen" + "/BKG/DE/P-2017/170922-TopPlus-Web-Open.html", + "Bundesamt für Kartographie und Geodäsie"), + ]), + ), + + # Overlay layers + TileSource( + 'opensea', + 'OpenSea', + 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', + LayerType.OVERLAY, + None, + LayerAccess.PUBLIC, + 'Kartendaten: © <a href="http://www.openseamap.org">OpenSeaMap</a> contributors', + ), + TileSource( + 'hiking', + 'Hiking', + 'https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png', + LayerType.OVERLAY, + None, + LayerAccess.PUBLIC, + f'© {_href("http://waymarkedtrails.org", "Sarah Hoffmann")} ' + f'({_href("https://creativecommons.org/licenses/by-sa/3.0/", "CC-BY-SA")})', + ), + TileSource( + 'cycling', + 'Cycling', + 'https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png', + LayerType.OVERLAY, + None, + LayerAccess.PUBLIC, + f'© {_href("http://waymarkedtrails.org", "Sarah Hoffmann")} ' + f'({_href("https://creativecommons.org/licenses/by-sa/3.0/", "CC-BY-SA")})', + ), +] + +TTL = datetime.timedelta(days=7) +"""Time to live of cached tiles.""" + +TIMEOUT = datetime.timedelta(seconds=1.5) +"""Timeout when requesting new tiles from a source server.""" + +PUNISHMENT_TTL = datetime.timedelta(minutes=10) +"""Block-out period after too many requests of a server have timed out.""" + +PUNISHMENT_THRESHOLD = 10 +"""Block a provider after that many requests have timed out.""" + + +@view_config(route_name='tile-proxy', http_cache=3600) +def tile_proxy(request): + """Requests the given tile from the proxy. + + :param request: The Pyramid request. + :type request: pyramid.request.Request + :return: The HTTP response. + :rtype: pyramid.response.Response + """ + if request.registry.settings.get("fietsboek.tile_proxy.disable"): + raise HTTPBadRequest("Tile proxying is disabled") + + provider = request.matchdict['provider'] + tile_sources = {source.key: source for source in sources_for(request)} + if provider not in tile_sources: + raise HTTPBadRequest("Invalid provider") + + x, y, z = (int(request.matchdict['x']), int(request.matchdict['y']), + int(request.matchdict['z'])) + cache_key = f"tile:{provider}-{x}-{y}-{z}" + content_type = "image/png" + + cached = request.redis.get(cache_key) + if cached is not None: + return Response(cached, content_type=content_type) + + timeout_tracker = f"provider-timeout:{provider}" + if int(request.redis.get(timeout_tracker) or "0") > PUNISHMENT_THRESHOLD: + # We've gotten too many timeouts from this provider recently, so avoid + # contacting it in the first place. + LOGGER.debug("Aborted attempt to contact %s due to previous timeouts", provider) + raise HTTPGatewayTimeout(f"Avoiding request to {provider}") + + url = tile_sources[provider].url_template.format(x=x, y=y, z=z, s=random.choice("abc")) + headers = { + "user-agent": f"Fietsboek-Tile-Proxy/{__VERSION__}", + } + from_mail = request.registry.settings.get('email.from') + if from_mail: + headers["from"] = from_mail + + try: + resp = requests.get(url, headers=headers, timeout=TIMEOUT.total_seconds()) + except ReadTimeout: + LOGGER.debug("Proxy timeout when accessing %r", url) + request.redis.incr(timeout_tracker) + request.redis.expire(timeout_tracker, PUNISHMENT_TTL) + raise HTTPGatewayTimeout(f"No response in time from {provider}") from None + else: + try: + resp.raise_for_status() + except requests.HTTPError as exc: + LOGGER.info("Proxy request failed for %s: %s", provider, exc) + return Response(f"Failed to get tile from {provider}", + status_code=resp.status_code) + request.redis.set(cache_key, resp.content, ex=TTL) + return Response(resp.content, content_type=resp.headers.get("Content-type", content_type)) + + +def sources_for(request): + """Returns all eligible tile sources for the given request. + + :param request: The Pyramid request. + :type request: pyramid.request.Request + :return: A list of tile sources. + :rtype: list[TileSource] + """ + settings = request.registry.settings + return [ + source for source in chain( + (default_layer for default_layer in DEFAULT_TILE_LAYERS + if default_layer.key in settings["fietsboek.default_tile_layers"]), + settings["fietsboek.tile_layers"] + ) + if source.access == LayerAccess.PUBLIC or request.identity is not None + ] + + +def extract_tile_layers(settings): + """Extract all defined tile layers from the settings. + + :param settings: The application settings. + :type settings: dict + :return: A list of extracted tile sources. + :rtype: list[TileSource] + """ + layers = [] + layers.extend(_extract_thunderforest(settings)) + layers.extend(_extract_user_layers(settings)) + return layers + + +def _extract_thunderforest(settings): + # Thunderforest Shortcut! + tf_api_key = settings.get("thunderforest.api_key") + if tf_api_key: + tf_access = LayerAccess(settings.get("thunderforest.access", "restricted")) + tf_attribution = ' | '.join([ + _jb_copy, + _href("https://www.thunderforest.com/", "Thunderforest"), + _href("https://www.openstreetmap.org/", "OpenStreetMap"), + ]) + for tf_map in settings["thunderforest.maps"]: + url = (f"https://tile.thunderforest.com/{tf_map}/" + f"{{z}}/{{x}}/{{y}}.png?apikey={tf_api_key}") + yield TileSource( + f"tf-{tf_map}", f"TF {tf_map.title()}", url, + LayerType.BASE, 22, tf_access, tf_attribution, + ) + + +def _extract_user_layers(settings): + # Any other custom maps + for key in settings.keys(): + match = re.match("^fietsboek\\.tile_layer\\.([A-Za-z0-9_-]+)$", key) + if not match: + continue + + provider_id = match.group(1) + name = settings[key] + url = settings[f"{key}.url"] + layer_type = LayerType(settings.get(f"{key}.type", "base")) + zoom = int(settings.get(f"{key}.zoom", 22)) + attribution = settings.get(f"{key}.attribution", _jb_copy) + access = LayerAccess(settings.get(f"{key}.access", "public")) + + yield TileSource(provider_id, name, url, layer_type, zoom, access, attribution) diff --git a/poetry.lock b/poetry.lock index d9fb521..314a8c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,6 +16,17 @@ SQLAlchemy = ">=1.3.0" tz = ["python-dateutil"] [[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + +[[package]] name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" @@ -72,6 +83,14 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] [[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." @@ -83,6 +102,17 @@ python-versions = "*" pycparser = "*" [[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" @@ -136,6 +166,20 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] + +[[package]] name = "exceptiongroup" version = "1.0.4" description = "Backport of PEP 654 (exception groups)" @@ -179,6 +223,14 @@ docs = ["Sphinx", "pylons-sphinx-themes", "watchdog"] testing = ["mock", "pytest", "pytest-cov", "watchdog"] [[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] name = "importlib-metadata" version = "5.0.0" description = "Read metadata from Python packages" @@ -276,7 +328,7 @@ name = "packaging" version = "21.3" description = "Core utilities for Python packages" category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -367,7 +419,7 @@ name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" -optional = true +optional = false python-versions = ">=3.6.8" [package.extras] @@ -525,6 +577,25 @@ optional = false python-versions = "*" [[package]] +name = "redis" +version = "4.3.4" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} +packaging = ">=20.4" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] name = "repoze-lru" version = "0.7" description = "A tiny LRU cache implementation and decorator" @@ -537,8 +608,26 @@ docs = ["Sphinx"] testing = ["coverage", "nose"] [[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] name = "setuptools" -version = "65.5.1" +version = "65.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -642,6 +731,19 @@ optional = false python-versions = ">=3.7" [[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] name = "venusian" version = "3.0.0" description = "A library for deferring decorator actions" @@ -703,6 +805,14 @@ docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"] [[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] name = "zipp" version = "3.10.0" description = "Backport of pathlib-compatible object wrapper for zip files" @@ -731,7 +841,7 @@ test = ["zope.testrunner"] [[package]] name = "zope-interface" -version = "5.5.1" +version = "5.5.2" description = "Interfaces for Python" category = "main" optional = false @@ -768,13 +878,17 @@ testing = ["WebTest", "pytest", "pytest-cov"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "5452a4dee7949b3ed0f47ddae85defaef989f8ffc06b77cfea1e0f3116e6a40c" +content-hash = "ca923fd9e1fe5d896c832286f2227c4a0a73fcf56124b8b18e1d70bcef22cbe7" [metadata.files] alembic = [ {file = "alembic-1.8.1-py3-none-any.whl", hash = "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4"}, {file = "alembic-1.8.1.tar.gz", hash = "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa"}, ] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, @@ -791,6 +905,10 @@ bleach = [ {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, ] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] cffi = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, @@ -857,6 +975,10 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -945,6 +1067,10 @@ cryptography = [ {file = "cryptography-38.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a"}, {file = "cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] exceptiongroup = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, @@ -1018,6 +1144,10 @@ hupper = [ {file = "hupper-1.10.3-py2.py3-none-any.whl", hash = "sha256:f683850d62598c02faf3c7cdaaa727d8cbe3c5a2497a5737a8358386903b2601"}, {file = "hupper-1.10.3.tar.gz", hash = "sha256:cd6f51b72c7587bc9bce8a65ecd025a1e95f1b03284519bfe91284d010316cd9"}, ] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] importlib-metadata = [ {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, @@ -1152,13 +1282,21 @@ pytz = [ {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, ] +redis = [ + {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, + {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, +] repoze-lru = [ {file = "repoze.lru-0.7-py3-none-any.whl", hash = "sha256:f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea"}, {file = "repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77"}, ] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] setuptools = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, + {file = "setuptools-65.6.0-py3-none-any.whl", hash = "sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840"}, + {file = "setuptools-65.6.0.tar.gz", hash = "sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1227,6 +1365,10 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] venusian = [ {file = "venusian-3.0.0-py3-none-any.whl", hash = "sha256:06e7385786ad3a15c70740b2af8d30dfb063a946a851dcb4159f9e2a2302578f"}, {file = "venusian-3.0.0.tar.gz", hash = "sha256:f6842b7242b1039c0c28f6feef29016e7e7dd3caaeb476a193acf737db31ee38"}, @@ -1247,6 +1389,72 @@ webtest = [ {file = "WebTest-3.0.0-py3-none-any.whl", hash = "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead"}, {file = "WebTest-3.0.0.tar.gz", hash = "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"}, ] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] zipp = [ {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, @@ -1256,45 +1464,42 @@ zope-deprecation = [ {file = "zope.deprecation-4.4.0.tar.gz", hash = "sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df"}, ] zope-interface = [ - {file = "zope.interface-5.5.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dd4b9251e95020c3d5d104b528dbf53629d09c146ce9c8dfaaf8f619ae1cce35"}, - {file = "zope.interface-5.5.1-cp27-cp27m-win32.whl", hash = "sha256:061a41a3f96f076686d7f1cb87f3deec6f0c9f0325dcc054ac7b504ae9bb0d82"}, - {file = "zope.interface-5.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:7f2e4ebe0a000c5727ee04227cf0ff5ae612fe599f88d494216e695b1dac744d"}, - {file = "zope.interface-5.5.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:c9552ee9e123b7997c7630fb95c466ee816d19e721c67e4da35351c5f4032726"}, - {file = "zope.interface-5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a923d2dec50f2b4d41ce198af3516517f2e458220942cf393839d2f9e22000"}, - {file = "zope.interface-5.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a20fc9cccbda2a28e8db8cabf2f47fead7e9e49d317547af6bf86a7269e4b9a1"}, - {file = "zope.interface-5.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6f51ffbdcf865f140f55c484001415505f5e68eb0a9eab1d37d0743b503b423"}, - {file = "zope.interface-5.5.1-cp310-cp310-win32.whl", hash = "sha256:8de7bde839d72d96e0c92e8d1fdb4862e89b8fc52514d14b101ca317d9bcf87c"}, - {file = "zope.interface-5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:90f611d4cdf82fb28837fe15c3940255755572a4edf4c72e2306dbce7dcb3092"}, - {file = "zope.interface-5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:489c4c46fcbd9364f60ff0dcb93ec9026eca64b2f43dc3b05d0724092f205e27"}, - {file = "zope.interface-5.5.1-cp311-cp311-win32.whl", hash = "sha256:9ad58724fabb429d1ebb6f334361f0a3b35f96be0e74bfca6f7de8530688b2df"}, - {file = "zope.interface-5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:a69f6d8b639f2317ba54278b64fef51d8250ad2c87acac1408b9cc461e4d6bb6"}, - {file = "zope.interface-5.5.1-cp35-cp35m-win32.whl", hash = "sha256:d743b03a72fefed807a4512c079fb1aa5e7777036cc7a4b6ff79ae4650a14f73"}, - {file = "zope.interface-5.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3e42b1c3f4fd863323a8275c52c78681281a8f2e1790f0e869d911c1c7b25c46"}, - {file = "zope.interface-5.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:7b4547a2f624a537e90fb99cec4d8b3b6be4af3f449c3477155aae65396724ad"}, - {file = "zope.interface-5.5.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a96d499ff6faa9b85b1309f50bf3744eb786e24833f7b500cbb7052dc4ae29"}, - {file = "zope.interface-5.5.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3c293c5c0e1cabe59c33e0d02fcee5c3eb365f79a20b8199a26ca784e406bd0d"}, - {file = "zope.interface-5.5.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8c8764226daad39004b7873c3880eb4860c594ff549ea47c045cdf313e1bad5"}, - {file = "zope.interface-5.5.1-cp36-cp36m-win32.whl", hash = "sha256:4477930451521ac7da97cc31d49f7b83086d5ae76e52baf16aac659053119f6d"}, - {file = "zope.interface-5.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:27c53aa2f46d42940ccdcb015fd525a42bf73f94acd886296794a41f229d5946"}, - {file = "zope.interface-5.5.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:2204a9d545fdbe0d9b0bf4d5e2fc67e7977de59666f7131c1433fde292fc3b41"}, - {file = "zope.interface-5.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:475b6e371cdbeb024f2302e826222bdc202186531f6dc095e8986c034e4b7961"}, - {file = "zope.interface-5.5.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a1393229c9c126dd1b4356338421e8882347347ab6fe3230cb7044edc813e424"}, - {file = "zope.interface-5.5.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e4988d94962f517f6da2d52337170b84856905b31b7dc504ed9c7b7e4bab2fc3"}, - {file = "zope.interface-5.5.1-cp37-cp37m-win32.whl", hash = "sha256:0eda7f61da6606a28b5efa5d8ad79b4b5bb242488e53a58993b2ec46c924ffee"}, - {file = "zope.interface-5.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:185f0faf6c3d8f2203e8755f7ca16b8964d97da0abde89c367177a04e36f2568"}, - {file = "zope.interface-5.5.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:026e7da51147910435950a46c55159d68af319f6e909f14873d35d411f4961db"}, - {file = "zope.interface-5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58331d2766e8e409360154d3178449d116220348d46386430097e63d02a1b6d2"}, - {file = "zope.interface-5.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0587d238b7867544134f4dcca19328371b8fd03fc2c56d15786f410792d0a68"}, - {file = "zope.interface-5.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd423d49abcf0ebf02c29c3daffe246ff756addb891f8aab717b3a4e2e1fd675"}, - {file = "zope.interface-5.5.1-cp38-cp38-win32.whl", hash = "sha256:13a7c6e3df8aa453583412de5725bf761217d06f66ff4ed776d44fbcd13ec4e4"}, - {file = "zope.interface-5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:72a93445937cc71f0b8372b0c9e7c185328e0db5e94d06383a1cb56705df1df4"}, - {file = "zope.interface-5.5.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:6cb8f9a1db47017929634264b3fc7ea4c1a42a3e28d67a14f14aa7b71deaa0d2"}, - {file = "zope.interface-5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e5540b7d703774fd171b7a7dc2a3cb70e98fc273b8b260b1bf2f7d3928f125b"}, - {file = "zope.interface-5.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d1f2d91c9c6cd54d750fa34f18bd73c71b372d0e6d06843bc7a5f21f5fd66fe0"}, - {file = "zope.interface-5.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:76cf472c79d15dce5f438a4905a1309be57d2d01bc1de2de30bda61972a79ab4"}, - {file = "zope.interface-5.5.1-cp39-cp39-win32.whl", hash = "sha256:509a8d39b64a5e8d473f3f3db981f3ca603d27d2bc023c482605c1b52ec15662"}, - {file = "zope.interface-5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:8343536ea4ee15d6525e3e726bb49ffc3f2034f828a49237a36be96842c06e7c"}, - {file = "zope.interface-5.5.1.tar.gz", hash = "sha256:6d678475fdeb11394dc9aaa5c564213a1567cc663082e0ee85d52f78d1fbaab2"}, + {file = "zope.interface-5.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5"}, + {file = "zope.interface-5.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9"}, + {file = "zope.interface-5.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f"}, + {file = "zope.interface-5.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d"}, + {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b"}, + {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c"}, + {file = "zope.interface-5.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7"}, + {file = "zope.interface-5.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296"}, + {file = "zope.interface-5.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d"}, + {file = "zope.interface-5.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d"}, + {file = "zope.interface-5.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6"}, + {file = "zope.interface-5.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f"}, + {file = "zope.interface-5.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c"}, + {file = "zope.interface-5.5.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32"}, + {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b"}, + {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf"}, + {file = "zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e"}, + {file = "zope.interface-5.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf"}, + {file = "zope.interface-5.5.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0"}, + {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d"}, + {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16"}, + {file = "zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452"}, + {file = "zope.interface-5.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7"}, + {file = "zope.interface-5.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e"}, + {file = "zope.interface-5.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f"}, + {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188"}, + {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a"}, + {file = "zope.interface-5.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a"}, + {file = "zope.interface-5.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0"}, + {file = "zope.interface-5.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f"}, + {file = "zope.interface-5.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4"}, + {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396"}, + {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc"}, + {file = "zope.interface-5.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b"}, + {file = "zope.interface-5.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189"}, + {file = "zope.interface-5.5.2.tar.gz", hash = "sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671"}, ] zope-sqlalchemy = [ {file = "zope.sqlalchemy-1.6-py2.py3-none-any.whl", hash = "sha256:63c4560fdd2d55c6e5658a22f6835a3c12eac1a3af8140a01400f7367aac91ed"}, diff --git a/pylint.toml b/pylint.toml index 7495cf7..9e3a63c 100644 --- a/pylint.toml +++ b/pylint.toml @@ -149,7 +149,7 @@ function-naming-style = "snake_case" # function-rgx = # Good variable names which should always be accepted, separated by a comma. -good-names = ["i", "j", "k", "ex", "Run", "_", "id"] +good-names = ["i", "j", "k", "ex", "Run", "_", "id", "x", "y", "z"] # Good variable names regexes, separated by a comma. If names match any regex, # they will always be accepted diff --git a/pyproject.toml b/pyproject.toml index 9fab1a6..515c83b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,14 +37,19 @@ SQLAlchemy = "^1.4" alembic = "^1.8" transaction = "^3" "zope.sqlalchemy" = "^1.6" +redis = "^4.3.4" +# Compatibility with old Python versions importlib_resources = "^5.10" +importlib_metadata = "^5.0.0" + Babel = "^2.11" cryptography = "^38" gpxpy = "^1.5" markdown = "^3.4" bleach = "^5" Click = "^8.1" +requests = "^2.28.1" WebTest = {version = "^3", optional = true} pytest = {version = "^7.2", optional = true} |