Merge branch 'dev' into oauth2

This commit is contained in:
Sam 2022-06-12 09:28:01 +02:00
commit 22c8105ea6
65 changed files with 961 additions and 627 deletions

View File

@ -1,5 +1,16 @@
# Change log
## Version 0.6.7 (2022/06/11)
### Issues Closed
#### Bugs Fixed
* [#156](https://github.com/SamR1/FitTrackee/issues/156) - Process gpx file with offset
In this release 1 issue was closed.
## Version 0.6.6 (2022/05/29)
### Misc

View File

@ -74,7 +74,7 @@ docker-stop:
docker-compose -f docker-compose-dev.yml stop
docker-up:
docker-compose -f docker-compose-dev.yml up fittrackeee
docker-compose -f docker-compose-dev.yml up fittrackee
downgrade-db:
$(FLASK) db downgrade --directory $(MIGRATIONS)

View File

@ -1 +1 @@
0.6.6
0.6.7

View File

@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 4cdeaae17c31d716c1f73eb46769543b
config: e7f46ba7f0469b1929d2349b0c52c449
tags: 645f666f9bcd5a90fca523b33c5a78b7

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -1,5 +1,16 @@
# Change log
## Version 0.6.7 (2022/06/11)
### Issues Closed
#### Bugs Fixed
* [#156](https://github.com/SamR1/FitTrackee/issues/156) - Process gpx file with offset
In this release 1 issue was closed.
## Version 0.6.6 (2022/05/29)
### Misc

View File

@ -395,13 +395,13 @@ Production environment
.. warning::
| Note that FitTrackee is under heavy development, some features may be unstable.
- Download the last release (for now, it is the release v0.6.6):
- Download the last release (for now, it is the release v0.6.7):
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ mv FitTrackee-0.6.6 FitTrackee
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ mv FitTrackee-0.6.7 FitTrackee
$ cd FitTrackee
- Create **.env** from example and update it
@ -521,13 +521,13 @@ Prod environment
- Change to the directory where FitTrackee directory is located
- Download the last release (for now, it is the release v0.6.6) and overwrite existing files:
- Download the last release (for now, it is the release v0.6.7) and overwrite existing files:
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ cp -R FitTrackee-0.6.6/* FitTrackee/
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ cp -R FitTrackee-0.6.7/* FitTrackee/
$ cd FitTrackee
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).

View File

@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.6.6',
VERSION: '0.6.7',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Authentication &#8212; FitTrackee 0.6.6
<title>Authentication &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Configuration &#8212; FitTrackee 0.6.6
<title>Configuration &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>
@ -151,7 +151,7 @@
<span class="w"> </span><span class="nt">&quot;max_users&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_zip_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;map_attribution&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;&amp;copy; &lt;a href=http://www.openstreetmap.org/copyright&gt;OpenStreetMap&lt;/a&gt; contributors&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.6&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.7&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
@ -191,7 +191,7 @@
<span class="w"> </span><span class="nt">&quot;max_users&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_zip_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;map_attribution&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;&amp;copy; &lt;a href=http://www.openstreetmap.org/copyright&gt;OpenStreetMap&lt;/a&gt; contributors&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.6&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.7&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>API documentation &#8212; FitTrackee 0.6.6
<title>API documentation &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Records &#8212; FitTrackee 0.6.6
<title>Records &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Sports &#8212; FitTrackee 0.6.6
<title>Sports &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Statistics &#8212; FitTrackee 0.6.6
<title>Statistics &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Users &#8212; FitTrackee 0.6.6
<title>Users &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Workouts &#8212; FitTrackee 0.6.6
<title>Workouts &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>
@ -284,7 +284,7 @@
<dl class="http get">
<dt class="sig sig-object http" id="get--api-workouts-(string-workout_short_id)">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get an workout</p>
<dd><p>Get a workout</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</pre></div>
@ -378,7 +378,7 @@
<dl class="http get">
<dt class="sig sig-object http" id="get--api-workouts-(string-workout_short_id)-gpx">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><span class="sig-name descname"><span class="pre">/gpx</span></span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx" title="Permalink to this definition"></a></dt>
<dd><p>Get gpx file for an workout displayed on map with Leaflet</p>
<dd><p>Get gpx file for a workout displayed on map with Leaflet</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -431,7 +431,7 @@
<dl class="http get">
<dt class="sig sig-object http" id="get--api-workouts-(string-workout_short_id)-chart_data">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><span class="sig-name descname"><span class="pre">/chart_data</span></span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data" title="Permalink to this definition"></a></dt>
<dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
<dd><p>Get chart data from a workout gpx file, to display it with Recharts</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -503,7 +503,7 @@
<dl class="http get">
<dt class="sig sig-object http" id="get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><span class="sig-name descname"><span class="pre">/chart_data/segment/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">int:</span> </em><em class="sig-param"><span class="pre">segment_id</span></em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
<dd><p>Get chart data from a workout gpx file, to display it with Recharts</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -573,7 +573,7 @@
<dl class="http get">
<dt class="sig sig-object http" id="get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><span class="sig-name descname"><span class="pre">/gpx/segment/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">int:</span> </em><em class="sig-param"><span class="pre">segment_id</span></em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)" title="Permalink to this definition"></a></dt>
<dd><p>Get gpx file for an workout segment displayed on map with Leaflet</p>
<dd><p>Get gpx file for a workout segment displayed on map with Leaflet</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -724,7 +724,7 @@
<dl class="http post">
<dt class="sig sig-object http" id="post--api-workouts">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/workouts</span></span><a class="headerlink" href="#post--api-workouts" title="Permalink to this definition"></a></dt>
<dd><p>Post an workout with a gpx file</p>
<dd><p>Post a workout with a gpx file</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">multipart/form-data</span>
@ -847,7 +847,7 @@
<dl class="http post">
<dt class="sig sig-object http" id="post--api-workouts-no_gpx">
<span class="sig-name descname"><span class="pre">POST</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/no_gpx</span></span><a class="headerlink" href="#post--api-workouts-no_gpx" title="Permalink to this definition"></a></dt>
<dd><p>Post an workout without gpx file</p>
<dd><p>Post a workout without gpx file</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/no_gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -935,7 +935,8 @@
<dl class="field-list simple">
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>workout_date</strong> (<em>string</em>) workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li>
<li><p><strong>workout_date</strong> (<em>string</em>) workout date, in user timezone
(format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li>
<li><p><strong>distance</strong> (<em>float</em>) workout distance in km</p></li>
<li><p><strong>duration</strong> (<em>integer</em>) workout duration in seconds</p></li>
<li><p><strong>notes</strong> (<em>string</em>) notes (not mandatory)</p></li>
@ -967,7 +968,7 @@
<dl class="http patch">
<dt class="sig sig-object http" id="patch--api-workouts-(string-workout_short_id)">
<span class="sig-name descname"><span class="pre">PATCH</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Update an workout</p>
<dd><p>Update a workout</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/workouts/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
@ -1060,7 +1061,8 @@
</dd>
<dt class="field-even">Request JSON Object</dt>
<dd class="field-even"><ul class="simple">
<li><p><strong>workout_date</strong> (<em>string</em>) workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)
<li><p><strong>workout_date</strong> (<em>string</em>) workout date in user timezone
(format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)
(only for workout without gpx)</p></li>
<li><p><strong>distance</strong> (<em>float</em>) workout distance in km
(only for workout without gpx)</p></li>
@ -1096,7 +1098,7 @@
<dl class="http delete">
<dt class="sig sig-object http" id="delete--api-workouts-(string-workout_short_id)">
<span class="sig-name descname"><span class="pre">DELETE</span> </span><span class="sig-name descname"><span class="pre">/api/workouts/</span></span><span class="sig-paren">(</span><em class="property"><span class="pre">string:</span> </em><em class="sig-param"><span class="pre">workout_short_id</span></em><span class="sig-paren">)</span><a class="headerlink" href="#delete--api-workouts-(string-workout_short_id)" title="Permalink to this definition"></a></dt>
<dd><p>Delete an workout</p>
<dd><p>Delete a workout</p>
<p><strong>Example request</strong>:</p>
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">DELETE</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Command line interface &#8212; FitTrackee 0.6.6
<title>Command line interface &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Features &#8212; FitTrackee 0.6.6
<title>Features &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; FitTrackee 0.6.6
<title>Index &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -37,7 +37,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HTTP Routing Table &#8212; FitTrackee 0.6.6
<title>HTTP Routing Table &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -44,7 +44,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>FitTrackee &#8212; FitTrackee 0.6.6
<title>FitTrackee &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -39,7 +39,7 @@
</button>
<a class="navbar-brand" href="#">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Installation &#8212; FitTrackee 0.6.6
<title>Installation &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>
@ -674,11 +674,11 @@ $ make install-db
</div>
</div>
<ul class="simple">
<li><p>Download the last release (for now, it is the release v0.6.6):</p></li>
<li><p>Download the last release (for now, it is the release v0.6.7):</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ mv FitTrackee-0.6.6 FitTrackee
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ mv FitTrackee-0.6.7 FitTrackee
$ <span class="nb">cd</span> FitTrackee
</pre></div>
</div>
@ -798,11 +798,11 @@ $ <span class="nb">source</span> .env
<ul class="simple">
<li><p>Stop the application</p></li>
<li><p>Change to the directory where FitTrackee directory is located</p></li>
<li><p>Download the last release (for now, it is the release v0.6.6) and overwrite existing files:</p></li>
<li><p>Download the last release (for now, it is the release v0.6.7) and overwrite existing files:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ cp -R FitTrackee-0.6.6/* FitTrackee/
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ cp -R FitTrackee-0.6.7/* FitTrackee/
$ <span class="nb">cd</span> FitTrackee
</pre></div>
</div>

Binary file not shown.

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; FitTrackee 0.6.6
<title>Search &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -44,7 +44,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Administrator &#8212; FitTrackee 0.6.6
<title>Administrator &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Troubleshooting &#8212; FitTrackee 0.6.6
<title>Troubleshooting &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>User &#8212; FitTrackee 0.6.6
<title>User &#8212; FitTrackee 0.6.7
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.6
<span class="navbar-text navbar-version pull-left"><b>0.6.7
</b></span>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 539 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -395,13 +395,13 @@ Production environment
.. warning::
| Note that FitTrackee is under heavy development, some features may be unstable.
- Download the last release (for now, it is the release v0.6.6):
- Download the last release (for now, it is the release v0.6.7):
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ mv FitTrackee-0.6.6 FitTrackee
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ mv FitTrackee-0.6.7 FitTrackee
$ cd FitTrackee
- Create **.env** from example and update it
@ -521,13 +521,13 @@ Prod environment
- Change to the directory where FitTrackee directory is located
- Download the last release (for now, it is the release v0.6.6) and overwrite existing files:
- Download the last release (for now, it is the release v0.6.7) and overwrite existing files:
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.6.tar.gz
$ tar -xzf v0.6.6.tar.gz
$ cp -R FitTrackee-0.6.6/* FitTrackee/
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.7.tar.gz
$ tar -xzf v0.6.7.tar.gz
$ cp -R FitTrackee-0.6.7/* FitTrackee/
$ cd FitTrackee
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).

View File

@ -21,7 +21,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
from fittrackee.emails.email import EmailService
from fittrackee.request import CustomRequest
VERSION = __version__ = '0.6.6'
VERSION = __version__ = '0.6.7'
db = SQLAlchemy()
bcrypt = Bcrypt()
migrate = Migrate()

View File

@ -48,7 +48,7 @@ def get_application_config() -> Union[Dict, HttpResponse]:
"max_users": 0,
"max_zip_file_size": 10485760,
"map_attribution": "&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
"version": "0.6.6"
"version": "0.6.7"
},
"status": "success"
}
@ -98,7 +98,7 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
"max_users": 10,
"max_zip_file_size": 10485760,
"map_attribution": "&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
"version": "0.6.6"
"version": "0.6.7"
},
"status": "success"
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.e335684a.js"></script><script defer="defer" src="/static/js/app.a9b9439a.js"></script><link href="/static/css/app.e8b7692c.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.e335684a.js"></script><script defer="defer" src="/static/js/app.8517c25d.js"></script><link href="/static/css/app.e8b7692c.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -452,6 +452,120 @@ def gpx_file_wo_name() -> str:
)
@pytest.fixture()
def gpx_file_with_offset() -> str:
return (
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
' <metadata/>'
' <trk>'
' <trkseg>'
' <trkpt lat="44.68095" lon="6.07367">'
' <ele>998</ele>'
' <time>2018-03-13T13:44:45+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68091" lon="6.07367">'
' <ele>998</ele>'
' <time>2018-03-13T13:44:50+01:00</time>'
' </trkpt>'
' <trkpt lat="44.6808" lon="6.07364">'
' <ele>994</ele>'
' <time>2018-03-13T13:45:00+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68075" lon="6.07364">'
' <ele>994</ele>'
' <time>2018-03-13T13:45:05+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68071" lon="6.07364">'
' <ele>994</ele>'
' <time>2018-03-13T13:45:10+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68049" lon="6.07361">'
' <ele>993</ele>'
' <time>2018-03-13T13:45:30+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68019" lon="6.07356">'
' <ele>992</ele>'
' <time>2018-03-13T13:45:55+01:00</time>'
' </trkpt>'
' <trkpt lat="44.68014" lon="6.07355">'
' <ele>992</ele>'
' <time>2018-03-13T13:46:00+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67995" lon="6.07358">'
' <ele>987</ele>'
' <time>2018-03-13T13:46:15+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67977" lon="6.07364">'
' <ele>987</ele>'
' <time>2018-03-13T13:46:30+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67972" lon="6.07367">'
' <ele>987</ele>'
' <time>2018-03-13T13:46:35+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67966" lon="6.07368">'
' <ele>987</ele>'
' <time>2018-03-13T13:46:40+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67961" lon="6.0737">'
' <ele>986</ele>'
' <time>2018-03-13T13:46:45+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67938" lon="6.07377">'
' <ele>986</ele>'
' <time>2018-03-13T13:47:05+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67933" lon="6.07381">'
' <ele>986</ele>'
' <time>2018-03-13T13:47:10+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67922" lon="6.07385">'
' <ele>985</ele>'
' <time>2018-03-13T13:47:20+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67911" lon="6.0739">'
' <ele>980</ele>'
' <time>2018-03-13T13:47:30+01:00</time>'
' </trkpt>'
' <trkpt lat="44.679" lon="6.07399">'
' <ele>980</ele>'
' <time>2018-03-13T13:47:40+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67896" lon="6.07402">'
' <ele>980</ele>'
' <time>2018-03-13T13:47:45+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67884" lon="6.07408">'
' <ele>979</ele>'
' <time>2018-03-13T13:47:55+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67863" lon="6.07423">'
' <ele>981</ele>'
' <time>2018-03-13T13:48:15+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67858" lon="6.07425">'
' <ele>980</ele>'
' <time>2018-03-13T13:48:20+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67842" lon="6.07434">'
' <ele>979</ele>'
' <time>2018-03-13T13:48:35+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67837" lon="6.07435">'
' <ele>979</ele>'
' <time>2018-03-13T13:48:40+01:00</time>'
' </trkpt>'
' <trkpt lat="44.67822" lon="6.07442">'
' <ele>975</ele>'
' <time>2018-03-13T13:48:55+01:00</time>'
' </trkpt>'
' </trkseg>'
' </trk>'
'</gpx>'
)
@pytest.fixture()
def gpx_file_wo_track() -> str:
return (

View File

@ -2,7 +2,7 @@ from unittest.mock import call, patch
import pytest
from flask import Flask
from gpxpy.gpx import MovingData
from gpxpy.gpx import IGNORE_TOP_SPEED_PERCENTILES, MovingData
from werkzeug.datastructures import FileStorage
from fittrackee.users.models import User, UserSportPreference
@ -55,7 +55,12 @@ class TestStoppedSpeedThreshold:
assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold
)
gpx_track_segment_mock.assert_called_with(expected_threshold)
gpx_track_segment_mock.assert_called_with(
expected_threshold, # stopped_speed_threshold
False, # raw
IGNORE_TOP_SPEED_PERCENTILES, # speed_extreemes_percentiles
True, # ignore_nonstandard_distances
)
def test_it_calls_get_moving_data_with_threshold_depending_from_user_preference( # noqa
self,
@ -85,4 +90,9 @@ class TestStoppedSpeedThreshold:
assert gpx_track_segment_mock.call_args_list[0] == call(
stopped_speed_threshold=expected_threshold
)
gpx_track_segment_mock.assert_called_with(expected_threshold)
gpx_track_segment_mock.assert_called_with(
expected_threshold, # stopped_speed_threshold
False, # raw
IGNORE_TOP_SPEED_PERCENTILES, # speed_extreemes_percentiles
True, # ignore_nonstandard_distances
)

View File

@ -1,9 +1,27 @@
from datetime import datetime
from statistics import mean
from typing import List
from typing import List, Union
import pytest
import pytz
from gpxpy.gpxfield import SimpleTZ
from fittrackee.workouts.utils.workouts import get_average_speed
from fittrackee.workouts.utils.workouts import (
get_average_speed,
get_workout_datetime,
)
utc_datetime = datetime(
year=2022, month=6, day=11, hour=10, minute=23, second=00, tzinfo=pytz.utc
)
input_workout_dates = [
utc_datetime,
utc_datetime.replace(tzinfo=None),
utc_datetime.replace(tzinfo=SimpleTZ('Z')),
utc_datetime.astimezone(pytz.timezone('Europe/Paris')),
utc_datetime.astimezone(pytz.timezone('America/Toronto')),
'2022-06-11 12:23:00',
]
class TestWorkoutAverageSpeed:
@ -30,3 +48,68 @@ class TestWorkoutAverageSpeed:
assert get_average_speed(
nb_workouts, total_average_speed, workout_average_speed
) == mean(ave_speeds_list)
class TestWorkoutGetWorkoutDatetime:
@pytest.mark.parametrize('input_workout_date', input_workout_dates)
def test_it_returns_naive_datetime(
self, input_workout_date: Union[datetime, str]
) -> None:
naive_workout_date, _ = get_workout_datetime(
workout_date=input_workout_date, user_timezone='Europe/Paris'
)
assert naive_workout_date == datetime(
year=2022, month=6, day=11, hour=10, minute=23, second=00
)
def test_it_return_naive_datetime_when_no_user_timezone(self) -> None:
naive_workout_date, _ = get_workout_datetime(
workout_date='2022-06-11 12:23:00', user_timezone=None
)
assert naive_workout_date == datetime(
year=2022, month=6, day=11, hour=12, minute=23, second=00
)
@pytest.mark.parametrize('input_workout_date', input_workout_dates)
def test_it_returns_datetime_with_user_timezone(
self, input_workout_date: Union[datetime, str]
) -> None:
timezone = 'Europe/Paris'
_, workout_date_with_tz = get_workout_datetime(
input_workout_date, user_timezone=timezone, with_timezone=True
)
assert workout_date_with_tz == datetime(
year=2022,
month=6,
day=11,
hour=10,
minute=23,
second=00,
tzinfo=pytz.utc,
).astimezone(pytz.timezone(timezone))
def test_it_does_not_return_datetime_with_user_timezone_when_no_user_tz(
self,
) -> None:
_, workout_date_with_tz = get_workout_datetime(
workout_date='2022-06-11 12:23:00',
user_timezone=None,
with_timezone=True,
)
assert workout_date_with_tz is None
def test_it_does_not_return_datetime_with_user_timezone_when_with_timezone_to_false( # noqa
self,
) -> None:
_, workout_date_with_tz = get_workout_datetime(
workout_date='2022-06-11 12:23:00',
user_timezone='Europe/Paris',
with_timezone=False,
)
assert workout_date_with_tz is None

View File

@ -969,7 +969,7 @@ class TestGetWorkoutsWithFiltersAndPagination(ApiTestCaseMixin):
class TestGetWorkout(ApiTestCaseMixin):
def test_it_gets_an_workout(
def test_it_gets_a_workout(
self,
app: Flask,
user_1: User,
@ -1105,7 +1105,7 @@ class TestGetWorkout(ApiTestCaseMixin):
response, f'no gpx file for this workout (id: {workout_short_id})'
)
def test_it_returns_500_on_getting_gpx_if_an_workout_has_invalid_gpx_pathname( # noqa
def test_it_returns_500_on_getting_gpx_if_a_workout_has_invalid_gpx_pathname( # noqa
self,
app: Flask,
user_1: User,
@ -1125,7 +1125,7 @@ class TestGetWorkout(ApiTestCaseMixin):
data = self.assert_500(response)
assert 'data' not in data
def test_it_returns_500_on_getting_chart_data_if_an_workout_has_invalid_gpx_pathname( # noqa
def test_it_returns_500_on_getting_chart_data_if_a_workout_has_invalid_gpx_pathname( # noqa
self,
app: Flask,
user_1: User,

View File

@ -2,7 +2,7 @@ import json
import os
from datetime import datetime
from io import BytesIO
from typing import Dict
from typing import Dict, Optional
from unittest.mock import Mock
import pytest
@ -92,9 +92,7 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert data['data']['workouts'][0]['descent'] == 23.4
assert data['data']['workouts'][0]['distance'] == 0.3
assert data['data']['workouts'][0]['max_alt'] == 998.0
assert (
data['data']['workouts'][0]['max_speed'] is None
) # not enough points
assert data['data']['workouts'][0]['max_speed'] == 5.25
assert data['data']['workouts'][0]['min_alt'] == 975.0
assert data['data']['workouts'][0]['moving'] == '0:03:55'
assert data['data']['workouts'][0]['pauses'] == '0:00:15'
@ -114,7 +112,7 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert segment['descent'] == 11.0
assert segment['distance'] == 0.113
assert segment['max_alt'] == 998.0
assert segment['max_speed'] is None
assert segment['max_speed'] == 5.25
assert segment['min_alt'] == 987.0
assert segment['moving'] == '0:01:30'
assert segment['pauses'] is None
@ -128,28 +126,33 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
assert segment['descent'] == 12.4
assert segment['distance'] == 0.186
assert segment['max_alt'] == 987.0
assert segment['max_speed'] is None
assert segment['max_speed'] == 5.12
assert segment['min_alt'] == 975.0
assert segment['moving'] == '0:02:25'
assert segment['pauses'] is None
records = data['data']['workouts'][0]['records']
assert len(records) == 3
assert len(records) == 4
assert records[0]['sport_id'] == 1
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
assert records[0]['record_type'] == 'LD'
assert records[0]['record_type'] == 'MS'
assert records[0]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[0]['value'] == '0:03:55'
assert records[0]['value'] == 5.25
assert records[1]['sport_id'] == 1
assert records[1]['workout_id'] == data['data']['workouts'][0]['id']
assert records[1]['record_type'] == 'FD'
assert records[1]['record_type'] == 'LD'
assert records[1]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[1]['value'] == 0.3
assert records[1]['value'] == '0:03:55'
assert records[2]['sport_id'] == 1
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
assert records[2]['record_type'] == 'AS'
assert records[2]['record_type'] == 'FD'
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[2]['value'] == 4.59
assert records[2]['value'] == 0.3
assert records[3]['sport_id'] == 1
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
assert records[3]['record_type'] == 'AS'
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
assert records[3]['value'] == 4.59
def assert_workout_data_wo_gpx(data: Dict) -> None:
@ -222,7 +225,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
self.assert_401(response)
def test_it_adds_an_workout_with_gpx_file(
def test_it_adds_a_workout_with_gpx_file(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -248,7 +251,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
assert 'just a workout' == data['data']['workouts'][0]['title']
assert_workout_data_with_gpx(data)
def test_it_adds_an_workout_with_gpx_without_name(
def test_it_adds_a_workout_with_gpx_without_name(
self,
app: Flask,
user_1: User,
@ -281,7 +284,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
)
assert_workout_data_with_gpx(data)
def test_it_adds_an_workout_with_gpx_without_name_timezone(
def test_it_adds_a_workout_with_gpx_without_name_timezone(
self,
app: Flask,
user_1: User,
@ -315,6 +318,41 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
)
assert_workout_data_with_gpx(data)
@pytest.mark.parametrize('input_user_timezone', [None, 'Europe/Paris'])
def test_it_adds_a_workout_with_gpx_with_offset(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
gpx_file_with_offset: str,
input_user_timezone: Optional[str],
) -> None:
user_1.timezone = input_user_timezone
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.post(
'/api/workouts',
data=dict(
file=(
BytesIO(str.encode(gpx_file_with_offset)),
'example.gpx',
),
data='{"sport_id": 1}',
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
data = json.loads(response.data.decode())
assert response.status_code == 201
assert 'created' in data['status']
assert len(data['data']['workouts']) == 1
assert_workout_data_with_gpx(data)
@pytest.mark.parametrize(
'input_description,input_notes',
[
@ -606,7 +644,7 @@ class TestPostWorkoutWithoutGpx(ApiTestCaseMixin):
self.assert_401(response)
def test_it_adds_an_workout_without_gpx(
def test_it_adds_a_workout_without_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -953,12 +991,12 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
self.assert_500(response)
def test_it_gets_an_workout_created_with_gpx(
def test_it_gets_a_workout_created_with_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
return self.workout_assertion(app, user_1, gpx_file, False)
def test_it_gets_an_workout_created_with_gpx_with_segments(
def test_it_gets_a_workout_created_with_gpx_with_segments(
self,
app: Flask,
user_1: User,
@ -969,7 +1007,7 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
app, user_1, gpx_file_with_segments, True
)
def test_it_gets_chart_data_for_an_workout_created_with_gpx(
def test_it_gets_chart_data_for_a_workout_created_with_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -1000,7 +1038,7 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
assert data['message'] == ''
assert data['data']['chart_data'] != ''
def test_it_gets_segment_chart_data_for_an_workout_created_with_gpx(
def test_it_gets_segment_chart_data_for_a_workout_created_with_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -1125,7 +1163,7 @@ class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
class TestPostAndGetWorkoutWithoutGpx(ApiTestCaseMixin):
def test_it_add_and_gets_an_workout_wo_gpx(
def test_it_add_and_gets_a_workout_wo_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -1158,7 +1196,7 @@ class TestPostAndGetWorkoutWithoutGpx(ApiTestCaseMixin):
assert len(data['data']['workouts']) == 1
assert_workout_data_wo_gpx(data)
def test_it_adds_and_gets_an_workout_wo_gpx_notes(
def test_it_adds_and_gets_a_workout_wo_gpx_notes(
self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
@ -1194,7 +1232,7 @@ class TestPostAndGetWorkoutWithoutGpx(ApiTestCaseMixin):
class TestPostAndGetWorkoutUsingTimezones(ApiTestCaseMixin):
def test_it_add_and_gets_an_workout_wo_gpx_with_timezone(
def test_it_add_and_gets_a_workout_wo_gpx_with_timezone(
self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None:
user_1.timezone = 'Europe/Paris'

View File

@ -9,7 +9,7 @@ from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
from .utils import get_random_short_id, post_an_workout
from .utils import get_random_short_id, post_a_workout
def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
@ -56,7 +56,7 @@ def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
class TestEditWorkoutWithGpx(ApiTestCaseMixin):
def test_it_updates_title_for_an_workout_with_gpx(
def test_it_updates_title_for_a_workout_with_gpx(
self,
app: Flask,
user_1: User,
@ -64,7 +64,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
sport_2_running: Sport,
gpx_file: str,
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.patch(
@ -100,7 +100,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
sport_2_running: Sport,
gpx_file: str,
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.patch(
@ -124,7 +124,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
sport_2_running: Sport,
gpx_file: str,
) -> None:
token, workout_short_id = post_an_workout(
token, workout_short_id = post_a_workout(
app, gpx_file, notes=uuid4().hex
)
client = app.test_client()
@ -142,7 +142,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
assert len(data['data']['workouts']) == 1
assert data['data']['workouts'][0]['notes'] == ''
def test_it_raises_403_when_editing_an_workout_from_different_user(
def test_it_raises_403_when_editing_a_workout_from_different_user(
self,
app: Flask,
user_1: User,
@ -151,7 +151,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
sport_2_running: Sport,
gpx_file: str,
) -> None:
_, workout_short_id = post_an_workout(app, gpx_file)
_, workout_short_id = post_a_workout(app, gpx_file)
client, auth_token = self.get_test_client_and_auth_token(
app, user_2.email
)
@ -173,7 +173,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
sport_2_running: Sport,
gpx_file: str,
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.patch(
@ -194,7 +194,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
def test_it_returns_400_if_payload_is_empty(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.patch(
@ -209,7 +209,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
def test_it_raises_500_if_sport_does_not_exists(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.patch(
@ -223,7 +223,7 @@ class TestEditWorkoutWithGpx(ApiTestCaseMixin):
class TestEditWorkoutWithoutGpx(ApiTestCaseMixin):
def test_it_updates_an_workout_wo_gpx(
def test_it_updates_a_workout_wo_gpx(
self,
app: Flask,
user_1: User,
@ -364,7 +364,7 @@ class TestEditWorkoutWithoutGpx(ApiTestCaseMixin):
assert len(data['data']['workouts']) == 1
assert data['data']['workouts'][0]['notes'] == ''
def test_returns_403_when_editing_an_workout_wo_gpx_from_different_user(
def test_returns_403_when_editing_a_workout_wo_gpx_from_different_user(
self,
app: Flask,
user_1: User,
@ -393,7 +393,7 @@ class TestEditWorkoutWithoutGpx(ApiTestCaseMixin):
self.assert_403(response)
def test_it_updates_an_workout_wo_gpx_with_timezone(
def test_it_updates_a_workout_wo_gpx_with_timezone(
self,
app: Flask,
user_1_paris: User,
@ -468,7 +468,7 @@ class TestEditWorkoutWithoutGpx(ApiTestCaseMixin):
assert records[3]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[3]['value'] == 8.0
def test_it_updates_only_sport_and_distance_an_workout_wo_gpx(
def test_it_updates_only_sport_and_distance_a_workout_wo_gpx(
self,
app: Flask,
user_1: User,

View File

@ -7,7 +7,7 @@ from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
from .utils import get_random_short_id, post_an_workout
from .utils import get_random_short_id, post_a_workout
def get_gpx_filepath(workout_id: int) -> str:
@ -16,10 +16,10 @@ def get_gpx_filepath(workout_id: int) -> str:
class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
def test_it_deletes_an_workout_with_gpx(
def test_it_deletes_a_workout_with_gpx(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
response = client.delete(
@ -29,7 +29,7 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
assert response.status_code == 204
def test_it_returns_403_when_deleting_an_workout_from_different_user(
def test_it_returns_403_when_deleting_a_workout_from_different_user(
self,
app: Flask,
user_1: User,
@ -37,7 +37,7 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
sport_1_cycling: Sport,
gpx_file: str,
) -> None:
_, workout_short_id = post_an_workout(app, gpx_file)
_, workout_short_id = post_a_workout(app, gpx_file)
client, auth_token = self.get_test_client_and_auth_token(
app, user_2.email
)
@ -64,10 +64,10 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
data = self.assert_404(response)
assert 'not found' in data['status']
def test_it_returns_500_when_deleting_an_workout_with_gpx_invalid_file(
def test_it_returns_500_when_deleting_a_workout_with_gpx_invalid_file(
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
) -> None:
token, workout_short_id = post_an_workout(app, gpx_file)
token, workout_short_id = post_a_workout(app, gpx_file)
client = app.test_client()
gpx_filepath = get_gpx_filepath(1)
gpx_filepath = get_absolute_file_path(gpx_filepath)
@ -82,7 +82,7 @@ class TestDeleteWorkoutWithGpx(ApiTestCaseMixin):
class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin):
def test_it_deletes_an_workout_wo_gpx(
def test_it_deletes_a_workout_wo_gpx(
self,
app: Flask,
user_1: User,
@ -98,7 +98,7 @@ class TestDeleteWorkoutWithoutGpx(ApiTestCaseMixin):
)
assert response.status_code == 204
def test_it_returns_403_when_deleting_an_workout_from_different_user(
def test_it_returns_403_when_deleting_a_workout_from_different_user(
self,
app: Flask,
user_1: User,

View File

@ -12,7 +12,7 @@ def get_random_short_id() -> str:
return encode_uuid(uuid4())
def post_an_workout(
def post_a_workout(
app: Flask, gpx_file: str, notes: Optional[str] = None
) -> Tuple[str, str]:
client = app.test_client()

View File

@ -1,5 +1,5 @@
from datetime import timedelta
from typing import Any, Dict, List, Optional, Tuple
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional, Tuple, Union
import gpxpy.gpx
@ -16,9 +16,9 @@ def open_gpx_file(gpx_file: str) -> Optional[gpxpy.gpx.GPX]:
def get_gpx_data(
parsed_gpx: gpxpy.gpx,
parsed_gpx: Union[gpxpy.gpx.GPX, gpxpy.gpx.GPXTrackSegment],
max_speed: float,
start: int,
start: Union[datetime, None],
stopped_time_between_seg: timedelta,
stopped_speed_threshold: float,
) -> Dict:
@ -32,7 +32,8 @@ def get_gpx_data(
duration = parsed_gpx.get_duration()
gpx_data['duration'] = (
timedelta(seconds=duration) + stopped_time_between_seg
timedelta(seconds=duration if duration else 0)
+ stopped_time_between_seg
)
ele = parsed_gpx.get_elevation_extremes()
@ -43,17 +44,23 @@ def get_gpx_data(
gpx_data['uphill'] = hill.uphill
gpx_data['downhill'] = hill.downhill
mv = parsed_gpx.get_moving_data(
moving_data = parsed_gpx.get_moving_data(
stopped_speed_threshold=stopped_speed_threshold
)
gpx_data['moving_time'] = timedelta(seconds=mv.moving_time)
if moving_data:
gpx_data['moving_time'] = timedelta(seconds=moving_data.moving_time)
gpx_data['stop_time'] = (
timedelta(seconds=mv.stopped_time) + stopped_time_between_seg
timedelta(seconds=moving_data.stopped_time)
+ stopped_time_between_seg
)
distance = mv.moving_distance + mv.stopped_distance
distance = moving_data.moving_distance + moving_data.stopped_distance
gpx_data['distance'] = distance / 1000
average_speed = distance / mv.moving_time if mv.moving_time > 0 else 0
average_speed = (
distance / moving_data.moving_time
if moving_data.moving_time > 0
else 0
)
gpx_data['average_speed'] = (average_speed / 1000) * 3600
return gpx_data
@ -72,9 +79,9 @@ def get_gpx_info(
if gpx is None:
raise WorkoutGPXException('not found', 'No gpx file')
gpx_data = {'name': gpx.tracks[0].name, 'segments': []}
max_speed = 0
start = 0
gpx_data: Dict = {'name': gpx.tracks[0].name, 'segments': []}
max_speed = 0.0
start: Optional[datetime] = None
map_data = []
weather_data = []
segments_nb = len(gpx.tracks[0].segments)
@ -83,14 +90,15 @@ def get_gpx_info(
stopped_time_between_seg = no_stopped_time
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
segment_start = 0
segment_start: Optional[datetime] = None
segment_points_nb = len(segment.points)
for point_idx, point in enumerate(segment.points):
if point_idx == 0:
segment_start = point.time
# first gpx point => get weather
if start == 0:
if start is None:
start = point.time
if update_weather_data:
if point.time and update_weather_data:
weather_data.append(get_weather(point))
# if a previous segment exists, calculate stopped time between
@ -108,13 +116,19 @@ def get_gpx_info(
if update_map_data:
map_data.append([point.longitude, point.latitude])
calculated_max_speed = segment.get_moving_data(
moving_data = segment.get_moving_data(
stopped_speed_threshold=stopped_speed_threshold
).max_speed
segment_max_speed = calculated_max_speed if calculated_max_speed else 0
)
if moving_data:
calculated_max_speed = moving_data.max_speed
segment_max_speed = (
calculated_max_speed if calculated_max_speed else 0
)
if segment_max_speed > max_speed:
max_speed = segment_max_speed
else:
segment_max_speed = 0.0
segment_data = get_gpx_data(
segment,
@ -137,12 +151,16 @@ def get_gpx_info(
if update_map_data:
bounds = gpx.get_bounds()
gpx_data['bounds'] = [
gpx_data['bounds'] = (
[
bounds.min_latitude,
bounds.min_longitude,
bounds.max_latitude,
bounds.max_longitude,
]
if bounds
else []
)
return gpx_data, map_data, weather_data
@ -222,7 +240,11 @@ def get_chart_data(
'latitude': point.latitude,
'longitude': point.longitude,
'speed': speed,
'time': point.time,
# workaround
# https://github.com/tkrajina/gpxpy/issues/209
'time': point.time.replace(
tzinfo=timezone(point.time.utcoffset())
),
}
)
previous_point = point

View File

@ -3,18 +3,22 @@ from typing import Dict, Optional
import forecastio
import pytz
from gpxpy.gpx import GPXRoutePoint
from gpxpy.gpx import GPXTrackPoint
from fittrackee import appLog
API_KEY = os.getenv('WEATHER_API_KEY')
def get_weather(point: GPXRoutePoint) -> Optional[Dict]:
if not API_KEY or API_KEY == '':
def get_weather(point: GPXTrackPoint) -> Optional[Dict]:
if not API_KEY or not point.time:
return None
try:
point_time = pytz.utc.localize(point.time)
point_time = (
pytz.utc.localize(point.time)
if point.time.tzinfo is None
else point.time.astimezone(pytz.utc)
)
forecast = forecastio.load_forecast(
API_KEY,
point.latitude,

View File

@ -22,31 +22,42 @@ from .gpx import get_gpx_info
from .maps import generate_map, get_map_hash
def get_datetime_with_tz(
timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None
) -> Tuple[Optional[datetime], datetime]:
def get_workout_datetime(
workout_date: Union[datetime, str],
user_timezone: Optional[str],
date_str_format: Optional[str] = None,
with_timezone: bool = False,
) -> Tuple[datetime, Optional[datetime]]:
"""
Return naive datetime and datetime with user timezone
Return naive datetime and datetime with user timezone if with_timezone
"""
workout_date_tz = None
if timezone:
user_tz = pytz.timezone(timezone)
utc_tz = pytz.utc
if gpx_data:
# workout date in gpx is in UTC, but in naive datetime
fmt = '%Y-%m-%d %H:%M:%S'
workout_date_string = workout_date.strftime(fmt)
workout_date_tmp = utc_tz.localize(
datetime.strptime(workout_date_string, fmt)
)
workout_date_tz = workout_date_tmp.astimezone(user_tz)
else:
workout_date_tz = user_tz.localize(workout_date)
workout_date = workout_date_tz.astimezone(utc_tz)
# make datetime 'naive' like in gpx file
workout_date = workout_date.replace(tzinfo=None)
workout_date_with_user_tz = None
return workout_date_tz, workout_date
# workout w/o gpx
if isinstance(workout_date, str):
if not date_str_format:
date_str_format = '%Y-%m-%d %H:%M:%S'
workout_date = datetime.strptime(workout_date, date_str_format)
if user_timezone:
workout_date = pytz.timezone(user_timezone).localize(workout_date)
if workout_date.tzinfo is None:
naive_workout_date = workout_date
if user_timezone and with_timezone:
pytz.utc.localize(naive_workout_date)
workout_date_with_user_tz = pytz.utc.localize(
naive_workout_date
).astimezone(pytz.timezone(user_timezone))
else:
naive_workout_date = workout_date.astimezone(pytz.utc).replace(
tzinfo=None
)
if user_timezone and with_timezone:
workout_date_with_user_tz = workout_date.astimezone(
pytz.timezone(user_timezone)
)
return naive_workout_date, workout_date_with_user_tz
def get_datetime_from_request_args(
@ -57,25 +68,32 @@ def get_datetime_from_request_args(
date_from_str = params.get('from')
if date_from_str:
date_from = datetime.strptime(date_from_str, '%Y-%m-%d')
_, date_from = get_datetime_with_tz(user.timezone, date_from)
date_from, _ = get_workout_datetime(
workout_date=date_from_str,
user_timezone=user.timezone,
date_str_format='%Y-%m-%d',
)
date_to_str = params.get('to')
if date_to_str:
date_to = datetime.strptime(
f'{date_to_str} 23:59:59', '%Y-%m-%d %H:%M:%S'
date_to, _ = get_workout_datetime(
workout_date=f'{date_to_str} 23:59:59',
user_timezone=user.timezone,
)
_, date_to = get_datetime_with_tz(user.timezone, date_to)
return date_from, date_to
def _remove_microseconds(delta: timedelta) -> timedelta:
return delta - timedelta(microseconds=delta.microseconds)
def update_workout_data(
workout: Union[Workout, WorkoutSegment], gpx_data: Dict
) -> Union[Workout, WorkoutSegment]:
"""
Update workout or workout segment with data from gpx file
"""
workout.pauses = gpx_data['stop_time']
workout.moving = gpx_data['moving_time']
workout.pauses = _remove_microseconds(gpx_data['stop_time'])
workout.moving = _remove_microseconds(gpx_data['moving_time'])
workout.min_alt = gpx_data['elevation_min']
workout.max_alt = gpx_data['elevation_max']
workout.descent = gpx_data['downhill']
@ -92,17 +110,17 @@ def create_workout(
Create Workout from data entered by user and from gpx if a gpx file is
provided
"""
workout_date = (
gpx_data['start']
workout_date, workout_date_tz = get_workout_datetime(
workout_date=gpx_data['start']
if gpx_data
else datetime.strptime(workout_data['workout_date'], '%Y-%m-%d %H:%M')
)
workout_date_tz, workout_date = get_datetime_with_tz(
user.timezone, workout_date, gpx_data
else workout_data['workout_date'],
date_str_format=None if gpx_data else '%Y-%m-%d %H:%M',
user_timezone=user.timezone,
with_timezone=True,
)
duration = (
gpx_data['duration']
_remove_microseconds(gpx_data['duration'])
if gpx_data
else timedelta(seconds=workout_data['duration'])
)
@ -202,11 +220,10 @@ def edit_workout(
workout.notes = workout_data.get('notes')
if not workout.gpx:
if workout_data.get('workout_date'):
workout_date = datetime.strptime(
workout_data['workout_date'], '%Y-%m-%d %H:%M'
)
_, workout.workout_date = get_datetime_with_tz(
auth_user.timezone, workout_date
workout.workout_date, _ = get_workout_datetime(
workout_date=workout_data.get('workout_date', ''),
date_str_format='%Y-%m-%d %H:%M',
user_timezone=auth_user.timezone,
)
if workout_data.get('duration'):

View File

@ -303,7 +303,7 @@ def get_workout(
auth_user: User, workout_short_id: str
) -> Union[Dict, HttpResponse]:
"""
Get an workout
Get a workout
**Example request**:
@ -405,7 +405,7 @@ def get_workout_data(
data_type: str,
segment_id: Optional[int] = None,
) -> Union[Dict, HttpResponse]:
"""Get data from an workout gpx file"""
"""Get data from a workout gpx file"""
workout_uuid = decode_short_id(workout_short_id)
workout = Workout.query.filter_by(uuid=workout_uuid).first()
if not workout:
@ -467,7 +467,7 @@ def get_workout_gpx(
auth_user: User, workout_short_id: str
) -> Union[Dict, HttpResponse]:
"""
Get gpx file for an workout displayed on map with Leaflet
Get gpx file for a workout displayed on map with Leaflet
**Example request**:
@ -517,7 +517,7 @@ def get_workout_chart_data(
auth_user: User, workout_short_id: str
) -> Union[Dict, HttpResponse]:
"""
Get chart data from an workout gpx file, to display it with Recharts
Get chart data from a workout gpx file, to display it with Recharts
**Example request**:
@ -587,7 +587,7 @@ def get_segment_gpx(
auth_user: User, workout_short_id: str, segment_id: int
) -> Union[Dict, HttpResponse]:
"""
Get gpx file for an workout segment displayed on map with Leaflet
Get gpx file for a workout segment displayed on map with Leaflet
**Example request**:
@ -639,7 +639,7 @@ def get_segment_chart_data(
auth_user: User, workout_short_id: str, segment_id: int
) -> Union[Dict, HttpResponse]:
"""
Get chart data from an workout gpx file, to display it with Recharts
Get chart data from a workout gpx file, to display it with Recharts
**Example request**:
@ -851,7 +851,7 @@ def get_map_tile(s: str, z: str, x: str, y: str) -> Tuple[Response, int]:
@require_auth(scopes='write')
def post_workout(auth_user: User) -> Union[Tuple[Dict, int], HttpResponse]:
"""
Post an workout with a gpx file
Post a workout with a gpx file
**Example request**:
@ -1021,7 +1021,7 @@ def post_workout_no_gpx(
auth_user: User,
) -> Union[Tuple[Dict, int], HttpResponse]:
"""
Post an workout without gpx file
Post a workout without gpx file
**Example request**:
@ -1111,7 +1111,8 @@ def post_workout_no_gpx(
"status": "success"
}
:<json string workout_date: workout date (format: ``%Y-%m-%d %H:%M``)
:<json string workout_date: workout date, in user timezone
(format: ``%Y-%m-%d %H:%M``)
:<json float distance: workout distance in km
:<json integer duration: workout duration in seconds
:<json string notes: notes (not mandatory)
@ -1169,7 +1170,7 @@ def update_workout(
auth_user: User, workout_short_id: str
) -> Union[Dict, HttpResponse]:
"""
Update an workout
Update a workout
**Example request**:
@ -1261,7 +1262,8 @@ def update_workout(
:param string workout_short_id: workout short id
:<json string workout_date: workout date (format: ``%Y-%m-%d %H:%M``)
:<json string workout_date: workout date in user timezone
(format: ``%Y-%m-%d %H:%M``)
(only for workout without gpx)
:<json float distance: workout distance in km
(only for workout without gpx)
@ -1316,7 +1318,7 @@ def delete_workout(
auth_user: User, workout_short_id: str
) -> Union[Tuple[Dict, int], HttpResponse]:
"""
Delete an workout
Delete a workout
**Example request**:

View File

@ -1,6 +1,6 @@
{
"name": "fittrackee_client",
"version": "0.6.6",
"version": "0.6.7",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",

264
poetry.lock generated
View File

@ -8,11 +8,11 @@ python-versions = "*"
[[package]]
name = "alembic"
version = "1.7.7"
version = "1.8.0"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
importlib-metadata = {version = "*", markers = "python_version < \"3.9\""}
@ -206,14 +206,14 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "coverage"
version = "6.4"
version = "6.4.1"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""}
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
@ -397,11 +397,11 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""
[[package]]
name = "gpxpy"
version = "1.3.4"
version = "1.5.0"
description = "GPX file parser and GPS track manipulation library"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=3.6"
[[package]]
name = "greenlet"
@ -580,7 +580,7 @@ python-versions = "*"
[[package]]
name = "mypy"
version = "0.960"
version = "0.961"
description = "Optional static typing for Python"
category = "dev"
optional = false
@ -1001,7 +1001,7 @@ sphinx = ">=1.3.1"
[[package]]
name = "redis"
version = "4.3.1"
version = "4.3.3"
description = "Python client for Redis database and key-value store"
category = "main"
optional = false
@ -1020,20 +1020,20 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
[[package]]
name = "requests"
version = "2.27.1"
version = "2.28.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
charset-normalizer = ">=2.0.0,<2.1.0"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
@ -1238,7 +1238,7 @@ test = ["pytest"]
[[package]]
name = "sqlalchemy"
version = "1.4.36"
version = "1.4.37"
description = "Database Abstraction Library"
category = "main"
optional = false
@ -1263,7 +1263,7 @@ mysql_connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"]
postgresql_pg8000 = ["pg8000 (>=1.16.6)"]
postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql_psycopg2binary = ["psycopg2-binary"]
postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1)", "pymysql"]
@ -1325,7 +1325,7 @@ python-versions = ">=3.7"
[[package]]
name = "trio"
version = "0.20.0"
version = "0.21.0"
description = "A friendly Python library for async concurrency and I/O"
category = "dev"
optional = false
@ -1363,7 +1363,7 @@ python-versions = ">=3.6"
[[package]]
name = "types-freezegun"
version = "1.1.9"
version = "1.1.10"
description = "Typing stubs for freezegun"
category = "dev"
optional = false
@ -1379,7 +1379,7 @@ python-versions = "*"
[[package]]
name = "types-requests"
version = "2.27.29"
version = "2.27.30"
description = "Typing stubs for requests"
category = "dev"
optional = false
@ -1477,7 +1477,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "84bb7759a500c94555fb8d8e5ca25bd98b287f6c53c80eb259bcc3c0d31b1031"
content-hash = "6d019761837f6c9003d5749a29af885eed271bc8409c9b3dcb07f290247f7437"
[metadata.files]
alabaster = [
@ -1485,8 +1485,8 @@ alabaster = [
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
]
alembic = [
{file = "alembic-1.7.7-py3-none-any.whl", hash = "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b"},
{file = "alembic-1.7.7.tar.gz", hash = "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58"},
{file = "alembic-1.8.0-py3-none-any.whl", hash = "sha256:b5ae4bbfc7d1302ed413989d39474d102e7cfa158f6d5969d2497955ffe85a30"},
{file = "alembic-1.8.0.tar.gz", hash = "sha256:a2d4d90da70b30e70352cd9455e35873a255a31402a438fe24815758d7a0e5e1"},
]
async-generator = [
{file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
@ -1627,47 +1627,47 @@ commonmark = [
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
coverage = [
{file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"},
{file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"},
{file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"},
{file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"},
{file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"},
{file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"},
{file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"},
{file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"},
{file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"},
{file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"},
{file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"},
{file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"},
{file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"},
{file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"},
{file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"},
{file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"},
{file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"},
{file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"},
{file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"},
{file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"},
{file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"},
{file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"},
{file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"},
{file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"},
{file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"},
{file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"},
{file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"},
{file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"},
{file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"},
{file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"},
{file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"},
{file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"},
{file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"},
{file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"},
]
cryptography = [
{file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"},
@ -1742,7 +1742,7 @@ gitpython = [
{file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
]
gpxpy = [
{file = "gpxpy-1.3.4.tar.gz", hash = "sha256:4a0f072ae5bdf9270c7450e452f93a6c5c91d888114e8d78868a8f163b0dbb15"},
{file = "gpxpy-1.5.0.tar.gz", hash = "sha256:e6993a8945eae07a833cd304b88bbc6c3c132d63b2bf4a9b0a5d9097616b8708"},
]
greenlet = [
{file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
@ -1890,29 +1890,29 @@ mccabe = [
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
mypy = [
{file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"},
{file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"},
{file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"},
{file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"},
{file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"},
{file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"},
{file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"},
{file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"},
{file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"},
{file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"},
{file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"},
{file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"},
{file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"},
{file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"},
{file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"},
{file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"},
{file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"},
{file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"},
{file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"},
{file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"},
{file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"},
{file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"},
{file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"},
{file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
{file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
{file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
{file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
{file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
{file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
{file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
{file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
{file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
{file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
{file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
{file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
{file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
{file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
{file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
{file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
{file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
{file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
{file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
{file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
{file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
{file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
{file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
@ -2175,12 +2175,12 @@ recommonmark = [
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
]
redis = [
{file = "redis-4.3.1-py3-none-any.whl", hash = "sha256:84316970995a7adb907a56754d2b92d88fc2d252963dc5ac34c88f0f1a22c25d"},
{file = "redis-4.3.1.tar.gz", hash = "sha256:94b617b4cd296e94991146f66fc5559756fbefe9493604f0312e4d3298ac63e9"},
{file = "redis-4.3.3-py3-none-any.whl", hash = "sha256:f57f8df5d238a8ecf92f499b6b21467bfee6c13d89953c27edf1e2bc673622e7"},
{file = "redis-4.3.3.tar.gz", hash = "sha256:2f7a57cf4af15cd543c4394bcbe2b9148db2606a37edba755368836e3a1d053e"},
]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
{file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"},
{file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"},
]
responses = [
{file = "responses-0.21.0-py3-none-any.whl", hash = "sha256:2dcc863ba63963c0c3d9ee3fa9507cbe36b7d7b0fccb4f0bdfd9e96c539b1487"},
@ -2250,42 +2250,42 @@ sphinxcontrib-serializinghtml = [
{file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
]
sqlalchemy = [
{file = "SQLAlchemy-1.4.36-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:81e53bd383c2c33de9d578bfcc243f559bd3801a0e57f2bcc9a943c790662e0c"},
{file = "SQLAlchemy-1.4.36-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e1fe00ee85c768807f2a139b83469c1e52a9ffd58a6eb51aa7aeb524325ab18"},
{file = "SQLAlchemy-1.4.36-cp27-cp27m-win32.whl", hash = "sha256:d57ac32f8dc731fddeb6f5d1358b4ca5456e72594e664769f0a9163f13df2a31"},
{file = "SQLAlchemy-1.4.36-cp27-cp27m-win_amd64.whl", hash = "sha256:fca8322e04b2dde722fcb0558682740eebd3bd239bea7a0d0febbc190e99dc15"},
{file = "SQLAlchemy-1.4.36-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:53d2d9ee93970c969bc4e3c78b1277d7129554642f6ffea039c282c7dc4577bc"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f0394a3acfb8925db178f7728adb38c027ed7e303665b225906bfa8099dc1ce8"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c606d8238feae2f360b8742ffbe67741937eb0a05b57f536948d198a3def96"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d07fe2de0325d06e7e73281e9a9b5e259fbd7cbfbe398a0433cbb0082ad8fa7"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5041474dcab7973baa91ec1f3112049a9dd4652898d6a95a6a895ff5c58beb6b"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-win32.whl", hash = "sha256:be094460930087e50fd08297db9d7aadaed8408ad896baf758e9190c335632da"},
{file = "SQLAlchemy-1.4.36-cp310-cp310-win_amd64.whl", hash = "sha256:64d796e9af522162f7f2bf7a3c5531a0a550764c426782797bbeed809d0646c5"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a0ae3aa2e86a4613f2d4c49eb7da23da536e6ce80b2bfd60bbb2f55fc02b0b32"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d50cb71c1dbed70646d521a0975fb0f92b7c3f84c61fa59e07be23a1aaeecfc"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:16abf35af37a3d5af92725fc9ec507dd9e9183d261c2069b6606d60981ed1c6e"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864a83bd345871ad9699ce466388f836db7572003d67d9392a71998092210e3"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-win32.whl", hash = "sha256:fbf8c09fe9728168f8cc1b40c239eab10baf9c422c18be7f53213d70434dea43"},
{file = "SQLAlchemy-1.4.36-cp36-cp36m-win_amd64.whl", hash = "sha256:6e859fa96605027bd50d8e966db1c4e1b03e7b3267abbc4b89ae658c99393c58"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:166a3887ec355f7d2f12738f7fa25dc8ac541867147a255f790f2f41f614cb44"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e885548da361aa3f8a9433db4cfb335b2107e533bf314359ae3952821d84b3e"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c90ef955d429966d84326d772eb34333178737ebb669845f1d529eb00c75e72"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a052bd9f53004f8993c624c452dfad8ec600f572dd0ed0445fbe64b22f5570e"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-win32.whl", hash = "sha256:dce3468bf1fc12374a1a732c9efd146ce034f91bb0482b602a9311cb6166a920"},
{file = "SQLAlchemy-1.4.36-cp37-cp37m-win_amd64.whl", hash = "sha256:6cb4c4f57a20710cea277edf720d249d514e587f796b75785ad2c25e1c0fed26"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e74ce103b81c375c3853b436297952ef8d7863d801dcffb6728d01544e5191b5"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b20c4178ead9bc398be479428568ff31b6c296eb22e75776273781a6551973f"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af2587ae11400157753115612d6c6ad255143efba791406ad8a0cbcccf2edcb3"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cf3077712be9f65c9aaa0b5bc47bc1a44789fd45053e2e3ecd59ff17c63fe9"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-win32.whl", hash = "sha256:ce20f5da141f8af26c123ebaa1b7771835ca6c161225ce728962a79054f528c3"},
{file = "SQLAlchemy-1.4.36-cp38-cp38-win_amd64.whl", hash = "sha256:316c7e5304dda3e3ad711569ac5d02698bbc71299b168ac56a7076b86259f7ea"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f522214f6749bc073262529c056f7dfd660f3b5ec4180c5354d985eb7219801e"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ecac4db8c1aa4a269f5829df7e706639a24b780d2ac46b3e485cbbd27ec0028"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3db741beaa983d4cbf9087558620e7787106319f7e63a066990a70657dd6b35"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ec89bf98cc6a0f5d1e28e3ad28e9be6f3b4bdbd521a4053c7ae8d5e1289a8a1"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-win32.whl", hash = "sha256:e12532c4d3f614678623da5d852f038ace1f01869b89f003ed6fe8c793f0c6a3"},
{file = "SQLAlchemy-1.4.36-cp39-cp39-win_amd64.whl", hash = "sha256:cb441ca461bf97d00877b607f132772644b623518b39ced54da433215adce691"},
{file = "SQLAlchemy-1.4.36.tar.gz", hash = "sha256:64678ac321d64a45901ef2e24725ec5e783f1f4a588305e196431447e7ace243"},
{file = "SQLAlchemy-1.4.37-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d9050b0c4a7f5538650c74aaba5c80cd64450e41c206f43ea6d194ae6d060ff9"},
{file = "SQLAlchemy-1.4.37-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b4c92823889cf9846b972ee6db30c0e3a92c0ddfc76c6060a6cda467aa5fb694"},
{file = "SQLAlchemy-1.4.37-cp27-cp27m-win32.whl", hash = "sha256:b55932fd0e81b43f4aff397c8ad0b3c038f540af37930423ab8f47a20b117e4c"},
{file = "SQLAlchemy-1.4.37-cp27-cp27m-win_amd64.whl", hash = "sha256:4a17c1a1152ca4c29d992714aa9df3054da3af1598e02134f2e7314a32ef69d8"},
{file = "SQLAlchemy-1.4.37-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ffe487570f47536b96eff5ef2b84034a8ba4e19aab5ab7647e677d94a119ea55"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:78363f400fbda80f866e8e91d37d36fe6313ff847ded08674e272873c1377ea5"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee34c85cbda7779d66abac392c306ec78c13f5c73a1f01b8b767916d4895d23"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b38e088659b30c2ca0af63e5d139fad1779a7925d75075a08717a21c406c0f6"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6629c79967a6c92e33fad811599adf9bc5cee6e504a1027bbf9cc1b6fb2d276d"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-win32.whl", hash = "sha256:2aac2a685feb9882d09f457f4e5586c885d578af4e97a2b759e91e8c457cbce5"},
{file = "SQLAlchemy-1.4.37-cp310-cp310-win_amd64.whl", hash = "sha256:7a44683cf97744a405103ef8fdd31199e9d7fc41b4a67e9044523b29541662b0"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:cffc67cdd07f0e109a1fc83e333972ae423ea5ad414585b63275b66b870ea62b"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17417327b87a0f703c9a20180f75e953315207d048159aff51822052f3e33e69"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa0e90e527066409c2ea5676282cf4afb4a40bb9dce0f56c8ec2768bff22a6e"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1d9fb3931e27d59166bb5c4dcc911400fee51082cfba66ceb19ac954ade068"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-win32.whl", hash = "sha256:0e7fd52e48e933771f177c2a1a484b06ea03774fc7741651ebdf19985a34037c"},
{file = "SQLAlchemy-1.4.37-cp36-cp36m-win_amd64.whl", hash = "sha256:eec39a17bab3f69c44c9df4e0ed87c7306f2d2bf1eca3070af644927ec4199fa"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:caca6acf3f90893d7712ae2c6616ecfeac3581b4cc677c928a330ce6fbad4319"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50c8eaf44c3fed5ba6758d375de25f163e46137c39fda3a72b9ee1d1bb327dfc"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:139c50b9384e6d32a74fc4dcd0e9717f343ed38f95dbacf832c782c68e3862f3"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4c3b009c9220ae6e33f17b45f43fb46b9a1d281d76118405af13e26376f2e11"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-win32.whl", hash = "sha256:9785d6f962d2c925aeb06a7539ac9d16608877da6aeaaf341984b3693ae80a02"},
{file = "SQLAlchemy-1.4.37-cp37-cp37m-win_amd64.whl", hash = "sha256:3197441772dc3b1c6419f13304402f2418a18d7fe78000aa5a026e7100836739"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3862a069a24f354145e01a76c7c720c263d62405fe5bed038c46a7ce900f5dd6"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8706919829d455a9fa687c6bbd1b048e36fec3919a59f2d366247c2bfdbd9c"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:06ec11a5e6a4b6428167d3ce33b5bd455c020c867dabe3e6951fa98836e0741d"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d58f2d9d1a4b1459e8956a0153a4119da80f54ee5a9ea623cd568e99459a3ef1"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-win32.whl", hash = "sha256:d6927c9e3965b194acf75c8e0fb270b4d54512db171f65faae15ef418721996e"},
{file = "SQLAlchemy-1.4.37-cp38-cp38-win_amd64.whl", hash = "sha256:a91d0668cada27352432f15b92ac3d43e34d8f30973fa8b86f5e9fddee928f3b"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f9940528bf9c4df9e3c3872d23078b6b2da6431c19565637c09f1b88a427a684"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29a742c29fea12259f1d2a9ee2eb7fe4694a85d904a4ac66d15e01177b17ad7f"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7e579d6e281cc937bdb59917017ab98e618502067e04efb1d24ac168925e1d2a"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a940c551cfbd2e1e646ceea2777944425f5c3edff914bc808fe734d9e66f8d71"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-win32.whl", hash = "sha256:5e4e517ce72fad35cce364a01aff165f524449e9c959f1837dc71088afa2824c"},
{file = "SQLAlchemy-1.4.37-cp39-cp39-win_amd64.whl", hash = "sha256:c37885f83b59e248bebe2b35beabfbea398cb40960cdc6d3a76eac863d4e1938"},
{file = "SQLAlchemy-1.4.37.tar.gz", hash = "sha256:3688f92c62db6c5df268e2264891078f17ecb91e3141b400f2e28d0f75796dea"},
]
staticmap = [
{file = "staticmap-0.5.5.tar.gz", hash = "sha256:007c507b4d42e00eaba179649753f2f8d69d4ece3028736e18d9e86493044387"},
@ -2307,8 +2307,8 @@ tomli = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
trio = [
{file = "trio-0.20.0-py3-none-any.whl", hash = "sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a"},
{file = "trio-0.20.0.tar.gz", hash = "sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070"},
{file = "trio-0.21.0-py3-none-any.whl", hash = "sha256:4dc0bf9d5cc78767fc4516325b6d80cc0968705a31d0eec2ecd7cdda466265b0"},
{file = "trio-0.21.0.tar.gz", hash = "sha256:523f39b7b69eef73501cebfe1aafd400a9aad5b03543a0eded52952488ff1c13"},
]
trio-websocket = [
{file = "trio-websocket-0.9.2.tar.gz", hash = "sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"},
@ -2341,16 +2341,16 @@ typed-ast = [
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
types-freezegun = [
{file = "types-freezegun-1.1.9.tar.gz", hash = "sha256:6f05108d468baecadf999873bd37e57b25ceb35d35d3f83e7a742f25d6fe8b0e"},
{file = "types_freezegun-1.1.9-py3-none-any.whl", hash = "sha256:fe1dd73372d96358dcb93e3aeb66d39f6ac63749e0724f13554cc145e2120efe"},
{file = "types-freezegun-1.1.10.tar.gz", hash = "sha256:cb3a2d2eee950eacbaac0673ab50499823365ceb8c655babb1544a41446409ec"},
{file = "types_freezegun-1.1.10-py3-none-any.whl", hash = "sha256:fadebe72213e0674036153366205038e1f95c8ca96deb4ef9b71ddc15413543e"},
]
types-pytz = [
{file = "types-pytz-2021.3.8.tar.gz", hash = "sha256:41253a3a2bf028b6a3f17b58749a692d955af0f74e975de94f6f4d2d3cd01dbd"},
{file = "types_pytz-2021.3.8-py3-none-any.whl", hash = "sha256:aef4a917ab28c585d3f474bfce4f4b44b91e95d9d47d4de29dd845e0db8e3910"},
]
types-requests = [
{file = "types-requests-2.27.29.tar.gz", hash = "sha256:fb453b3a76a48eca66381cea8004feaaea12835e838196f5c7ac87c75c5c19ef"},
{file = "types_requests-2.27.29-py3-none-any.whl", hash = "sha256:014f4f82db7b96c41feea9adaea30e68cd64c230eeab34b70c29bebb26ec74ac"},
{file = "types-requests-2.27.30.tar.gz", hash = "sha256:ca8d7cc549c3d10dbcb3c69c1b53e3ffd1270089c1001a65c1e9e1017eb5e704"},
{file = "types_requests-2.27.30-py3-none-any.whl", hash = "sha256:b9b6cd0a6e5d500e56419b79f44ec96f316e9375ff6c8ee566c39d25e9612621"},
]
types-urllib3 = [
{file = "types-urllib3-1.26.15.tar.gz", hash = "sha256:c89283541ef92e344b7f59f83ea9b5a295b16366ceee3f25ecfc5593c79f794e"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "fittrackee"
version = "0.6.6"
version = "0.6.7"
description = "Self-hosted outdoor workout/activity tracker"
authors = ["SamR1"]
license = "AGPL-3.0"
@ -29,7 +29,7 @@ flask = "^2.1"
flask-bcrypt = "^1.0"
flask-dramatiq = "^0.6.0"
flask-migrate = "^3.1"
gpxpy = "=1.3.4"
gpxpy = "=1.5.0"
gunicorn = "^20.1"
humanize = "^4.1"
psycopg2-binary = "^2.9"
@ -38,7 +38,7 @@ python-forecastio = "^1.4"
pytz = "^2022.1"
shortuuid = "^1.0.9"
staticmap = "^0.5.4"
SQLAlchemy = "1.4.36"
SQLAlchemy = "1.4.37"
pyOpenSSL = "^22.0"
ua-parser = "^0.10.0"
Authlib = "^1.0.1"
@ -46,7 +46,7 @@ Authlib = "^1.0.1"
[tool.poetry.dev-dependencies]
black = "^22.3"
freezegun = "^1.2"
mypy = "^0.960"
mypy = "^0.961"
pytest = "^7.1"
pytest-black = "^0.3.12"
pytest-cov = "^3.0"