Merge branch 'release-v0.6.7'
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 539 KiB After Width: | Height: | Size: 539 KiB  | 
| 
		 Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 368 KiB  | 
| 
		 Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 103 KiB  | 
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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>`__).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/_static/documentation_options.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Authentication — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Configuration — 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">"max_users"</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">"max_zip_file_size"</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">"map_attribution"</span><span class="p">:</span><span class="w"> </span><span class="nt">"&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.6"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.7"</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="p">},</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w"></span>
 | 
			
		||||
<span class="p">}</span><span class="w"></span>
 | 
			
		||||
@@ -191,7 +191,7 @@
 | 
			
		||||
<span class="w">    </span><span class="nt">"max_users"</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">"max_zip_file_size"</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">"map_attribution"</span><span class="p">:</span><span class="w"> </span><span class="nt">"&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.6"</span><span class="w"></span>
 | 
			
		||||
<span class="w">    </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.6.7"</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="p">},</span><span class="w"></span>
 | 
			
		||||
<span class="w">  </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w"></span>
 | 
			
		||||
<span class="p">}</span><span class="w"></span>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>API documentation — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Records — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Sports — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Statistics — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Users — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Workouts — 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Command line interface — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Features — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Index — FitTrackee 0.6.6
 | 
			
		||||
    <title>Index — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>HTTP Routing Table — FitTrackee 0.6.6
 | 
			
		||||
    <title>HTTP Routing Table — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>FitTrackee — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Installation — 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>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/objects.inv
									
									
									
									
									
								
							
							
						
						@@ -4,7 +4,7 @@
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Search — FitTrackee 0.6.6
 | 
			
		||||
    <title>Search — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Administrator — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>Troubleshooting — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 — FitTrackee 0.6.6
 | 
			
		||||
    <title>User — 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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 539 KiB After Width: | Height: | Size: 539 KiB  | 
| 
		 Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 368 KiB  | 
| 
		 Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 103 KiB  | 
@@ -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>`__).
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ from sqlalchemy.exc import ProgrammingError
 | 
			
		||||
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()
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ def get_application_config() -> Union[Dict, HttpResponse]:
 | 
			
		||||
          "max_users": 0,
 | 
			
		||||
          "max_zip_file_size": 10485760,
 | 
			
		||||
          "map_attribution": "© <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": "© <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
 | 
			
		||||
          "version": "0.6.6"
 | 
			
		||||
          "version": "0.6.7"
 | 
			
		||||
        },
 | 
			
		||||
        "status": "success"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -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>
 | 
			
		||||
							
								
								
									
										2
									
								
								fittrackee/dist/service-worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										2
									
								
								fittrackee/dist/service-worker.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										114
									
								
								fittrackee/tests/fixtures/fixtures_workouts.py
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -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 (
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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,18 +44,24 @@ 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)
 | 
			
		||||
    gpx_data['stop_time'] = (
 | 
			
		||||
        timedelta(seconds=mv.stopped_time) + stopped_time_between_seg
 | 
			
		||||
    )
 | 
			
		||||
    distance = mv.moving_distance + mv.stopped_distance
 | 
			
		||||
    gpx_data['distance'] = distance / 1000
 | 
			
		||||
    if moving_data:
 | 
			
		||||
        gpx_data['moving_time'] = timedelta(seconds=moving_data.moving_time)
 | 
			
		||||
        gpx_data['stop_time'] = (
 | 
			
		||||
            timedelta(seconds=moving_data.stopped_time)
 | 
			
		||||
            + stopped_time_between_seg
 | 
			
		||||
        )
 | 
			
		||||
        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
 | 
			
		||||
    gpx_data['average_speed'] = (average_speed / 1000) * 3600
 | 
			
		||||
        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
 | 
			
		||||
            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'] = [
 | 
			
		||||
            bounds.min_latitude,
 | 
			
		||||
            bounds.min_longitude,
 | 
			
		||||
            bounds.max_latitude,
 | 
			
		||||
            bounds.max_longitude,
 | 
			
		||||
        ]
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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'):
 | 
			
		||||
 
 | 
			
		||||
@@ -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]:
 | 
			
		||||
@authenticate
 | 
			
		||||
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**:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
									
									
								
							
							
						
						@@ -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\""}
 | 
			
		||||
@@ -195,14 +195,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"]
 | 
			
		||||
@@ -386,11 +386,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"
 | 
			
		||||
@@ -569,7 +569,7 @@ python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "mypy"
 | 
			
		||||
version = "0.960"
 | 
			
		||||
version = "0.961"
 | 
			
		||||
description = "Optional static typing for Python"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -990,7 +990,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
 | 
			
		||||
@@ -1009,20 +1009,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]]
 | 
			
		||||
@@ -1227,7 +1227,7 @@ test = ["pytest"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlalchemy"
 | 
			
		||||
version = "1.4.36"
 | 
			
		||||
version = "1.4.37"
 | 
			
		||||
description = "Database Abstraction Library"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -1252,7 +1252,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"]
 | 
			
		||||
@@ -1314,7 +1314,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
 | 
			
		||||
@@ -1352,7 +1352,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
 | 
			
		||||
@@ -1368,7 +1368,7 @@ python-versions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "types-requests"
 | 
			
		||||
version = "2.27.29"
 | 
			
		||||
version = "2.27.30"
 | 
			
		||||
description = "Typing stubs for requests"
 | 
			
		||||
category = "dev"
 | 
			
		||||
optional = false
 | 
			
		||||
@@ -1466,7 +1466,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
 | 
			
		||||
[metadata]
 | 
			
		||||
lock-version = "1.1"
 | 
			
		||||
python-versions = "^3.7"
 | 
			
		||||
content-hash = "e3bc153f918e57b66b117505b29466555da5ed642b0bbdc58bb5bcb9bd2ea688"
 | 
			
		||||
content-hash = "40b8491c8b82a7b29f57a2845209965c6351b527aebb7d504f5f8ec0ace4141e"
 | 
			
		||||
 | 
			
		||||
[metadata.files]
 | 
			
		||||
alabaster = [
 | 
			
		||||
@@ -1474,8 +1474,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"},
 | 
			
		||||
@@ -1612,47 +1612,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"},
 | 
			
		||||
@@ -1727,7 +1727,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"},
 | 
			
		||||
@@ -1875,29 +1875,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"},
 | 
			
		||||
@@ -2160,12 +2160,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"},
 | 
			
		||||
@@ -2235,42 +2235,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"},
 | 
			
		||||
@@ -2292,8 +2292,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"},
 | 
			
		||||
@@ -2326,16 +2326,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"},
 | 
			
		||||
 
 | 
			
		||||
@@ -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,14 +38,14 @@ 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"
 | 
			
		||||
 | 
			
		||||
[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"
 | 
			
		||||
 
 | 
			
		||||