Merge branch 'api-refactor'

This commit is contained in:
Sam 2021-01-20 17:07:19 +01:00
commit 7a1f44ccc5
58 changed files with 209 additions and 161 deletions

View File

@ -9,7 +9,7 @@ from flask_dramatiq import Dramatiq
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from .email.email import Email from fittrackee.emails.email import Email
db = SQLAlchemy() db = SQLAlchemy()
bcrypt = Bcrypt() bcrypt = Bcrypt()

View File

@ -5,15 +5,16 @@ import shutil
from typing import Dict, Optional from typing import Dict, Optional
import gunicorn.app.base import gunicorn.app.base
from flask import Flask
from flask_dramatiq import worker
from flask_migrate import upgrade
from tqdm import tqdm
from fittrackee import create_app, db from fittrackee import create_app, db
from fittrackee.application.utils import init_config from fittrackee.application.utils import init_config
from fittrackee.database_utils import init_database from fittrackee.database_utils import init_database
from fittrackee.workouts.models import Workout from fittrackee.workouts.models import Workout
from fittrackee.workouts.utils import update_workout from fittrackee.workouts.utils import update_workout
from flask import Flask
from flask_dramatiq import worker
from flask_migrate import upgrade
from tqdm import tqdm
HOST = os.getenv('HOST', '0.0.0.0') HOST = os.getenv('HOST', '0.0.0.0')
PORT = os.getenv('PORT', '5000') PORT = os.getenv('PORT', '5000')

View File

@ -1,15 +1,16 @@
from typing import Dict, Union from typing import Dict, Union
from flask import Blueprint, current_app, request
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from fittrackee import db from fittrackee import db
from fittrackee.responses import ( from fittrackee.responses import (
HttpResponse, HttpResponse,
InvalidPayloadErrorResponse, InvalidPayloadErrorResponse,
handle_error_and_return_response, handle_error_and_return_response,
) )
from flask import Blueprint, current_app, request from fittrackee.users.decorators import authenticate_as_admin
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from ..users.utils import authenticate_as_admin
from .models import AppConfig from .models import AppConfig
from .utils import update_app_config_from_database from .utils import update_app_config_from_database

View File

@ -1,6 +1,5 @@
from typing import Dict from typing import Dict
from fittrackee import db
from flask import current_app from flask import current_app
from sqlalchemy.engine.base import Connection from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for from sqlalchemy.event import listens_for
@ -8,7 +7,8 @@ from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm.mapper import Mapper from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from ..users.models import User from fittrackee import db
from fittrackee.users.models import User
BaseModel: DeclarativeMeta = db.Model BaseModel: DeclarativeMeta = db.Model

View File

@ -1,9 +1,10 @@
import os import os
from typing import Tuple from typing import Tuple
from flask import Flask
from fittrackee import db from fittrackee import db
from fittrackee.users.models import User from fittrackee.users.models import User
from flask import Flask
from .models import AppConfig from .models import AppConfig

View File

@ -26,7 +26,7 @@ class BaseConfig:
) )
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'} PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
WORKOUT_ALLOWED_EXTENSIONS = {'gpx', 'zip'} WORKOUT_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
TEMPLATES_FOLDER = os.path.join(current_app.root_path, 'email/templates') TEMPLATES_FOLDER = os.path.join(current_app.root_path, 'emails/templates')
UI_URL = os.environ.get('UI_URL') UI_URL = os.environ.get('UI_URL')
EMAIL_URL = os.environ.get('EMAIL_URL') EMAIL_URL = os.environ.get('EMAIL_URL')
SENDER_EMAIL = os.environ.get('SENDER_EMAIL') SENDER_EMAIL = os.environ.get('SENDER_EMAIL')

View File

@ -1,3 +1,5 @@
from flask import Flask
from fittrackee import db from fittrackee import db
from fittrackee.application.utils import ( from fittrackee.application.utils import (
init_config, init_config,
@ -5,7 +7,6 @@ from fittrackee.application.utils import (
) )
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport from fittrackee.workouts.models import Sport
from flask import Flask
def init_database(app: Flask) -> None: def init_database(app: Flask) -> None:

View File

@ -0,0 +1,2 @@
class InvalidEmailUrlScheme(Exception):
...

View File

@ -2,9 +2,7 @@ from typing import Dict
from urllib3.util import parse_url from urllib3.util import parse_url
from .exceptions import InvalidEmailUrlScheme
class InvalidEmailUrlScheme(Exception):
...
def parse_email_url(email_url: str) -> Dict: def parse_email_url(email_url: str) -> Dict:

11
fittrackee/exceptions.py Normal file
View File

@ -0,0 +1,11 @@
from typing import Optional
class GenericException(Exception):
def __init__(
self, status: str, message: str, e: Optional[Exception] = None
) -> None:
super().__init__(message)
self.status = status
self.message = message
self.e = e

View File

@ -1,10 +1,11 @@
from json import dumps from json import dumps
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union
from fittrackee import appLog
from flask import Response from flask import Response
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from fittrackee import appLog
def get_empty_data_for_datatype(data_type: str) -> Union[str, List]: def get_empty_data_for_datatype(data_type: str) -> Union[str, List]:
return '' if data_type in ['gpx', 'chart_data'] else [] return '' if data_type in ['gpx', 'chart_data'] else []

View File

@ -1,8 +1,9 @@
import json import json
from fittrackee.users.models import User
from flask import Flask from flask import Flask
from fittrackee.users.models import User
class TestGetConfig: class TestGetConfig:
def test_it_gets_application_config( def test_it_gets_application_config(

View File

@ -1,6 +1,7 @@
from fittrackee.application.models import AppConfig
from flask import Flask from flask import Flask
from fittrackee.application.models import AppConfig
class TestConfigModel: class TestConfigModel:
def test_application_config(self, app: Flask) -> None: def test_application_config(self, app: Flask) -> None:

View File

@ -3,6 +3,7 @@ import os
from typing import Generator, Optional from typing import Generator, Optional
import pytest import pytest
from fittrackee import create_app, db from fittrackee import create_app, db
from fittrackee.application.models import AppConfig from fittrackee.application.models import AppConfig
from fittrackee.application.utils import update_app_config_from_database from fittrackee.application.utils import update_app_config_from_database

View File

@ -1,11 +1,12 @@
from typing import Any from typing import Any
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from fittrackee import email_service
from fittrackee.email.email import EmailMessage
from flask import Flask from flask import Flask
from ..template_results.password_reset_request import expected_en_text_body from fittrackee import email_service
from fittrackee.emails.email import EmailMessage
from .template_results.password_reset_request import expected_en_text_body
class TestEmailMessage: class TestEmailMessage:

View File

@ -1,8 +1,9 @@
import pytest import pytest
from fittrackee.email.email import EmailTemplate
from flask import Flask from flask import Flask
from ..template_results.password_reset_request import ( from fittrackee.emails.email import EmailTemplate
from .template_results.password_reset_request import (
expected_en_html_body, expected_en_html_body,
expected_en_text_body, expected_en_text_body,
expected_fr_html_body, expected_fr_html_body,

View File

@ -1,5 +1,9 @@
import pytest import pytest
from fittrackee.email.utils_email import InvalidEmailUrlScheme, parse_email_url
from fittrackee.emails.utils_email import (
InvalidEmailUrlScheme,
parse_email_url,
)
class TestEmailUrlParser: class TestEmailUrlParser:

View File

@ -3,11 +3,12 @@ from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from flask import Flask
from freezegun import freeze_time
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.users.utils_token import get_user_token from fittrackee.users.utils_token import get_user_token
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
from freezegun import freeze_time
class TestUserRegistration: class TestUserRegistration:

View File

@ -3,9 +3,10 @@ from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from unittest.mock import patch from unittest.mock import patch
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
class TestGetUser: class TestGetUser:

View File

@ -1,6 +1,7 @@
from fittrackee.users.models import User
from flask import Flask from flask import Flask
from fittrackee.users.models import User
class TestUserModel: class TestUserModel:
def test_user_model(self, app: Flask, user_1: User) -> None: def test_user_model(self, app: Flask, user_1: User) -> None:

View File

@ -1,8 +1,9 @@
import json import json
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
class TestGetRecords: class TestGetRecords:

View File

@ -1,8 +1,9 @@
import datetime import datetime
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Record, Sport, Workout from fittrackee.workouts.models import Record, Sport, Workout
from flask import Flask
class TestRecordModel: class TestRecordModel:

View File

@ -1,8 +1,9 @@
import json import json
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
expected_sport_1_cycling_result = { expected_sport_1_cycling_result = {
'id': 1, 'id': 1,

View File

@ -1,8 +1,9 @@
from typing import Dict, Optional from typing import Dict, Optional
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
class TestSportModel: class TestSportModel:

View File

@ -1,8 +1,9 @@
import json import json
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
class TestGetStatsByTime: class TestGetStatsByTime:

View File

@ -1,9 +1,10 @@
import json import json
from uuid import uuid4 from uuid import uuid4
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from flask import Flask
from .utils import get_random_short_id from .utils import get_random_short_id

View File

@ -4,10 +4,11 @@ from datetime import datetime
from io import BytesIO from io import BytesIO
from typing import Dict from typing import Dict
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id from fittrackee.workouts.utils_id import decode_short_id
from flask import Flask
def assert_workout_data_with_gpx(data: Dict) -> None: def assert_workout_data_with_gpx(data: Dict) -> None:

View File

@ -1,10 +1,11 @@
import json import json
from typing import Dict from typing import Dict
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id from fittrackee.workouts.utils_id import decode_short_id
from flask import Flask
from .utils import get_random_short_id, post_an_workout from .utils import get_random_short_id, post_an_workout

View File

@ -1,10 +1,11 @@
import json import json
import os import os
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils import get_absolute_file_path from fittrackee.workouts.utils import get_absolute_file_path
from flask import Flask
from .utils import get_random_short_id, post_an_workout from .utils import get_random_short_id, post_an_workout

View File

@ -1,9 +1,10 @@
from uuid import UUID from uuid import UUID
from flask import Flask
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils_id import decode_short_id from fittrackee.workouts.utils_id import decode_short_id
from flask import Flask
class TestWorkoutModel: class TestWorkoutModel:

View File

@ -3,9 +3,10 @@ from io import BytesIO
from typing import Tuple from typing import Tuple
from uuid import uuid4 from uuid import uuid4
from fittrackee.workouts.utils_id import encode_uuid
from flask import Flask from flask import Flask
from fittrackee.workouts.utils_id import encode_uuid
def get_random_short_id() -> str: def get_random_short_id() -> str:
return encode_uuid(uuid4()) return encode_uuid(uuid4())

View File

@ -3,6 +3,11 @@ import os
from typing import Dict, Tuple, Union from typing import Dict, Tuple, Union
import jwt import jwt
from flask import Blueprint, current_app, request
from sqlalchemy import exc, or_
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
from fittrackee import appLog, bcrypt, db from fittrackee import appLog, bcrypt, db
from fittrackee.responses import ( from fittrackee.responses import (
ForbiddenErrorResponse, ForbiddenErrorResponse,
@ -13,15 +18,11 @@ from fittrackee.responses import (
handle_error_and_return_response, handle_error_and_return_response,
) )
from fittrackee.tasks import reset_password_email from fittrackee.tasks import reset_password_email
from flask import Blueprint, current_app, request from fittrackee.workouts.utils_files import get_absolute_file_path
from sqlalchemy import exc, or_
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
from ..workouts.utils_files import get_absolute_file_path from .decorators import authenticate
from .models import User from .models import User
from .utils import ( from .utils import (
authenticate,
check_passwords, check_passwords,
display_readable_file_size, display_readable_file_size,
get_readable_duration, get_readable_duration,

View File

@ -0,0 +1,36 @@
from functools import wraps
from typing import Any, Callable, Union
from flask import request
from fittrackee.responses import HttpResponse
from .utils import verify_user
def authenticate(f: Callable) -> Callable:
@wraps(f)
def decorated_function(
*args: Any, **kwargs: Any
) -> Union[Callable, HttpResponse]:
verify_admin = False
response_object, resp = verify_user(request, verify_admin)
if response_object:
return response_object
return f(resp, *args, **kwargs)
return decorated_function
def authenticate_as_admin(f: Callable) -> Callable:
@wraps(f)
def decorated_function(
*args: Any, **kwargs: Any
) -> Union[Callable, HttpResponse]:
verify_admin = True
response_object, resp = verify_user(request, verify_admin)
if response_object:
return response_object
return f(resp, *args, **kwargs)
return decorated_function

View File

@ -2,14 +2,15 @@ from datetime import datetime
from typing import Dict, Optional, Union from typing import Dict, Optional, Union
import jwt import jwt
from fittrackee import bcrypt, db
from flask import current_app from flask import current_app
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql.expression import select from sqlalchemy.sql.expression import select
from ..workouts.models import Workout from fittrackee import bcrypt, db
from fittrackee.workouts.models import Workout
from .utils_token import decode_user_token, get_user_token from .utils_token import decode_user_token, get_user_token
BaseModel: DeclarativeMeta = db.Model BaseModel: DeclarativeMeta = db.Model

View File

@ -2,6 +2,9 @@ import os
import shutil import shutil
from typing import Any, Dict, Tuple, Union from typing import Any, Dict, Tuple, Union
from flask import Blueprint, request, send_file
from sqlalchemy import exc
from fittrackee import db from fittrackee import db
from fittrackee.responses import ( from fittrackee.responses import (
ForbiddenErrorResponse, ForbiddenErrorResponse,
@ -11,12 +14,10 @@ from fittrackee.responses import (
UserNotFoundErrorResponse, UserNotFoundErrorResponse,
handle_error_and_return_response, handle_error_and_return_response,
) )
from flask import Blueprint, request, send_file from fittrackee.workouts.utils_files import get_absolute_file_path
from sqlalchemy import exc
from ..workouts.utils_files import get_absolute_file_path from .decorators import authenticate, authenticate_as_admin
from .models import User, Workout from .models import User, Workout
from .utils import authenticate, authenticate_as_admin
users_blueprint = Blueprint('users', __name__) users_blueprint = Blueprint('users', __name__)

View File

@ -1,9 +1,10 @@
import re import re
from datetime import timedelta from datetime import timedelta
from functools import wraps from typing import Optional, Tuple, Union
from typing import Any, Callable, Optional, Tuple, Union
import humanize import humanize
from flask import Request, current_app
from fittrackee.responses import ( from fittrackee.responses import (
ForbiddenErrorResponse, ForbiddenErrorResponse,
HttpResponse, HttpResponse,
@ -11,7 +12,6 @@ from fittrackee.responses import (
PayloadTooLargeErrorResponse, PayloadTooLargeErrorResponse,
UnauthorizedErrorResponse, UnauthorizedErrorResponse,
) )
from flask import Request, current_app, request
from .models import User from .models import User
@ -130,34 +130,6 @@ def verify_user(
return None, resp return None, resp
def authenticate(f: Callable) -> Callable:
@wraps(f)
def decorated_function(
*args: Any, **kwargs: Any
) -> Union[Callable, HttpResponse]:
verify_admin = False
response_object, resp = verify_user(request, verify_admin)
if response_object:
return response_object
return f(resp, *args, **kwargs)
return decorated_function
def authenticate_as_admin(f: Callable) -> Callable:
@wraps(f)
def decorated_function(
*args: Any, **kwargs: Any
) -> Union[Callable, HttpResponse]:
verify_admin = True
response_object, resp = verify_user(request, verify_admin)
if response_object:
return response_object
return f(resp, *args, **kwargs)
return decorated_function
def can_view_workout( def can_view_workout(
auth_user_id: int, workout_user_id: int auth_user_id: int, workout_user_id: int
) -> Optional[HttpResponse]: ) -> Optional[HttpResponse]:

View File

@ -0,0 +1,9 @@
from fittrackee.exceptions import GenericException
class WorkoutException(GenericException):
...
class WorkoutGPXException(GenericException):
...

View File

@ -3,7 +3,6 @@ import os
from typing import Any, Dict, Optional, Union from typing import Any, Dict, Optional, Union
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from fittrackee import db
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from sqlalchemy.engine.base import Connection from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for from sqlalchemy.event import listens_for
@ -13,6 +12,8 @@ from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session, object_session from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.types import JSON, Enum from sqlalchemy.types import JSON, Enum
from fittrackee import db
from .utils_files import get_absolute_file_path from .utils_files import get_absolute_file_path
from .utils_format import convert_in_duration, convert_value_to_integer from .utils_format import convert_in_duration, convert_value_to_integer
from .utils_id import encode_uuid from .utils_id import encode_uuid

View File

@ -2,7 +2,8 @@ from typing import Dict
from flask import Blueprint from flask import Blueprint
from ..users.utils import authenticate from fittrackee.users.decorators import authenticate
from .models import Record from .models import Record
records_blueprint = Blueprint('records', __name__) records_blueprint = Blueprint('records', __name__)

View File

@ -1,5 +1,8 @@
from typing import Dict, Union from typing import Dict, Union
from flask import Blueprint, request
from sqlalchemy import exc
from fittrackee import db from fittrackee import db
from fittrackee.responses import ( from fittrackee.responses import (
DataNotFoundErrorResponse, DataNotFoundErrorResponse,
@ -7,11 +10,9 @@ from fittrackee.responses import (
InvalidPayloadErrorResponse, InvalidPayloadErrorResponse,
handle_error_and_return_response, handle_error_and_return_response,
) )
from flask import Blueprint, request from fittrackee.users.decorators import authenticate, authenticate_as_admin
from sqlalchemy import exc from fittrackee.users.models import User
from ..users.models import User
from ..users.utils import authenticate, authenticate_as_admin
from .models import Sport from .models import Sport
sports_blueprint = Blueprint('sports', __name__) sports_blueprint = Blueprint('sports', __name__)

View File

@ -1,6 +1,9 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, Union from typing import Dict, Union
from flask import Blueprint, request
from sqlalchemy import func
from fittrackee import db from fittrackee import db
from fittrackee.responses import ( from fittrackee.responses import (
HttpResponse, HttpResponse,
@ -9,11 +12,9 @@ from fittrackee.responses import (
UserNotFoundErrorResponse, UserNotFoundErrorResponse,
handle_error_and_return_response, handle_error_and_return_response,
) )
from flask import Blueprint, request from fittrackee.users.decorators import authenticate, authenticate_as_admin
from sqlalchemy import func from fittrackee.users.models import User
from ..users.models import User
from ..users.utils import authenticate, authenticate_as_admin
from .models import Sport, Workout from .models import Sport, Workout
from .utils import get_datetime_with_tz, get_upload_dir_size from .utils import get_datetime_with_tz, get_upload_dir_size
from .utils_format import convert_timedelta_to_integer from .utils_format import convert_timedelta_to_integer

View File

@ -8,28 +8,21 @@ from uuid import UUID
import gpxpy.gpx import gpxpy.gpx
import pytz import pytz
from fittrackee import appLog, db
from flask import current_app from flask import current_app
from sqlalchemy import exc from sqlalchemy import exc
from staticmap import Line, StaticMap from staticmap import Line, StaticMap
from werkzeug.datastructures import FileStorage from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from ..users.models import User from fittrackee import appLog, db
from fittrackee.users.models import User
from .exceptions import WorkoutException
from .models import Sport, Workout, WorkoutSegment from .models import Sport, Workout, WorkoutSegment
from .utils_files import get_absolute_file_path from .utils_files import get_absolute_file_path
from .utils_gpx import get_gpx_info from .utils_gpx import get_gpx_info
class WorkoutException(Exception):
def __init__(
self, status: str, message: str, e: Optional[Exception] = None
) -> None:
self.status = status
self.message = message
self.e = e
def get_datetime_with_tz( def get_datetime_with_tz(
timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None
) -> Tuple[Optional[datetime], datetime]: ) -> Tuple[Optional[datetime], datetime]:

View File

@ -3,18 +3,10 @@ from typing import Any, Dict, List, Optional, Tuple
import gpxpy.gpx import gpxpy.gpx
from .exceptions import WorkoutGPXException
from .utils_weather import get_weather from .utils_weather import get_weather
class WorkoutGPXException(Exception):
def __init__(
self, status: str, message: str, e: Optional[Exception] = None
) -> None:
self.status = status
self.message = message
self.e = e
def open_gpx_file(gpx_file: str) -> Optional[gpxpy.gpx.GPX]: def open_gpx_file(gpx_file: str) -> Optional[gpxpy.gpx.GPX]:
gpx_file = open(gpx_file, 'r') # type: ignore gpx_file = open(gpx_file, 'r') # type: ignore
gpx = gpxpy.parse(gpx_file) gpx = gpxpy.parse(gpx_file)

View File

@ -3,9 +3,10 @@ from typing import Dict, Optional
import forecastio import forecastio
import pytz import pytz
from fittrackee import appLog
from gpxpy.gpx import GPXRoutePoint from gpxpy.gpx import GPXRoutePoint
from fittrackee import appLog
API_KEY = os.getenv('WEATHER_API_KEY') API_KEY = os.getenv('WEATHER_API_KEY')

View File

@ -5,6 +5,9 @@ from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
import requests import requests
from flask import Blueprint, Response, current_app, request, send_file
from sqlalchemy import exc
from fittrackee import appLog, db from fittrackee import appLog, db
from fittrackee.responses import ( from fittrackee.responses import (
DataInvalidPayloadErrorResponse, DataInvalidPayloadErrorResponse,
@ -15,15 +18,13 @@ from fittrackee.responses import (
NotFoundErrorResponse, NotFoundErrorResponse,
handle_error_and_return_response, handle_error_and_return_response,
) )
from flask import Blueprint, Response, current_app, request, send_file from fittrackee.users.decorators import authenticate
from sqlalchemy import exc from fittrackee.users.utils import (
from ..users.utils import (
User, User,
authenticate,
can_view_workout, can_view_workout,
verify_extension_and_size, verify_extension_and_size,
) )
from .models import Workout from .models import Workout
from .utils import ( from .utils import (
WorkoutException, WorkoutException,

71
poetry.lock generated
View File

@ -8,17 +8,17 @@ python-versions = "*"
[[package]] [[package]]
name = "alembic" name = "alembic"
version = "1.4.3" version = "1.5.1"
description = "A database migration tool for SQLAlchemy." description = "A database migration tool for SQLAlchemy."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
[package.dependencies] [package.dependencies]
Mako = "*" Mako = "*"
python-dateutil = "*" python-dateutil = "*"
python-editor = ">=0.3" python-editor = ">=0.3"
SQLAlchemy = ">=1.1.0" SQLAlchemy = ">=1.3.0"
[[package]] [[package]]
name = "appdirs" name = "appdirs"
@ -277,7 +277,7 @@ dramatiq = ">=1.5,<2.0"
[[package]] [[package]]
name = "flask-migrate" name = "flask-migrate"
version = "2.5.3" version = "2.6.0"
description = "SQLAlchemy database migrations for Flask applications using Alembic" description = "SQLAlchemy database migrations for Flask applications using Alembic"
category = "main" category = "main"
optional = false optional = false
@ -302,7 +302,7 @@ SQLAlchemy = ">=0.8.0"
[[package]] [[package]]
name = "freezegun" name = "freezegun"
version = "1.0.0" version = "1.1.0"
description = "Let your Python tests travel through time" description = "Let your Python tests travel through time"
category = "dev" category = "dev"
optional = false optional = false
@ -368,7 +368,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "3.3.0" version = "3.4.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "dev" category = "dev"
optional = false optional = false
@ -376,8 +376,8 @@ python-versions = ">=3.6"
marker = "python_version < \"3.8\"" marker = "python_version < \"3.8\""
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[package.dependencies] [package.dependencies]
zipp = ">=0.5" zipp = ">=0.5"
@ -431,7 +431,7 @@ MarkupSafe = ">=0.23"
[[package]] [[package]]
name = "mako" name = "mako"
version = "1.1.3" version = "1.1.4"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages." description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
category = "main" category = "main"
optional = false optional = false
@ -580,7 +580,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.7.3" version = "2.7.4"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "dev" category = "dev"
optional = false optional = false
@ -588,7 +588,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "pyjwt" name = "pyjwt"
version = "2.0.0" version = "2.0.1"
description = "JSON Web Token implementation in Python" description = "JSON Web Token implementation in Python"
category = "main" category = "main"
optional = false optional = false
@ -679,7 +679,7 @@ python = ">=3.6"
[[package]] [[package]]
name = "pytest-cov" name = "pytest-cov"
version = "2.10.1" version = "2.11.1"
description = "Pytest plugin for measuring coverage." description = "Pytest plugin for measuring coverage."
category = "dev" category = "dev"
optional = false optional = false
@ -689,7 +689,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
[package.dependencies] [package.dependencies]
coverage = ">=4.4" coverage = ">=5.2.1"
pytest = ">=4.6" pytest = ">=4.6"
[[package]] [[package]]
@ -718,7 +718,7 @@ pytest-metadata = "*"
[[package]] [[package]]
name = "pytest-isort" name = "pytest-isort"
version = "1.2.0" version = "1.3.0"
description = "py.test plugin to check import ordering using isort" description = "py.test plugin to check import ordering using isort"
category = "dev" category = "dev"
optional = false optional = false
@ -1111,7 +1111,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "tqdm" name = "tqdm"
version = "4.55.2" version = "4.56.0"
description = "Fast, Extensible Progress Meter" description = "Fast, Extensible Progress Meter"
category = "main" category = "main"
optional = false optional = false
@ -1178,7 +1178,7 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt
[metadata] [metadata]
lock-version = "1.0" lock-version = "1.0"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "11c3a22ee800d71d1d9804e33e756be81c5b7c56bba85d7de03a0f27acb35148" content-hash = "463a3452e6d951925f8d8747dc2fca0016de60c587961006e2dff2d2f8c59fd0"
[metadata.files] [metadata.files]
alabaster = [ alabaster = [
@ -1186,8 +1186,8 @@ alabaster = [
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
] ]
alembic = [ alembic = [
{file = "alembic-1.4.3-py2.py3-none-any.whl", hash = "sha256:4e02ed2aa796bd179965041afa092c55b51fb077de19d61835673cc80672c01c"}, {file = "alembic-1.5.1-py2.py3-none-any.whl", hash = "sha256:944f8d541d204f3ae22de17ebcfdd0da9539074f1d2fee4eb42735039b3072d8"},
{file = "alembic-1.4.3.tar.gz", hash = "sha256:5334f32314fb2a56d86b4c4dd1ae34b08c03cae4cb888bc699942104d66bc245"}, {file = "alembic-1.5.1.tar.gz", hash = "sha256:52d1d48109f17959982779e3c4b5cdeca701e449897bacb75bab173bd6ba984e"},
] ]
appdirs = [ appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
@ -1365,16 +1365,16 @@ flask-dramatiq = [
{file = "flask_dramatiq-0.6.0-py3-none-any.whl", hash = "sha256:7d4a9289721577f726183f7c44c6713a16bbdff54b946f27abc2ffcc65768adf"}, {file = "flask_dramatiq-0.6.0-py3-none-any.whl", hash = "sha256:7d4a9289721577f726183f7c44c6713a16bbdff54b946f27abc2ffcc65768adf"},
] ]
flask-migrate = [ flask-migrate = [
{file = "Flask-Migrate-2.5.3.tar.gz", hash = "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"}, {file = "Flask-Migrate-2.6.0.tar.gz", hash = "sha256:8626af845e6071ef80c70b0dc16d373f761c981f0ad61bb143a529cab649e725"},
{file = "Flask_Migrate-2.5.3-py2.py3-none-any.whl", hash = "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732"}, {file = "Flask_Migrate-2.6.0-py2.py3-none-any.whl", hash = "sha256:c1601dfd46b9204233935e5d73473cd7fa959db7a4b0e894c7aa7a9e8aeebf0e"},
] ]
flask-sqlalchemy = [ flask-sqlalchemy = [
{file = "Flask-SQLAlchemy-2.4.4.tar.gz", hash = "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"}, {file = "Flask-SQLAlchemy-2.4.4.tar.gz", hash = "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"},
{file = "Flask_SQLAlchemy-2.4.4-py2.py3-none-any.whl", hash = "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e"}, {file = "Flask_SQLAlchemy-2.4.4-py2.py3-none-any.whl", hash = "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e"},
] ]
freezegun = [ freezegun = [
{file = "freezegun-1.0.0-py2.py3-none-any.whl", hash = "sha256:02b35de52f4699a78f6ac4518e4cd3390dddc43b0aeb978335a8f270a2d9668b"}, {file = "freezegun-1.1.0-py2.py3-none-any.whl", hash = "sha256:2ae695f7eb96c62529f03a038461afe3c692db3465e215355e1bb4b0ab408712"},
{file = "freezegun-1.0.0.tar.gz", hash = "sha256:1cf08e441f913ff5e59b19cc065a8faa9dd1ddc442eaf0375294f344581a0643"}, {file = "freezegun-1.1.0.tar.gz", hash = "sha256:177f9dd59861d871e27a484c3332f35a6e3f5d14626f2bf91be37891f18927f3"},
] ]
gpxpy = [ gpxpy = [
{file = "gpxpy-1.3.4.tar.gz", hash = "sha256:4a0f072ae5bdf9270c7450e452f93a6c5c91d888114e8d78868a8f163b0dbb15"}, {file = "gpxpy-1.3.4.tar.gz", hash = "sha256:4a0f072ae5bdf9270c7450e452f93a6c5c91d888114e8d78868a8f163b0dbb15"},
@ -1396,8 +1396,8 @@ imagesize = [
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
{file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
] ]
iniconfig = [ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@ -1416,8 +1416,7 @@ jinja2 = [
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
] ]
mako = [ mako = [
{file = "Mako-1.1.3-py2.py3-none-any.whl", hash = "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"}, {file = "Mako-1.1.4.tar.gz", hash = "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab"},
{file = "Mako-1.1.3.tar.gz", hash = "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27"},
] ]
markupsafe = [ markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
@ -1575,12 +1574,12 @@ pyflakes = [
{file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
] ]
pygments = [ pygments = [
{file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"},
{file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"},
] ]
pyjwt = [ pyjwt = [
{file = "PyJWT-2.0.0-py3-none-any.whl", hash = "sha256:5c2ff2eb27d7e342dfc3cafcc16412781f06db2690fbef81922b0172598f085b"}, {file = "PyJWT-2.0.1-py3-none-any.whl", hash = "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847"},
{file = "PyJWT-2.0.0.tar.gz", hash = "sha256:7a2b271c6dac2fda9e0c33d176c4253faba2c6c6b3a99c7f28a32c3c97522779"}, {file = "PyJWT-2.0.1.tar.gz", hash = "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7"},
] ]
pyopenssl = [ pyopenssl = [
{file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"},
@ -1602,8 +1601,8 @@ pytest-black = [
{file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"}, {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"},
] ]
pytest-cov = [ pytest-cov = [
{file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"},
{file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"},
] ]
pytest-flake8 = [ pytest-flake8 = [
{file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"}, {file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"},
@ -1614,8 +1613,8 @@ pytest-html = [
{file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"}, {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"},
] ]
pytest-isort = [ pytest-isort = [
{file = "pytest-isort-1.2.0.tar.gz", hash = "sha256:f0fcf9674f3a627b36e07466d335e82b0f7c4f9e0f7ec39f2a1750b0189d5371"}, {file = "pytest-isort-1.3.0.tar.gz", hash = "sha256:46a12331a701e2f21d48548b2828c8b0a7956dbf1cd5347163f537deb24332dd"},
{file = "pytest_isort-1.2.0-py3-none-any.whl", hash = "sha256:2c6a1d210e8c478e418ab25df2408c235c97b1b8958fb0b139d790d0ec246f58"}, {file = "pytest_isort-1.3.0-py3-none-any.whl", hash = "sha256:074255ad393088a2daee6ca7f2305b7b86358ff632f62302896d8d4b2b339107"},
] ]
pytest-metadata = [ pytest-metadata = [
{file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"},
@ -1813,8 +1812,8 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
tqdm = [ tqdm = [
{file = "tqdm-4.55.2-py2.py3-none-any.whl", hash = "sha256:54985cecd51071f3faa2a07a862c24be1426f925353d82b3dde03f7609d63248"}, {file = "tqdm-4.56.0-py2.py3-none-any.whl", hash = "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a"},
{file = "tqdm-4.55.2.tar.gz", hash = "sha256:86ca00c4942c3b3dc7ed31bae44cd2db38ef85ca05a7920f6a6c52ad7fcac904"}, {file = "tqdm-4.56.0.tar.gz", hash = "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"},
] ]
typed-ast = [ typed-ast = [
{file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"},

View File

@ -27,7 +27,7 @@ dramatiq = {extras = ["redis"], version = "^1.10.0"}
flask = "^1.1" flask = "^1.1"
flask-bcrypt = "^0.7.1" flask-bcrypt = "^0.7.1"
flask-dramatiq = "^0.6.0" flask-dramatiq = "^0.6.0"
flask-migrate = "^2.5" flask-migrate = "^2.6"
gpxpy = "=1.3.4" gpxpy = "=1.3.4"
gunicorn = "^20.0" gunicorn = "^20.0"
humanize = "^3.2.0" humanize = "^3.2.0"
@ -37,18 +37,18 @@ python-forecastio = "^1.4"
pytz = "^2020.5" pytz = "^2020.5"
shortuuid = "^1.0.1" shortuuid = "^1.0.1"
staticmap = "^0.5.4" staticmap = "^0.5.4"
tqdm = "^4.55" tqdm = "^4.56"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = "^20.8b1" black = "^20.8b1"
freezegun = "^1.0.0" freezegun = "^1.1"
mypy = "^0.790" mypy = "^0.790"
pyopenssl = "^20.0" pyopenssl = "^20.0"
pytest = "^6.2" pytest = "^6.2"
pytest-black = "^0.3.12" pytest-black = "^0.3.12"
pytest-cov = "^2.10" pytest-cov = "^2.11"
pytest-flake8 = "^1.0" pytest-flake8 = "^1.0"
pytest-isort = "^1.1" pytest-isort = "^1.3"
pytest-runner = "^5.2" pytest-runner = "^5.2"
pytest-selenium = "^2.0.1" pytest-selenium = "^2.0.1"
recommonmark = "^0.7" recommonmark = "^0.7"
@ -70,7 +70,6 @@ include = ".py$"
exclude = "migrations" exclude = "migrations"
[tool.isort] [tool.isort]
known_third_party = "fittrackee"
multi_line_output = 3 multi_line_output = 3
include_trailing_comma = true include_trailing_comma = true
force_grid_wrap = 0 force_grid_wrap = 0