Skip to content

Commit

Permalink
Upgrade SQLAlchemy to v2.0, and also upgrade supporting dependencies. #…
Browse files Browse the repository at this point in the history
…1224 #1225

Co-authored-by: Michael Wellman <[email protected]>
  • Loading branch information
kalbfled and Michael Wellman committed Mar 19, 2024
1 parent 037c0a7 commit 5c188ad
Show file tree
Hide file tree
Showing 25 changed files with 655 additions and 669 deletions.
6 changes: 5 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ fileignoreconfig:
- filename: tests/app/db.py
checksum: df9a020fece4a10601c2e3200e663eed4bf327e2f0b16eac2d028aff56bca2ef
- filename: .github/workflows/dev-tests.yml
checksum: 0700d2eba3138fc34ef5bf2787ce5403cbd06797200922d35ce1f3f6ff8aaf74
checksum: 0700d2eba3138fc34ef5bf2787ce5403cbd06797200922d35ce1f3f6ff8aaf74
- filename: app/model/user.py
checksum: 3933f8bd02c0719c941b140659cf033ed74ac6e187e95ad9eadee6cddecade9c
- filename: app/models.py
checksum: 428afbe66c93668cc253813d6265b7211199fd3739e33e28767e0c006d411fcf
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ install-safety:

check-dependencies: install-safety ## Scan dependencies for security vulnerabilities
# Ignored issues not described here are documented in requirements-app.txt.
# 12 Dec 2023: 51668 is fixed with >= 2.0.0b1 of SQLAlchemy. Ongoing refactor to upgrade.

safety check -r requirements.txt --full-report -i 51668
safety check -r requirements.txt --full-report

.PHONY:
help \
Expand Down
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def create_app(application):
init_app(application)
request_helper.init_app(application)

# https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/api/#flask_sqlalchemy.SQLAlchemy.init_app
# https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/quickstart/#configure-the-extension
db.init_app(application)

migrate.init_app(application, db=db)
Expand Down
13 changes: 10 additions & 3 deletions app/db.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/api/
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
pass


class SQLAlchemy(_SQLAlchemy):
"""Subclass SQLAlchemy in order to override create_engine options."""
"""
Subclass SQLAlchemy in order to override create_engine options.
https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/quickstart/#initialize-the-extension
"""

def apply_driver_hacks(
self,
Expand All @@ -19,4 +26,4 @@ def apply_driver_hacks(
)


db = SQLAlchemy()
db = SQLAlchemy(model_class=Base)
48 changes: 29 additions & 19 deletions app/history_meta.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
"""Versioned mixin class and other utilities.
"""
Versioned mixin class and other utilities.
This is an adapted version of:
https://bitbucket.org/zzzeek/sqlalchemy/raw/master/examples/versioned_history/history_meta.py
It does not use the create_version function from the orginal which looks for changes to models
It does not use the create_version function from the orginal, which looks for changes to models,
as we just insert a copy of a model to the history table on create or update.
Also it does not add a created_at timestamp to the history table as we already have created_at
It does not add a created_at timestamp to the history table because we already have created_at
and updated_at timestamps.
Lastly when to create a version is done manually in dao_utils version decorator and not via
Lastly, when to create a version is done manually in dao_utils version decorator and not via
session events.
"""

import datetime
from app import db
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import mapper, attributes, object_mapper
from sqlalchemy.orm import attributes, object_mapper, registry
from sqlalchemy.orm.properties import RelationshipProperty, ColumnProperty
from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer
from sqlalchemy import util
Expand All @@ -38,6 +39,10 @@ def _is_versioning_col(col):


def _history_mapper(local_mapper): # noqa (C901 too complex)
"""
Set the "__history_mapper__" attribute on the class associated with "local_mapper".
"""

cls = local_mapper.class_

# set the "active_history" flag
Expand Down Expand Up @@ -123,7 +128,8 @@ def _col_copy(col):
bases = local_mapper.base_mapper.class_.__bases__
versioned_cls = type.__new__(type, '%sHistory' % cls.__name__, bases, {})

m = mapper(
# m = mapper_registry.map_imperatively(
m = cls.registry.map_imperatively(
versioned_cls,
table,
inherits=super_history_mapper,
Expand All @@ -138,19 +144,23 @@ def _col_copy(col):
local_mapper.add_property('version', local_mapper.local_table.c.version)


class Versioned(object):
@declared_attr
def __mapper_cls__(cls):
def map(
cls,
*arg,
**kw,
):
mp = mapper(cls, *arg, **kw)
_history_mapper(mp)
return mp
class Versioned:
# @declared_attr
# def __mapper_cls__(cls):
# return
# def the_map(
# cls,
# *arg,
# **kw,
# ):
# mp = mapper_registry.map_imperatively(cls, *arg, **kw)
# mp = cls.registry.map_imperatively(cls, *arg, **kw)

# Set the __history_mapper__ attribute of the mapper.
# _history_mapper(mp)
# return mp

return map
# return the_map

@classmethod
def get_history_model(cls):
Expand Down
37 changes: 19 additions & 18 deletions app/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import uuid

from sqlalchemy import CheckConstraint, select
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import mapped_column
from app import DATETIME_FORMAT
from app.db import db
from app.encryption import hashpw, check_hash
Expand All @@ -28,27 +29,27 @@ def __init__(
if idp_name and idp_id:
self.idp_ids.append(IdentityProviderIdentifier(self.id, idp_name, str(idp_id)))

id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = db.Column(db.String, nullable=False, index=True, unique=False)
email_address = db.Column(db.String(255), nullable=False, index=True, unique=True)
created_at = db.Column(db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow)
updated_at = db.Column(db.DateTime, index=False, unique=False, nullable=True, onupdate=datetime.datetime.utcnow)
_password = db.Column(db.String, index=False, unique=False, nullable=True)
mobile_number = db.Column(db.String, index=False, unique=False, nullable=True)
password_changed_at = db.Column(
id = mapped_column(db.UUID, primary_key=True, default=uuid.uuid4)
name = mapped_column(db.String, nullable=False, index=True, unique=False)
email_address = mapped_column(db.String(255), nullable=False, index=True, unique=True)
created_at = mapped_column(db.DateTime, index=False, unique=False, nullable=False, default=datetime.datetime.utcnow)
updated_at = mapped_column(db.DateTime, index=False, unique=False, nullable=True, onupdate=datetime.datetime.utcnow)
_password = mapped_column(db.String, index=False, unique=False, nullable=True)
mobile_number = mapped_column(db.String, index=False, unique=False, nullable=True)
password_changed_at = mapped_column(
db.DateTime, index=False, unique=False, nullable=True, default=datetime.datetime.utcnow
)
logged_in_at = db.Column(db.DateTime, nullable=True)
failed_login_count = db.Column(db.Integer, nullable=False, default=0)
state = db.Column(db.String, nullable=False, default='pending')
platform_admin = db.Column(db.Boolean, nullable=False, default=False)
current_session_id = db.Column(UUID(as_uuid=True), nullable=True)
auth_type = db.Column(
logged_in_at = mapped_column(db.DateTime, nullable=True)
failed_login_count = mapped_column(db.Integer, nullable=False, default=0)
state = mapped_column(db.String, nullable=False, default='pending')
platform_admin = mapped_column(db.Boolean, nullable=False, default=False)
current_session_id = mapped_column(db.UUID, nullable=True)
auth_type = mapped_column(
db.String, db.ForeignKey('auth_type.name'), index=True, nullable=True, default=EMAIL_AUTH_TYPE
)
blocked = db.Column(db.Boolean, nullable=False, default=False)
additional_information = db.Column(JSONB(none_as_null=True), nullable=True, default={})
_identity_provider_user_id = db.Column(
blocked = mapped_column(db.Boolean, nullable=False, default=False)
additional_information = mapped_column(JSONB(none_as_null=True), nullable=True, default={})
_identity_provider_user_id = mapped_column(
'identity_provider_user_id', db.String, index=True, unique=True, nullable=True
)
idp_ids = db.relationship('IdentityProviderIdentifier', cascade='all, delete-orphan')
Expand Down
Loading

0 comments on commit 5c188ad

Please sign in to comment.