Merge pull request #440 from SamR1/flask-sqlalchemy-update-rollback

API - FlaskSQLAlchemy update rollback
This commit is contained in:
Sam 2023-10-04 15:59:08 +02:00 committed by GitHub
commit abdf6c6cee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 221 additions and 266 deletions

View File

@ -20,7 +20,6 @@ from flask_limiter.util import get_remote_address
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import ProgrammingError
from sqlalchemy.orm import DeclarativeBase
from werkzeug.middleware.proxy_fix import ProxyFix
from fittrackee.emails.email import EmailService
@ -39,12 +38,7 @@ logging.basicConfig(
)
appLog = logging.getLogger('fittrackee')
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
db = SQLAlchemy()
bcrypt = Bcrypt()
migrate = Migrate()
email_service = EmailService()

View File

@ -5,30 +5,29 @@ from flask import current_app
from sqlalchemy import exc
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.orm import mapped_column
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session
from sqlalchemy.sql import text
from fittrackee import VERSION, db
from fittrackee.users.models import User
BaseModel: DeclarativeMeta = db.Model
class AppConfig(db.Model): # type: ignore
class AppConfig(BaseModel):
__tablename__ = 'app_config'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
max_users = mapped_column(db.Integer, default=0, nullable=False)
gpx_limit_import = mapped_column(db.Integer, default=10, nullable=False)
max_single_file_size = mapped_column(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
max_users = db.Column(db.Integer, default=0, nullable=False)
gpx_limit_import = db.Column(db.Integer, default=10, nullable=False)
max_single_file_size = db.Column(
db.Integer, default=1048576, nullable=False
)
max_zip_file_size = mapped_column(
db.Integer, default=10485760, nullable=False
)
admin_contact = mapped_column(db.String(255), nullable=True)
privacy_policy_date = mapped_column(db.DateTime, nullable=True)
privacy_policy = mapped_column(db.Text, nullable=True)
about = mapped_column(db.Text, nullable=True)
max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
admin_contact = db.Column(db.String(255), nullable=True)
privacy_policy_date = db.Column(db.DateTime, nullable=True)
privacy_policy = db.Column(db.Text, nullable=True)
about = db.Column(db.Text, nullable=True)
@property
def is_registration_enabled(self) -> bool:
@ -37,9 +36,8 @@ class AppConfig(db.Model): # type: ignore
except exc.ProgrammingError as e:
# workaround for user model related migrations
if 'psycopg2.errors.UndefinedColumn' in str(e):
query = db.session.execute(text("SELECT COUNT(*) FROM users;"))
result = query.fetchone()
nb_users = result[0] if result else 0
result = db.engine.execute("SELECT COUNT(*) FROM users;")
nb_users = result.fetchone()[0]
else:
raise e
return self.max_users == 0 or nb_users < self.max_users

View File

@ -3,7 +3,6 @@ import shutil
import click
from flask_migrate import upgrade
from sqlalchemy.sql import text
from fittrackee import db
from fittrackee.cli.app import app
@ -37,7 +36,7 @@ def drop_db() -> None:
err=True,
)
return
db.session.execute(text("DROP TABLE IF EXISTS alembic_version;"))
db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
db.drop_all()
db.session.commit()
click.echo('Database dropped.')

View File

@ -4,6 +4,6 @@ from fittrackee.utils import clean
def clean_tokens(days: int) -> int:
sql = """
DELETE FROM oauth2_token
WHERE oauth2_token.issued_at + oauth2_token.expires_in < :limit;
WHERE oauth2_token.issued_at + oauth2_token.expires_in < %(limit)s;
"""
return clean(sql, days)

View File

@ -8,22 +8,23 @@ from authlib.integrations.sqla_oauth2 import (
)
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.orm import mapped_column, relationship
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session
from sqlalchemy.sql import text
from fittrackee import db
BaseModel: DeclarativeMeta = db.Model
class OAuth2Client(OAuth2ClientMixin, db.Model): # type: ignore
class OAuth2Client(BaseModel, OAuth2ClientMixin):
__tablename__ = 'oauth2_client'
id = mapped_column(db.Integer, primary_key=True)
user_id = mapped_column(
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
)
user = relationship('User')
user = db.relationship('User')
def serialize(self, with_secret: bool = False) -> Dict:
client = {
@ -62,9 +63,7 @@ def on_old_oauth2_delete(
).delete(synchronize_session=False)
class OAuth2AuthorizationCode(
OAuth2AuthorizationCodeMixin, db.Model # type: ignore
):
class OAuth2AuthorizationCode(BaseModel, OAuth2AuthorizationCodeMixin):
__tablename__ = 'oauth2_code'
__table_args__ = (
db.Index(
@ -73,21 +72,21 @@ class OAuth2AuthorizationCode(
),
)
id = mapped_column(db.Integer, primary_key=True)
user_id = mapped_column(
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
)
user = relationship('User')
user = db.relationship('User')
class OAuth2Token(OAuth2TokenMixin, db.Model): # type: ignore
class OAuth2Token(BaseModel, OAuth2TokenMixin):
__tablename__ = 'oauth2_token'
id = mapped_column(db.Integer, primary_key=True)
user_id = mapped_column(
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
)
user = relationship('User')
user = db.relationship('User')
def is_refresh_token_active(self) -> bool:
if self.is_revoked():
@ -99,10 +98,10 @@ class OAuth2Token(OAuth2TokenMixin, db.Model): # type: ignore
def revoke_client_tokens(cls, client_id: str) -> None:
sql = """
UPDATE oauth2_token
SET access_token_revoked_at = :revoked_at
WHERE client_id = :client_id;
SET access_token_revoked_at = %(revoked_at)s
WHERE client_id = %(client_id)s;
"""
db.session.execute(
text(sql), {'client_id': client_id, 'revoked_at': int(time.time())}
db.engine.execute(
sql, {'client_id': client_id, 'revoked_at': int(time.time())}
)
db.session.commit()

View File

@ -23,29 +23,6 @@ class TestUserModel:
) -> None:
assert '<User \'test\'>' == str(user_1)
def test_it_returns_workout_count_when_no_workouts(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
workout_cycling_user_2: Workout,
) -> None:
assert user_1.workouts_count == 0
def test_it_returns_workout_count_when_user_has_workout(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
workout_cycling_user_2: Workout,
) -> None:
assert user_1.workouts_count == 2
class UserModelAssertMixin:
@staticmethod

View File

@ -7,8 +7,8 @@ from flask import current_app
from sqlalchemy import func
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import mapped_column, relationship
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.expression import select
@ -21,48 +21,48 @@ from .exceptions import UserNotFoundException
from .roles import UserRole
from .utils.token import decode_user_token, get_user_token
BaseModel: DeclarativeMeta = db.Model
class User(db.Model): # type: ignore
class User(BaseModel):
__tablename__ = 'users'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
username = mapped_column(db.String(255), unique=True, nullable=False)
email = mapped_column(db.String(255), unique=True, nullable=False)
password = mapped_column(db.String(255), nullable=False)
created_at = mapped_column(db.DateTime, nullable=False)
admin = mapped_column(db.Boolean, default=False, nullable=False)
first_name = mapped_column(db.String(80), nullable=True)
last_name = mapped_column(db.String(80), nullable=True)
birth_date = mapped_column(db.DateTime, nullable=True)
location = mapped_column(db.String(80), nullable=True)
bio = mapped_column(db.String(200), nullable=True)
picture = mapped_column(db.String(255), nullable=True)
timezone = mapped_column(db.String(50), nullable=True)
date_format = mapped_column(db.String(50), nullable=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(255), unique=True, nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
admin = db.Column(db.Boolean, default=False, nullable=False)
first_name = db.Column(db.String(80), nullable=True)
last_name = db.Column(db.String(80), nullable=True)
birth_date = db.Column(db.DateTime, nullable=True)
location = db.Column(db.String(80), nullable=True)
bio = db.Column(db.String(200), nullable=True)
picture = db.Column(db.String(255), nullable=True)
timezone = db.Column(db.String(50), nullable=True)
date_format = db.Column(db.String(50), nullable=True)
# does the week start Monday?
weekm = mapped_column(db.Boolean, default=False, nullable=False)
workouts = relationship(
weekm = db.Column(db.Boolean, default=False, nullable=False)
workouts = db.relationship(
'Workout',
lazy=True,
backref=db.backref('user', lazy='joined', single_parent=True),
)
records = relationship(
records = db.relationship(
'Record',
lazy=True,
backref=db.backref('user', lazy='joined', single_parent=True),
)
language = mapped_column(db.String(50), nullable=True)
imperial_units = mapped_column(db.Boolean, default=False, nullable=False)
is_active = mapped_column(db.Boolean, default=False, nullable=False)
email_to_confirm = mapped_column(db.String(255), nullable=True)
confirmation_token = mapped_column(db.String(255), nullable=True)
display_ascent = mapped_column(db.Boolean, default=True, nullable=False)
accepted_policy_date = mapped_column(db.DateTime, nullable=True)
start_elevation_at_zero = mapped_column(
language = db.Column(db.String(50), nullable=True)
imperial_units = db.Column(db.Boolean, default=False, nullable=False)
is_active = db.Column(db.Boolean, default=False, nullable=False)
email_to_confirm = db.Column(db.String(255), nullable=True)
confirmation_token = db.Column(db.String(255), nullable=True)
display_ascent = db.Column(db.Boolean, default=True, nullable=False)
accepted_policy_date = db.Column(db.DateTime, nullable=True)
start_elevation_at_zero = db.Column(
db.Boolean, default=True, nullable=False
)
use_raw_gpx_speed = mapped_column(
db.Boolean, default=False, nullable=False
)
use_raw_gpx_speed = db.Column(db.Boolean, default=False, nullable=False)
def __repr__(self) -> str:
return f'<User {self.username!r}>'
@ -138,7 +138,7 @@ class User(db.Model): # type: ignore
@workouts_count.expression # type: ignore
def workouts_count(self) -> int:
return (
select(func.count(Workout.id))
select([func.count(Workout.id)])
.where(Workout.user_id == self.id)
.label('workouts_count')
)
@ -225,24 +225,22 @@ class User(db.Model): # type: ignore
return serialized_user
class UserSportPreference(db.Model): # type: ignore
class UserSportPreference(BaseModel):
__tablename__ = 'users_sports_preferences'
user_id = mapped_column(
user_id = db.Column(
db.Integer,
db.ForeignKey('users.id'),
primary_key=True,
)
sport_id = mapped_column(
sport_id = db.Column(
db.Integer,
db.ForeignKey('sports.id'),
primary_key=True,
)
color = mapped_column(db.String(50), nullable=True)
is_active = mapped_column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = mapped_column(
db.Float, default=1.0, nullable=False
)
color = db.Column(db.String(50), nullable=True)
is_active = db.Column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = db.Column(db.Float, default=1.0, nullable=False)
def __init__(
self,
@ -265,13 +263,13 @@ class UserSportPreference(db.Model): # type: ignore
}
class BlacklistedToken(db.Model): # type: ignore
class BlacklistedToken(BaseModel):
__tablename__ = 'blacklisted_tokens'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
token = mapped_column(db.String(500), unique=True, nullable=False)
expired_at = mapped_column(db.Integer, nullable=False)
blacklisted_on = mapped_column(db.DateTime, nullable=False)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
token = db.Column(db.String(500), unique=True, nullable=False)
expired_at = db.Column(db.Integer, nullable=False)
blacklisted_on = db.Column(db.DateTime, nullable=False)
def __init__(
self, token: str, blacklisted_on: Optional[datetime] = None
@ -292,25 +290,25 @@ class BlacklistedToken(db.Model): # type: ignore
return cls.query.filter_by(token=str(auth_token)).first() is not None
class UserDataExport(db.Model): # type: ignore
class UserDataExport(BaseModel):
__tablename__ = 'users_data_export'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
user_id = mapped_column(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(
db.Integer,
db.ForeignKey('users.id', ondelete='CASCADE'),
index=True,
unique=True,
)
created_at = mapped_column(
created_at = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow
)
updated_at = mapped_column(
updated_at = db.Column(
db.DateTime, nullable=True, onupdate=datetime.utcnow
)
completed = mapped_column(db.Boolean, nullable=False, default=False)
file_name = mapped_column(db.String(100), nullable=True)
file_size = mapped_column(db.Integer, nullable=True)
completed = db.Column(db.Boolean, nullable=False, default=False)
file_name = db.Column(db.String(100), nullable=True)
file_size = db.Column(db.Integer, nullable=True)
def __init__(
self,

View File

@ -55,6 +55,6 @@ def clean_blacklisted_tokens(days: int) -> int:
"""
sql = """
DELETE FROM blacklisted_tokens
WHERE blacklisted_tokens.expired_at < :limit;
WHERE blacklisted_tokens.expired_at < %(limit)s;
"""
return clean(sql, days)

View File

@ -3,7 +3,6 @@ from datetime import timedelta
from typing import Optional
import humanize
from sqlalchemy.sql import text
from fittrackee import db
@ -27,6 +26,5 @@ def get_readable_duration(duration: int, locale: Optional[str] = None) -> str:
def clean(sql: str, days: int) -> int:
limit = int(time.time()) - (days * 86400)
result = db.session.execute(text(sql), {'limit': limit})
# DELETE statement returns CursorResult with the rowcount attribute
return result.rowcount # type: ignore
result = db.engine.execute(sql, {'limit': limit})
return result.rowcount

View File

@ -1,13 +1,13 @@
import datetime
import os
from datetime import datetime, timedelta
from typing import Any, Dict, Optional, Union
from uuid import UUID, uuid4
from sqlalchemy.dialects import postgresql
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import mapped_column, relationship
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.types import JSON, Enum
@ -18,6 +18,7 @@ from fittrackee.files import get_absolute_file_path
from .utils.convert import convert_in_duration, convert_value_to_integer
from .utils.short_id import encode_uuid
BaseModel: DeclarativeMeta = db.Model
record_types = [
'AS', # 'Best Average Speed'
'FD', # 'Farthest Distance'
@ -66,20 +67,18 @@ def update_records(
)
class Sport(db.Model): # type: ignore
class Sport(BaseModel):
__tablename__ = 'sports'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
label = mapped_column(db.String(50), unique=True, nullable=False)
is_active = mapped_column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = mapped_column(
db.Float, default=1.0, nullable=False
)
workouts = relationship(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
label = db.Column(db.String(50), unique=True, nullable=False)
is_active = db.Column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = db.Column(db.Float, default=1.0, nullable=False)
workouts = db.relationship(
'Workout',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
)
records = relationship(
records = db.relationship(
'Record',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
@ -121,49 +120,51 @@ class Sport(db.Model): # type: ignore
return serialized_sport
class Workout(db.Model): # type: ignore
class Workout(BaseModel):
__tablename__ = 'workouts'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
uuid = mapped_column(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column(
postgresql.UUID(as_uuid=True),
default=uuid4,
unique=True,
nullable=False,
)
user_id = mapped_column(
user_id = db.Column(
db.Integer, db.ForeignKey('users.id'), index=True, nullable=False
)
sport_id = mapped_column(
sport_id = db.Column(
db.Integer, db.ForeignKey('sports.id'), index=True, nullable=False
)
title = mapped_column(db.String(255), nullable=True)
gpx = mapped_column(db.String(255), nullable=True)
creation_date = mapped_column(db.DateTime, default=datetime.utcnow)
modification_date = mapped_column(db.DateTime, onupdate=datetime.utcnow)
workout_date = mapped_column(db.DateTime, index=True, nullable=False)
duration = mapped_column(db.Interval, nullable=False)
pauses = mapped_column(db.Interval, nullable=True)
moving = mapped_column(db.Interval, nullable=True)
distance = mapped_column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
max_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
descent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
ascent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
max_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
bounds = mapped_column(postgresql.ARRAY(db.Float), nullable=True)
map = mapped_column(db.String(255), nullable=True)
map_id = mapped_column(db.String(50), index=True, nullable=True)
weather_start = mapped_column(JSON, nullable=True)
weather_end = mapped_column(JSON, nullable=True)
notes = mapped_column(db.String(500), nullable=True)
segments = relationship(
title = db.Column(db.String(255), nullable=True)
gpx = db.Column(db.String(255), nullable=True)
creation_date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
modification_date = db.Column(
db.DateTime, onupdate=datetime.datetime.utcnow
)
workout_date = db.Column(db.DateTime, index=True, nullable=False)
duration = db.Column(db.Interval, nullable=False)
pauses = db.Column(db.Interval, nullable=True)
moving = db.Column(db.Interval, nullable=True)
distance = db.Column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
descent = db.Column(db.Numeric(8, 3), nullable=True) # meters
ascent = db.Column(db.Numeric(8, 3), nullable=True) # meters
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
bounds = db.Column(postgresql.ARRAY(db.Float), nullable=True)
map = db.Column(db.String(255), nullable=True)
map_id = db.Column(db.String(50), index=True, nullable=True)
weather_start = db.Column(JSON, nullable=True)
weather_end = db.Column(JSON, nullable=True)
notes = db.Column(db.String(500), nullable=True)
segments = db.relationship(
'WorkoutSegment',
lazy=True,
cascade='all, delete',
backref=db.backref('workout', lazy='joined', single_parent=True),
)
records = relationship(
records = db.relationship(
'Record',
lazy=True,
cascade='all, delete',
@ -177,9 +178,9 @@ class Workout(db.Model): # type: ignore
self,
user_id: int,
sport_id: int,
workout_date: datetime,
workout_date: datetime.datetime,
distance: float,
duration: timedelta,
duration: datetime.timedelta,
) -> None:
self.user_id = user_id
self.sport_id = sport_id
@ -241,10 +242,11 @@ class Workout(db.Model): # type: ignore
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date <= self.workout_date,
Workout.workout_date
>= datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -282,10 +284,11 @@ class Workout(db.Model): # type: ignore
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date >= self.workout_date,
Workout.workout_date
>= datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -377,10 +380,9 @@ def on_workout_insert(
def on_workout_update(
mapper: Mapper, connection: Connection, workout: Workout
) -> None:
workout_session = object_session(workout)
if workout_session is not None and workout_session.is_modified(
if object_session(workout).is_modified(
workout, include_collections=True
):
): # noqa
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session: Session, context: Any) -> None:
@ -411,23 +413,23 @@ def on_workout_delete(
appLog.error('gpx file not found when deleting workout')
class WorkoutSegment(db.Model): # type: ignore
class WorkoutSegment(BaseModel):
__tablename__ = 'workout_segments'
workout_id = mapped_column(
workout_id = db.Column(
db.Integer, db.ForeignKey('workouts.id'), primary_key=True
)
workout_uuid = mapped_column(postgresql.UUID(as_uuid=True), nullable=False)
segment_id = mapped_column(db.Integer, primary_key=True)
duration = mapped_column(db.Interval, nullable=False)
pauses = mapped_column(db.Interval, nullable=True)
moving = mapped_column(db.Interval, nullable=True)
distance = mapped_column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
max_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
descent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
ascent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
max_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
segment_id = db.Column(db.Integer, primary_key=True)
duration = db.Column(db.Interval, nullable=False)
pauses = db.Column(db.Interval, nullable=True)
moving = db.Column(db.Interval, nullable=True)
distance = db.Column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
descent = db.Column(db.Numeric(8, 3), nullable=True) # meters
ascent = db.Column(db.Numeric(8, 3), nullable=True) # meters
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
def __str__(self) -> str:
return (
@ -459,27 +461,25 @@ class WorkoutSegment(db.Model): # type: ignore
}
class Record(db.Model): # type: ignore
class Record(BaseModel):
__tablename__ = "records"
__table_args__ = (
db.UniqueConstraint(
'user_id', 'sport_id', 'record_type', name='user_sports_records'
),
)
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
user_id = mapped_column(
db.Integer, db.ForeignKey('users.id'), nullable=False
)
sport_id = mapped_column(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
sport_id = db.Column(
db.Integer, db.ForeignKey('sports.id'), nullable=False
)
workout_id = mapped_column(
workout_id = db.Column(
db.Integer, db.ForeignKey('workouts.id'), nullable=False
)
workout_uuid = mapped_column(postgresql.UUID(as_uuid=True), nullable=False)
record_type = mapped_column(Enum(*record_types, name="record_types"))
workout_date = mapped_column(db.DateTime, nullable=False)
_value = mapped_column("value", db.Integer, nullable=True)
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
record_type = db.Column(Enum(*record_types, name="record_types"))
workout_date = db.Column(db.DateTime, nullable=False)
_value = db.Column("value", db.Integer, nullable=True)
def __str__(self) -> str:
return (
@ -497,11 +497,11 @@ class Record(db.Model): # type: ignore
self.workout_date = workout.workout_date
@hybrid_property
def value(self) -> Optional[Union[timedelta, float]]:
def value(self) -> Optional[Union[datetime.timedelta, float]]:
if self._value is None:
return None
if self.record_type == 'LD':
return timedelta(seconds=self._value)
return datetime.timedelta(seconds=self._value)
elif self.record_type in ['AS', 'MS']:
return float(self._value / 100)
else: # 'FD' or 'HA'

117
poetry.lock generated
View File

@ -738,18 +738,18 @@ Flask-SQLAlchemy = ">=1.0"
[[package]]
name = "flask-sqlalchemy"
version = "3.1.1"
version = "3.0.5"
description = "Add SQLAlchemy support to your Flask application."
optional = false
python-versions = ">=3.8"
python-versions = ">=3.7"
files = [
{file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"},
{file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"},
{file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"},
{file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"},
]
[package.dependencies]
flask = ">=2.2.5"
sqlalchemy = ">=2.0.16"
sqlalchemy = ">=1.4.18"
[[package]]
name = "freezegun"
@ -2293,80 +2293,73 @@ test = ["pytest"]
[[package]]
name = "sqlalchemy"
version = "2.0.21"
version = "1.4.49"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
{file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"},
{file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"},
{file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"},
{file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"},
{file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"},
{file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"},
{file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"},
{file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"},
{file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"},
{file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"},
{file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"},
{file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"},
{file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"},
{file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"},
{file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"},
{file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"},
{file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"},
{file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"},
{file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"},
{file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"},
{file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"},
]
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""}
typing-extensions = ">=4.2.0"
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"}
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
pymysql = ["pymysql", "pymysql (<1)"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
@ -2720,4 +2713,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.8.1"
content-hash = "fe398b8d9c8bb4223284e01983948f0837ef862554b2673705fb8de963f3ed4b"
content-hash = "8455a7b70d2a966152da804dcbaa3780bb516953105ce2b73c7e2442432f3ae6"

View File

@ -33,7 +33,6 @@ flask = "^3.0"
flask-bcrypt = "^1.0"
flask-dramatiq = "^0.6"
flask-limiter = {version = "^3.5", extras = ["redis"]}
flask-sqlalchemy = "^3.1"
flask-migrate = "^4.0"
gpxpy = "=1.5.0"
gunicorn = "^21.0"
@ -44,7 +43,7 @@ pyopenssl = "^23.2"
pytz = "^2023.3"
shortuuid = "^1.0.11"
staticmap = "^0.5.7"
sqlalchemy = "=2.0.21"
sqlalchemy = "=1.4.49"
ua-parser = "^0.18.0"
[tool.poetry.group.dev.dependencies]