commit
728007024f
@ -10,18 +10,23 @@ export APP_SECRET_KEY='just for test'
|
|||||||
export APP_LOG=fittrackee.log
|
export APP_LOG=fittrackee.log
|
||||||
export UPLOAD_FOLDER=/usr/src/app/uploads
|
export UPLOAD_FOLDER=/usr/src/app/uploads
|
||||||
|
|
||||||
# Database
|
# PostgreSQL
|
||||||
export DATABASE_URL=postgresql://fittrackee:fittrackee@fittrackee-db:5432/fittrackee
|
export DATABASE_URL=postgresql://fittrackee:fittrackee@fittrackee-db:5432/fittrackee
|
||||||
export DATABASE_TEST_URL=postgresql://fittrackee:fittrackee@fittrackee-db:5432/fittrackee_test
|
export DATABASE_TEST_URL=postgresql://fittrackee:fittrackee@fittrackee-db:5432/fittrackee_test
|
||||||
# export DATABASE_DISABLE_POOLING=
|
# export DATABASE_DISABLE_POOLING=
|
||||||
|
|
||||||
|
# Redis (required for API rate limits and email sending)
|
||||||
|
export REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
|
# API rate limits
|
||||||
|
# export API_RATE_LIMITS=300 per 5 minutes
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
export UI_URL=http://0.0.0.0:5000
|
export UI_URL=http://0.0.0.0:5000
|
||||||
# For development:
|
# For development:
|
||||||
# export UI_URL=http://0.0.0.0:3000
|
# export UI_URL=http://0.0.0.0:3000
|
||||||
export EMAIL_URL=smtp://mail:1025
|
export EMAIL_URL=smtp://mail:1025
|
||||||
export SENDER_EMAIL=fittrackee@example.com
|
export SENDER_EMAIL=fittrackee@example.com
|
||||||
export REDIS_URL=redis://redis:6379
|
|
||||||
export WORKERS_PROCESSES=2
|
export WORKERS_PROCESSES=2
|
||||||
|
|
||||||
# Workouts
|
# Workouts
|
||||||
|
@ -12,15 +12,20 @@ export APP_SECRET_KEY='please change me'
|
|||||||
export APP_LOG=fittrackee.log
|
export APP_LOG=fittrackee.log
|
||||||
export UPLOAD_FOLDER=
|
export UPLOAD_FOLDER=
|
||||||
|
|
||||||
# Database
|
# PostgreSQL
|
||||||
# export DATABASE_URL=postgresql://fittrackee:fittrackee@${HOST}:5432/fittrackee
|
# export DATABASE_URL=postgresql://fittrackee:fittrackee@${HOST}:5432/fittrackee
|
||||||
# export DATABASE_DISABLE_POOLING=
|
# export DATABASE_DISABLE_POOLING=
|
||||||
|
|
||||||
|
# Redis (required for API rate limits and email sending)
|
||||||
|
# export REDIS_URL=
|
||||||
|
|
||||||
|
# API rate limits
|
||||||
|
# export API_RATE_LIMITS=300 per 5 minutes
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
export UI_URL=
|
export UI_URL=
|
||||||
export EMAIL_URL=
|
export EMAIL_URL=
|
||||||
export SENDER_EMAIL=
|
export SENDER_EMAIL=
|
||||||
# export REDIS_URL=
|
|
||||||
# export WORKERS_PROCESSES=
|
# export WORKERS_PROCESSES=
|
||||||
|
|
||||||
# Workouts
|
# Workouts
|
||||||
|
@ -10,6 +10,7 @@ This application is written in Python (API) and Typescript (client):
|
|||||||
- `python-forecast.io <https://github.com/ZeevG/python-forecast.io>`_ to fetch weather data from `Dark Sky <https://darksky.net>`__ (former forecast.io)
|
- `python-forecast.io <https://github.com/ZeevG/python-forecast.io>`_ to fetch weather data from `Dark Sky <https://darksky.net>`__ (former forecast.io)
|
||||||
- `dramatiq <https://flask-dramatiq.readthedocs.io/en/latest/>`_ for task queue
|
- `dramatiq <https://flask-dramatiq.readthedocs.io/en/latest/>`_ for task queue
|
||||||
- `Authlib <https://docs.authlib.org/en/latest/>`_ for OAuth 2.0 Authorization support
|
- `Authlib <https://docs.authlib.org/en/latest/>`_ for OAuth 2.0 Authorization support
|
||||||
|
- `Flask-Limiter <https://flask-limiter.readthedocs.io/en/stable>`_ for API rate limits
|
||||||
- Client:
|
- Client:
|
||||||
- Vue3/Vuex
|
- Vue3/Vuex
|
||||||
- `Leaflet <https://leafletjs.com/>`__ to display map
|
- `Leaflet <https://leafletjs.com/>`__ to display map
|
||||||
@ -21,10 +22,13 @@ This application is written in Python (API) and Typescript (client):
|
|||||||
Prerequisites
|
Prerequisites
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- mandatory
|
||||||
- Python 3.7+
|
- Python 3.7+
|
||||||
- PostgreSQL database (10+)
|
- PostgreSQL 10+
|
||||||
- SMTP provider and Redis for task queue (if email sending is enabled)
|
- optional
|
||||||
- API key from `Dark Sky <https://darksky.net/dev>`__ (not mandatory)
|
- Redis for task queue (if email sending is enabled) and API rate limits
|
||||||
|
- SMTP provider (if email sending is enabled)
|
||||||
|
- API key from `Dark Sky <https://darksky.net/dev>`__
|
||||||
- `Poetry <https://poetry.eustace.io>`__ (for installation from sources only)
|
- `Poetry <https://poetry.eustace.io>`__ (for installation from sources only)
|
||||||
- `Yarn <https://yarnpkg.com>`__ (for development only)
|
- `Yarn <https://yarnpkg.com>`__ (for development only)
|
||||||
- Docker and Docker Compose (for development or evaluation purposes)
|
- Docker and Docker Compose (for development or evaluation purposes)
|
||||||
@ -156,7 +160,7 @@ deployment method.
|
|||||||
|
|
||||||
.. versionadded:: 0.3.0
|
.. versionadded:: 0.3.0
|
||||||
|
|
||||||
Redis instance used by **Dramatiq**.
|
Redis instance used by **Dramatiq** and **Flask-Limiter**.
|
||||||
|
|
||||||
:default: local Redis instance (``redis://``)
|
:default: local Redis instance (``redis://``)
|
||||||
|
|
||||||
@ -168,6 +172,15 @@ deployment method.
|
|||||||
Number of processes used by **Dramatiq**.
|
Number of processes used by **Dramatiq**.
|
||||||
|
|
||||||
|
|
||||||
|
.. envvar:: API_RATE_LIMITS 🆕
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.0
|
||||||
|
|
||||||
|
API rate limits, see `API rate limits <installation.html#api-rate-limits>`__.
|
||||||
|
|
||||||
|
:default: `300 per 5 minutes`
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: TILE_SERVER_URL
|
.. envvar:: TILE_SERVER_URL
|
||||||
|
|
||||||
.. versionadded:: 0.4.0
|
.. versionadded:: 0.4.0
|
||||||
@ -178,7 +191,7 @@ deployment method.
|
|||||||
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
|
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: STATICMAP_SUBDOMAINS 🆕
|
.. envvar:: STATICMAP_SUBDOMAINS
|
||||||
|
|
||||||
.. versionadded:: 0.6.10
|
.. versionadded:: 0.6.10
|
||||||
|
|
||||||
@ -289,6 +302,42 @@ For instance, to set OSM France tile server, the expected values are:
|
|||||||
The subdomain will be chosen randomly.
|
The subdomain will be chosen randomly.
|
||||||
|
|
||||||
|
|
||||||
|
API rate limits 🆕
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
.. versionadded:: 0.7.0
|
||||||
|
|
||||||
|
| API rate limits are managed by `Flask-Limiter <https://flask-limiter.readthedocs.io/en/stable>`_, based on IP with fixed window strategy.
|
||||||
|
| To enable rate limits, **Redis** must be available.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
| If no Redis instance is available for rate limits, FitTrackee can still start.
|
||||||
|
|
||||||
|
| All endpoints are subject to rate limits, except endpoints serving assets.
|
||||||
|
| Limits can be modified by setting the environment variable ``API_RATE_LIMITS`` (see `Flask-Limiter documentation for notation <https://flask-limiter.readthedocs.io/en/stable/configuration.html#rate-limit-string-notation>`_).
|
||||||
|
| Rate limits must be separated by a comma, for instance:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
export API_RATE_LIMITS="200 per day, 50 per hour"
|
||||||
|
|
||||||
|
**Flask-Limiter** provides a `Command Line Interface <https://flask-limiter.readthedocs.io/en/stable/cli.html>`_ for maintenance and diagnostic purposes.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ flask limiter
|
||||||
|
Usage: flask limiter [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
|
Flask-Limiter maintenance & utility commmands
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help Show this message and exit.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
clear Clear limits for a specific key
|
||||||
|
config View the extension configuration
|
||||||
|
limits Enumerate details about all routes with rate limits
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -124,6 +124,8 @@
|
|||||||
environment variable
|
environment variable
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="installation.html#envvar-API_RATE_LIMITS">API_RATE_LIMITS 🆕</a>
|
||||||
|
</li>
|
||||||
<li><a href="installation.html#envvar-APP_LOG">APP_LOG</a>
|
<li><a href="installation.html#envvar-APP_LOG">APP_LOG</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="installation.html#envvar-APP_SECRET_KEY">APP_SECRET_KEY</a>
|
<li><a href="installation.html#envvar-APP_SECRET_KEY">APP_SECRET_KEY</a>
|
||||||
@ -152,7 +154,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li><a href="installation.html#envvar-SENDER_EMAIL">SENDER_EMAIL</a>
|
<li><a href="installation.html#envvar-SENDER_EMAIL">SENDER_EMAIL</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="installation.html#envvar-STATICMAP_SUBDOMAINS">STATICMAP_SUBDOMAINS 🆕</a>
|
<li><a href="installation.html#envvar-STATICMAP_SUBDOMAINS">STATICMAP_SUBDOMAINS</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="installation.html#envvar-TILE_SERVER_URL">TILE_SERVER_URL</a>
|
<li><a href="installation.html#envvar-TILE_SERVER_URL">TILE_SERVER_URL</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -85,9 +85,10 @@
|
|||||||
<li><a class="reference internal" href="#environment-variables">Environment variables</a><ul>
|
<li><a class="reference internal" href="#environment-variables">Environment variables</a><ul>
|
||||||
<li><a class="reference internal" href="#emails">Emails</a></li>
|
<li><a class="reference internal" href="#emails">Emails</a></li>
|
||||||
<li><a class="reference internal" href="#map-tile-server">Map tile server</a></li>
|
<li><a class="reference internal" href="#map-tile-server">Map tile server</a></li>
|
||||||
|
<li><a class="reference internal" href="#api-rate-limits">API rate limits 🆕</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="reference internal" href="#id1">Installation</a><ul>
|
<li><a class="reference internal" href="#id2">Installation</a><ul>
|
||||||
<li><a class="reference internal" href="#from-pypi">From PyPI</a></li>
|
<li><a class="reference internal" href="#from-pypi">From PyPI</a></li>
|
||||||
<li><a class="reference internal" href="#from-sources">From sources</a><ul>
|
<li><a class="reference internal" href="#from-sources">From sources</a><ul>
|
||||||
<li><a class="reference internal" href="#dev-environment">Dev environment</a></li>
|
<li><a class="reference internal" href="#dev-environment">Dev environment</a></li>
|
||||||
@ -97,9 +98,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="reference internal" href="#upgrade">Upgrade</a><ul>
|
<li><a class="reference internal" href="#upgrade">Upgrade</a><ul>
|
||||||
<li><a class="reference internal" href="#id2">From PyPI</a></li>
|
<li><a class="reference internal" href="#id3">From PyPI</a></li>
|
||||||
<li><a class="reference internal" href="#id3">From sources</a><ul>
|
<li><a class="reference internal" href="#id4">From sources</a><ul>
|
||||||
<li><a class="reference internal" href="#id4">Dev environment</a></li>
|
<li><a class="reference internal" href="#id5">Dev environment</a></li>
|
||||||
<li><a class="reference internal" href="#prod-environment">Prod environment</a></li>
|
<li><a class="reference internal" href="#prod-environment">Prod environment</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -107,7 +108,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li><a class="reference internal" href="#deployment">Deployment</a></li>
|
<li><a class="reference internal" href="#deployment">Deployment</a></li>
|
||||||
<li><a class="reference internal" href="#docker">Docker</a><ul>
|
<li><a class="reference internal" href="#docker">Docker</a><ul>
|
||||||
<li><a class="reference internal" href="#id5">Installation</a></li>
|
<li><a class="reference internal" href="#id6">Installation</a></li>
|
||||||
<li><a class="reference internal" href="#development">Development</a></li>
|
<li><a class="reference internal" href="#development">Development</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -172,6 +173,7 @@
|
|||||||
<li><p><a class="reference external" href="https://github.com/ZeevG/python-forecast.io">python-forecast.io</a> to fetch weather data from <a class="reference external" href="https://darksky.net">Dark Sky</a> (former forecast.io)</p></li>
|
<li><p><a class="reference external" href="https://github.com/ZeevG/python-forecast.io">python-forecast.io</a> to fetch weather data from <a class="reference external" href="https://darksky.net">Dark Sky</a> (former forecast.io)</p></li>
|
||||||
<li><p><a class="reference external" href="https://flask-dramatiq.readthedocs.io/en/latest/">dramatiq</a> for task queue</p></li>
|
<li><p><a class="reference external" href="https://flask-dramatiq.readthedocs.io/en/latest/">dramatiq</a> for task queue</p></li>
|
||||||
<li><p><a class="reference external" href="https://docs.authlib.org/en/latest/">Authlib</a> for OAuth 2.0 Authorization support</p></li>
|
<li><p><a class="reference external" href="https://docs.authlib.org/en/latest/">Authlib</a> for OAuth 2.0 Authorization support</p></li>
|
||||||
|
<li><p><a class="reference external" href="https://flask-limiter.readthedocs.io/en/stable">Flask-Limiter</a> for API rate limits</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
@ -193,14 +195,27 @@
|
|||||||
<section id="prerequisites">
|
<section id="prerequisites">
|
||||||
<h2>Prerequisites<a class="headerlink" href="#prerequisites" title="Permalink to this heading">¶</a></h2>
|
<h2>Prerequisites<a class="headerlink" href="#prerequisites" title="Permalink to this heading">¶</a></h2>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
|
<li><dl class="simple">
|
||||||
|
<dt>mandatory</dt><dd><ul>
|
||||||
<li><p>Python 3.7+</p></li>
|
<li><p>Python 3.7+</p></li>
|
||||||
<li><p>PostgreSQL database (10+)</p></li>
|
<li><p>PostgreSQL 10+</p></li>
|
||||||
<li><p>SMTP provider and Redis for task queue (if email sending is enabled)</p></li>
|
</ul>
|
||||||
<li><p>API key from <a class="reference external" href="https://darksky.net/dev">Dark Sky</a> (not mandatory)</p></li>
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
<li><dl class="simple">
|
||||||
|
<dt>optional</dt><dd><ul>
|
||||||
|
<li><p>Redis for task queue (if email sending is enabled) and API rate limits</p></li>
|
||||||
|
<li><p>SMTP provider (if email sending is enabled)</p></li>
|
||||||
|
<li><p>API key from <a class="reference external" href="https://darksky.net/dev">Dark Sky</a></p></li>
|
||||||
<li><p><a class="reference external" href="https://poetry.eustace.io">Poetry</a> (for installation from sources only)</p></li>
|
<li><p><a class="reference external" href="https://poetry.eustace.io">Poetry</a> (for installation from sources only)</p></li>
|
||||||
<li><p><a class="reference external" href="https://yarnpkg.com">Yarn</a> (for development only)</p></li>
|
<li><p><a class="reference external" href="https://yarnpkg.com">Yarn</a> (for development only)</p></li>
|
||||||
<li><p>Docker and Docker Compose (for development or evaluation purposes)</p></li>
|
<li><p>Docker and Docker Compose (for development or evaluation purposes)</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<div class="admonition note">
|
<div class="admonition note">
|
||||||
<p class="admonition-title">Note</p>
|
<p class="admonition-title">Note</p>
|
||||||
<div class="line-block">
|
<div class="line-block">
|
||||||
@ -392,7 +407,7 @@ see <a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/p
|
|||||||
<dd><div class="versionadded">
|
<dd><div class="versionadded">
|
||||||
<p><span class="versionmodified added">New in version 0.3.0.</span></p>
|
<p><span class="versionmodified added">New in version 0.3.0.</span></p>
|
||||||
</div>
|
</div>
|
||||||
<p>Redis instance used by <strong>Dramatiq</strong>.</p>
|
<p>Redis instance used by <strong>Dramatiq</strong> and <strong>Flask-Limiter</strong>.</p>
|
||||||
<dl class="field-list simple">
|
<dl class="field-list simple">
|
||||||
<dt class="field-odd">Default<span class="colon">:</span></dt>
|
<dt class="field-odd">Default<span class="colon">:</span></dt>
|
||||||
<dd class="field-odd"><p>local Redis instance (<code class="docutils literal notranslate"><span class="pre">redis://</span></code>)</p>
|
<dd class="field-odd"><p>local Redis instance (<code class="docutils literal notranslate"><span class="pre">redis://</span></code>)</p>
|
||||||
@ -409,6 +424,20 @@ see <a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/p
|
|||||||
<p>Number of processes used by <strong>Dramatiq</strong>.</p>
|
<p>Number of processes used by <strong>Dramatiq</strong>.</p>
|
||||||
</dd></dl>
|
</dd></dl>
|
||||||
|
|
||||||
|
<dl class="std envvar">
|
||||||
|
<dt class="sig sig-object std" id="envvar-API_RATE_LIMITS">
|
||||||
|
<span class="sig-name descname"><span class="pre">API_RATE_LIMITS</span> <span class="pre">🆕</span></span><a class="headerlink" href="#envvar-API_RATE_LIMITS" title="Permalink to this definition">¶</a></dt>
|
||||||
|
<dd><div class="versionadded">
|
||||||
|
<p><span class="versionmodified added">New in version 0.7.0.</span></p>
|
||||||
|
</div>
|
||||||
|
<p>API rate limits, see <a class="reference external" href="installation.html#api-rate-limits">API rate limits</a>.</p>
|
||||||
|
<dl class="field-list simple">
|
||||||
|
<dt class="field-odd">Default<span class="colon">:</span></dt>
|
||||||
|
<dd class="field-odd"><p><cite>300 per 5 minutes</cite></p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</dd></dl>
|
||||||
|
|
||||||
<dl class="std envvar">
|
<dl class="std envvar">
|
||||||
<dt class="sig sig-object std" id="envvar-TILE_SERVER_URL">
|
<dt class="sig sig-object std" id="envvar-TILE_SERVER_URL">
|
||||||
<span class="sig-name descname"><span class="pre">TILE_SERVER_URL</span></span><a class="headerlink" href="#envvar-TILE_SERVER_URL" title="Permalink to this definition">¶</a></dt>
|
<span class="sig-name descname"><span class="pre">TILE_SERVER_URL</span></span><a class="headerlink" href="#envvar-TILE_SERVER_URL" title="Permalink to this definition">¶</a></dt>
|
||||||
@ -428,7 +457,7 @@ see <a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/p
|
|||||||
|
|
||||||
<dl class="std envvar">
|
<dl class="std envvar">
|
||||||
<dt class="sig sig-object std" id="envvar-STATICMAP_SUBDOMAINS">
|
<dt class="sig sig-object std" id="envvar-STATICMAP_SUBDOMAINS">
|
||||||
<span class="sig-name descname"><span class="pre">STATICMAP_SUBDOMAINS</span> <span class="pre">🆕</span></span><a class="headerlink" href="#envvar-STATICMAP_SUBDOMAINS" title="Permalink to this definition">¶</a></dt>
|
<span class="sig-name descname"><span class="pre">STATICMAP_SUBDOMAINS</span></span><a class="headerlink" href="#envvar-STATICMAP_SUBDOMAINS" title="Permalink to this definition">¶</a></dt>
|
||||||
<dd><div class="versionadded">
|
<dd><div class="versionadded">
|
||||||
<p><span class="versionmodified added">New in version 0.6.10.</span></p>
|
<p><span class="versionmodified added">New in version 0.6.10.</span></p>
|
||||||
</div>
|
</div>
|
||||||
@ -572,9 +601,48 @@ The tile server can be changed by updating <code class="docutils literal notrans
|
|||||||
</ul>
|
</ul>
|
||||||
<p>The subdomain will be chosen randomly.</p>
|
<p>The subdomain will be chosen randomly.</p>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="api-rate-limits">
|
||||||
|
<h3>API rate limits 🆕<a class="headerlink" href="#api-rate-limits" title="Permalink to this heading">¶</a></h3>
|
||||||
|
<div class="versionadded">
|
||||||
|
<p><span class="versionmodified added">New in version 0.7.0.</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="line-block">
|
||||||
|
<div class="line">API rate limits are managed by <a class="reference external" href="https://flask-limiter.readthedocs.io/en/stable">Flask-Limiter</a>, based on IP with fixed window strategy.</div>
|
||||||
|
<div class="line">To enable rate limits, <strong>Redis</strong> must be available.</div>
|
||||||
|
</div>
|
||||||
|
<div class="admonition note">
|
||||||
|
<p class="admonition-title">Note</p>
|
||||||
|
<div class="line-block">
|
||||||
|
<div class="line">If no Redis instance is available for rate limits, FitTrackee can still start.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line-block">
|
||||||
|
<div class="line">All endpoints are subject to rate limits, except endpoints serving assets.</div>
|
||||||
|
<div class="line">Limits can be modified by setting the environment variable <code class="docutils literal notranslate"><span class="pre">API_RATE_LIMITS</span></code> (see <a class="reference external" href="https://flask-limiter.readthedocs.io/en/stable/configuration.html#rate-limit-string-notation">Flask-Limiter documentation for notation</a>).</div>
|
||||||
|
<div class="line">Rate limits must be separated by a comma, for instance:</div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">export</span> <span class="n">API_RATE_LIMITS</span><span class="o">=</span><span class="s2">"200 per day, 50 per hour"</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<p><strong>Flask-Limiter</strong> provides a <a class="reference external" href="https://flask-limiter.readthedocs.io/en/stable/cli.html">Command Line Interface</a> for maintenance and diagnostic purposes.</p>
|
||||||
|
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ flask limiter
|
||||||
|
Usage: flask limiter <span class="o">[</span>OPTIONS<span class="o">]</span> COMMAND <span class="o">[</span>ARGS<span class="o">]</span>...
|
||||||
|
|
||||||
|
Flask-Limiter maintenance <span class="p">&</span> utility commmands
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help Show this message and exit.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
clear Clear limits <span class="k">for</span> a specific key
|
||||||
|
config View the extension configuration
|
||||||
|
limits Enumerate details about all routes with rate limits
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="id1">
|
</section>
|
||||||
<h2>Installation<a class="headerlink" href="#id1" title="Permalink to this heading">¶</a></h2>
|
<section id="id2">
|
||||||
|
<h2>Installation<a class="headerlink" href="#id2" title="Permalink to this heading">¶</a></h2>
|
||||||
<div class="admonition warning">
|
<div class="admonition warning">
|
||||||
<p class="admonition-title">Warning</p>
|
<p class="admonition-title">Warning</p>
|
||||||
<div class="line-block">
|
<div class="line-block">
|
||||||
@ -776,8 +844,8 @@ database credentials</strong>):</p></li>
|
|||||||
<div class="line">- upload directory (see <a class="reference external" href="installation.html#environment-variables">Environment variables</a>)</div>
|
<div class="line">- upload directory (see <a class="reference external" href="installation.html#environment-variables">Environment variables</a>)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section id="id2">
|
<section id="id3">
|
||||||
<h3>From PyPI<a class="headerlink" href="#id2" title="Permalink to this heading">¶</a></h3>
|
<h3>From PyPI<a class="headerlink" href="#id3" title="Permalink to this heading">¶</a></h3>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li><p>Stop the application and activate the virtualenv</p></li>
|
<li><p>Stop the application and activate the virtualenv</p></li>
|
||||||
<li><p>Upgrade with pip</p></li>
|
<li><p>Upgrade with pip</p></li>
|
||||||
@ -802,10 +870,10 @@ $ <span class="nb">source</span> .env
|
|||||||
<li><p>Restart the application and task queue workers (if email sending is enabled).</p></li>
|
<li><p>Restart the application and task queue workers (if email sending is enabled).</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section id="id3">
|
|
||||||
<h3>From sources<a class="headerlink" href="#id3" title="Permalink to this heading">¶</a></h3>
|
|
||||||
<section id="id4">
|
<section id="id4">
|
||||||
<h4>Dev environment<a class="headerlink" href="#id4" title="Permalink to this heading">¶</a></h4>
|
<h3>From sources<a class="headerlink" href="#id4" title="Permalink to this heading">¶</a></h3>
|
||||||
|
<section id="id5">
|
||||||
|
<h4>Dev environment<a class="headerlink" href="#id5" title="Permalink to this heading">¶</a></h4>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li><p>Stop the application and pull the repository:</p></li>
|
<li><p>Stop the application and pull the repository:</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -996,8 +1064,8 @@ server {
|
|||||||
</section>
|
</section>
|
||||||
<section id="docker">
|
<section id="docker">
|
||||||
<h2>Docker<a class="headerlink" href="#docker" title="Permalink to this heading">¶</a></h2>
|
<h2>Docker<a class="headerlink" href="#docker" title="Permalink to this heading">¶</a></h2>
|
||||||
<section id="id5">
|
<section id="id6">
|
||||||
<h3>Installation<a class="headerlink" href="#id5" title="Permalink to this heading">¶</a></h3>
|
<h3>Installation<a class="headerlink" href="#id6" title="Permalink to this heading">¶</a></h3>
|
||||||
<div class="versionadded">
|
<div class="versionadded">
|
||||||
<p><span class="versionmodified added">New in version 0.4.4.</span></p>
|
<p><span class="versionmodified added">New in version 0.4.4.</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -10,6 +10,7 @@ This application is written in Python (API) and Typescript (client):
|
|||||||
- `python-forecast.io <https://github.com/ZeevG/python-forecast.io>`_ to fetch weather data from `Dark Sky <https://darksky.net>`__ (former forecast.io)
|
- `python-forecast.io <https://github.com/ZeevG/python-forecast.io>`_ to fetch weather data from `Dark Sky <https://darksky.net>`__ (former forecast.io)
|
||||||
- `dramatiq <https://flask-dramatiq.readthedocs.io/en/latest/>`_ for task queue
|
- `dramatiq <https://flask-dramatiq.readthedocs.io/en/latest/>`_ for task queue
|
||||||
- `Authlib <https://docs.authlib.org/en/latest/>`_ for OAuth 2.0 Authorization support
|
- `Authlib <https://docs.authlib.org/en/latest/>`_ for OAuth 2.0 Authorization support
|
||||||
|
- `Flask-Limiter <https://flask-limiter.readthedocs.io/en/stable>`_ for API rate limits
|
||||||
- Client:
|
- Client:
|
||||||
- Vue3/Vuex
|
- Vue3/Vuex
|
||||||
- `Leaflet <https://leafletjs.com/>`__ to display map
|
- `Leaflet <https://leafletjs.com/>`__ to display map
|
||||||
@ -21,10 +22,13 @@ This application is written in Python (API) and Typescript (client):
|
|||||||
Prerequisites
|
Prerequisites
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- mandatory
|
||||||
- Python 3.7+
|
- Python 3.7+
|
||||||
- PostgreSQL database (10+)
|
- PostgreSQL 10+
|
||||||
- SMTP provider and Redis for task queue (if email sending is enabled)
|
- optional
|
||||||
- API key from `Dark Sky <https://darksky.net/dev>`__ (not mandatory)
|
- Redis for task queue (if email sending is enabled) and API rate limits
|
||||||
|
- SMTP provider (if email sending is enabled)
|
||||||
|
- API key from `Dark Sky <https://darksky.net/dev>`__
|
||||||
- `Poetry <https://poetry.eustace.io>`__ (for installation from sources only)
|
- `Poetry <https://poetry.eustace.io>`__ (for installation from sources only)
|
||||||
- `Yarn <https://yarnpkg.com>`__ (for development only)
|
- `Yarn <https://yarnpkg.com>`__ (for development only)
|
||||||
- Docker and Docker Compose (for development or evaluation purposes)
|
- Docker and Docker Compose (for development or evaluation purposes)
|
||||||
@ -156,7 +160,7 @@ deployment method.
|
|||||||
|
|
||||||
.. versionadded:: 0.3.0
|
.. versionadded:: 0.3.0
|
||||||
|
|
||||||
Redis instance used by **Dramatiq**.
|
Redis instance used by **Dramatiq** and **Flask-Limiter**.
|
||||||
|
|
||||||
:default: local Redis instance (``redis://``)
|
:default: local Redis instance (``redis://``)
|
||||||
|
|
||||||
@ -168,6 +172,15 @@ deployment method.
|
|||||||
Number of processes used by **Dramatiq**.
|
Number of processes used by **Dramatiq**.
|
||||||
|
|
||||||
|
|
||||||
|
.. envvar:: API_RATE_LIMITS 🆕
|
||||||
|
|
||||||
|
.. versionadded:: 0.7.0
|
||||||
|
|
||||||
|
API rate limits, see `API rate limits <installation.html#api-rate-limits>`__.
|
||||||
|
|
||||||
|
:default: `300 per 5 minutes`
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: TILE_SERVER_URL
|
.. envvar:: TILE_SERVER_URL
|
||||||
|
|
||||||
.. versionadded:: 0.4.0
|
.. versionadded:: 0.4.0
|
||||||
@ -178,7 +191,7 @@ deployment method.
|
|||||||
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
|
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: STATICMAP_SUBDOMAINS 🆕
|
.. envvar:: STATICMAP_SUBDOMAINS
|
||||||
|
|
||||||
.. versionadded:: 0.6.10
|
.. versionadded:: 0.6.10
|
||||||
|
|
||||||
@ -289,6 +302,42 @@ For instance, to set OSM France tile server, the expected values are:
|
|||||||
The subdomain will be chosen randomly.
|
The subdomain will be chosen randomly.
|
||||||
|
|
||||||
|
|
||||||
|
API rate limits 🆕
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
.. versionadded:: 0.7.0
|
||||||
|
|
||||||
|
| API rate limits are managed by `Flask-Limiter <https://flask-limiter.readthedocs.io/en/stable>`_, based on IP with fixed window strategy.
|
||||||
|
| To enable rate limits, **Redis** must be available.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
| If no Redis instance is available for rate limits, FitTrackee can still start.
|
||||||
|
|
||||||
|
| All endpoints are subject to rate limits, except endpoints serving assets.
|
||||||
|
| Limits can be modified by setting the environment variable ``API_RATE_LIMITS`` (see `Flask-Limiter documentation for notation <https://flask-limiter.readthedocs.io/en/stable/configuration.html#rate-limit-string-notation>`_).
|
||||||
|
| Rate limits must be separated by a comma, for instance:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
export API_RATE_LIMITS="200 per day, 50 per hour"
|
||||||
|
|
||||||
|
**Flask-Limiter** provides a `Command Line Interface <https://flask-limiter.readthedocs.io/en/stable/cli.html>`_ for maintenance and diagnostic purposes.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ flask limiter
|
||||||
|
Usage: flask limiter [OPTIONS] COMMAND [ARGS]...
|
||||||
|
|
||||||
|
Flask-Limiter maintenance & utility commmands
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help Show this message and exit.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
clear Clear limits for a specific key
|
||||||
|
config View the extension configuration
|
||||||
|
limits Enumerate details about all routes with rate limits
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from importlib import import_module, reload
|
from importlib import import_module, reload
|
||||||
from typing import Any
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
|
import redis
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
Response,
|
Response,
|
||||||
@ -13,6 +14,9 @@ from flask import (
|
|||||||
)
|
)
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from flask_dramatiq import Dramatiq
|
from flask_dramatiq import Dramatiq
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
from flask_limiter.errors import RateLimitExceeded
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from sqlalchemy.exc import ProgrammingError
|
from sqlalchemy.exc import ProgrammingError
|
||||||
@ -22,11 +26,10 @@ from fittrackee.emails.email import EmailService
|
|||||||
from fittrackee.request import CustomRequest
|
from fittrackee.request import CustomRequest
|
||||||
|
|
||||||
VERSION = __version__ = '0.6.12'
|
VERSION = __version__ = '0.6.12'
|
||||||
db = SQLAlchemy()
|
REDIS_URL = os.getenv('REDIS_URL', 'redis://')
|
||||||
bcrypt = Bcrypt()
|
API_RATE_LIMITS = os.environ.get('API_RATE_LIMITS', '300 per 5 minutes').split(
|
||||||
migrate = Migrate()
|
','
|
||||||
email_service = EmailService()
|
)
|
||||||
dramatiq = Dramatiq()
|
|
||||||
log_file = os.getenv('APP_LOG')
|
log_file = os.getenv('APP_LOG')
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=log_file,
|
filename=log_file,
|
||||||
@ -35,6 +38,27 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
appLog = logging.getLogger('fittrackee')
|
appLog = logging.getLogger('fittrackee')
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
bcrypt = Bcrypt()
|
||||||
|
migrate = Migrate()
|
||||||
|
email_service = EmailService()
|
||||||
|
dramatiq = Dramatiq()
|
||||||
|
limiter = Limiter(
|
||||||
|
key_func=get_remote_address,
|
||||||
|
default_limits=API_RATE_LIMITS, # type: ignore
|
||||||
|
default_limits_per_method=True,
|
||||||
|
headers_enabled=True,
|
||||||
|
storage_uri=REDIS_URL,
|
||||||
|
strategy='fixed-window',
|
||||||
|
)
|
||||||
|
# if redis is not available, disable the rate limiter
|
||||||
|
r = redis.from_url(REDIS_URL)
|
||||||
|
try:
|
||||||
|
r.ping()
|
||||||
|
except redis.exceptions.ConnectionError:
|
||||||
|
limiter.enabled = False
|
||||||
|
appLog.warning('Redis not available, API rate limits are disabled.')
|
||||||
|
|
||||||
|
|
||||||
class CustomFlask(Flask):
|
class CustomFlask(Flask):
|
||||||
# add custom Request to handle user-agent parsing
|
# add custom Request to handle user-agent parsing
|
||||||
@ -64,6 +88,7 @@ def create_app(init_email: bool = True) -> Flask:
|
|||||||
bcrypt.init_app(app)
|
bcrypt.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
dramatiq.init_app(app)
|
dramatiq.init_app(app)
|
||||||
|
limiter.init_app(app)
|
||||||
|
|
||||||
# set oauth2
|
# set oauth2
|
||||||
from fittrackee.oauth2.config import config_oauth
|
from fittrackee.oauth2.config import config_oauth
|
||||||
@ -140,7 +165,15 @@ def create_app(init_email: bool = True) -> Flask:
|
|||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@app.errorhandler(429)
|
||||||
|
def rate_limit_handler(error: RateLimitExceeded) -> Tuple[Dict, int]:
|
||||||
|
return {
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'rate limit exceeded ({error.description})',
|
||||||
|
}, 429
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
|
@limiter.exempt
|
||||||
def favicon() -> Any:
|
def favicon() -> Any:
|
||||||
return send_file(
|
return send_file(
|
||||||
os.path.join(app.root_path, 'dist/favicon.ico') # type: ignore
|
os.path.join(app.root_path, 'dist/favicon.ico') # type: ignore
|
||||||
@ -148,6 +181,7 @@ def create_app(init_email: bool = True) -> Flask:
|
|||||||
|
|
||||||
@app.route('/', defaults={'path': ''})
|
@app.route('/', defaults={'path': ''})
|
||||||
@app.route('/<path:path>')
|
@app.route('/<path:path>')
|
||||||
|
@limiter.exempt
|
||||||
def catch_all(path: str) -> Any:
|
def catch_all(path: str) -> Any:
|
||||||
# workaround to serve images (not in static directory)
|
# workaround to serve images (not in static directory)
|
||||||
if path.startswith('img/'):
|
if path.startswith('img/'):
|
||||||
|
3
fittrackee/tests/fixtures/fixtures_app.py
vendored
3
fittrackee/tests/fixtures/fixtures_app.py
vendored
@ -5,7 +5,7 @@ from typing import Generator, Optional, Union
|
|||||||
import pytest
|
import pytest
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from fittrackee import create_app, db
|
from fittrackee import create_app, db, limiter
|
||||||
from fittrackee.application.models import AppConfig
|
from fittrackee.application.models import AppConfig
|
||||||
from fittrackee.application.utils import update_app_config_from_database
|
from fittrackee.application.utils import update_app_config_from_database
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ def get_app(
|
|||||||
max_users: Optional[int] = None,
|
max_users: Optional[int] = None,
|
||||||
) -> Generator:
|
) -> Generator:
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
limiter.enabled = False
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
try:
|
try:
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any, Dict, Tuple, Union
|
|||||||
from flask import Blueprint, current_app, request, send_file
|
from flask import Blueprint, current_app, request, send_file
|
||||||
from sqlalchemy import exc
|
from sqlalchemy import exc
|
||||||
|
|
||||||
from fittrackee import db
|
from fittrackee import db, limiter
|
||||||
from fittrackee.emails.tasks import (
|
from fittrackee.emails.tasks import (
|
||||||
email_updated_to_new_address,
|
email_updated_to_new_address,
|
||||||
password_change_email,
|
password_change_email,
|
||||||
@ -379,6 +379,7 @@ def get_single_user(
|
|||||||
|
|
||||||
|
|
||||||
@users_blueprint.route('/users/<user_name>/picture', methods=['GET'])
|
@users_blueprint.route('/users/<user_name>/picture', methods=['GET'])
|
||||||
|
@limiter.exempt
|
||||||
def get_picture(user_name: str) -> Any:
|
def get_picture(user_name: str) -> Any:
|
||||||
"""get user picture
|
"""get user picture
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from sqlalchemy import exc
|
|||||||
from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from fittrackee import appLog, db
|
from fittrackee import appLog, db, limiter
|
||||||
from fittrackee.oauth2.server import require_auth
|
from fittrackee.oauth2.server import require_auth
|
||||||
from fittrackee.responses import (
|
from fittrackee.responses import (
|
||||||
DataInvalidPayloadErrorResponse,
|
DataInvalidPayloadErrorResponse,
|
||||||
@ -784,6 +784,7 @@ def download_workout_gpx(
|
|||||||
|
|
||||||
|
|
||||||
@workouts_blueprint.route('/workouts/map/<map_id>', methods=['GET'])
|
@workouts_blueprint.route('/workouts/map/<map_id>', methods=['GET'])
|
||||||
|
@limiter.exempt
|
||||||
def get_map(map_id: int) -> Union[HttpResponse, Response]:
|
def get_map(map_id: int) -> Union[HttpResponse, Response]:
|
||||||
"""
|
"""
|
||||||
Get map image for workouts with gpx.
|
Get map image for workouts with gpx.
|
||||||
@ -830,6 +831,7 @@ def get_map(map_id: int) -> Union[HttpResponse, Response]:
|
|||||||
@workouts_blueprint.route(
|
@workouts_blueprint.route(
|
||||||
'/workouts/map_tile/<s>/<z>/<x>/<y>.png', methods=['GET']
|
'/workouts/map_tile/<s>/<z>/<x>/<y>.png', methods=['GET']
|
||||||
)
|
)
|
||||||
|
@limiter.exempt
|
||||||
def get_map_tile(s: str, z: str, x: str, y: str) -> Tuple[Response, int]:
|
def get_map_tile(s: str, z: str, x: str, y: str) -> Tuple[Response, int]:
|
||||||
"""
|
"""
|
||||||
Get map tile from tile server.
|
Get map tile from tile server.
|
||||||
|
92
poetry.lock
generated
92
poetry.lock
generated
@ -186,7 +186,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||||||
name = "commonmark"
|
name = "commonmark"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
description = "Python parser for the CommonMark Markdown spec"
|
description = "Python parser for the CommonMark Markdown spec"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
@ -325,6 +325,28 @@ python-versions = ">=3.6,<4.0"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
dramatiq = ">=1.5,<2.0"
|
dramatiq = ">=1.5,<2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-limiter"
|
||||||
|
version = "2.6.2"
|
||||||
|
description = "Rate limiting for flask applications"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Flask = ">=2"
|
||||||
|
limits = [
|
||||||
|
{version = ">=2.3"},
|
||||||
|
{version = "*", extras = ["redis"], optional = true, markers = "extra == \"redis\""},
|
||||||
|
]
|
||||||
|
rich = ">=12,<13"
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
memcached = ["limits"]
|
||||||
|
mongodb = ["limits"]
|
||||||
|
redis = ["limits"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask-migrate"
|
name = "flask-migrate"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@ -534,6 +556,30 @@ MarkupSafe = ">=2.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
i18n = ["Babel (>=2.7)"]
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "limits"
|
||||||
|
version = "2.7.0"
|
||||||
|
description = "Rate limiting utilities"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
deprecated = ">=1.2"
|
||||||
|
packaging = ">=21,<22"
|
||||||
|
redis = {version = ">3,<5.0.0", optional = true, markers = "extra == \"redis\""}
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["redis (>3,<5.0.0)", "redis (>=4.2.0)", "pymemcache (>3,<4.0.0)", "pymongo (>3,<5)", "motor (>=2.5,<4)", "emcache (>=0.6.1)", "coredis (>=3.4.0,<5)"]
|
||||||
|
async-memcached = ["emcache (>=0.6.1)"]
|
||||||
|
async-mongodb = ["motor (>=2.5,<4)"]
|
||||||
|
async-redis = ["coredis (>=3.4.0,<5)"]
|
||||||
|
memcached = ["pymemcache (>3,<4.0.0)"]
|
||||||
|
mongodb = ["pymongo (>3,<5)"]
|
||||||
|
redis = ["redis (>3,<5.0.0)"]
|
||||||
|
rediscluster = ["redis (>=4.2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mako"
|
name = "mako"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@ -726,7 +772,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.13.0"
|
version = "2.13.0"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
@ -1043,6 +1089,22 @@ urllib3 = ">=1.25.10"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-localserver", "flake8", "types-mock", "types-requests", "mypy"]
|
tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-localserver", "flake8", "types-mock", "types-requests", "mypy"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "12.5.1"
|
||||||
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.3,<4.0.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
commonmark = ">=0.9.0,<0.10.0"
|
||||||
|
pygments = ">=2.6.0,<3.0.0"
|
||||||
|
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selenium"
|
name = "selenium"
|
||||||
version = "4.4.3"
|
version = "4.4.3"
|
||||||
@ -1369,6 +1431,14 @@ category = "dev"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-redis"
|
||||||
|
version = "4.3.20"
|
||||||
|
description = "Typing stubs for redis"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-requests"
|
name = "types-requests"
|
||||||
version = "2.28.10"
|
version = "2.28.10"
|
||||||
@ -1465,7 +1535,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "579fd9abe2d025aaa5dc84fbd587a04c03db2fc0d8bdf063edb29fa9a7093015"
|
content-hash = "468ee5a0ea6984ed5f6a2a63ffa1de67a0d774bd6405ccefc00536a5ab7d8f42"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alabaster = [
|
alabaster = [
|
||||||
@ -1721,6 +1791,10 @@ flask-dramatiq = [
|
|||||||
{file = "flask-dramatiq-0.6.0.tar.gz", hash = "sha256:63709e73d7c8d2e5d9bc554d1e859d91c5c5c9a4ebc9461752655bf1e0b87420"},
|
{file = "flask-dramatiq-0.6.0.tar.gz", hash = "sha256:63709e73d7c8d2e5d9bc554d1e859d91c5c5c9a4ebc9461752655bf1e0b87420"},
|
||||||
{file = "flask_dramatiq-0.6.0-py3-none-any.whl", hash = "sha256:7d4a9289721577f726183f7c44c6713a16bbdff54b946f27abc2ffcc65768adf"},
|
{file = "flask_dramatiq-0.6.0-py3-none-any.whl", hash = "sha256:7d4a9289721577f726183f7c44c6713a16bbdff54b946f27abc2ffcc65768adf"},
|
||||||
]
|
]
|
||||||
|
flask-limiter = [
|
||||||
|
{file = "Flask-Limiter-2.6.2.tar.gz", hash = "sha256:58b361347f68942ea2d0a9004427098b41da705081494fe3b9be7b67c4ae32c4"},
|
||||||
|
{file = "Flask_Limiter-2.6.2-py3-none-any.whl", hash = "sha256:c8451532f88818e839bbdd650cfd424ec11e89fa87e0034f525401399a160e1e"},
|
||||||
|
]
|
||||||
flask-migrate = [
|
flask-migrate = [
|
||||||
{file = "Flask-Migrate-3.1.0.tar.gz", hash = "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9"},
|
{file = "Flask-Migrate-3.1.0.tar.gz", hash = "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9"},
|
||||||
{file = "Flask_Migrate-3.1.0-py3-none-any.whl", hash = "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897"},
|
{file = "Flask_Migrate-3.1.0-py3-none-any.whl", hash = "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897"},
|
||||||
@ -1840,6 +1914,10 @@ jinja2 = [
|
|||||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||||
]
|
]
|
||||||
|
limits = [
|
||||||
|
{file = "limits-2.7.0-py3-none-any.whl", hash = "sha256:cf043c376f14208a8204dc4cd74af1210fcaf24082bf7e3a38028706aa129899"},
|
||||||
|
{file = "limits-2.7.0.tar.gz", hash = "sha256:847a78b24a47e822a8f8713028a1340db44003763ab1cb90e798ccf462880355"},
|
||||||
|
]
|
||||||
mako = [
|
mako = [
|
||||||
{file = "Mako-1.2.2-py3-none-any.whl", hash = "sha256:8efcb8004681b5f71d09c983ad5a9e6f5c40601a6ec469148753292abc0da534"},
|
{file = "Mako-1.2.2-py3-none-any.whl", hash = "sha256:8efcb8004681b5f71d09c983ad5a9e6f5c40601a6ec469148753292abc0da534"},
|
||||||
{file = "Mako-1.2.2.tar.gz", hash = "sha256:3724869b363ba630a272a5f89f68c070352137b8fd1757650017b7e06fda163f"},
|
{file = "Mako-1.2.2.tar.gz", hash = "sha256:3724869b363ba630a272a5f89f68c070352137b8fd1757650017b7e06fda163f"},
|
||||||
@ -2180,6 +2258,10 @@ responses = [
|
|||||||
{file = "responses-0.21.0-py3-none-any.whl", hash = "sha256:2dcc863ba63963c0c3d9ee3fa9507cbe36b7d7b0fccb4f0bdfd9e96c539b1487"},
|
{file = "responses-0.21.0-py3-none-any.whl", hash = "sha256:2dcc863ba63963c0c3d9ee3fa9507cbe36b7d7b0fccb4f0bdfd9e96c539b1487"},
|
||||||
{file = "responses-0.21.0.tar.gz", hash = "sha256:b82502eb5f09a0289d8e209e7bad71ef3978334f56d09b444253d5ad67bf5253"},
|
{file = "responses-0.21.0.tar.gz", hash = "sha256:b82502eb5f09a0289d8e209e7bad71ef3978334f56d09b444253d5ad67bf5253"},
|
||||||
]
|
]
|
||||||
|
rich = [
|
||||||
|
{file = "rich-12.5.1-py3-none-any.whl", hash = "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb"},
|
||||||
|
{file = "rich-12.5.1.tar.gz", hash = "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca"},
|
||||||
|
]
|
||||||
selenium = [
|
selenium = [
|
||||||
{file = "selenium-4.4.3-py3-none-any.whl", hash = "sha256:ca6ed4a58a426bb40bf5aa2b027ce211cc5200f1acdcdfb8258b32b24624150c"},
|
{file = "selenium-4.4.3-py3-none-any.whl", hash = "sha256:ca6ed4a58a426bb40bf5aa2b027ce211cc5200f1acdcdfb8258b32b24624150c"},
|
||||||
]
|
]
|
||||||
@ -2344,6 +2426,10 @@ types-pytz = [
|
|||||||
{file = "types-pytz-2022.2.1.0.tar.gz", hash = "sha256:47cfb19c52b9f75896440541db392fd312a35b279c6307a531db71152ea63e2b"},
|
{file = "types-pytz-2022.2.1.0.tar.gz", hash = "sha256:47cfb19c52b9f75896440541db392fd312a35b279c6307a531db71152ea63e2b"},
|
||||||
{file = "types_pytz-2022.2.1.0-py3-none-any.whl", hash = "sha256:50ead2254b524a3d4153bc65d00289b66898060d2938e586170dce918dbaf3b3"},
|
{file = "types_pytz-2022.2.1.0-py3-none-any.whl", hash = "sha256:50ead2254b524a3d4153bc65d00289b66898060d2938e586170dce918dbaf3b3"},
|
||||||
]
|
]
|
||||||
|
types-redis = [
|
||||||
|
{file = "types-redis-4.3.20.tar.gz", hash = "sha256:74ed02945470ddea2dd21447c185dabb3169e5a5328d26b25cf3547d949b8e04"},
|
||||||
|
{file = "types_redis-4.3.20-py3-none-any.whl", hash = "sha256:b22e0f5a18b98b6a197dd403daed52a22cb76f50e3cbd7ddc539196af52ec23e"},
|
||||||
|
]
|
||||||
types-requests = [
|
types-requests = [
|
||||||
{file = "types-requests-2.28.10.tar.gz", hash = "sha256:97d8f40aa1ffe1e58c3726c77d63c182daea9a72d9f1fa2cafdea756b2a19f2c"},
|
{file = "types-requests-2.28.10.tar.gz", hash = "sha256:97d8f40aa1ffe1e58c3726c77d63c182daea9a72d9f1fa2cafdea756b2a19f2c"},
|
||||||
{file = "types_requests-2.28.10-py3-none-any.whl", hash = "sha256:45b485725ed58752f2b23461252f1c1ad9205b884a1e35f786bb295525a3e16a"},
|
{file = "types_requests-2.28.10-py3-none-any.whl", hash = "sha256:45b485725ed58752f2b23461252f1c1ad9205b884a1e35f786bb295525a3e16a"},
|
||||||
|
@ -44,6 +44,7 @@ ua-parser = "^0.16.1"
|
|||||||
Babel = "^2.10.3"
|
Babel = "^2.10.3"
|
||||||
Werkzeug = "2.1" # removal of parse_rule in 2.2 breaks sphinxcontrib-httpdomain autoflask
|
Werkzeug = "2.1" # removal of parse_rule in 2.2 breaks sphinxcontrib-httpdomain autoflask
|
||||||
Authlib = "=1.0.1"
|
Authlib = "=1.0.1"
|
||||||
|
Flask-Limiter = {version = "^2.6.2", extras = ["redis"]}
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^22.6"
|
black = "^22.6"
|
||||||
@ -64,6 +65,7 @@ types-requests = "^2.28"
|
|||||||
types-freezegun = "^1.1"
|
types-freezegun = "^1.1"
|
||||||
Sphinx = "^5.1"
|
Sphinx = "^5.1"
|
||||||
bandit = "^1.7.4"
|
bandit = "^1.7.4"
|
||||||
|
types-redis = "^4.3.20"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
fittrackee = 'fittrackee.__main__:main'
|
fittrackee = 'fittrackee.__main__:main'
|
||||||
|
Loading…
Reference in New Issue
Block a user