From 2b22c1cc893334aed62e77e758a5f69161be35b1 Mon Sep 17 00:00:00 2001
From: Michael Plunkett <5885605+michplunkett@users.noreply.github.com>
Date: Fri, 19 Jul 2024 14:32:29 -0500
Subject: [PATCH] Address `SQLAlchemy` deprecation warnings (#1111)
## Fixes issue
Part of https://github.com/lucyparsons/OpenOversight/issues/1054
## Description of Changes
Address warnings for for `RemovedIn20Warning` and `LegacyAPIWarning`
while using the [`SQLALCHEMY_WARN_20`
flag](https://docs.sqlalchemy.org/en/20/changelog/migration_14.html#sqlalchemy-2-0-deprecations-mode).
If you would like me to do any additional manual checks, etc. to verify
these changes, please don't hesitate to ask.
Unmodified warnings
```console
=============================================================================================== warnings summary ===============================================================================================
../../local/lib/python3.11/site-packages/flask_wtf/recaptcha/widgets.py:2: 18 warnings
/usr/local/lib/python3.11/site-packages/flask_wtf/recaptcha/widgets.py:2: DeprecationWarning: 'flask.Markup' is deprecated and will be removed in Flask 2.4. Import 'markupsafe.Markup' instead.
from flask import Markup
OpenOversight/tests/test_commands.py::test_add_department__success
OpenOversight/tests/test_database_cache.py::test_get_database_cache_entry
OpenOversight/tests/test_utils.py::test_department_filter
OpenOversight/tests/test_models.py::test_department_repr
OpenOversight/tests/test_alembic.py::test_alembic_has_single_head
OpenOversight/tests/routes/test_image_tagging.py::test_routes_ok[/labels]
OpenOversight/tests/test_email_client.py::test_smtp_email_provider_send_email
/usr/src/app/OpenOversight/tests/conftest.py:678: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Location.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
Incident(
OpenOversight/tests/test_commands.py::test_add_department__success
OpenOversight/tests/test_database_cache.py::test_get_database_cache_entry
OpenOversight/tests/test_utils.py::test_department_filter
OpenOversight/tests/test_models.py::test_department_repr
OpenOversight/tests/test_alembic.py::test_alembic_has_single_head
OpenOversight/tests/routes/test_image_tagging.py::test_routes_ok[/labels]
OpenOversight/tests/test_email_client.py::test_smtp_email_provider_send_email
/usr/src/app/OpenOversight/tests/conftest.py:691: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Location.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
Incident(
OpenOversight/tests/test_commands.py::test_add_department__success
OpenOversight/tests/test_database_cache.py::test_get_database_cache_entry
OpenOversight/tests/test_utils.py::test_department_filter
OpenOversight/tests/test_models.py::test_department_repr
OpenOversight/tests/test_alembic.py::test_alembic_has_single_head
OpenOversight/tests/routes/test_image_tagging.py::test_routes_ok[/labels]
OpenOversight/tests/test_email_client.py::test_smtp_email_provider_send_email
/usr/src/app/OpenOversight/tests/conftest.py:704: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Location.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
Incident(
OpenOversight/tests/test_commands.py::test_add_department__success
OpenOversight/tests/test_database_cache.py::test_get_database_cache_entry
OpenOversight/tests/test_utils.py::test_department_filter
OpenOversight/tests/test_models.py::test_department_repr
OpenOversight/tests/test_alembic.py::test_alembic_has_single_head
OpenOversight/tests/routes/test_image_tagging.py::test_routes_ok[/labels]
OpenOversight/tests/test_email_client.py::test_smtp_email_provider_send_email
/usr/src/app/OpenOversight/tests/conftest.py:725: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
first_officer = Officer.query.get(1)
OpenOversight/tests/test_commands.py::test_add_department__success
OpenOversight/tests/test_database_cache.py::test_get_database_cache_entry
OpenOversight/tests/test_utils.py::test_department_filter
OpenOversight/tests/test_models.py::test_department_repr
OpenOversight/tests/test_alembic.py::test_alembic_has_single_head
OpenOversight/tests/routes/test_image_tagging.py::test_routes_ok[/labels]
OpenOversight/tests/test_email_client.py::test_smtp_email_provider_send_email
/usr/src/app/OpenOversight/tests/conftest.py:740: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
first_officer = Officer.query.get(1)
OpenOversight/tests/test_commands.py::test_add_department__duplicate
OpenOversight/tests/test_models.py::test__uuid_uniqueness_constraint
OpenOversight/tests/routes/test_singular_redirects.py::test_redirect_add_salary
/usr/src/app/OpenOversight/tests/conftest.py:325: SAWarning: transaction already deassociated from connection
transaction.rollback()
OpenOversight/tests/test_commands.py::test_add_job_title__success
OpenOversight/tests/test_commands.py::test_add_job_title__different_departments
/usr/src/app/OpenOversight/app/commands.py:645: RemovedIn20Warning: "Job" object is being merged into a Session along the backref cascade path for relationship "Department.jobs"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
job = Job(
OpenOversight/tests/test_utils.py::test_filters_do_not_exclude_officers_without_assignments
/usr/src/app/OpenOversight/tests/test_utils.py:102: RemovedIn20Warning: "Officer" object is being merged into a Session along the backref cascade path for relationship "Department.officers"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
officer = Officer(
OpenOversight/tests/test_models.py::test_salary_repr
/usr/src/app/OpenOversight/tests/test_models.py:170: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.
salary = Salary.query.first()
OpenOversight/tests/test_commands.py::test_csv_import_new
/usr/src/app/OpenOversight/tests/conftest.py:855: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.
if len(list(officer.salaries)) > 0:
OpenOversight/tests/test_database_cache.py::test_documented_assignments
OpenOversight/tests/routes/test_image_tagging.py::test_admin_can_delete_tag
OpenOversight/tests/routes/test_descriptions.py::test_officer_descriptions_markdown
OpenOversight/tests/routes/test_notes.py::test_officer_notes_markdown
/usr/local/lib/python3.11/site-packages/jinja2/environment.py:487: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.
return getattr(obj, attribute)
OpenOversight/tests/test_database_cache.py: 1 warning
OpenOversight/tests/routes/test_incidents.py: 9 warnings
OpenOversight/tests/routes/test_officer_and_department.py: 1 warning
/usr/src/app/OpenOversight/app/utils/forms.py:203: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Location.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
return Incident(
OpenOversight/tests/test_database_cache.py: 2 warnings
OpenOversight/tests/routes/test_descriptions.py: 29 warnings
OpenOversight/tests/routes/test_incidents.py: 34 warnings
OpenOversight/tests/routes/test_notes.py: 29 warnings
OpenOversight/tests/routes/test_singular_redirects.py: 21 warnings
OpenOversight/tests/routes/test_officer_and_department.py: 52 warnings
/usr/local/lib/python3.11/site-packages/flask_sqlalchemy/query.py:30: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
rv = self.get(ident)
OpenOversight/tests/test_database_cache.py: 1 warning
OpenOversight/tests/routes/test_officer_and_department.py: 11 warnings
/usr/src/app/OpenOversight/app/utils/forms.py:78: RemovedIn20Warning: "Assignment" object is being merged into a Session along the backref cascade path for relationship "Officer.assignments"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assignment = Assignment(
OpenOversight/tests/routes/test_image_tagging.py::test_ac_cannot_delete_tag_not_in_their_dept
/usr/src/app/OpenOversight/tests/routes/test_image_tagging.py:126: RemovedIn20Warning: The ``aliased`` and ``from_joinpoint`` keyword arguments to Query.join() are deprecated and will be removed in SQLAlchemy 2.0. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
Face.query.join(Face.officer, aliased=True)
OpenOversight/tests/routes/test_image_tagging.py::test_user_is_redirected_to_correct_department_after_tagging
/usr/src/app/OpenOversight/tests/routes/test_image_tagging.py:285: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
department = Department.query.get(department_id)
OpenOversight/tests/test_commands.py::test_csv_new_salary
/usr/src/app/OpenOversight/tests/test_commands.py:463: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value '123456.78' has dtype incompatible with float64, please explicitly cast to a compatible dtype first.
df.loc[0, "salary"] = "123456.78"
OpenOversight/tests/test_commands.py::test_bulk_add_officers__success
/usr/src/app/OpenOversight/tests/test_commands.py:511: RemovedIn20Warning: "Assignment" object is being merged into a Session along the backref cascade path for relationship "Officer.assignments"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assignment = Assignment(base_officer=first_officer, job_id=job.id)
OpenOversight/tests/test_commands.py::test_bulk_add_officers__success
/usr/src/app/OpenOversight/tests/test_commands.py:519: RemovedIn20Warning: "Assignment" object is being merged into a Session along the backref cascade path for relationship "Officer.assignments"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assignment = Assignment(base_officer=different_officer, job=job, created_by=user.id)
OpenOversight/tests/routes/test_incidents.py::test_admins_can_edit_incident_date_and_address
/usr/src/app/OpenOversight/tests/routes/test_incidents.py:253: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
updated = Incident.query.get(inc_id)
OpenOversight/tests/routes/test_descriptions.py::test_admins_can_delete_descriptions
/usr/src/app/OpenOversight/tests/routes/test_descriptions.py:293: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Description.query.get(description_id)
OpenOversight/tests/routes/test_image_tagging.py::test_ac_cannot_set_featured_tag_not_in_their_dept
/usr/src/app/OpenOversight/tests/routes/test_image_tagging.py:326: RemovedIn20Warning: The ``aliased`` and ``from_joinpoint`` keyword arguments to Query.join() are deprecated and will be removed in SQLAlchemy 2.0. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
Face.query.join(Face.officer, aliased=True)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__success
/usr/src/app/OpenOversight/tests/test_commands.py:889: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Officer.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
incident.officers = [officer]
OpenOversight/tests/test_commands.py::test_advanced_csv_import__success
OpenOversight/tests/test_commands.py::test_advanced_csv_import__success
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/app/models/database_imports.py:308: RemovedIn20Warning: "Incident" object is being merged into a Session along the backref cascade path for relationship "Officer.incidents"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
incident.officers = data.get("officers", [])
OpenOversight/tests/test_commands.py::test_advanced_csv_import__success
/usr/src/app/OpenOversight/tests/test_commands.py:1012: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
incident3 = Incident.query.get(123456)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__success
/usr/src/app/OpenOversight/tests/test_commands.py:1035: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
updated_link = Link.query.get(55051)
OpenOversight/tests/routes/test_descriptions.py::test_acs_can_delete_their_descriptions_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_descriptions.py:322: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Description.query.get(description_id)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/app/csv_imports.py:560: RemovedIn20Warning: Using plain strings to indicate SQL statements without using the text() construct is deprecated and will be removed in version 2.0. Ensure plain SQL statements are passed using the text() construct. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
db.session.execute(raw_sql)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1160: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
cop1 = Officer.query.get(99001)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1163: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
cop2 = Officer.query.get(99002)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1165: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert cop2.assignments[0] == Assignment.query.get(98001)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1167: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
cop3 = Officer.query.get(99003)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1169: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert cop3.salaries[0] == Salary.query.get(77001)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1171: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
incident = Incident.query.get(66001)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__force_create
/usr/src/app/OpenOversight/tests/test_commands.py:1176: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
link = Link.query.get(55001)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__overwrite_assignments
/usr/src/app/OpenOversight/tests/test_commands.py:1279: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
cop1 = Officer.query.get(cop1_id)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__overwrite_assignments
/usr/src/app/OpenOversight/tests/test_commands.py:1283: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
cop2 = Officer.query.get(cop2_id)
OpenOversight/tests/test_commands.py::test_advanced_csv_import__overwrite_assignments
/usr/src/app/OpenOversight/tests/test_commands.py:1285: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert cop2.assignments[0] == Assignment.query.get(a2_id)
OpenOversight/tests/routes/test_descriptions.py::test_acs_cannot_delete_descriptions_not_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_descriptions.py:354: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
not_deleted = Description.query.get(description_id)
OpenOversight/tests/routes/test_incidents.py: 12 warnings
/usr/src/app/OpenOversight/app/main/forms.py:505: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
officer = Officer.query.get(officer_id)
OpenOversight/tests/routes/test_notes.py::test_admins_can_delete_notes
/usr/src/app/OpenOversight/tests/routes/test_notes.py:263: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Note.query.get(note.id)
OpenOversight/tests/routes/test_notes.py::test_acs_can_delete_their_notes_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_notes.py:288: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Note.query.get(note.id)
OpenOversight/tests/routes/test_notes.py::test_acs_cannot_delete_notes_not_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_notes.py:317: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
not_deleted = Note.query.get(note.id)
OpenOversight/tests/routes/test_incidents.py::test_admins_can_delete_incidents
/usr/src/app/OpenOversight/tests/routes/test_incidents.py:683: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Incident.query.get(inc_id)
OpenOversight/tests/routes/test_incidents.py::test_acs_can_delete_incidents_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_incidents.py:697: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
deleted = Incident.query.get(inc_id)
OpenOversight/tests/routes/test_incidents.py::test_acs_cannot_delete_incidents_not_in_their_department
/usr/src/app/OpenOversight/tests/routes/test_incidents.py:713: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
not_deleted = Incident.query.get(inc_id)
OpenOversight/tests/routes/test_user_api.py: 21 warnings
/usr/src/app/OpenOversight/app/auth/views.py:295: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user_id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_delete_user
OpenOversight/tests/routes/test_user_api.py::test_admin_can_delete_user
OpenOversight/tests/routes/test_user_api.py::test_admin_cannot_delete_other_admin
/usr/src/app/OpenOversight/app/auth/views.py:342: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user_id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_delete_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:137: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert not User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_cannot_delete_other_admin
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:153: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert User.query.get(user.id) is not None
OpenOversight/tests/routes/test_user_api.py::test_admin_can_disable_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:178: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_cannot_disable_self
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:201: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_enable_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:213: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_enable_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:229: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_approve_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:296: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_can_approve_user
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:312: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-False-True-True]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-False-False-False]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[True-False-True-False]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-True-True-False]
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:349: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-False-True-True]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-False-False-False]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[True-False-True-False]
OpenOversight/tests/routes/test_user_api.py::test_admin_approval_sends_confirmation_email[False-True-True-False]
/usr/src/app/OpenOversight/tests/routes/test_user_api.py:366: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
user = User.query.get(user.id)
OpenOversight/tests/routes/test_officer_and_department.py::test_admin_can_add_new_officer
/usr/src/app/OpenOversight/app/utils/forms.py:97: RemovedIn20Warning: "Note" object is being merged into a Session along the backref cascade path for relationship "Officer.notes"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
new_note = Note(
OpenOversight/tests/routes/test_officer_and_department.py::test_admin_can_add_new_officer
/usr/src/app/OpenOversight/app/utils/forms.py:108: RemovedIn20Warning: "Description" object is being merged into a Session along the backref cascade path for relationship "Officer.descriptions"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
new_description = Description(
OpenOversight/tests/routes/test_officer_and_department.py::test_admin_can_add_new_officer
/usr/src/app/OpenOversight/app/utils/forms.py:119: RemovedIn20Warning: "Salary" object is being merged into a Session along the backref cascade path for relationship "Officer.salaries"; in SQLAlchemy 2.0, this reverse cascade will not take place. Set cascade_backrefs to False in either the relationship() or backref() function for the 2.0 behavior; or to set globally for the whole Session, set the future=True flag (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
new_salary = Salary(
OpenOversight/tests/routes/test_officer_and_department.py: 109 warnings
/usr/src/app/OpenOversight/tests/routes/test_officer_and_department.py:1777: LegacyAPIWarning: The Query.get() method is considered legacy as of the 1.x series of SQLAlchemy and becomes a legacy construct in 2.0. The method is now available as Session.get() (deprecated since: 1.4) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
assert (
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
---------- coverage: platform linux, python 3.11.9-final-0 -----------
```
## Tests and Linting
- [x] This branch is up-to-date with the `develop` branch.
- [x] `pytest` passes on my local development environment.
- [x] `pre-commit` passes on my local development environment.
---
OpenOversight/app/auth/views.py | 4 +-
OpenOversight/app/csv_imports.py | 3 +-
OpenOversight/app/main/forms.py | 6 +-
OpenOversight/app/main/model_view.py | 6 +-
OpenOversight/app/main/views.py | 12 +--
OpenOversight/app/models/database.py | 73 ++++++++++++++-----
OpenOversight/tests/conftest.py | 4 +-
.../tests/routes/test_descriptions.py | 6 +-
.../tests/routes/test_image_tagging.py | 6 +-
OpenOversight/tests/routes/test_incidents.py | 8 +-
OpenOversight/tests/routes/test_notes.py | 6 +-
.../routes/test_officer_and_department.py | 3 +-
OpenOversight/tests/routes/test_user_api.py | 20 ++---
OpenOversight/tests/test_commands.py | 24 +++---
OpenOversight/tests/test_utils.py | 10 ++-
requirements.txt | 2 +-
16 files changed, 117 insertions(+), 76 deletions(-)
diff --git a/OpenOversight/app/auth/views.py b/OpenOversight/app/auth/views.py
index caa4cfabc..75c02f734 100644
--- a/OpenOversight/app/auth/views.py
+++ b/OpenOversight/app/auth/views.py
@@ -292,7 +292,7 @@ def get_users():
@auth.route("/users/", methods=[HTTPMethod.GET, HTTPMethod.POST])
@admin_required
def edit_user(user_id):
- user = User.query.get(user_id)
+ user = db.session.get(User, user_id)
if not user:
return render_template("404.html"), HTTPStatus.NOT_FOUND
@@ -339,7 +339,7 @@ def edit_user(user_id):
@auth.route("/users//delete", methods=[HTTPMethod.GET, HTTPMethod.POST])
@admin_required
def delete_user(user_id):
- user = User.query.get(user_id)
+ user = db.session.get(User, user_id)
if not user or user.is_administrator:
return render_template("403.html"), HTTPStatus.FORBIDDEN
if request.method == HTTPMethod.POST:
diff --git a/OpenOversight/app/csv_imports.py b/OpenOversight/app/csv_imports.py
index ebb63c7da..7904b2bd4 100644
--- a/OpenOversight/app/csv_imports.py
+++ b/OpenOversight/app/csv_imports.py
@@ -2,6 +2,7 @@
from contextlib import contextmanager
from typing import Dict, Optional
+from sqlalchemy import sql
from sqlalchemy.exc import SQLAlchemyError
from OpenOversight.app.models.database import (
@@ -557,7 +558,7 @@ def import_csv_files(
select setval('incidents_id_seq', (select max(id) from incidents));
"""
try:
- db.session.execute(raw_sql)
+ db.session.execute(sql.text(raw_sql))
print("Updated sequences.")
except SQLAlchemyError:
print("Failed to update sequences")
diff --git a/OpenOversight/app/main/forms.py b/OpenOversight/app/main/forms.py
index da6a7b0cf..ce1dc0145 100644
--- a/OpenOversight/app/main/forms.py
+++ b/OpenOversight/app/main/forms.py
@@ -30,7 +30,7 @@
from wtforms_sqlalchemy.fields import QuerySelectField
from OpenOversight.app.formfields import TimeField
-from OpenOversight.app.models.database import Officer
+from OpenOversight.app.models.database import Officer, db
from OpenOversight.app.utils.choices import (
AGE_CHOICES,
DEPARTMENT_STATE_CHOICES,
@@ -498,11 +498,11 @@ def process_data(self, value):
def validate_oo_id(self, oo_id):
if oo_id.data and isinstance(oo_id.data, str):
if oo_id.data.isnumeric():
- officer = Officer.query.get(oo_id.data)
+ officer = db.session.get(Officer, oo_id.data)
else:
try:
officer_id = oo_id.data.split('value="')[1][:-2]
- officer = Officer.query.get(officer_id)
+ officer = db.session.get(Officer, officer_id)
# Sometimes we get a string in field.data with py.test, this parses it
except IndexError:
diff --git a/OpenOversight/app/main/model_view.py b/OpenOversight/app/main/model_view.py
index 7f8688584..023ccfd76 100644
--- a/OpenOversight/app/main/model_view.py
+++ b/OpenOversight/app/main/model_view.py
@@ -60,7 +60,7 @@ def get(self, obj_id):
url=f"main.{self.model_name}_api",
)
else:
- obj = self.model.query.get_or_404(obj_id)
+ obj = db.get_or_404(self.model, obj_id)
return render_template(
f"{self.model_name}_detail.html",
obj=obj,
@@ -111,7 +111,7 @@ def new(self, form=None):
@login_required
@ac_or_admin_required
def edit(self, obj_id, form=None):
- obj = self.model.query.get_or_404(obj_id)
+ obj = db.get_or_404(self.model, obj_id)
if self.department_check:
if (
not current_user.is_administrator
@@ -158,7 +158,7 @@ def edit(self, obj_id, form=None):
@login_required
@ac_or_admin_required
def delete(self, obj_id):
- obj = self.model.query.get_or_404(obj_id)
+ obj = db.get_or_404(self.model, obj_id)
if self.department_check:
if (
not current_user.is_administrator
diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py
index a05e1ca50..b09447752 100644
--- a/OpenOversight/app/main/views.py
+++ b/OpenOversight/app/main/views.py
@@ -743,7 +743,7 @@ def redirect_edit_department(department_id: int):
@login_required
@admin_required
def edit_department(department_id: int):
- department = Department.query.get_or_404(department_id)
+ department = db.get_or_404(Department, department_id)
previous_name = department.name
form = EditDepartmentForm(obj=department)
original_ranks = department.jobs
@@ -1831,7 +1831,7 @@ def redirect_submit_officer_images(officer_id: int):
@login_required
@ac_or_admin_required
def submit_officer_images(officer_id: int):
- officer = Officer.query.get_or_404(officer_id)
+ officer = db.get_or_404(Officer, officer_id)
return render_template("submit_officer_image.html", officer=officer)
@@ -1953,7 +1953,7 @@ def get(self, obj_id: int):
dept = None
if department_id := request.args.get("department_id"):
- dept = Department.query.get_or_404(department_id)
+ dept = db.get_or_404(Department, department_id)
form.department_id.data = department_id
incidents = incidents.filter_by(department_id=department_id)
@@ -2141,7 +2141,7 @@ def get_edit_form(self, obj: TextForm):
def dispatch_request(self, *args, **kwargs):
if "officer_id" in kwargs:
- officer = Officer.query.get_or_404(kwargs["officer_id"])
+ officer = db.get_or_404(Officer, kwargs["officer_id"])
self.officer_id = kwargs.pop("officer_id")
self.department_id = officer.department_id
return super(TextApi, self).dispatch_request(*args, **kwargs)
@@ -2392,7 +2392,7 @@ def new(self, form: FlaskForm = None):
@login_required
@ac_or_admin_required
def delete(self, obj_id: int):
- obj = self.model.query.get_or_404(obj_id)
+ obj = db.get_or_404(self.model, obj_id)
if (
not current_user.is_administrator
and current_user.ac_department_id != self.get_department_id(obj)
@@ -2435,7 +2435,7 @@ def get_department_id(self, obj):
def dispatch_request(self, *args, **kwargs):
if "officer_id" in kwargs:
- officer = Officer.query.get_or_404(kwargs["officer_id"])
+ officer = db.get_or_404(Officer, kwargs["officer_id"])
self.officer_id = kwargs.pop("officer_id")
self.department_id = officer.department_id
return super(OfficerLinkApi, self).dispatch_request(*args, **kwargs)
diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py
index 347ad1465..1ea4f5c36 100644
--- a/OpenOversight/app/models/database.py
+++ b/OpenOversight/app/models/database.py
@@ -187,7 +187,9 @@ class Job(BaseModel, TrackUpdates):
department_id = db.Column(
db.Integer, db.ForeignKey("departments.id", name="jobs_department_id_fkey")
)
- department = db.relationship("Department", backref="jobs")
+ department = db.relationship(
+ "Department", backref=db.backref("jobs", cascade_backrefs=False)
+ )
__table_args__ = (
UniqueConstraint(
@@ -232,27 +234,45 @@ class Officer(BaseModel, TrackUpdates):
gender = db.Column(db.String(5), index=True, unique=False, nullable=True)
employment_date = db.Column(db.Date, index=True, unique=False, nullable=True)
birth_year = db.Column(db.Integer, index=True, unique=False, nullable=True)
- assignments = db.relationship("Assignment", back_populates="base_officer")
- face = db.relationship("Face", backref="officer")
+ assignments = db.relationship(
+ "Assignment", back_populates="base_officer", cascade_backrefs=False
+ )
+ face = db.relationship(
+ "Face", backref=db.backref("officer", cascade_backrefs=False)
+ )
department_id = db.Column(
db.Integer, db.ForeignKey("departments.id", name="officers_department_id_fkey")
)
- department = db.relationship("Department", backref="officers")
+ department = db.relationship(
+ "Department", backref=db.backref("officers", cascade_backrefs=False)
+ )
unique_internal_identifier = db.Column(
db.String(50), index=True, unique=True, nullable=True
)
links = db.relationship(
- "Link", secondary=officer_links, backref=db.backref("officers", lazy=True)
+ "Link",
+ secondary=officer_links,
+ backref=db.backref("officers", lazy=True, cascade_backrefs=False),
+ lazy=True,
)
notes = db.relationship(
- "Note", back_populates="officer", order_by="Note.created_at"
+ "Note",
+ back_populates="officer",
+ cascade_backrefs=False,
+ order_by="Note.created_at",
)
descriptions = db.relationship(
- "Description", back_populates="officer", order_by="Description.created_at"
+ "Description",
+ back_populates="officer",
+ cascade_backrefs=False,
+ order_by="Description.created_at",
)
salaries = db.relationship(
- "Salary", back_populates="officer", order_by="Salary.year.desc()"
+ "Salary",
+ back_populates="officer",
+ cascade_backrefs=False,
+ order_by="Salary.year.desc()",
)
__table_args__ = (
@@ -407,7 +427,9 @@ class Unit(BaseModel, TrackUpdates):
db.ForeignKey("departments.id", name="unit_types_department_id_fkey"),
)
department = db.relationship(
- "Department", backref="unit_types", order_by="Unit.description.asc()"
+ "Department",
+ backref=db.backref("unit_types", cascade_backrefs=False),
+ order_by="Unit.description.asc()",
)
def __repr__(self):
@@ -445,9 +467,16 @@ class Face(BaseModel, TrackUpdates):
face_position_y = db.Column(db.Integer, unique=False)
face_width = db.Column(db.Integer, unique=False)
face_height = db.Column(db.Integer, unique=False)
- image = db.relationship("Image", backref="faces", foreign_keys=[img_id])
+ image = db.relationship(
+ "Image",
+ backref=db.backref("faces", cascade_backrefs=False),
+ foreign_keys=[img_id],
+ )
original_image = db.relationship(
- "Image", backref="tags", foreign_keys=[original_image_id], lazy=True
+ "Image",
+ backref=db.backref("tags", cascade_backrefs=False),
+ foreign_keys=[original_image_id],
+ lazy=True,
)
featured = db.Column(
db.Boolean, nullable=False, default=False, server_default="false"
@@ -478,7 +507,9 @@ class Image(BaseModel, TrackUpdates):
db.Integer,
db.ForeignKey("departments.id", name="raw_images_department_id_fkey"),
)
- department = db.relationship("Department", backref="raw_images")
+ department = db.relationship(
+ "Department", backref=db.backref("raw_images", cascade_backrefs=False)
+ )
def __repr__(self):
return f""
@@ -641,29 +672,33 @@ class Incident(BaseModel, TrackUpdates):
address_id = db.Column(
db.Integer, db.ForeignKey("locations.id", name="incidents_address_id_fkey")
)
- address = db.relationship("Location", backref="incidents")
+ address = db.relationship(
+ "Location", backref=db.backref("incidents", cascade_backrefs=False)
+ )
license_plates = db.relationship(
"LicensePlate",
secondary=incident_license_plates,
lazy="subquery",
- backref=db.backref("incidents", lazy=True),
+ backref=db.backref("incidents", cascade_backrefs=False, lazy=True),
)
links = db.relationship(
"Link",
secondary=incident_links,
lazy="subquery",
- backref=db.backref("incidents", lazy=True),
+ backref=db.backref("incidents", cascade_backrefs=False, lazy=True),
)
officers = db.relationship(
"Officer",
secondary=officer_incidents,
lazy="subquery",
- backref=db.backref("incidents"),
+ backref=db.backref("incidents", cascade_backrefs=False),
)
department_id = db.Column(
db.Integer, db.ForeignKey("departments.id", name="incidents_department_id_fkey")
)
- department = db.relationship("Department", backref="incidents", lazy=True)
+ department = db.relationship(
+ "Department", backref=db.backref("incidents", cascade_backrefs=False), lazy=True
+ )
class User(UserMixin, BaseModel):
@@ -690,7 +725,9 @@ class User(UserMixin, BaseModel):
db.Integer, db.ForeignKey("departments.id", name="users_ac_department_id_fkey")
)
ac_department = db.relationship(
- "Department", backref="coordinators", foreign_keys=[ac_department_id]
+ "Department",
+ backref=db.backref("coordinators", cascade_backrefs=False),
+ foreign_keys=[ac_department_id],
)
is_administrator = db.Column(db.Boolean, default=False)
is_disabled = db.Column(db.Boolean, default=False)
diff --git a/OpenOversight/tests/conftest.py b/OpenOversight/tests/conftest.py
index abfa3009d..d9904ef6e 100644
--- a/OpenOversight/tests/conftest.py
+++ b/OpenOversight/tests/conftest.py
@@ -722,7 +722,7 @@ def add_mockdata(session):
users_that_can_create_notes = [test_admin, test_area_coordinator]
# for testing routes
- first_officer = Officer.query.get(1)
+ first_officer = session.get(Officer, 1)
note = build_note(
first_officer, test_admin, "### A markdown note\nA **test** note!"
)
@@ -737,7 +737,7 @@ def add_mockdata(session):
users_that_can_create_descriptions = [test_admin, test_area_coordinator]
# for testing routes
- first_officer = Officer.query.get(1)
+ first_officer = session.get(Officer, 1)
description = build_description(
first_officer, test_admin, "### A markdown description\nA **test** description!"
)
diff --git a/OpenOversight/tests/routes/test_descriptions.py b/OpenOversight/tests/routes/test_descriptions.py
index a9421732c..b3bcb5d41 100644
--- a/OpenOversight/tests/routes/test_descriptions.py
+++ b/OpenOversight/tests/routes/test_descriptions.py
@@ -290,7 +290,7 @@ def test_admins_can_delete_descriptions(mockdata, client, session):
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Description.query.get(description_id)
+ deleted = session.get(Description, description_id)
assert deleted is None
@@ -319,7 +319,7 @@ def test_acs_can_delete_their_descriptions_in_their_department(
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Description.query.get(description_id)
+ deleted = session.get(Description, description_id)
assert deleted is None
@@ -351,7 +351,7 @@ def test_acs_cannot_delete_descriptions_not_in_their_department(
)
assert rv.status_code == HTTPStatus.FORBIDDEN
- not_deleted = Description.query.get(description_id)
+ not_deleted = session.get(Description, description_id)
assert not_deleted is not None
diff --git a/OpenOversight/tests/routes/test_image_tagging.py b/OpenOversight/tests/routes/test_image_tagging.py
index 7f050ac07..2cae18f1b 100644
--- a/OpenOversight/tests/routes/test_image_tagging.py
+++ b/OpenOversight/tests/routes/test_image_tagging.py
@@ -123,7 +123,7 @@ def test_ac_cannot_delete_tag_not_in_their_dept(mockdata, client, session):
login_ac(client)
tag = (
- Face.query.join(Face.officer, aliased=True)
+ Face.query.join(Face.officer)
.except_(Face.query.filter(Face.officer.has(department_id=AC_DEPT)))
.first()
)
@@ -282,7 +282,7 @@ def test_user_is_redirected_to_correct_department_after_tagging(
),
follow_redirects=True,
)
- department = Department.query.get(department_id)
+ department = session.get(Department, department_id)
assert rv.status_code == HTTPStatus.OK
assert department.name in rv.data.decode(ENCODING_UTF_8)
@@ -323,7 +323,7 @@ def test_ac_cannot_set_featured_tag_not_in_their_dept(mockdata, client, session)
login_ac(client)
tag = (
- Face.query.join(Face.officer, aliased=True)
+ Face.query.join(Face.officer)
.except_(Face.query.filter(Face.officer.has(department_id=AC_DEPT)))
.first()
)
diff --git a/OpenOversight/tests/routes/test_incidents.py b/OpenOversight/tests/routes/test_incidents.py
index bef5e79a3..eb3f9e4df 100644
--- a/OpenOversight/tests/routes/test_incidents.py
+++ b/OpenOversight/tests/routes/test_incidents.py
@@ -250,7 +250,7 @@ def test_admins_can_edit_incident_date_and_address(mockdata, client, session):
)
assert rv.status_code == HTTPStatus.OK
assert "successfully updated" in rv.data.decode(ENCODING_UTF_8)
- updated = Incident.query.get(inc_id)
+ updated = session.get(Incident, inc_id)
assert updated.date == test_date
assert updated.time == test_time
assert updated.address.street_name == street_name
@@ -680,7 +680,7 @@ def test_admins_can_delete_incidents(mockdata, client, session):
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Incident.query.get(inc_id)
+ deleted = session.get(Incident, inc_id)
assert deleted is None
@@ -694,7 +694,7 @@ def test_acs_can_delete_incidents_in_their_department(mockdata, client, session)
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Incident.query.get(inc_id)
+ deleted = session.get(Incident, inc_id)
assert deleted is None
@@ -710,7 +710,7 @@ def test_acs_cannot_delete_incidents_not_in_their_department(mockdata, client, s
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.FORBIDDEN
- not_deleted = Incident.query.get(inc_id)
+ not_deleted = session.get(Incident, inc_id)
assert not_deleted.id is inc_id
diff --git a/OpenOversight/tests/routes/test_notes.py b/OpenOversight/tests/routes/test_notes.py
index 159beccea..5e50c5059 100644
--- a/OpenOversight/tests/routes/test_notes.py
+++ b/OpenOversight/tests/routes/test_notes.py
@@ -260,7 +260,7 @@ def test_admins_can_delete_notes(mockdata, client, session):
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Note.query.get(note.id)
+ deleted = session.get(Note, note.id)
assert deleted is None
assert has_database_cache_entry(*cache_params) is False
@@ -285,7 +285,7 @@ def test_acs_can_delete_their_notes_in_their_department(mockdata, client, sessio
follow_redirects=True,
)
assert rv.status_code == HTTPStatus.OK
- deleted = Note.query.get(note.id)
+ deleted = session.get(Note, note.id)
assert deleted is None
@@ -314,7 +314,7 @@ def test_acs_cannot_delete_notes_not_in_their_department(
)
assert rv.status_code == HTTPStatus.FORBIDDEN
- not_deleted = Note.query.get(note.id)
+ not_deleted = session.get(Note, note.id)
assert not_deleted is not None
diff --git a/OpenOversight/tests/routes/test_officer_and_department.py b/OpenOversight/tests/routes/test_officer_and_department.py
index ebfc65e86..7f4953859 100644
--- a/OpenOversight/tests/routes/test_officer_and_department.py
+++ b/OpenOversight/tests/routes/test_officer_and_department.py
@@ -1775,7 +1775,8 @@ def test_assignments_csv(mockdata, client, session, department):
all_rows = list(csv.DictReader(csv_data.split("\n")))
for row in all_rows:
assert (
- Officer.query.get(int(row["officer id"])).department_id == department.id
+ session.get(Officer, int(row["officer id"])).department_id
+ == department.id
)
lines = [row for row in all_rows if int(row["officer id"]) == officer.id]
assert len(lines) == 2
diff --git a/OpenOversight/tests/routes/test_user_api.py b/OpenOversight/tests/routes/test_user_api.py
index 715c021d6..84f7cef09 100644
--- a/OpenOversight/tests/routes/test_user_api.py
+++ b/OpenOversight/tests/routes/test_user_api.py
@@ -134,7 +134,7 @@ def test_admin_can_delete_user(mockdata, client, session):
assert f"User {user.username} has been deleted!" in rv.data.decode(
ENCODING_UTF_8
)
- assert not User.query.get(user.id)
+ assert not session.get(User, user.id)
def test_admin_cannot_delete_other_admin(mockdata, client, session):
@@ -150,7 +150,7 @@ def test_admin_cannot_delete_other_admin(mockdata, client, session):
)
assert rv.status_code == HTTPStatus.FORBIDDEN
- assert User.query.get(user.id) is not None
+ assert session.get(User, user.id) is not None
def test_admin_can_disable_user(mockdata, client, session):
@@ -175,7 +175,7 @@ def test_admin_can_disable_user(mockdata, client, session):
assert "updated!" in rv.data.decode(ENCODING_UTF_8)
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert user.is_disabled
@@ -198,7 +198,7 @@ def test_admin_cannot_disable_self(mockdata, client, session):
assert "You cannot edit your own account!" in rv.data.decode(ENCODING_UTF_8)
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert not user.is_disabled
@@ -210,7 +210,7 @@ def test_admin_can_enable_user(mockdata, client, session):
user.is_disabled = True
db.session.commit()
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert user.is_disabled
form = EditUserForm(
@@ -226,7 +226,7 @@ def test_admin_can_enable_user(mockdata, client, session):
assert "updated!" in rv.data.decode(ENCODING_UTF_8)
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert not user.is_disabled
@@ -293,7 +293,7 @@ def test_admin_can_approve_user(mockdata, client, session):
user.approved = False
db.session.commit()
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert not user.approved
form = EditUserForm(
@@ -309,7 +309,7 @@ def test_admin_can_approve_user(mockdata, client, session):
assert "updated!" in rv.data.decode(ENCODING_UTF_8)
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert user.approved
@@ -346,7 +346,7 @@ def test_admin_approval_sends_confirmation_email(
user.confirmed = currently_confirmed
db.session.commit()
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert user.approved == currently_approved
assert user.confirmed == currently_confirmed
@@ -363,5 +363,5 @@ def test_admin_approval_sends_confirmation_email(
) == should_send_email
assert "updated!" in rv.data.decode(ENCODING_UTF_8)
- user = User.query.get(user.id)
+ user = session.get(User, user.id)
assert user.approved
diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py
index d3704fadb..3bde7a114 100644
--- a/OpenOversight/tests/test_commands.py
+++ b/OpenOversight/tests/test_commands.py
@@ -1009,7 +1009,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir):
assert address.zip_code == "60603"
assert incident2.officers == [cop1]
- incident3 = Incident.query.get(123456)
+ incident3 = session.get(Incident, 123456)
assert incident3.report_number == "CR-39283"
assert incident3.description == "Don't know where it happened"
assert incident3.officers == [cop1]
@@ -1032,7 +1032,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir):
assert incident_link.title == "Another Link"
assert incident_link.author == "Example Times"
- updated_link = Link.query.get(55051)
+ updated_link = session.get(Link, 55051)
assert updated_link.title == "Updated Link"
assert updated_link.officers == []
assert updated_link.incidents == [incident3]
@@ -1157,23 +1157,23 @@ def test_advanced_csv_import__force_create(session, department, tmp_path):
assert result.exit_code == 0
# make sure all the data is imported as expected
- cop1 = Officer.query.get(99001)
+ cop1 = session.get(Officer, 99001)
assert cop1.first_name == "First"
- cop2 = Officer.query.get(99002)
+ cop2 = session.get(Officer, 99002)
assert cop2.assignments[0].star_no == "12345"
- assert cop2.assignments[0] == Assignment.query.get(98001)
+ assert cop2.assignments[0] == session.get(Assignment, 98001)
- cop3 = Officer.query.get(99003)
+ cop3 = session.get(Officer, 99003)
assert cop3.salaries[0].salary == 98765
- assert cop3.salaries[0] == Salary.query.get(77001)
+ assert cop3.salaries[0] == session.get(Salary, 77001)
- incident = Incident.query.get(66001)
+ incident = session.get(Incident, 66001)
assert incident.address.street_name == "Fake Street"
assert cop1.incidents[0] == incident
assert cop2.incidents[0] == incident
- link = Link.query.get(55001)
+ link = session.get(Link, 55001)
assert link.url == "https://www.example.org/3629"
assert cop1.links[0] == link
@@ -1276,13 +1276,13 @@ def test_advanced_csv_import__overwrite_assignments(session, department, tmp_pat
assert result.exit_code == 0
# make sure all the data is imported as expected
- cop1 = Officer.query.get(cop1_id)
+ cop1 = session.get(Officer, cop1_id)
assert len(cop1.assignments) == 1
assert cop1.assignments[0].star_no == b1
- cop2 = Officer.query.get(cop2_id)
+ cop2 = session.get(Officer, cop2_id)
assert len(cop2.assignments) == 1
- assert cop2.assignments[0] == Assignment.query.get(a2_id)
+ assert cop2.assignments[0] == session.get(Assignment, a2_id)
cop3 = Officer.query.filter_by(first_name="Second", last_name="Test").first()
assert len(cop3.assignments) == 1
diff --git a/OpenOversight/tests/test_utils.py b/OpenOversight/tests/test_utils.py
index e4b9f1072..420718927 100644
--- a/OpenOversight/tests/test_utils.py
+++ b/OpenOversight/tests/test_utils.py
@@ -99,11 +99,13 @@ def test_filter_by_name(mockdata):
def test_filters_do_not_exclude_officers_without_assignments(mockdata):
department = Department.query.first()
- officer = Officer(
- first_name="Rachel", last_name="S", department=department, birth_year=1992
- )
results = grab_officers({"name": "S", "dept": department})
- assert officer in results.all()
+ no_assignments = False
+ for element in results.all():
+ if len(element.assignments) == 0:
+ no_assignments = True
+ break
+ assert no_assignments
def test_filter_by_badge_no(mockdata):
diff --git a/requirements.txt b/requirements.txt
index c964db2b6..4d02641a1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -59,7 +59,7 @@ s3transfer~=0.6.1
selenium~=4.10.0
six~=1.16.0
sphinx==7.1.2
-SQLAlchemy==1.4.47 # Updating this breaks the build for python 3.9
+SQLAlchemy==1.4.52
tornado~=6.3.2
trio==0.22.1
urllib3~=1.26.16 # This version is required for other packages to run