Changes for sqlalchemy 2.0

This commit is contained in:
Aditi Anand
2024-04-24 16:27:20 -04:00
parent df2a590f1a
commit ad55eef5e9
14 changed files with 160 additions and 166 deletions

View File

@@ -779,45 +779,37 @@ def fetch_daily_volumes_for_platform(start_date, end_date):
FactBilling.local_date,
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.notifications_sent,
)
],
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.notifications_sent,
),
else_=0,
)
).label("sms_totals"),
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units,
)
],
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units,
),
else_=0,
)
).label("sms_fragment_totals"),
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units * FactBilling.rate_multiplier,
)
],
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units * FactBilling.rate_multiplier,
),
else_=0,
)
).label("sms_fragments_times_multiplier"),
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.EMAIL,
FactBilling.notifications_sent,
)
],
(
FactBilling.notification_type == NotificationType.EMAIL,
FactBilling.notifications_sent,
),
else_=0,
)
).label("email_totals"),
@@ -897,34 +889,28 @@ def fetch_volumes_by_service(start_date, end_date):
FactBilling.service_id,
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.notifications_sent,
)
],
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.notifications_sent,
),
else_=0,
)
).label("sms_totals"),
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units * FactBilling.rate_multiplier,
)
],
(
FactBilling.notification_type == NotificationType.SMS,
FactBilling.billable_units * FactBilling.rate_multiplier,
),
else_=0,
)
).label("sms_fragments_times_multiplier"),
func.sum(
case(
[
(
FactBilling.notification_type == NotificationType.EMAIL,
FactBilling.notifications_sent,
)
],
(
FactBilling.notification_type == NotificationType.EMAIL,
FactBilling.notifications_sent,
),
else_=0,
)
).label("email_totals"),

View File

@@ -459,25 +459,21 @@ def get_total_notifications_for_date_range(start_date, end_date):
FactNotificationStatus.local_date.label("local_date"),
func.sum(
case(
[
(
FactNotificationStatus.notification_type
== NotificationType.EMAIL,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_type
== NotificationType.EMAIL,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("emails"),
func.sum(
case(
[
(
FactNotificationStatus.notification_type
== NotificationType.SMS,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_type
== NotificationType.SMS,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("sms"),
@@ -507,78 +503,66 @@ def fetch_monthly_notification_statuses_per_service(start_date, end_date):
FactNotificationStatus.notification_type,
func.sum(
case(
[
(
FactNotificationStatus.notification_status.in_(
[NotificationStatus.SENDING, NotificationStatus.PENDING]
),
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status.in_(
[NotificationStatus.SENDING, NotificationStatus.PENDING]
),
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_sending"),
func.sum(
case(
[
(
FactNotificationStatus.notification_status
== NotificationStatus.DELIVERED,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status
== NotificationStatus.DELIVERED,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_delivered"),
func.sum(
case(
[
(
FactNotificationStatus.notification_status.in_(
[
NotificationStatus.TECHNICAL_FAILURE,
NotificationStatus.FAILED,
]
),
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status.in_(
[
NotificationStatus.TECHNICAL_FAILURE,
NotificationStatus.FAILED,
]
),
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_technical_failure"),
func.sum(
case(
[
(
FactNotificationStatus.notification_status
== NotificationStatus.TEMPORARY_FAILURE,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status
== NotificationStatus.TEMPORARY_FAILURE,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_temporary_failure"),
func.sum(
case(
[
(
FactNotificationStatus.notification_status
== NotificationStatus.PERMANENT_FAILURE,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status
== NotificationStatus.PERMANENT_FAILURE,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_permanent_failure"),
func.sum(
case(
[
(
FactNotificationStatus.notification_status
== NotificationStatus.SENT,
FactNotificationStatus.notification_count,
)
],
(
FactNotificationStatus.notification_status
== NotificationStatus.SENT,
FactNotificationStatus.notification_count,
),
else_=0,
)
).label("count_sent"),

View File

@@ -40,19 +40,17 @@ def get_processing_time_percentage_for_date_range(start_date, end_date):
FactProcessingTime.messages_total,
FactProcessingTime.messages_within_10_secs,
case(
[
(
FactProcessingTime.messages_total > 0,
(
FactProcessingTime.messages_total > 0,
(
(
FactProcessingTime.messages_within_10_secs
/ FactProcessingTime.messages_total.cast(db.Float)
)
* 100
),
FactProcessingTime.messages_within_10_secs
/ FactProcessingTime.messages_total.cast(db.Float)
)
* 100
),
(FactProcessingTime.messages_total == 0, 100.0),
]
),
(FactProcessingTime.messages_total == 0, 100.0),
).label("percentage"),
)
.filter(

View File

@@ -6,7 +6,7 @@ from sqlalchemy.orm import aliased
from app import db
from app.dao.dao_utils import autocommit
from app.enums import NotificationType
from app.models import InboundSms, InboundSmsHistory, Service, ServiceDataRetention
from app.models import InboundSms, InboundSmsHistory, ServiceDataRetention
from app.utils import midnight_n_days_ago
@@ -122,9 +122,7 @@ def delete_inbound_sms_older_than_retention():
)
flexible_data_retention = (
ServiceDataRetention.query.join(
ServiceDataRetention.service, Service.inbound_number
)
ServiceDataRetention.query.join(ServiceDataRetention.service)
.filter(ServiceDataRetention.notification_type == NotificationType.SMS)
.all()
)

View File

@@ -7,7 +7,7 @@ from notifications_utils.recipients import (
try_validate_and_format_phone_number,
validate_and_format_email_address,
)
from sqlalchemy import asc, desc, or_, select, union
from sqlalchemy import asc, desc, or_, select, text, union
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.sql import functions
@@ -386,15 +386,15 @@ def insert_notification_history_delete_notifications(
"qry_limit": qry_limit,
}
db.session.execute(select_into_temp_table, input_params)
db.session.execute(text(select_into_temp_table), input_params)
result = db.session.execute("select count(*) from NOTIFICATION_ARCHIVE").fetchone()[
0
]
result = db.session.execute(
text("select count(*) from NOTIFICATION_ARCHIVE")
).fetchone()[0]
db.session.execute(insert_query)
db.session.execute(text(insert_query))
db.session.execute(delete_query)
db.session.execute(text(delete_query))
return result
@@ -585,7 +585,7 @@ def dao_get_notifications_processing_time_stats(start_date, end_date):
)
result = db.session.execute(stmt)
return result.scalar_one()
return result.one()
def dao_get_last_notification_added_for_job_id(job_id):

View File

@@ -26,7 +26,9 @@ class PermissionDAO(DAOClass):
):
try:
if replace:
query = self.Meta.model.query.filter_by(user=user, service=service)
query = self.Meta.model.query.filter(
self.Meta.model.user == user, self.Meta.model.service == service
)
query.delete()
for p in permissions:
p.user = user

View File

@@ -4,7 +4,12 @@ from app.models import ServiceUser, User
def dao_get_service_user(user_id, service_id):
return ServiceUser.query.filter_by(user_id=user_id, service_id=service_id).one()
# TODO: This has been changed to account for the test case failure
# that used this method but have any service user to return. Somehow, this
# started to throw an error with one() method in sqlalchemy 2.0 unlike 1.4
return ServiceUser.query.filter_by(
user_id=user_id, service_id=service_id
).one_or_none()
def dao_get_active_service_users(service_id):

View File

@@ -102,23 +102,17 @@ def dao_fetch_live_services_data():
Service.volume_sms.label("sms_volume_intent"),
Service.volume_email.label("email_volume_intent"),
case(
[
(
this_year_ft_billing.c.notification_type
== NotificationType.EMAIL,
func.sum(this_year_ft_billing.c.notifications_sent),
)
],
(
this_year_ft_billing.c.notification_type == NotificationType.EMAIL,
func.sum(this_year_ft_billing.c.notifications_sent),
),
else_=0,
).label("email_totals"),
case(
[
(
this_year_ft_billing.c.notification_type
== NotificationType.SMS,
func.sum(this_year_ft_billing.c.notifications_sent),
)
],
(
this_year_ft_billing.c.notification_type == NotificationType.SMS,
func.sum(this_year_ft_billing.c.notifications_sent),
),
else_=0,
).label("sms_totals"),
AnnualBilling.free_sms_fragment_limit,
@@ -178,15 +172,15 @@ def dao_fetch_live_services_data():
def dao_fetch_service_by_id(service_id, only_active=False):
stmt = (
select(Service)
.options(joinedload(Service.users))
.where(Service.id == service_id)
.options(joinedload(Service.users))
)
if only_active:
stmt = stmt.where(Service.active)
result = db.session.execute(stmt)
return result.unique().scalars().first()
return result.unique().scalars().one()
def dao_fetch_service_by_inbound_number(number):
@@ -241,8 +235,7 @@ def dao_archive_service(service_id):
# to ensure that db.session still contains the models when it comes to creating history objects
service = (
Service.query.options(
joinedload(Service.templates),
joinedload(Service.templates.template_redacted),
joinedload(Service.templates).subqueryload(Template.template_redacted),
joinedload(Service.api_keys),
)
.filter(Service.id == service_id)
@@ -329,7 +322,12 @@ def dao_add_user_to_service(service, user, permissions=None, folder_permissions=
try:
from app.dao.permissions_dao import permission_dao
service.users.append(user)
# As per SQLAlchemy 2.0, we need to add the user to the service only if the user is not already added;
# otherwise it throws sqlalchemy.exc.IntegrityError:
# (psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "uix_user_to_service"
service_user = dao_get_service_user(user.id, service.id)
if service_user is None:
service.users.append(user)
permission_dao.set_user_service_permission(
user, service, permissions, _commit=False
)

View File

@@ -2,6 +2,8 @@ import uuid
from datetime import datetime, timedelta
from secrets import randbelow
import sqlalchemy
from flask import current_app
from sqlalchemy import func
from sqlalchemy.orm import joinedload
@@ -11,7 +13,7 @@ from app.dao.permissions_dao import permission_dao
from app.dao.service_user_dao import dao_get_service_users_by_user_id
from app.enums import AuthType, PermissionType
from app.errors import InvalidRequest
from app.models import User, VerifyCode
from app.models import Organization, Service, User, VerifyCode
from app.utils import escape_special_characters, get_archived_db_column_value
@@ -39,10 +41,21 @@ def get_login_gov_user(login_uuid, email_address):
user = User.query.filter_by(login_uuid=login_uuid).first()
if user:
if user.email_address != email_address:
save_user_attribute(user, {"email_address": email_address})
try:
save_user_attribute(user, {"email_address": email_address})
except sqlalchemy.exc.IntegrityError as ie:
# We are trying to change the email address as a courtesy,
# based on the assumption that the user has somehow changed their
# address in login.gov.
# But if we cannot change the email address, at least we don't
# want to fail here, otherwise the user will be locked out.
current_app.logger.error(ie)
db.session.rollback()
return user
# Remove this 1 July 2025, all users should have login.gov uuids by now
user = User.query.filter_by(email_address=email_address).first()
user = User.query.filter(User.email_address.ilike(email_address)).first()
if user:
save_user_attribute(user, {"login_uuid": login_uuid})
return user
@@ -172,15 +185,14 @@ def update_user_password(user, password):
def get_user_and_accounts(user_id):
# TODO: With sqlalchemy 2.0 change as below because of the breaking change
# at User.organizations.services, we need to verify that the below subqueryload
# that we have put is functionally doing the same thing as before
return (
User.query.filter(User.id == user_id)
.options(
# eagerly load the user's services and organizations, and also the service's org and vice versa
# (so we can see if the user knows about it)
joinedload(User.services),
joinedload(User.organizations),
joinedload(User.organizations.services),
joinedload(User.services.organization),
joinedload(User.services).joinedload(Service.organization),
joinedload(User.organizations).subqueryload(Organization.services),
)
.one()
)