API - update to latest version of Flask-SQLAlchemy

(and to SQLAlchemy 2.x)
This commit is contained in:
Sam 2023-10-02 18:57:53 +02:00
parent fecd93a7b4
commit 7e65eb8334
12 changed files with 265 additions and 221 deletions

View File

@ -20,6 +20,7 @@ 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
@ -38,7 +39,12 @@ logging.basicConfig(
)
appLog = logging.getLogger('fittrackee')
db = SQLAlchemy()
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
bcrypt = Bcrypt()
migrate = Migrate()
email_service = EmailService()

View File

@ -5,29 +5,30 @@ from flask import current_app
from sqlalchemy import exc
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm import mapped_column
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(BaseModel):
class AppConfig(db.Model): # type: ignore
__tablename__ = 'app_config'
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(
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(
db.Integer, default=1048576, nullable=False
)
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)
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)
@property
def is_registration_enabled(self) -> bool:
@ -36,8 +37,9 @@ class AppConfig(BaseModel):
except exc.ProgrammingError as e:
# workaround for user model related migrations
if 'psycopg2.errors.UndefinedColumn' in str(e):
result = db.engine.execute("SELECT COUNT(*) FROM users;")
nb_users = result.fetchone()[0]
query = db.session.execute(text("SELECT COUNT(*) FROM users;"))
result = query.fetchone()
nb_users = result[0] if result else 0
else:
raise e
return self.max_users == 0 or nb_users < self.max_users

View File

@ -3,6 +3,7 @@ import shutil
import click
from flask_migrate import upgrade
from sqlalchemy.sql import text
from fittrackee import db
from fittrackee.cli.app import app
@ -36,7 +37,7 @@ def drop_db() -> None:
err=True,
)
return
db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
db.session.execute(text("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)s;
WHERE oauth2_token.issued_at + oauth2_token.expires_in < :limit;
"""
return clean(sql, days)

View File

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

View File

@ -23,6 +23,29 @@ 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(BaseModel):
class User(db.Model): # type: ignore
__tablename__ = 'users'
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)
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)
# does the week start Monday?
weekm = db.Column(db.Boolean, default=False, nullable=False)
workouts = db.relationship(
weekm = mapped_column(db.Boolean, default=False, nullable=False)
workouts = relationship(
'Workout',
lazy=True,
backref=db.backref('user', lazy='joined', single_parent=True),
)
records = db.relationship(
records = relationship(
'Record',
lazy=True,
backref=db.backref('user', lazy='joined', single_parent=True),
)
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(
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(
db.Boolean, default=True, nullable=False
)
use_raw_gpx_speed = db.Column(db.Boolean, default=False, nullable=False)
use_raw_gpx_speed = mapped_column(
db.Boolean, default=False, nullable=False
)
def __repr__(self) -> str:
return f'<User {self.username!r}>'
@ -138,7 +138,7 @@ class User(BaseModel):
@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,22 +225,24 @@ class User(BaseModel):
return serialized_user
class UserSportPreference(BaseModel):
class UserSportPreference(db.Model): # type: ignore
__tablename__ = 'users_sports_preferences'
user_id = db.Column(
user_id = mapped_column(
db.Integer,
db.ForeignKey('users.id'),
primary_key=True,
)
sport_id = db.Column(
sport_id = mapped_column(
db.Integer,
db.ForeignKey('sports.id'),
primary_key=True,
)
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)
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
)
def __init__(
self,
@ -263,13 +265,13 @@ class UserSportPreference(BaseModel):
}
class BlacklistedToken(BaseModel):
class BlacklistedToken(db.Model): # type: ignore
__tablename__ = 'blacklisted_tokens'
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)
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)
def __init__(
self, token: str, blacklisted_on: Optional[datetime] = None
@ -290,25 +292,25 @@ class BlacklistedToken(BaseModel):
return cls.query.filter_by(token=str(auth_token)).first() is not None
class UserDataExport(BaseModel):
class UserDataExport(db.Model): # type: ignore
__tablename__ = 'users_data_export'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
user_id = mapped_column(
db.Integer,
db.ForeignKey('users.id', ondelete='CASCADE'),
index=True,
unique=True,
)
created_at = db.Column(
created_at = mapped_column(
db.DateTime, nullable=False, default=datetime.utcnow
)
updated_at = db.Column(
updated_at = mapped_column(
db.DateTime, nullable=True, onupdate=datetime.utcnow
)
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)
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)
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)s;
WHERE blacklisted_tokens.expired_at < :limit;
"""
return clean(sql, days)

View File

@ -3,6 +3,7 @@ from datetime import timedelta
from typing import Optional
import humanize
from sqlalchemy.sql import text
from fittrackee import db
@ -26,5 +27,6 @@ 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.engine.execute(sql, {'limit': limit})
return result.rowcount
result = db.session.execute(text(sql), {'limit': limit})
# DELETE statement returns CursorResult with the rowcount attribute
return result.rowcount # type: ignore

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,7 +18,6 @@ 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'
@ -67,18 +66,20 @@ def update_records(
)
class Sport(BaseModel):
class Sport(db.Model): # type: ignore
__tablename__ = 'sports'
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(
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(
'Workout',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
)
records = db.relationship(
records = relationship(
'Record',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
@ -120,51 +121,49 @@ class Sport(BaseModel):
return serialized_sport
class Workout(BaseModel):
class Workout(db.Model): # type: ignore
__tablename__ = 'workouts'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column(
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
uuid = mapped_column(
postgresql.UUID(as_uuid=True),
default=uuid4,
unique=True,
nullable=False,
)
user_id = db.Column(
user_id = mapped_column(
db.Integer, db.ForeignKey('users.id'), index=True, nullable=False
)
sport_id = db.Column(
sport_id = mapped_column(
db.Integer, db.ForeignKey('sports.id'), index=True, nullable=False
)
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(
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(
'WorkoutSegment',
lazy=True,
cascade='all, delete',
backref=db.backref('workout', lazy='joined', single_parent=True),
)
records = db.relationship(
records = relationship(
'Record',
lazy=True,
cascade='all, delete',
@ -178,9 +177,9 @@ class Workout(BaseModel):
self,
user_id: int,
sport_id: int,
workout_date: datetime.datetime,
workout_date: datetime,
distance: float,
duration: datetime.timedelta,
duration: timedelta,
) -> None:
self.user_id = user_id
self.sport_id = sport_id
@ -242,11 +241,10 @@ class Workout(BaseModel):
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date <= self.workout_date,
Workout.workout_date
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -284,11 +282,10 @@ class Workout(BaseModel):
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date >= self.workout_date,
Workout.workout_date
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -380,9 +377,10 @@ def on_workout_insert(
def on_workout_update(
mapper: Mapper, connection: Connection, workout: Workout
) -> None:
if object_session(workout).is_modified(
workout_session = object_session(workout)
if workout_session is not None and workout_session.is_modified(
workout, include_collections=True
): # noqa
):
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session: Session, context: Any) -> None:
@ -413,23 +411,23 @@ def on_workout_delete(
appLog.error('gpx file not found when deleting workout')
class WorkoutSegment(BaseModel):
class WorkoutSegment(db.Model): # type: ignore
__tablename__ = 'workout_segments'
workout_id = db.Column(
workout_id = mapped_column(
db.Integer, db.ForeignKey('workouts.id'), primary_key=True
)
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
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
def __str__(self) -> str:
return (
@ -461,25 +459,27 @@ class WorkoutSegment(BaseModel):
}
class Record(BaseModel):
class Record(db.Model): # type: ignore
__tablename__ = "records"
__table_args__ = (
db.UniqueConstraint(
'user_id', 'sport_id', 'record_type', name='user_sports_records'
),
)
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(
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(
db.Integer, db.ForeignKey('sports.id'), nullable=False
)
workout_id = db.Column(
workout_id = mapped_column(
db.Integer, db.ForeignKey('workouts.id'), nullable=False
)
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)
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)
def __str__(self) -> str:
return (
@ -497,11 +497,11 @@ class Record(BaseModel):
self.workout_date = workout.workout_date
@hybrid_property
def value(self) -> Optional[Union[datetime.timedelta, float]]:
def value(self) -> Optional[Union[timedelta, float]]:
if self._value is None:
return None
if self.record_type == 'LD':
return datetime.timedelta(seconds=self._value)
return 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.0.5"
version = "3.1.1"
description = "Add SQLAlchemy support to your Flask application."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"},
{file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"},
{file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"},
{file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"},
]
[package.dependencies]
flask = ">=2.2.5"
sqlalchemy = ">=1.4.18"
sqlalchemy = ">=2.0.16"
[[package]]
name = "freezegun"
@ -2291,73 +2291,80 @@ test = ["pytest"]
[[package]]
name = "sqlalchemy"
version = "1.4.49"
version = "2.0.21"
description = "Database Abstraction Library"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
python-versions = ">=3.7"
files = [
{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"},
{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"},
]
[package.dependencies]
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\")"}
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"
[package.extras]
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiomysql = ["aiomysql (>=0.2.0)", "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)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
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)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql", "pymysql (<1)"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
@ -2711,4 +2718,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.8.1"
content-hash = "269f524bf7a13303da047ed0c2757b60ff5fc344f842648d3d9fa7a729e0812a"
content-hash = "f85f8c64c0d6b2f490a0adde219d5729c80a8a200ee84211898ea5c396164279"

View File

@ -41,7 +41,7 @@ pyopenssl = "^23.2"
pytz = "^2023.3"
shortuuid = "^1.0.11"
staticmap = "^0.5.7"
sqlalchemy = "=1.4.49"
sqlalchemy = "=2.0.21"
ua-parser = "^0.18.0"
[tool.poetry.group.dev.dependencies]