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