From 7751e4b2003a3fa573e35d4e3d7e54be57195b71 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 28 Jan 2025 13:27:29 +0000 Subject: [PATCH] Apply more Ruff linter rules --- .../ruff/lint/per_file_ignores/tests/tail | 6 ++ bin/run_data_task.py | 12 ++-- lms/config.py | 4 +- lms/db/_columns.py | 6 +- lms/db/_util.py | 4 +- lms/events/event.py | 4 +- lms/events/subscribers.py | 2 +- lms/extensions/feature_flags/__init__.py | 2 +- lms/extensions/feature_flags/_helpers.py | 4 +- lms/extensions/feature_flags/_providers.py | 2 +- .../feature_flags/views/cookie_form.py | 2 +- .../088813d3e6f8_email_digest_enable_02.py | 1 - .../versions/0c52a13c6cad_canvas_sso_urls.py | 8 +-- .../versions/396f46318023_events_backfill.py | 4 +- .../3a786e91f59c_email_digest_enable_04.py | 3 +- ...db_migration_to_back_fill_organizations.py | 16 +++--- .../versions/52ff45973d5b_remove_ext_id.py | 2 +- .../9207d2192903_email_digest_enable_03.py | 2 +- ...b7_backfill_lmsuserassignmentmembership.py | 2 +- .../b11713314988_email_digest_enable_01.py | 4 +- .../b512a5cf64ed_public_id_migration.py | 2 +- ...nd_course_groups_exported_from_h_tables.py | 10 ++-- ...d7_clean_grouping_and_assignment_titles.py | 6 +- .../f3d631c110bf_canvas_files_enabled.py | 4 +- lms/models/_hashed_id.py | 2 +- lms/models/application_instance.py | 4 +- lms/models/grouping.py | 16 +++--- lms/models/lti_params.py | 2 +- lms/models/lti_registration.py | 2 +- lms/models/lti_role.py | 6 +- lms/models/lti_user.py | 2 +- lms/product/blackboard/product.py | 4 +- lms/product/canvas/_plugin/grouping.py | 2 +- lms/product/canvas/_plugin/misc.py | 10 ++-- lms/product/canvas/product.py | 4 +- lms/product/d2l/_plugin/course_copy.py | 5 +- lms/product/d2l/_plugin/grouping.py | 2 +- lms/product/d2l/_plugin/misc.py | 2 +- lms/product/d2l/product.py | 4 +- lms/product/factory.py | 8 +-- lms/product/moodle/_plugin/course_copy.py | 5 +- lms/product/moodle/_plugin/grouping.py | 2 +- lms/product/moodle/_plugin/misc.py | 2 +- lms/product/moodle/product.py | 4 +- lms/product/plugin/__init__.py | 2 +- lms/product/plugin/course_copy.py | 8 +-- lms/product/plugin/plugin.py | 2 +- lms/product/product.py | 6 +- lms/pshell.py | 10 ++-- lms/resources/_js_config/__init__.py | 8 +-- lms/security.py | 10 ++-- lms/services/__init__.py | 2 +- lms/services/application_instance.py | 30 +++++----- lms/services/assignment.py | 6 +- lms/services/async_oauth_http.py | 2 +- lms/services/auto_grading.py | 10 ++-- lms/services/blackboard_api/_basic.py | 2 +- lms/services/blackboard_api/client.py | 12 +++- lms/services/canvas.py | 15 +++-- lms/services/canvas_api/_authenticated.py | 6 +- lms/services/canvas_api/_basic.py | 6 +- lms/services/canvas_api/client.py | 22 ++++---- lms/services/canvas_studio.py | 28 +++++----- lms/services/course.py | 18 +++--- lms/services/d2l_api/_basic.py | 4 +- lms/services/d2l_api/client.py | 2 +- lms/services/dashboard.py | 20 +++---- lms/services/digest.py | 4 +- lms/services/email_preferences.py | 8 +-- lms/services/event.py | 4 +- lms/services/exceptions.py | 4 +- lms/services/grant_token.py | 2 +- lms/services/group_info.py | 2 +- lms/services/grouping/service.py | 10 ++-- lms/services/h_api.py | 10 ++-- lms/services/http.py | 4 +- lms/services/hubspot/_client.py | 2 +- lms/services/hubspot/service.py | 4 +- lms/services/jstor/service.py | 10 ++-- lms/services/jwt.py | 6 +- lms/services/jwt_oauth2_token.py | 11 ++-- lms/services/launch_verifier.py | 8 +-- lms/services/lti_grading/_v11.py | 13 +++-- lms/services/lti_grading/_v13.py | 10 ++-- lms/services/lti_grading/interface.py | 10 ++-- lms/services/lti_names_roles.py | 2 +- lms/services/lti_registration.py | 2 +- lms/services/lti_user.py | 4 +- lms/services/ltia_http.py | 8 +-- lms/services/mailchimp.py | 6 +- lms/services/moodle.py | 14 ++--- lms/services/oauth1.py | 2 +- lms/services/oauth2_token.py | 8 +-- lms/services/oauth_http.py | 14 +++-- lms/services/organization.py | 32 ++++++----- lms/services/organization_usage_report.py | 10 ++-- lms/services/roster.py | 24 ++++---- lms/services/rsa_key.py | 4 +- lms/services/upsert.py | 4 +- lms/services/user.py | 8 +-- lms/services/vitalsource/_client.py | 8 +-- lms/services/vitalsource/model.py | 6 +- lms/services/vitalsource/service.py | 30 +++++----- lms/services/youtube.py | 2 +- lms/tasks/celery.py | 4 +- lms/tasks/email_digests.py | 12 ++-- lms/tasks/event.py | 6 +- lms/tasks/grading.py | 8 +-- lms/tasks/hubspot.py | 6 +- lms/tasks/organization.py | 6 +- lms/tasks/roster.py | 18 +++--- lms/tasks/task_done.py | 4 +- lms/validation/__init__.py | 2 +- lms/validation/_base.py | 8 +-- lms/validation/_exceptions.py | 8 +-- lms/validation/_lti_launch_params.py | 11 ++-- .../authentication/_bearer_token.py | 3 +- lms/validation/authentication/_exceptions.py | 2 +- lms/validation/authentication/_lti.py | 2 +- lms/validation/authentication/_oauth.py | 8 +-- lms/views/admin/_schemas.py | 6 +- lms/views/admin/application_instance/_core.py | 4 +- .../admin/application_instance/update.py | 2 +- .../admin/application_instance/upgrade.py | 2 +- lms/views/admin/assignment.py | 4 +- lms/views/admin/course.py | 4 +- lms/views/admin/email.py | 26 ++++----- lms/views/admin/lti_registration.py | 4 +- lms/views/admin/organization.py | 16 +++--- lms/views/admin/role.py | 2 +- lms/views/api/blackboard/files.py | 9 +-- lms/views/api/canvas/authorize.py | 6 +- lms/views/api/canvas/files.py | 2 +- lms/views/api/canvas/pages.py | 8 ++- lms/views/api/canvas_studio.py | 7 ++- lms/views/api/d2l/authorize.py | 2 +- lms/views/api/d2l/files.py | 9 +-- lms/views/api/exceptions.py | 4 +- lms/views/api/grading.py | 2 +- lms/views/api/moodle/files.py | 6 +- lms/views/api/moodle/pages.py | 6 +- lms/views/dashboard/api/grading.py | 2 +- lms/views/dashboard/pagination.py | 12 ++-- lms/views/email.py | 2 +- lms/views/favicon.py | 4 +- lms/views/helpers/_via.py | 2 +- lms/views/lti/basic_launch.py | 4 +- lms/views/lti/deep_linking.py | 6 +- lms/views/status.py | 2 +- pyproject.toml | 55 +++++-------------- tests/factories/oauth2_token.py | 2 +- tests/functional/bin/run_data_task_test.py | 10 ++-- tests/functional/conftest.py | 2 +- .../lti_certification/v13/conftest.py | 6 +- .../v13/core/test_bad_payloads.py | 2 +- tests/unit/conftest.py | 4 +- tests/unit/lms/config_test.py | 6 +- .../test_01_date_functions.py | 4 +- .../extensions/feature_flags/_helpers_test.py | 2 +- .../feature_flags/_providers_test.py | 2 +- .../lms/models/application_instance_test.py | 2 +- .../lms/models/assignment_grouping_test.py | 2 +- .../lms/models/assignment_membership_test.py | 2 +- tests/unit/lms/models/group_info_test.py | 2 +- tests/unit/lms/models/lti_params_test.py | 2 +- tests/unit/lms/models/lti_role_test.py | 2 +- tests/unit/lms/models/lti_user_test.py | 2 +- tests/unit/lms/models/oauth2_token_test.py | 14 ++--- tests/unit/lms/product/__init___test.py | 2 +- tests/unit/lms/product/factory_test.py | 2 +- tests/unit/lms/product/family_test.py | 2 +- .../product/moodle/_plugin/grouping_test.py | 2 +- tests/unit/lms/renderers_test.py | 2 +- tests/unit/lms/security_test.py | 4 +- .../lms/services/application_instance_test.py | 6 +- tests/unit/lms/services/assignment_test.py | 13 +++-- .../lms/services/async_oauth_http_test.py | 2 +- tests/unit/lms/services/auto_grading_test.py | 20 ++++--- .../services/blackboard_api/_basic_test.py | 2 +- .../services/blackboard_api/client_test.py | 2 +- .../canvas_api/_authenticated_test.py | 4 +- .../lms/services/canvas_api/client_test.py | 2 +- tests/unit/lms/services/canvas_studio_test.py | 10 ++-- tests/unit/lms/services/course_test.py | 2 +- tests/unit/lms/services/dashboard_test.py | 6 +- tests/unit/lms/services/digest_test.py | 24 ++++---- .../lms/services/email_preferences_test.py | 2 +- tests/unit/lms/services/exceptions_test.py | 2 +- tests/unit/lms/services/file_test.py | 4 +- tests/unit/lms/services/grant_token_test.py | 2 +- .../lms/services/grouping/service_test.py | 2 +- tests/unit/lms/services/h_api_test.py | 14 ++--- tests/unit/lms/services/http_test.py | 2 +- .../unit/lms/services/hubspot/service_test.py | 4 +- .../lms/services/jwt_oauth2_token_test.py | 16 +++--- tests/unit/lms/services/jwt_test.py | 22 +++++--- .../lms/services/lti_grading/_v13_test.py | 4 +- .../lms/services/lti_role_service_test.py | 2 +- tests/unit/lms/services/ltia_http_test.py | 4 +- tests/unit/lms/services/oauth2_token_test.py | 6 +- tests/unit/lms/services/oauth_http_test.py | 2 +- .../organization_usage_report_test.py | 16 +++--- tests/unit/lms/services/roster_test.py | 12 ++-- .../unit/lms/services/rsa_key_service_test.py | 4 +- tests/unit/lms/services/upsert_test.py | 4 +- .../lms/services/vitalsource/_client_test.py | 10 +++- .../lms/services/vitalsource/model_test.py | 4 +- tests/unit/lms/tasks/email_digests_test.py | 14 +++-- tests/unit/lms/tasks/event_test.py | 6 +- tests/unit/lms/tasks/grading_test.py | 2 +- tests/unit/lms/tasks/roster_test.py | 20 +++---- tests/unit/lms/tweens_test.py | 2 +- tests/unit/lms/validation/__init___test.py | 4 +- tests/unit/lms/validation/_exceptions_test.py | 2 +- .../lms/validation/_lti_launch_params_test.py | 2 +- .../admin/application_instance/create_test.py | 7 ++- .../admin/application_instance/update_test.py | 2 +- .../lms/views/admin/lti_registration_test.py | 2 +- .../unit/lms/views/admin/organization_test.py | 10 ++-- .../lms/views/api/canvas/authorize_test.py | 4 +- .../unit/lms/views/api/canvas_studio_test.py | 2 +- tests/unit/lms/views/api/exceptions_test.py | 2 +- tests/unit/lms/views/api/moodle/files_test.py | 8 +-- .../views/dashboard/api/assignment_test.py | 2 +- .../unit/lms/views/dashboard/api/user_test.py | 12 ++-- .../lms/views/dashboard/pagination_test.py | 2 +- tests/unit/lms/views/lti/basic_launch_test.py | 11 +++- tests/unit/lms/views/lti/deep_linking_test.py | 10 ++-- tests/unit/services.py | 2 +- 229 files changed, 771 insertions(+), 739 deletions(-) create mode 100644 .cookiecutter/includes/ruff/lint/per_file_ignores/tests/tail diff --git a/.cookiecutter/includes/ruff/lint/per_file_ignores/tests/tail b/.cookiecutter/includes/ruff/lint/per_file_ignores/tests/tail new file mode 100644 index 0000000000..fb041f64f4 --- /dev/null +++ b/.cookiecutter/includes/ruff/lint/per_file_ignores/tests/tail @@ -0,0 +1,6 @@ +"PT006", # Enforces a consistent style for the type of the `argnames` parameter to + # pytest.mark.parametrize. We have too many pre-existing violations of + # this. +"PT007", # Enforces a consistent style for the type of the `argvalues` parameter to + # pytest.mark.parametrize. We have too many pre-existing violations of + # this. diff --git a/bin/run_data_task.py b/bin/run_data_task.py index d8dfc4d4ac..ade705a143 100755 --- a/bin/run_data_task.py +++ b/bin/run_data_task.py @@ -4,7 +4,7 @@ This is a general mechanism for running tasks defined in SQL, however it's currently only used to perform the aggregations and mappings required for reporting. -""" +""" # noqa: EXE002 from argparse import ArgumentParser @@ -47,7 +47,7 @@ def main(): args = parser.parse_args() - with bootstrap(args.config_file) as env: # noqa: PLR1702 + with bootstrap(args.config_file) as env: request = env["request"] settings = env["registry"].settings @@ -62,18 +62,18 @@ def main(): ) # Run the update in a transaction, so we roll back if it goes wrong - with request.db.bind.connect() as connection: + with request.db.bind.connect() as connection: # noqa: SIM117 with connection.begin(): for script in scripts: if args.no_python and isinstance(script, PythonScript): - print(f"Skipping: {script}") + print(f"Skipping: {script}") # noqa: T201 continue for step in script.execute(connection, dry_run=args.dry_run): if args.dry_run: - print("Dry run!") + print("Dry run!") # noqa: T201 - print(step.dump(indent=" ") + "\n") + print(step.dump(indent=" ") + "\n") # noqa: T201 if __name__ == "__main__": diff --git a/lms/config.py b/lms/config.py index 35ac0e357a..bdf59e482c 100644 --- a/lms/config.py +++ b/lms/config.py @@ -31,7 +31,7 @@ def _aes_to_16_chars(string): try: return string.encode("ascii")[0:16] if string else None except UnicodeEncodeError as err: - raise SettingError("LMS_SECRET must contain only ASCII characters") from err + raise SettingError("LMS_SECRET must contain only ASCII characters") from err # noqa: EM101, TRY003 @dataclass(frozen=True) @@ -39,7 +39,7 @@ class _Setting: """The properties of a setting and how to read it.""" name: str - read_from: str = None # type: ignore + read_from: str = None # type: ignore # noqa: PGH003 value_mapper: Callable | None = None def __post_init__(self): diff --git a/lms/db/_columns.py b/lms/db/_columns.py index daa054b1a0..76985c3528 100644 --- a/lms/db/_columns.py +++ b/lms/db/_columns.py @@ -2,13 +2,13 @@ from sqlalchemy.orm import Mapped, mapped_column -def varchar_enum( # noqa: PLR0913, PLR0917 +def varchar_enum( # noqa: PLR0913 enum, default=None, max_length=64, - nullable=False, + nullable=False, # noqa: FBT002 server_default=None, - unique=False, + unique=False, # noqa: FBT002 ) -> Mapped: """Return a SA column type to store the python enum.Enum as a varchar in a table.""" return mapped_column( diff --git a/lms/db/_util.py b/lms/db/_util.py index 964f25c23c..aefc9e6793 100644 --- a/lms/db/_util.py +++ b/lms/db/_util.py @@ -3,13 +3,13 @@ from sqlalchemy.orm import Query -def compile_query(query: Query | Select, literal_binds: bool = True) -> str: +def compile_query(query: Query | Select, literal_binds: bool = True) -> str: # noqa: FBT001, FBT002 """ Return the SQL representation of `query` for postgres. :param literal_binds: Whether or not replace the query parameters by their values. """ - if isinstance(query, Query): + if isinstance(query, Query): # noqa: SIM108 # Support for SQLAlchemy 1.X style queryies, eg: db.query(Model).filter_by() statement = query.statement else: diff --git a/lms/events/event.py b/lms/events/event.py index 1f0507d88d..16d71ba4b6 100644 --- a/lms/events/event.py +++ b/lms/events/event.py @@ -1,5 +1,5 @@ +from collections.abc import Mapping from dataclasses import asdict, dataclass, field, fields -from typing import Mapping from pyramid.request import Request from sqlalchemy import inspect @@ -109,7 +109,7 @@ def from_instance(cls, instance, **kwargs): changes = {} instance_details = inspect(instance) for attr in instance_details.attrs: - history = instance_details.get_history(attr.key, True) + history = instance_details.get_history(attr.key, True) # noqa: FBT003 if not history.has_changes(): continue diff --git a/lms/events/subscribers.py b/lms/events/subscribers.py index 4f5268228b..dbd4f78ca1 100644 --- a/lms/events/subscribers.py +++ b/lms/events/subscribers.py @@ -7,5 +7,5 @@ @subscriber(BaseEvent) def handle_event(event: BaseEvent): """Record the event in the Event model's table.""" - assert event.request + assert event.request # noqa: S101 event.request.find_service(EventService).insert_event(event) diff --git a/lms/extensions/feature_flags/__init__.py b/lms/extensions/feature_flags/__init__.py index d123ab01a5..22c2bf5adb 100644 --- a/lms/extensions/feature_flags/__init__.py +++ b/lms/extensions/feature_flags/__init__.py @@ -176,7 +176,7 @@ def feature(request, feature_flag_name): def add_feature_flag_providers(_config, *providers): """Adapt feature_flags.add_providers().""" - providers = [config.maybe_dotted(provider) for provider in providers] # type:ignore + providers = [config.maybe_dotted(provider) for provider in providers] # type:ignore # noqa: PGH003 return feature_flags.add_providers(*providers) # Register the Pyramid request method and config directive. These are this diff --git a/lms/extensions/feature_flags/_helpers.py b/lms/extensions/feature_flags/_helpers.py index 784f201aad..82a5e7616c 100644 --- a/lms/extensions/feature_flags/_helpers.py +++ b/lms/extensions/feature_flags/_helpers.py @@ -63,8 +63,8 @@ def __init__(self, name, request): try: self._secret = request.registry.settings["feature_flags_cookie_secret"] except KeyError as err: - raise SettingError( - "The feature_flags_cookie_secret deployment setting is required" + raise SettingError( # noqa: TRY003 + "The feature_flags_cookie_secret deployment setting is required" # noqa: EM101 ) from err self._name = name diff --git a/lms/extensions/feature_flags/_providers.py b/lms/extensions/feature_flags/_providers.py index 87cb16a658..fcdf17e74d 100644 --- a/lms/extensions/feature_flags/_providers.py +++ b/lms/extensions/feature_flags/_providers.py @@ -11,8 +11,8 @@ __all__ = [ "config_file_provider", - "envvar_provider", "cookie_provider", + "envvar_provider", "query_string_provider", ] diff --git a/lms/extensions/feature_flags/views/cookie_form.py b/lms/extensions/feature_flags/views/cookie_form.py index 9919d246f9..11eddab80b 100644 --- a/lms/extensions/feature_flags/views/cookie_form.py +++ b/lms/extensions/feature_flags/views/cookie_form.py @@ -23,7 +23,7 @@ def get(self): return { "flags": flags, # The final state of each feature flag - "state": {flag: self._request.feature(flag) for flag in flags.keys()}, + "state": {flag: self._request.feature(flag) for flag in flags.keys()}, # noqa: SIM118 } @view_config(request_method="POST") diff --git a/lms/migrations/versions/088813d3e6f8_email_digest_enable_02.py b/lms/migrations/versions/088813d3e6f8_email_digest_enable_02.py index e702a9a314..ef20b388e8 100644 --- a/lms/migrations/versions/088813d3e6f8_email_digest_enable_02.py +++ b/lms/migrations/versions/088813d3e6f8_email_digest_enable_02.py @@ -24,4 +24,3 @@ def upgrade(): def downgrade(): """No downgrade section as we might manually change some values after running `upgrade`.""" - pass diff --git a/lms/migrations/versions/0c52a13c6cad_canvas_sso_urls.py b/lms/migrations/versions/0c52a13c6cad_canvas_sso_urls.py index 45046bf72c..32c1ea82eb 100644 --- a/lms/migrations/versions/0c52a13c6cad_canvas_sso_urls.py +++ b/lms/migrations/versions/0c52a13c6cad_canvas_sso_urls.py @@ -49,9 +49,9 @@ def upgrade() -> None: UPDATE lti_registration SET {field} = '{new_url}' WHERE {field} ='{old_url}' - """ + """ # noqa: S608 ) - print(f"\tUpdated lti_registration.{field}:", result.rowcount) + print(f"\tUpdated lti_registration.{field}:", result.rowcount) # noqa: T201 def downgrade() -> None: @@ -63,6 +63,6 @@ def downgrade() -> None: UPDATE lti_registration SET {field} = '{old_url}' WHERE {field} ='{new_url}' - """ + """ # noqa: S608 ) - print(f"\tDowngraded lti_registration.{field}:", result.rowcount) + print(f"\tDowngraded lti_registration.{field}:", result.rowcount) # noqa: T201 diff --git a/lms/migrations/versions/396f46318023_events_backfill.py b/lms/migrations/versions/396f46318023_events_backfill.py index 401bebc8b6..5f2d798d16 100644 --- a/lms/migrations/versions/396f46318023_events_backfill.py +++ b/lms/migrations/versions/396f46318023_events_backfill.py @@ -62,7 +62,7 @@ def upgrade(): ORDER BY lti_launches.created ASC """ ) - print("\tInserted lti_launches rows into events:", result.rowcount) + print("\tInserted lti_launches rows into events:", result.rowcount) # noqa: T201 def downgrade(): @@ -77,5 +77,5 @@ def downgrade(): -- will have empty assignments. AND assignment_id is null AND timestamp < '{CUT_OFF_DATE}' - """ + """ # noqa: S608 ) diff --git a/lms/migrations/versions/3a786e91f59c_email_digest_enable_04.py b/lms/migrations/versions/3a786e91f59c_email_digest_enable_04.py index e0ccbcd9f3..46a97577bc 100644 --- a/lms/migrations/versions/3a786e91f59c_email_digest_enable_04.py +++ b/lms/migrations/versions/3a786e91f59c_email_digest_enable_04.py @@ -34,9 +34,8 @@ def upgrade(): application_instances.id = candidates.id""" ) ) - print("\tEnabled email digest in new AIs", result.rowcount) + print("\tEnabled email digest in new AIs", result.rowcount) # noqa: T201 def downgrade(): """No downgrade section as we might manually change some values after running `upgrade`.""" - pass diff --git a/lms/migrations/versions/52755322151e_db_migration_to_back_fill_organizations.py b/lms/migrations/versions/52755322151e_db_migration_to_back_fill_organizations.py index 5497752bb4..1946262f8d 100644 --- a/lms/migrations/versions/52755322151e_db_migration_to_back_fill_organizations.py +++ b/lms/migrations/versions/52755322151e_db_migration_to_back_fill_organizations.py @@ -103,7 +103,7 @@ def connected_subgraphs(edges): # Put every node in a group on its own group_to_nodes = {node: {node} for node in itertools.chain(*edges)} # ... and record it's location (which will change) - node_to_group = {node: node for node in group_to_nodes.keys()} + node_to_group = {node: node for node in group_to_nodes.keys()} # noqa: SIM118 # Repeatedly merge groups if they are joined by an edge for left_node, right_node in edges: @@ -150,20 +150,20 @@ def pick_name(names): def upgrade(): db_session = sa.orm.Session(bind=op.get_bind()) - print("Clearing old organizations...") + print("Clearing old organizations...") # noqa: T201 op.execute("UPDATE application_instances SET organization_id = NULL") op.execute("DELETE FROM organization") - with open(__file__.replace(".py", ".sql"), encoding="utf-8") as handle: + with open(__file__.replace(".py", ".sql"), encoding="utf-8") as handle: # noqa: PTH123 query = handle.read() - print("Detecting GUIDs...") + print("Detecting GUIDs...") # noqa: T201 edges = list(db_session.execute(query)) - print("Grouping GUIDs...") + print("Grouping GUIDs...") # noqa: T201 grouped_guids = connected_subgraphs(edges) - print("Creating new organizations...") + print("Creating new organizations...") # noqa: T201 total = 0 for ai_ids in grouped_guids: @@ -179,7 +179,7 @@ def upgrade(): name=pick_name([ai.tool_consumer_instance_name for ai in ais]) ) total += 1 - print( + print( # noqa: T201 f"\tCreating '{organization.name}' for {len(ais)} application instance(s)" ) for ai in ais: @@ -187,7 +187,7 @@ def upgrade(): db_session.add(organization) - print(f"Created {total} organization(s). Done") + print(f"Created {total} organization(s). Done") # noqa: T201 def downgrade(): diff --git a/lms/migrations/versions/52ff45973d5b_remove_ext_id.py b/lms/migrations/versions/52ff45973d5b_remove_ext_id.py index ee79f5c1d5..0a97d274d2 100644 --- a/lms/migrations/versions/52ff45973d5b_remove_ext_id.py +++ b/lms/migrations/versions/52ff45973d5b_remove_ext_id.py @@ -29,7 +29,7 @@ def upgrade(): AND resource_link_id IS NULL""" ) # We expect this to affect no more that 3 rows after checking the data in production. - assert result.rowcount <= 3, "Trying to delete more rows that expected" + assert result.rowcount <= 3, "Trying to delete more rows that expected" # noqa: S101 # Make resource_link_id not nullable op.alter_column( diff --git a/lms/migrations/versions/9207d2192903_email_digest_enable_03.py b/lms/migrations/versions/9207d2192903_email_digest_enable_03.py index 2bfbe56fec..4db3007ade 100644 --- a/lms/migrations/versions/9207d2192903_email_digest_enable_03.py +++ b/lms/migrations/versions/9207d2192903_email_digest_enable_03.py @@ -24,4 +24,4 @@ def upgrade(): def downgrade(): """No downgrade section as we might manually change some values after running `upgrade`.""" - pass + pass # noqa: PIE790 diff --git a/lms/migrations/versions/a9e5a33e96b7_backfill_lmsuserassignmentmembership.py b/lms/migrations/versions/a9e5a33e96b7_backfill_lmsuserassignmentmembership.py index db4e14497b..6eb8de0062 100644 --- a/lms/migrations/versions/a9e5a33e96b7_backfill_lmsuserassignmentmembership.py +++ b/lms/migrations/versions/a9e5a33e96b7_backfill_lmsuserassignmentmembership.py @@ -43,7 +43,7 @@ def upgrade() -> None: ) ) - pass + pass # noqa: PIE790 def downgrade() -> None: diff --git a/lms/migrations/versions/b11713314988_email_digest_enable_01.py b/lms/migrations/versions/b11713314988_email_digest_enable_01.py index 72cd12dfa1..12d416a8d9 100644 --- a/lms/migrations/versions/b11713314988_email_digest_enable_01.py +++ b/lms/migrations/versions/b11713314988_email_digest_enable_01.py @@ -48,7 +48,7 @@ def enable_email_digest(conn, limit=500): ), limit=limit, ) - print("\tEnabled email digest in new AIs", result.rowcount) + print("\tEnabled email digest in new AIs", result.rowcount) # noqa: T201 def upgrade(): @@ -58,4 +58,4 @@ def upgrade(): def downgrade(): """No downgrade section as we might manually change some values after running `upgrade`.""" - pass + pass # noqa: PIE790 diff --git a/lms/migrations/versions/b512a5cf64ed_public_id_migration.py b/lms/migrations/versions/b512a5cf64ed_public_id_migration.py index 5bc4fc72ef..594f217345 100644 --- a/lms/migrations/versions/b512a5cf64ed_public_id_migration.py +++ b/lms/migrations/versions/b512a5cf64ed_public_id_migration.py @@ -18,7 +18,7 @@ def upgrade() -> None: conn = op.get_bind() conn.execute( sa.text( - f"""UPDATE "organization" set public_id = '{region}.lms.org.' || "public_id";""" + f"""UPDATE "organization" set public_id = '{region}.lms.org.' || "public_id";""" # noqa: S608 ) ) diff --git a/lms/migrations/versions/db3779df3f44_back_fill_course_and_course_groups_exported_from_h_tables.py b/lms/migrations/versions/db3779df3f44_back_fill_course_and_course_groups_exported_from_h_tables.py index 8fb8347345..b30eefc42c 100644 --- a/lms/migrations/versions/db3779df3f44_back_fill_course_and_course_groups_exported_from_h_tables.py +++ b/lms/migrations/versions/db3779df3f44_back_fill_course_and_course_groups_exported_from_h_tables.py @@ -44,7 +44,7 @@ def upgrade(): continue authority_provided_id = group["authority_provided_id"] - created = datetime.datetime.strptime(group["created"], "%Y-%m-%dT%H:%M:%S.%fZ") + created = datetime.datetime.strptime(group["created"], "%Y-%m-%dT%H:%M:%S.%fZ") # noqa: DTZ007 # We can't tell which course groups belong to which application instance # in the period since we released sections and now, so instead we will @@ -76,14 +76,14 @@ def upgrade(): session.commit() log.info( - f"Inserted {rows_inserted_into_course_table} rows into course table with" + f"Inserted {rows_inserted_into_course_table} rows into course table with" # noqa: G004 " sections enabled" ) log.info( - f"Inserted {rows_inserted_into_course_groups_exported_from_h_table} rows into" + f"Inserted {rows_inserted_into_course_groups_exported_from_h_table} rows into" # noqa: G004 " course_groups_exported_from_h table" ) - log.info(f"Inserted {rows_inserted} rows in total") + log.info(f"Inserted {rows_inserted} rows in total") # noqa: G004 def downgrade(): @@ -114,7 +114,7 @@ def is_course_group(session, group): def maybe_commit(session, rows_inserted): """Commit the session every 1000 rows.""" if rows_inserted % 1000 == 0: - log.info(f"Commit {rows_inserted}") + log.info(f"Commit {rows_inserted}") # noqa: G004 session.commit() diff --git a/lms/migrations/versions/e5a9845d55d7_clean_grouping_and_assignment_titles.py b/lms/migrations/versions/e5a9845d55d7_clean_grouping_and_assignment_titles.py index aaa44b1300..3948000826 100644 --- a/lms/migrations/versions/e5a9845d55d7_clean_grouping_and_assignment_titles.py +++ b/lms/migrations/versions/e5a9845d55d7_clean_grouping_and_assignment_titles.py @@ -22,7 +22,7 @@ def upgrade() -> None: """ ) ) - print(f"Assignment titles updated: {result.rowcount}") + print(f"Assignment titles updated: {result.rowcount}") # noqa: T201 result = conn.execute( sa.text( @@ -32,7 +32,7 @@ def upgrade() -> None: """ ) ) - print(f"Empty assignment titles: {result.rowcount}") + print(f"Empty assignment titles: {result.rowcount}") # noqa: T201 result = conn.execute( sa.text( @@ -42,7 +42,7 @@ def upgrade() -> None: """ ) ) - print(f"Grouping titles updated: {result.rowcount}") + print(f"Grouping titles updated: {result.rowcount}") # noqa: T201 def downgrade() -> None: diff --git a/lms/migrations/versions/f3d631c110bf_canvas_files_enabled.py b/lms/migrations/versions/f3d631c110bf_canvas_files_enabled.py index e842b2bc77..4e57b2ae44 100644 --- a/lms/migrations/versions/f3d631c110bf_canvas_files_enabled.py +++ b/lms/migrations/versions/f3d631c110bf_canvas_files_enabled.py @@ -53,9 +53,9 @@ def upgrade(): ) """ ) - print("\tApplication instances marked with canvas->files_enabled:", result.rowcount) + print("\tApplication instances marked with canvas->files_enabled:", result.rowcount) # noqa: T201 def downgrade(): """No downgrade section as we might manually change some values after running `upgrade`.""" - pass + pass # noqa: PIE790 diff --git a/lms/models/_hashed_id.py b/lms/models/_hashed_id.py index e7f49cd46b..1e4ff7036e 100644 --- a/lms/models/_hashed_id.py +++ b/lms/models/_hashed_id.py @@ -11,7 +11,7 @@ def hashed_id(*parts): :param *parts: An iterable of objects which can be converted to strings :return: A string which can be used as an id """ - hash_object = hashlib.sha1() + hash_object = hashlib.sha1() # noqa: S324 for part in parts: hash_object.update(str(part).encode()) diff --git a/lms/models/application_instance.py b/lms/models/application_instance.py index 663bc4ba68..8a4ccf3b5d 100644 --- a/lms/models/application_instance.py +++ b/lms/models/application_instance.py @@ -199,8 +199,8 @@ def lms_host(self): # For some URLs urlparse(url).netloc returns an empty string. if not lms_host: - raise ValueError( - f"Couldn't parse self.lms_url ({self.lms_url}): urlparse() returned an empty netloc" + raise ValueError( # noqa: TRY003 + f"Couldn't parse self.lms_url ({self.lms_url}): urlparse() returned an empty netloc" # noqa: EM102 ) return lms_host diff --git a/lms/models/grouping.py b/lms/models/grouping.py index 914d82cf5b..36a58c806c 100644 --- a/lms/models/grouping.py +++ b/lms/models/grouping.py @@ -32,7 +32,7 @@ class Type(StrEnum): GROUP = "group" __tablename__ = "grouping" - __mapper_args__ = {"polymorphic_on": "type"} + __mapper_args__ = {"polymorphic_on": "type"} # noqa: RUF012 __table_args__ = ( # Within a given application instance no two groupings should have the # same authority_provided_id. @@ -54,7 +54,7 @@ class Type(StrEnum): # SQLAlchemy forced us to add this constraint in order to make the # ForeignKeyConstraint below work, otherwise you get this error: # - # sqlalchemy.exc.ProgrammingError: (psycopg2.errors.InvalidForeignKey) + # sqlalchemy.exc.ProgrammingError: (psycopg2.errors.InvalidForeignKey) # noqa: ERA001 # there is no unique constraint matching given keys for referenced # table "grouping" sa.UniqueConstraint("id", "application_instance_id"), @@ -155,27 +155,27 @@ def groupid(self, authority): class CanvasSection(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.CANVAS_SECTION} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.CANVAS_SECTION} # noqa: RUF012 class CanvasGroup(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.CANVAS_GROUP} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.CANVAS_GROUP} # noqa: RUF012 class BlackboardGroup(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.BLACKBOARD_GROUP} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.BLACKBOARD_GROUP} # noqa: RUF012 class D2LGroup(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.D2L_GROUP} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.D2L_GROUP} # noqa: RUF012 class MoodleGroup(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.MOODLE_GROUP} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.MOODLE_GROUP} # noqa: RUF012 class Course(Grouping): - __mapper_args__ = {"polymorphic_identity": Grouping.Type.COURSE} + __mapper_args__ = {"polymorphic_identity": Grouping.Type.COURSE} # noqa: RUF012 _assignments: Mapped[list["Assignment"]] = sa.orm.relationship( secondary="assignment_grouping", viewonly=True diff --git a/lms/models/lti_params.py b/lms/models/lti_params.py index 5a4d27f0c1..5799a7bfc6 100644 --- a/lms/models/lti_params.py +++ b/lms/models/lti_params.py @@ -83,7 +83,7 @@ def _apply_canvas_quirks(lti_params, request): # This is canvas only, call the right plugin directly # Importing the plugin here to avoid a circular dependency hell - from lms.product.canvas._plugin.misc import ( # noqa: PLC0415 + from lms.product.canvas._plugin.misc import ( CanvasMiscPlugin, ) diff --git a/lms/models/lti_registration.py b/lms/models/lti_registration.py index 038e7a7ef9..f337026c71 100644 --- a/lms/models/lti_registration.py +++ b/lms/models/lti_registration.py @@ -42,7 +42,7 @@ class LTIRegistration(CreatedUpdatedMixin, Base): @property def product_family(self) -> str: """To which LMS (Canvas, D2L, BB..) does this registration belong.""" - from lms.product.family import Family # noqa: PLC0415 + from lms.product.family import Family if self.issuer.endswith(".instructure.com"): return Family.CANVAS diff --git a/lms/models/lti_role.py b/lms/models/lti_role.py index 63277780c7..fb8da72d2b 100644 --- a/lms/models/lti_role.py +++ b/lms/models/lti_role.py @@ -106,7 +106,7 @@ class Role: class _RoleParser: """Close collaborator class for parsing roles.""" - _ROLE_REGEXP = [ + _ROLE_REGEXP = [ # noqa: RUF012 # LTI 1.1 scoped role re.compile(r"urn:lti:(?Pinstrole|role|sysrole):ims/lis/(?P\w+)"), # LTI 1.3 scoped role @@ -133,7 +133,7 @@ class _RoleParser: re.compile(r"^(?!http|urn)(?P\w+)"), ] - _SCOPE_MAP = { + _SCOPE_MAP = { # noqa: RUF012 # LTI 1.1 https://www.imsglobal.org/specs/ltiv1p0/implementation-guide "instrole": RoleScope.INSTITUTION, "role": RoleScope.COURSE, @@ -143,7 +143,7 @@ class _RoleParser: "institution": RoleScope.INSTITUTION, "membership": RoleScope.COURSE, } - _TYPE_MAP = { + _TYPE_MAP = { # noqa: RUF012 "AccountAdmin": RoleType.ADMIN, "Administrator": RoleType.ADMIN, "Alumni": RoleType.LEARNER, diff --git a/lms/models/lti_user.py b/lms/models/lti_user.py index 32d05789ac..8d62590768 100644 --- a/lms/models/lti_user.py +++ b/lms/models/lti_user.py @@ -115,7 +115,7 @@ def display_name( given_name = given_name.strip() family_name = family_name.strip() - name = " ".join((given_name, family_name)).strip() + name = " ".join((given_name, family_name)).strip() # noqa: FLY002 if not name: return "Anonymous" diff --git a/lms/product/blackboard/product.py b/lms/product/blackboard/product.py index 0ac19fdd02..e026a8ed12 100644 --- a/lms/product/blackboard/product.py +++ b/lms/product/blackboard/product.py @@ -12,12 +12,12 @@ class Blackboard(Product): family: Family = Family.BLACKBOARD - route: Routes = Routes( + route: Routes = Routes( # noqa: RUF009 oauth2_authorize="blackboard_api.oauth.authorize", oauth2_refresh="blackboard_api.oauth.refresh", ) - plugin_config: PluginConfig = PluginConfig( + plugin_config: PluginConfig = PluginConfig( # noqa: RUF009 grouping=BlackboardGroupingPlugin, course_copy=BlackboardCourseCopyPlugin, misc=BlackboardMiscPlugin, diff --git a/lms/product/canvas/_plugin/grouping.py b/lms/product/canvas/_plugin/grouping.py index b6867610d4..eb70007bea 100644 --- a/lms/product/canvas/_plugin/grouping.py +++ b/lms/product/canvas/_plugin/grouping.py @@ -24,7 +24,7 @@ def __init__( self, canvas_api, group_set_service: GroupSetService, - strict_section_membership: bool, + strict_section_membership: bool, # noqa: FBT001 request, ): self._canvas_api = canvas_api diff --git a/lms/product/canvas/_plugin/misc.py b/lms/product/canvas/_plugin/misc.py index 0448f9e60e..bceeb006f7 100644 --- a/lms/product/canvas/_plugin/misc.py +++ b/lms/product/canvas/_plugin/misc.py @@ -66,7 +66,7 @@ def get_assignment_configuration( return assignment_config - @lru_cache(1) + @lru_cache(1) # noqa: B019 def _get_document_url(self, request) -> str | None: """ Get the configured document for this LTI launch. @@ -99,7 +99,7 @@ def _from_deep_linking_provided_url(cls, _request, deep_linked_configuration: di back to us from the LMS. """ - if url := deep_linked_configuration.get("url"): + if url := deep_linked_configuration.get("url"): # noqa: SIM102 # Work around a bug in Canvas's handling of LTI Launch URLs in # SpeedGrader launches where query params get double-encoded. # See https://github.com/instructure/canvas-lms/issues/1486 @@ -159,9 +159,9 @@ def get_deep_linked_assignment_configuration(self, request) -> dict: ] for param in possible_parameters: - if value := request.params.get(param): - params[param] = value - elif value := request.lti_params.get(f"custom_{param}"): + if (value := request.params.get(param)) or ( + value := request.lti_params.get(f"custom_{param}") + ): params[param] = value return params diff --git a/lms/product/canvas/product.py b/lms/product/canvas/product.py index 5cae18fa10..283d6524d4 100644 --- a/lms/product/canvas/product.py +++ b/lms/product/canvas/product.py @@ -12,12 +12,12 @@ class Canvas(Product): family: Family = Family.CANVAS - route: Routes = Routes( + route: Routes = Routes( # noqa: RUF009 oauth2_authorize="canvas_api.oauth.authorize", oauth2_refresh="canvas_api.oauth.refresh", ) - plugin_config: PluginConfig = PluginConfig( + plugin_config: PluginConfig = PluginConfig( # noqa: RUF009 grouping=CanvasGroupingPlugin, course_copy=CanvasCourseCopyPlugin, misc=CanvasMiscPlugin, diff --git a/lms/product/d2l/_plugin/course_copy.py b/lms/product/d2l/_plugin/course_copy.py index 12db081010..48c852a927 100644 --- a/lms/product/d2l/_plugin/course_copy.py +++ b/lms/product/d2l/_plugin/course_copy.py @@ -1,4 +1,7 @@ -from lms.product.plugin.course_copy import CourseCopyFilesHelper, CourseCopyGroupsHelper +from lms.product.plugin.course_copy import ( # noqa: INP001 + CourseCopyFilesHelper, + CourseCopyGroupsHelper, +) from lms.services.d2l_api import D2LAPIClient diff --git a/lms/product/d2l/_plugin/grouping.py b/lms/product/d2l/_plugin/grouping.py index b0948e88a9..d48d921330 100644 --- a/lms/product/d2l/_plugin/grouping.py +++ b/lms/product/d2l/_plugin/grouping.py @@ -1,4 +1,4 @@ -from enum import StrEnum +from enum import StrEnum # noqa: INP001 from lms.models import Course, Grouping from lms.product.plugin.grouping import GroupError, GroupingPlugin diff --git a/lms/product/d2l/_plugin/misc.py b/lms/product/d2l/_plugin/misc.py index ffbd7f846a..b52c18572c 100644 --- a/lms/product/d2l/_plugin/misc.py +++ b/lms/product/d2l/_plugin/misc.py @@ -1,4 +1,4 @@ -from lms.product.plugin.misc import MiscPlugin +from lms.product.plugin.misc import MiscPlugin # noqa: INP001 class D2LMiscPlugin(MiscPlugin): diff --git a/lms/product/d2l/product.py b/lms/product/d2l/product.py index c7d172dae8..1c4e00d459 100644 --- a/lms/product/d2l/product.py +++ b/lms/product/d2l/product.py @@ -12,11 +12,11 @@ class D2L(Product): family: Family = Family.D2L - plugin_config: PluginConfig = PluginConfig( + plugin_config: PluginConfig = PluginConfig( # noqa: RUF009 grouping=D2LGroupingPlugin, misc=D2LMiscPlugin, course_copy=D2LCourseCopyPlugin ) - route: Routes = Routes( + route: Routes = Routes( # noqa: RUF009 oauth2_authorize="d2l_api.oauth.authorize", oauth2_refresh="d2l_api.oauth.refresh", ) diff --git a/lms/product/factory.py b/lms/product/factory.py index a399aeedd2..edc14fd9a9 100644 --- a/lms/product/factory.py +++ b/lms/product/factory.py @@ -5,10 +5,10 @@ def get_product_from_request(request) -> Product: """Get the correct product object from the provided request.""" - from lms.product.blackboard import Blackboard # noqa: PLC0415 - from lms.product.canvas import Canvas # noqa: PLC0415 - from lms.product.d2l import D2L # noqa: PLC0415 - from lms.product.moodle import Moodle # noqa: PLC0415 + from lms.product.blackboard import Blackboard + from lms.product.canvas import Canvas + from lms.product.d2l import D2L + from lms.product.moodle import Moodle family = Family.UNKNOWN settings = {} diff --git a/lms/product/moodle/_plugin/course_copy.py b/lms/product/moodle/_plugin/course_copy.py index a12d961583..72b96f488f 100644 --- a/lms/product/moodle/_plugin/course_copy.py +++ b/lms/product/moodle/_plugin/course_copy.py @@ -1,4 +1,7 @@ -from lms.product.plugin.course_copy import CourseCopyFilesHelper, CourseCopyGroupsHelper +from lms.product.plugin.course_copy import ( # noqa: INP001 + CourseCopyFilesHelper, + CourseCopyGroupsHelper, +) from lms.services.moodle import MoodleAPIClient diff --git a/lms/product/moodle/_plugin/grouping.py b/lms/product/moodle/_plugin/grouping.py index 83cd62269a..def3d8f4e3 100644 --- a/lms/product/moodle/_plugin/grouping.py +++ b/lms/product/moodle/_plugin/grouping.py @@ -1,4 +1,4 @@ -from enum import StrEnum +from enum import StrEnum # noqa: INP001 from lms.models import Course, Grouping from lms.product.plugin.grouping import GroupError, GroupingPlugin diff --git a/lms/product/moodle/_plugin/misc.py b/lms/product/moodle/_plugin/misc.py index bc067ed84e..1ff710c3b6 100644 --- a/lms/product/moodle/_plugin/misc.py +++ b/lms/product/moodle/_plugin/misc.py @@ -1,4 +1,4 @@ -from lms.models import Assignment +from lms.models import Assignment # noqa: INP001 from lms.product.plugin.misc import AssignmentConfig, MiscPlugin diff --git a/lms/product/moodle/product.py b/lms/product/moodle/product.py index d8582dadda..10c7d50916 100644 --- a/lms/product/moodle/product.py +++ b/lms/product/moodle/product.py @@ -10,12 +10,12 @@ class Moodle(Product): family: Family = Family.MOODLE - plugin_config: PluginConfig = PluginConfig( + plugin_config: PluginConfig = PluginConfig( # noqa: RUF009 grouping=MoodleGroupingPlugin, course_copy=MoodleCourseCopyPlugin, misc=MoodleMiscPlugin, ) - route: Routes = Routes() + route: Routes = Routes() # noqa: RUF009 settings_key = "moodle" diff --git a/lms/product/plugin/__init__.py b/lms/product/plugin/__init__.py index fe74edd0d5..f3ba99a2a6 100644 --- a/lms/product/plugin/__init__.py +++ b/lms/product/plugin/__init__.py @@ -1,4 +1,4 @@ -# type: ignore +# type: ignore # noqa: PGH003 from lms.product.plugin.course_copy import CourseCopyPlugin from lms.product.plugin.grouping import GroupingPlugin from lms.product.plugin.misc import MiscPlugin diff --git a/lms/product/plugin/course_copy.py b/lms/product/plugin/course_copy.py index f0f097afad..ba213286d2 100644 --- a/lms/product/plugin/course_copy.py +++ b/lms/product/plugin/course_copy.py @@ -132,13 +132,13 @@ class CourseCopyPlugin: # pragma: nocover """ def is_file_in_course(self, course_id, file_id): - raise NotImplementedError() + raise NotImplementedError def find_matching_file_in_course(self, original_file_id, new_course_id): - raise NotImplementedError() + raise NotImplementedError def find_matching_group_set_in_course(self, _course, group_set_id): - raise NotImplementedError() + raise NotImplementedError def find_matching_page_in_course(self, original_file_id, new_course_id): - raise NotImplementedError() + raise NotImplementedError diff --git a/lms/product/plugin/plugin.py b/lms/product/plugin/plugin.py index e4204b0de6..9c77947fd7 100644 --- a/lms/product/plugin/plugin.py +++ b/lms/product/plugin/plugin.py @@ -1,4 +1,4 @@ -# type: ignore +# type: ignore # noqa: PGH003 from dataclasses import dataclass from typing import cast diff --git a/lms/product/product.py b/lms/product/product.py index d6877ed1ec..3228ab72a4 100644 --- a/lms/product/product.py +++ b/lms/product/product.py @@ -3,7 +3,7 @@ from dataclasses import InitVar, dataclass from lms.product.family import Family -from lms.product.plugin import PluginConfig, Plugins # type: ignore +from lms.product.plugin import PluginConfig, Plugins # type: ignore # noqa: PGH003 @dataclass(frozen=True) @@ -44,8 +44,8 @@ class Product: plugin: Plugins settings: Settings - plugin_config: PluginConfig = PluginConfig() - route: Routes = Routes() + plugin_config: PluginConfig = PluginConfig() # noqa: RUF009 + route: Routes = Routes() # noqa: RUF009 family: Family = Family.UNKNOWN settings_key: str | None = None """Key in the ai.settings dictionary that holds the product specific settings""" diff --git a/lms/pshell.py b/lms/pshell.py index 14f0a7f32e..4d3b2e610c 100644 --- a/lms/pshell.py +++ b/lms/pshell.py @@ -1,4 +1,4 @@ -# type: ignore +# type: ignore # noqa: PGH003 import os import sys from contextlib import suppress @@ -9,13 +9,13 @@ def setup(env): - from h_testkit import ( # noqa: PLC0415 - set_factoryboy_sqlalchemy_session, # type: ignore + from h_testkit import ( + set_factoryboy_sqlalchemy_session, # type: ignore # noqa: PGH003 ) - sys.path = ["."] + sys.path + sys.path = ["."] + sys.path # noqa: RUF005 - from tests import factories # noqa: PLC0415 + from tests import factories sys.path = sys.path[1:] diff --git a/lms/resources/_js_config/__init__.py b/lms/resources/_js_config/__init__.py index 6560fa86e1..00758e42bd 100644 --- a/lms/resources/_js_config/__init__.py +++ b/lms/resources/_js_config/__init__.py @@ -339,13 +339,13 @@ def enable_lti_launch_mode(self, course, assignment: Assignment): }, } - def enable_file_picker_mode( # noqa: PLR0913 + def enable_file_picker_mode( self, form_action, form_fields, course: Course, assignment: Assignment | None = None, - prompt_for_title=False, + prompt_for_title=False, # noqa: FBT002 ): """ Put the JavaScript code into "file picker" mode. @@ -549,7 +549,7 @@ def auth_token(self): return BearerTokenSchema(self._request).authorization_param(self._lti_user) @property - @functools.lru_cache + @functools.lru_cache # noqa: B019 def _config(self): """ Return the current configuration dict. @@ -633,7 +633,7 @@ def _get_product_info(self) -> dict: return product_info @property - @functools.lru_cache + @functools.lru_cache # noqa: B019 def _hypothesis_client(self) -> dict[str, Any]: """ Return the config object for the Hypothesis client. diff --git a/lms/security.py b/lms/security.py index c4d09bd6e4..ef2efcfe59 100644 --- a/lms/security.py +++ b/lms/security.py @@ -104,14 +104,14 @@ def get_policy(request: Request): # noqa: C901, PLR0911 if path.startswith("/api/dashboard"): # For the dashboard API we prefer, in this order: - # - HeadersBearerTokenLTIUserPolicy() + # - HeadersBearerTokenLTIUserPolicy() # noqa: ERA001 # For requests authorized via an API token, this applies to LMS users # - # - LMSGoogleSecurityPolicy() + # - LMSGoogleSecurityPolicy() # noqa: ERA001 # If the header token policy didn't succeed we try the google auth cookie, for staff users policies = [HeadersBearerTokenLTIUserPolicy(), LMSGoogleSecurityPolicy()] for policy in policies: - if policy.identity(request): # type: ignore + if policy.identity(request): # type: ignore # noqa: PGH003 return policy if path in {"/lti_launches", "/content_item_selection"}: @@ -172,7 +172,7 @@ class LTIUserSecurityPolicy: """Security policy based on the information of an LTIUser.""" def get_lti_user(self, request): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError @staticmethod def _get_userid(lti_user): @@ -293,7 +293,7 @@ def __init__( secret: str, domain: str, email_preferences_service: EmailPreferencesService, - use_secure_cookie: bool, + use_secure_cookie: bool, # noqa: FBT001 ): self.cookie = AuthTktCookieHelper( secret=secret, diff --git a/lms/services/__init__.py b/lms/services/__init__.py index 41813936ec..4df0311c88 100644 --- a/lms/services/__init__.py +++ b/lms/services/__init__.py @@ -170,7 +170,7 @@ def includeme(config): # noqa: PLR0915 # Importing them here to: # - Don't pollute the lms.services namespace # - Ease some circular-dependency problems - from lms.product.plugin.course_copy import ( # noqa: PLC0415 + from lms.product.plugin.course_copy import ( CourseCopyFilesHelper, CourseCopyGroupsHelper, ) diff --git a/lms/services/application_instance.py b/lms/services/application_instance.py index a72766bc90..2ec7444270 100644 --- a/lms/services/application_instance.py +++ b/lms/services/application_instance.py @@ -59,7 +59,7 @@ def _email_or_domain_match(columns, email: str): This will match the full email if it contains '@' or interpret the text as a domain if not. This will search over all the provided fields. """ - return sa.or_( # type: ignore + return sa.or_( # type: ignore # noqa: PGH003 ( sa.func.lower(column) == email.lower() if "@" in email @@ -80,7 +80,7 @@ def __init__( self._aes_service = aes_service self._organization_service = organization_service - @lru_cache(maxsize=1) + @lru_cache(maxsize=1) # noqa: B019 def get_for_launch(self, id_) -> ApplicationInstance: """ Return the current request's `ApplicationInstance`. @@ -117,16 +117,16 @@ def get_for_launch(self, id_) -> ApplicationInstance: return application_instance - raise ApplicationInstanceNotFound() + raise ApplicationInstanceNotFound - @lru_cache(maxsize=1) + @lru_cache(maxsize=1) # noqa: B019 def get_by_id(self, id_) -> ApplicationInstance: try: return self._ai_search_query(id_=id_).one() except NoResultFound as err: - raise ApplicationInstanceNotFound() from err + raise ApplicationInstanceNotFound from err - @lru_cache(maxsize=128) + @lru_cache(maxsize=128) # noqa: B019 def get_by_consumer_key(self, consumer_key) -> ApplicationInstance: """ Return the `ApplicationInstance` with the given `consumer_key`. @@ -136,19 +136,19 @@ def get_by_consumer_key(self, consumer_key) -> ApplicationInstance: `ApplicationInstance` """ if not consumer_key: - raise ApplicationInstanceNotFound() + raise ApplicationInstanceNotFound try: return self._ai_search_query(consumer_key=consumer_key).one() except NoResultFound as err: - raise ApplicationInstanceNotFound() from err + raise ApplicationInstanceNotFound from err - @lru_cache(maxsize=128) + @lru_cache(maxsize=128) # noqa: B019 def get_by_deployment_id( self, issuer: str, client_id: str, deployment_id: str ) -> ApplicationInstance: if not all([issuer, client_id, deployment_id]): - raise ApplicationInstanceNotFound() + raise ApplicationInstanceNotFound try: return self._ai_search_query( @@ -156,7 +156,7 @@ def get_by_deployment_id( ).one() except NoResultFound as err: - raise ApplicationInstanceNotFound() from err + raise ApplicationInstanceNotFound from err def search( # noqa: PLR0913 self, @@ -260,7 +260,7 @@ def _ai_search_query( # noqa: C901, PLR0913 return query - def update_application_instance( # noqa: PLR0913, PLR0917 + def update_application_instance( # noqa: PLR0913 self, application_instance, name=None, @@ -301,7 +301,7 @@ def update_application_instance( # noqa: PLR0913, PLR0917 } ) - def create_application_instance( # noqa: PLR0913, PLR0917 + def create_application_instance( # noqa: PLR0913 self, lms_url, email, @@ -323,7 +323,7 @@ def create_application_instance( # noqa: PLR0913, PLR0917 shared_secret=secrets.token_hex(32), lms_url=lms_url, requesters_email=email, - created=datetime.utcnow(), + created=datetime.utcnow(), # noqa: DTZ003 # Some helpful defaults for settings settings={ "canvas": { @@ -386,7 +386,7 @@ def update_from_lti_params(self, application_instance, params: LTIParams): # This is a potentially misleading, as we can get here from deep-linked # "launches". Depending on whether you count that as a launch or not. - application_instance.last_launched = datetime.now() + application_instance.last_launched = datetime.now() # noqa: DTZ005 def factory(_context, request): diff --git a/lms/services/assignment.py b/lms/services/assignment.py index c077dea69f..3dbd35ecd0 100644 --- a/lms/services/assignment.py +++ b/lms/services/assignment.py @@ -54,7 +54,7 @@ def create_assignment(self, tool_consumer_instance_guid, resource_link_id): return assignment - def update_assignment( # noqa: PLR0913, PLR0917 + def update_assignment( # noqa: PLR0913 self, request, assignment: Assignment, @@ -280,7 +280,7 @@ def is_member(self, assignment: Assignment, h_userid: str) -> bool: assignment.membership.join(User).filter(User.h_userid == h_userid).first() ) - def get_assignments( # noqa: PLR0913 + def get_assignments( self, instructor_h_userid: str | None = None, admin_organization_ids: list[int] | None = None, @@ -336,7 +336,7 @@ def get_courses_assignments_count(self, **kwargs) -> dict[int, int]: .group_by(Assignment.course_id) ) - return {x.course_id: x.count for x in self._db.execute(query)} # type: ignore + return {x.course_id: x.count for x in self._db.execute(query)} # type: ignore # noqa: PGH003 def get_assignment_groups(self, assignment) -> Sequence[Grouping]: """Get the relevant groups for the assignment from the DB.""" diff --git a/lms/services/async_oauth_http.py b/lms/services/async_oauth_http.py index 2137f14255..e74107e24d 100644 --- a/lms/services/async_oauth_http.py +++ b/lms/services/async_oauth_http.py @@ -68,7 +68,7 @@ async def _prepare_requests(method, urls, **kwargs): for task in tasks: task.cancel() - raise ExternalAsyncRequestError() from err + raise ExternalAsyncRequestError() from err # noqa: RSE102 def factory(_context, request): diff --git a/lms/services/auto_grading.py b/lms/services/auto_grading.py index 3b954335c0..c96fec250a 100644 --- a/lms/services/auto_grading.py +++ b/lms/services/auto_grading.py @@ -63,7 +63,9 @@ def _search_query(self, assignment, statuses: list[str] | None = None): return query def get_last_grades( - self, assignment: Assignment, success=True + self, + assignment: Assignment, + success=True, # noqa: FBT002 ) -> dict[str, GradingSyncGrade]: """Return a dictionary keyed by h_userid, containing the most recent GradeSync.""" result = self._db.scalars( @@ -104,7 +106,7 @@ def calculate_grade( else: grade = 0 case ("all_or_nothing", "separate"): - assert auto_grading_config.required_replies is not None, ( + assert auto_grading_config.required_replies is not None, ( # noqa: S101 "'separate' auto grade config with empty replies" ) if ( @@ -121,7 +123,7 @@ def calculate_grade( grade = combined_count / auto_grading_config.required_annotations case ("scaled", "separate"): - assert auto_grading_config.required_replies is not None, ( + assert auto_grading_config.required_replies is not None, ( # noqa: S101 "'separate' auto grade config with empty replies" ) # Let's make sure we do not count annotations or replies above the requirement, otherwise, a person @@ -138,7 +140,7 @@ def calculate_grade( + auto_grading_config.required_replies ) case _: - raise ValueError("Unknown auto grading configuration") + raise ValueError("Unknown auto grading configuration") # noqa: EM101, TRY003 grade = min(1, grade) # Proportional grades are capped at 1 return round(grade, 2) diff --git a/lms/services/blackboard_api/_basic.py b/lms/services/blackboard_api/_basic.py index d097fc74e7..dbd5ed026d 100644 --- a/lms/services/blackboard_api/_basic.py +++ b/lms/services/blackboard_api/_basic.py @@ -45,7 +45,7 @@ def parse(self, *args, **kwargs): class BasicClient: """A low-level Blackboard API client.""" - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, blackboard_host, client_id, diff --git a/lms/services/blackboard_api/client.py b/lms/services/blackboard_api/client.py index 4be3d00f3e..2b71bb1143 100644 --- a/lms/services/blackboard_api/client.py +++ b/lms/services/blackboard_api/client.py @@ -78,7 +78,8 @@ def public_url(self, course_id, file_id): except ExternalRequestError as err: if err.status_code == 404: raise FileNotFoundInCourse( - "blackboard_file_not_found_in_course", file_id + "blackboard_file_not_found_in_course", # noqa: EM101 + file_id, ) from err raise @@ -100,7 +101,10 @@ def group_set_groups(self, course_id, group_set_id): return BlackboardListGroups(response).parse() def course_groups( - self, course_id, group_set_id=None, current_student_own_groups_only=True + self, + course_id, + group_set_id=None, + current_student_own_groups_only=True, # noqa: FBT002 ): """ Return the groups in a course. @@ -146,7 +150,9 @@ def course_groups( ) # We are going to be modifying `self_enrollment_groups` within the loop # Create a copy here to iterate over. - for group, response in zip(list(self_enrollment_groups), responses): + for group, response in zip( + list(self_enrollment_groups), responses, strict=False + ): # If we are a member of any of the SelfEnrollment groups # we'll get a 200 response from the endpoint if response.status == 200: diff --git a/lms/services/canvas.py b/lms/services/canvas.py index 4255dab4b5..2af8a3a114 100644 --- a/lms/services/canvas.py +++ b/lms/services/canvas.py @@ -5,14 +5,18 @@ class CanvasService: """A high level Canvas service.""" - api: CanvasAPIClient = None # type:ignore + api: CanvasAPIClient = None # type:ignore # noqa: PGH003 def __init__(self, canvas_api, course_copy_plugin): self.api = canvas_api self._course_copy_plugin = course_copy_plugin def public_url_for_file( - self, assignment, file_id, current_course_id, check_in_course=False + self, + assignment, + file_id, + current_course_id, + check_in_course=False, # noqa: FBT002 ): """ Return a public URL for file_id. @@ -31,12 +35,13 @@ def public_url_for_file( # If there's a previously stored mapping for file_id use that instead. effective_file_id = assignment.get_canvas_mapped_file_id(file_id) try: - if check_in_course: + if check_in_course: # noqa: SIM102 if not self._course_copy_plugin.is_file_in_course( current_course_id, effective_file_id ): - raise FileNotFoundInCourse( - "canvas_file_not_found_in_course", file_id + raise FileNotFoundInCourse( # noqa: TRY301 + "canvas_file_not_found_in_course", # noqa: EM101 + file_id, ) return self.api.public_url(effective_file_id) except (FileNotFoundInCourse, CanvasAPIPermissionError): diff --git a/lms/services/canvas_api/_authenticated.py b/lms/services/canvas_api/_authenticated.py index 55b3033e7e..75e5348072 100644 --- a/lms/services/canvas_api/_authenticated.py +++ b/lms/services/canvas_api/_authenticated.py @@ -18,7 +18,7 @@ class AuthenticatedClient: :raise CanvasAPIServerError: if the request fails for any other reason """ - def __init__( # noqa: PLR0913 + def __init__( self, basic_client, oauth2_token_service, @@ -43,7 +43,7 @@ def __init__( # noqa: PLR0913 self._client_secret = client_secret self._redirect_uri = redirect_uri - def send(self, method, path, schema, timeout=DEFAULT_TIMEOUT, params=None): # noqa: PLR0913 + def send(self, method, path, schema, timeout=DEFAULT_TIMEOUT, params=None): """ Send a Canvas API request, and retry it if there are OAuth problems. @@ -93,7 +93,7 @@ def get_refreshed_token(self, refresh_token): try: self._oauth2_token_service.try_lock_for_refresh() except CouldNotAcquireLock as exc: - raise ConcurrentTokenRefreshError() from exc + raise ConcurrentTokenRefreshError() from exc # noqa: RSE102 return self._send_token_request( grant_type="refresh_token", refresh_token=refresh_token diff --git a/lms/services/canvas_api/_basic.py b/lms/services/canvas_api/_basic.py index 9731779543..8bc79bf06d 100644 --- a/lms/services/canvas_api/_basic.py +++ b/lms/services/canvas_api/_basic.py @@ -41,7 +41,7 @@ def __init__(self, canvas_host, session=None): self._session = session or Session() self._canvas_host = canvas_host - def send( # noqa: PLR0913, PLR0917 + def send( # noqa: PLR0913 self, method, path, @@ -98,7 +98,7 @@ def _get_url(self, path, params, url_stub): ) def _send_prepared(self, request, schema, timeout, request_depth=1) -> list: - response: Response = None # type:ignore + response: Response = None # type:ignore # noqa: PGH003 try: response = self._session.send(request, timeout=timeout) @@ -106,7 +106,7 @@ def _send_prepared(self, request, schema, timeout, request_depth=1) -> list: except RequestException as err: CanvasAPIError.raise_from(err, request, response) - result: list = None # type: ignore + result: list = None # type: ignore # noqa: PGH003 try: result = schema(response).parse() except ExternalRequestError as err: diff --git a/lms/services/canvas_api/client.py b/lms/services/canvas_api/client.py index 0980dd43d0..a9f323c1cb 100644 --- a/lms/services/canvas_api/client.py +++ b/lms/services/canvas_api/client.py @@ -63,7 +63,7 @@ def __init__( file_service: FileService, pages_client: CanvasPagesClient, application_instance: ApplicationInstance, - folders_enabled: bool = False, + folders_enabled: bool = False, # noqa: FBT001, FBT002 ): """ Create a new CanvasAPIClient. @@ -164,7 +164,7 @@ def post_load(self, data, **_kwargs): # Return the contents of sections without the key return data["sections"] - def course_sections(self, course_id, with_students=False): + def course_sections(self, course_id, with_students=False): # noqa: FBT002 """ Return all the sections for the given course_id. @@ -201,7 +201,7 @@ def _validate_length(self, data, **_kwargs): # If we get as far as this method then data is guaranteed to be a list # so the only way it can be falsey is if it's an empty list. if not data: - raise marshmallow.ValidationError("Shorter than minimum length 1.") + raise marshmallow.ValidationError("Shorter than minimum length 1.") # noqa: EM101, TRY003 def users_sections(self, user_id, course_id): """ @@ -251,7 +251,7 @@ def post_load(self, data, **_kwargs): for enrollment in data["enrollments"] ] - @lru_cache(maxsize=128) + @lru_cache(maxsize=128) # noqa: B019 def list_files(self, course_id, sort="position") -> list[dict]: """ Return the list of files for the given `course_id`. @@ -318,7 +318,7 @@ def list_files(self, course_id, sort="position") -> list[dict]: items_by_parent[folder["folder_id"]].append(dict(folder, type="Folder")) # Find the root folder, the one with a None parent - root_folder_id = [f for f in folders if not f["folder_id"]][0]["id"] + root_folder_id = [f for f in folders if not f["folder_id"]][0]["id"] # noqa: RUF015 return sorted( self._files_tree(items_by_parent, folder_id=root_folder_id), @@ -386,7 +386,7 @@ class _ListFoldersSchema(RequestsResponseSchema): id = fields.Integer(required=True) updated_at = fields.String(required=True) - @lru_cache(maxsize=128) + @lru_cache(maxsize=128) # noqa: B019 def public_url(self, file_id): """ Get a new temporary public download URL for the file with the given ID. @@ -440,13 +440,13 @@ def group_category_groups(self, course_id, group_category_id): # The group set is not in the given course. # For example this could be because the course was copied (assignments in copied # courses still have the group set IDs from the original course) - raise CanvasAPIError( - f"Group set {group_category_id} doesn't belong to course {course_id}" + raise CanvasAPIError( # noqa: TRY003 + f"Group set {group_category_id} doesn't belong to course {course_id}" # noqa: EM102 ) return groups - def course_groups(self, course_id, only_own_groups=True, include_users=False): + def course_groups(self, course_id, only_own_groups=True, include_users=False): # noqa: FBT002 """ Get all the groups of a course. @@ -518,7 +518,7 @@ def user_groups(self, course_id, user_id, group_category_id=None): for user in group.get("users", []): if user["id"] == int(user_id): - groups.append(group) + groups.append(group) # noqa: PERF401 return groups @@ -562,7 +562,7 @@ def _ensure_sections_unique(cls, sections) -> list: duplicate = sections_by_id.get(section["id"]) if duplicate and section.get("name") != duplicate.get("name"): - raise CanvasAPIError(f"Duplicate section id on {section}") + raise CanvasAPIError(f"Duplicate section id on {section}") # noqa: EM102, TRY003 sections_by_id[section["id"]] = section diff --git a/lms/services/canvas_studio.py b/lms/services/canvas_studio.py index 9c3de79353..d4352fdbdd 100644 --- a/lms/services/canvas_studio.py +++ b/lms/services/canvas_studio.py @@ -324,7 +324,7 @@ def media_id_from_url(cls, url: str) -> str | None: def get_canonical_video_url(self, media_id: str) -> str: """Return the URL to associate with annotations on a Canvas Studio video.""" # We use the REST resource URL as a stable URL for the video. - # Example: "https://hypothesis.instructuremedia.com/api/public/v1/media/4" + # Example: "https://hypothesis.instructuremedia.com/api/public/v1/media/4" # noqa: ERA001 return self._api_url(f"v1/media/{media_id}") def get_video_download_url(self, media_id: str) -> str | None: @@ -385,7 +385,7 @@ def _api_request( self, path: str, schema_cls: type[RequestsResponseSchema], - as_admin=False, + as_admin=False, # noqa: FBT002 ) -> dict: """ Make a request to the Canvas Studio API and parse the JSON response. @@ -402,7 +402,7 @@ def _paginated_api_request( path: str, schema_cls: type[RequestsResponseSchema], field: str, - as_admin=False, + as_admin=False, # noqa: FBT002 ) -> list: """ Fetch a paginated collection via the Canvas Studio API. @@ -427,7 +427,7 @@ def _paginated_api_request( return collated - def _admin_api_request(self, path: str, allow_redirects=True) -> requests.Response: + def _admin_api_request(self, path: str, allow_redirects=True) -> requests.Response: # noqa: FBT002 """ Make a request to the Canvas Studio API using the admin user identity. @@ -453,8 +453,8 @@ def _admin_api_request(self, path: str, allow_redirects=True) -> requests.Respon # re-authenticate. That won't help for admin-authenticated # requests. if isinstance(err, OAuth2TokenError): - raise HTTPBadRequest( - "The Canvas Studio admin needs to authenticate the Hypothesis integration" + raise HTTPBadRequest( # noqa: TRY003 + "The Canvas Studio admin needs to authenticate the Hypothesis integration" # noqa: EM101 ) from err raise @@ -467,8 +467,8 @@ def _admin_api_request(self, path: str, allow_redirects=True) -> requests.Respon def _bare_api_request( self, path: str, - as_admin=False, - allow_redirects=True, + as_admin=False, # noqa: FBT002 + allow_redirects=True, # noqa: FBT002 params: Mapping[str, str | int] | None = None, ) -> requests.Response: """ @@ -517,8 +517,8 @@ def _admin_email(self) -> str: "canvas_studio", "admin_email" ) if not admin_email: - raise HTTPBadRequest( - "Admin account is not configured for Canvas Studio integration" + raise HTTPBadRequest( # noqa: TRY003 + "Admin account is not configured for Canvas Studio integration" # noqa: EM101 ) return admin_email @@ -527,7 +527,7 @@ def is_admin(self) -> bool: return self._request.lti_user.email == self._admin_email() @property - @lru_cache + @lru_cache # noqa: B019 def _admin_oauth_http(self) -> OAuthHTTPService: """ Return an OAuthHTTPService that makes calls using the admin user account. @@ -541,7 +541,7 @@ def _admin_oauth_http(self) -> OAuthHTTPService: # The caller should check for this condition before calling this method # and use the standard `self._oauth_http_service` property instead. - assert not self.is_admin() + assert not self.is_admin() # noqa: S101 # Find the (user_id, application_instance_id) for an admin user who # belongs to the same LMS as the user making the request, and has @@ -581,8 +581,8 @@ def _admin_oauth_http(self) -> OAuthHTTPService: admin_email, guid, ) - raise HTTPBadRequest( - "The Canvas Studio admin needs to authenticate the Hypothesis integration" + raise HTTPBadRequest( # noqa: TRY003 + "The Canvas Studio admin needs to authenticate the Hypothesis integration" # noqa: EM101 ) return oauth_http_factory( diff --git a/lms/services/course.py b/lms/services/course.py index 129a8135fb..328619c15f 100644 --- a/lms/services/course.py +++ b/lms/services/course.py @@ -1,7 +1,7 @@ import json +from collections.abc import Mapping from copy import deepcopy from datetime import datetime -from typing import Mapping from dateutil import parser from sqlalchemy import Select, select, union @@ -37,7 +37,7 @@ def __init__(self, db, application_instance, grouping_service: GroupingService): self._application_instance = application_instance self._grouping_service = grouping_service - def any_with_setting(self, group, key, value=True) -> bool: + def any_with_setting(self, group, key, value=True) -> bool: # noqa: FBT002 """ Return whether any course has the specified setting. @@ -83,7 +83,7 @@ def get_from_launch(self, product_family: Family, lti_params) -> Course: copied_from=historical_course, ) - def _search_query( # noqa: PLR0913, PLR0917 + def _search_query( # noqa: PLR0913 self, id_: int | None = None, context_id: str | None = None, @@ -118,7 +118,7 @@ def _search_query( # noqa: PLR0913, PLR0917 return query.limit(limit) - def search( # noqa: PLR0913, PLR0917 + def search( # noqa: PLR0913 self, id_: int | None = None, context_id: str | None = None, @@ -136,7 +136,7 @@ def search( # noqa: PLR0913, PLR0917 organization_ids=organization_ids, ).all() - def get_courses( # noqa: PLR0913 + def get_courses( self, instructor_h_userid: str | None = None, admin_organization_ids: list[int] | None = None, @@ -195,8 +195,8 @@ def courses_permission_check_query( admin_organization_ids: list[int] | None = None, course_ids: list[int] | None = None, ): - courses_where_instructor_query = select(None) # type: ignore - courses_where_admin_query = select(None) # type: ignore + courses_where_instructor_query = select(None) # type: ignore # noqa: PGH003 + courses_where_admin_query = select(None) # type: ignore # noqa: PGH003 if instructor_h_userid: # Get all the courses where instructor_h_userid is an instructor. This will query AssignmentMembership, which includes roles @@ -252,7 +252,7 @@ def _course_ids_with_role_query( ) ) - def get_by_context_id(self, context_id, raise_on_missing=False) -> Course | None: + def get_by_context_id(self, context_id, raise_on_missing=False) -> Course | None: # noqa: FBT002 """ Get a course (if one exists) by the GUID and context id. @@ -402,7 +402,7 @@ def _new_course_settings(self, context_id): if course_settings.get("canvas", "sections_enabled") and self._is_pre_sections( context_id ): - course_settings.set("canvas", "sections_enabled", False) + course_settings.set("canvas", "sections_enabled", False) # noqa: FBT003 return course_settings diff --git a/lms/services/d2l_api/_basic.py b/lms/services/d2l_api/_basic.py index b867de8eaf..1a5d2afc82 100644 --- a/lms/services/d2l_api/_basic.py +++ b/lms/services/d2l_api/_basic.py @@ -1,6 +1,6 @@ from lms.services.exceptions import ExternalRequestError, OAuth2TokenError -TOKEN_URL = "https://auth.brightspace.com/core/connect/token" +TOKEN_URL = "https://auth.brightspace.com/core/connect/token" # noqa: S105 """This is constant for all D2L instances""" API_VERSIONS = { @@ -19,7 +19,7 @@ class BasicClient: """A low-level D2L API client.""" - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, client_id, client_secret, diff --git a/lms/services/d2l_api/client.py b/lms/services/d2l_api/client.py index d1beee5c7c..3ac9cdd36c 100644 --- a/lms/services/d2l_api/client.py +++ b/lms/services/d2l_api/client.py @@ -43,7 +43,7 @@ class Meta: @validates_schema def validate_url(self, data, **_kwargs): if not data.get("is_broken", False) and not data.get("url"): - raise ValidationError("URL is required for topics", "url") + raise ValidationError("URL is required for topics", "url") # noqa: EM101, TRY003 class _D2LModuleSchema(Schema): diff --git a/lms/services/dashboard.py b/lms/services/dashboard.py index 4705c5b893..42c3d0d4b9 100644 --- a/lms/services/dashboard.py +++ b/lms/services/dashboard.py @@ -23,7 +23,7 @@ class DashboardService: - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, request, segment_service: SegmentService, @@ -48,7 +48,7 @@ def get_request_segment(self, request, authority_provided_id: str) -> LMSSegment """Get and authorize a segment for the given request.""" segment = self._segment_service.get_segment(authority_provided_id) if not segment: - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 if request.has_permission(Permissions.STAFF): # STAFF members in our admin pages can access all segments @@ -67,7 +67,7 @@ def get_request_segment(self, request, authority_provided_id: str) -> LMSSegment if not self._course_service.is_member( segment.lms_course.course, request.user.h_userid ): - raise HTTPUnauthorized() + raise HTTPUnauthorized() # noqa: RSE102 return segment @@ -75,7 +75,7 @@ def get_request_assignment(self, request, assigment_id: int) -> Assignment: """Get and authorize an assignment for the given request.""" assignment = self._assignment_service.get_by_id(assigment_id) if not assignment: - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 if request.has_permission(Permissions.STAFF): # STAFF members in our admin pages can access all assignments @@ -92,7 +92,7 @@ def get_request_assignment(self, request, assigment_id: int) -> Assignment: # Access to the assignment is determined by access to its course. if not self._course_service.is_member(assignment.course, request.user.h_userid): - raise HTTPUnauthorized() + raise HTTPUnauthorized() # noqa: RSE102 return assignment @@ -100,7 +100,7 @@ def get_request_course(self, request, course_id: int) -> Course: """Get and authorize a course for the given request.""" course = self._course_service.get_by_id(course_id) if not course: - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 if request.has_permission(Permissions.STAFF): # STAFF members in our admin pages can access all courses @@ -115,7 +115,7 @@ def get_request_course(self, request, course_id: int) -> Course: return course if not self._course_service.is_member(course, request.user.h_userid): - raise HTTPUnauthorized() + raise HTTPUnauthorized() # noqa: RSE102 return course @@ -192,7 +192,7 @@ def get_request_admin_organizations(self, request) -> list[Organization]: request_public_id ) if not organization: - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 return self._db.scalars( select(Organization).where( @@ -270,7 +270,7 @@ def get_segments_roster( self, segments: list[LMSSegment], h_userids: list[str] | None = None ) -> tuple[datetime | None, Select[tuple[LMSUser, bool]]]: """Return a query that fetches the roster for a list of segments.""" - assert len({segment.lms_course_id for segment in segments}) == 1, ( + assert len({segment.lms_course_id for segment in segments}) == 1, ( # noqa: S101 "Segments must belong to the same course" ) @@ -311,7 +311,7 @@ def _get_segment_rosters_last_updated( ] if all(rosters_last_updated): # Use the oldest last updated date as the last updated date for the combined roster - return min(rosters_last_updated) # type: ignore + return min(rosters_last_updated) # type: ignore # noqa: PGH003 return None diff --git a/lms/services/digest.py b/lms/services/digest.py index f72591e7f9..95d4ed3cea 100644 --- a/lms/services/digest.py +++ b/lms/services/digest.py @@ -34,13 +34,13 @@ def __init__( self._email_preferences_service = email_preferences_service self._sender = sender - def send_instructor_email_digest( # noqa: PLR0913 + def send_instructor_email_digest( self, h_userid, created_after, created_before, override_to_email=None, - deduplicate=True, + deduplicate=True, # noqa: FBT002 ): """Send instructor email digests for the given users and timeframe.""" annotation_dicts = self._h_api.get_annotations( diff --git a/lms/services/email_preferences.py b/lms/services/email_preferences.py index 5259f84f95..c5ac4be539 100644 --- a/lms/services/email_preferences.py +++ b/lms/services/email_preferences.py @@ -18,7 +18,7 @@ class InvalidTokenError(Exception): @dataclass(frozen=True) class EmailPrefs: - DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] + DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] # noqa: RUF012 h_userid: str mon: bool = True @@ -42,7 +42,7 @@ class TokenPayload: class EmailPreferencesService: - def __init__( # noqa: PLR0913 + def __init__( self, db, secret: str, @@ -97,14 +97,14 @@ def decode(self, url: str) -> TokenPayload: try: token = parse_qs(urlparse(url).query)["token"][0] except (KeyError, ValueError) as err: - raise UnrecognisedURLError() from err + raise UnrecognisedURLError from err try: return TokenPayload( **self._jwt_service.decode_with_secret(token, self._secret) ) except (ExpiredJWTError, InvalidJWTError) as err: - raise InvalidTokenError() from err + raise InvalidTokenError from err KEY_PREFIX = "instructor_email_digests.days." diff --git a/lms/services/event.py b/lms/services/event.py index e36e111eee..e5159ae9b3 100644 --- a/lms/services/event.py +++ b/lms/services/event.py @@ -31,7 +31,7 @@ def insert_event(self, event: BaseEvent): self._db.add(db_event) if event.user_id: - for role_id in event.role_ids or [None]: # type: ignore + for role_id in event.role_ids or [None]: # type: ignore # noqa: PGH003 self._db.add( EventUser( event=db_event, user_id=event.user_id, lti_role_id=role_id @@ -56,7 +56,7 @@ def queue_event(event: BaseEvent) -> None: except OperationalError: log.exception("Error while queueing event") - @lru_cache(maxsize=10) + @lru_cache(maxsize=10) # noqa: B019 def _get_type_pk(self, type_: EventType.Type) -> int: """Cache the PK of the event_type table to avoid an extra query while inserting events.""" event_type = self._db.query(EventType).filter_by(type=type_).one_or_none() diff --git a/lms/services/exceptions.py b/lms/services/exceptions.py index 4ab4b53dcc..7b046d6d47 100644 --- a/lms/services/exceptions.py +++ b/lms/services/exceptions.py @@ -43,13 +43,13 @@ class ExternalRequestError(Exception): :type validation_errors: JSON-serializable dict """ - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, message=None, request=None, response=None, validation_errors=None, - refreshable=False, + refreshable=False, # noqa: FBT002 refresh_route: str | None = None, refresh_service: Service | None = None, ): diff --git a/lms/services/grant_token.py b/lms/services/grant_token.py index e7c874a462..410195bf46 100644 --- a/lms/services/grant_token.py +++ b/lms/services/grant_token.py @@ -31,7 +31,7 @@ def generate_token(self, h_user): :type h_user: models.HUser """ - now = datetime.datetime.utcnow() + now = datetime.datetime.utcnow() # noqa: DTZ003 claims = { "aud": urlparse(self._h_api_url_public).hostname, diff --git a/lms/services/group_info.py b/lms/services/group_info.py index 15ed8bca06..7d60e4f102 100644 --- a/lms/services/group_info.py +++ b/lms/services/group_info.py @@ -10,7 +10,7 @@ def __init__(self, _context, request): self._db = request.db self._lti_user = request.lti_user - _GROUPING_TYPES = { + _GROUPING_TYPES = { # noqa: RUF012 "course": "course_group", "canvas_section": "section_group", "canvas_group": "canvas_group_group", diff --git a/lms/services/grouping/service.py b/lms/services/grouping/service.py index c2196dae42..cb33167281 100644 --- a/lms/services/grouping/service.py +++ b/lms/services/grouping/service.py @@ -27,10 +27,10 @@ def get_authority_provided_id( guid = self.application_instance.tool_consumer_instance_guid if type_ == Grouping.Type.COURSE: - assert parent is None, "Course groupings can't have a parent" + assert parent is None, "Course groupings can't have a parent" # noqa: S101 return hashed_id(guid, lms_id) - assert parent is not None, "Non-course groupings must have a parent" + assert parent is not None, "Non-course groupings must have a parent" # noqa: S101 if type_ == Grouping.Type.CANVAS_SECTION: return hashed_id(guid, parent.lms_id, lms_id) @@ -186,7 +186,7 @@ def get_sections( lti_roles=lti_user.lti_roles, ) - def get_groups( # noqa: PLR0913 + def get_groups( self, user: User, lti_user: LTIUser, @@ -244,9 +244,7 @@ def get_launch_grouping_type(self, request, course, assignment) -> Grouping.Type return Grouping.Type.COURSE - def _to_groupings( # noqa: PLR0913 - self, user, groupings, course, type_, lti_roles: list[LTIRole] - ): + def _to_groupings(self, user, groupings, course, type_, lti_roles: list[LTIRole]): if groupings and not isinstance(groupings[0], Grouping): groupings = [ { diff --git a/lms/services/h_api.py b/lms/services/h_api.py index d29894df02..071eca11df 100644 --- a/lms/services/h_api.py +++ b/lms/services/h_api.py @@ -58,7 +58,7 @@ class HAPI: class HAPIGroup: authority_provided_id: str - def __init__( # noqa: PLR0913 + def __init__( self, authority, client_id, @@ -79,7 +79,7 @@ def execute_bulk(self, commands): """ commands = list(commands) - commands = [ + commands = [ # noqa: RUF005 CommandBuilder.configure( effective_user=HUser(username="lms").userid(self._authority), total_instructions=len(commands) + 1, @@ -145,7 +145,7 @@ def get_annotations( for line in response.iter_lines(): annotation = json.loads(line) author = annotation.get("author") - if author: + if author: # noqa: SIM102 if "username" in author and "userid" not in author: author["userid"] = self.get_userid(author["username"]) yield annotation @@ -217,7 +217,7 @@ def get_annotation_counts( ) return response.json() - def _api_request(self, method, path, body=None, headers=None, stream=False): # noqa: PLR0913 + def _api_request(self, method, path, body=None, headers=None, stream=False): # noqa: FBT002 """ Send any kind of HTTP request to the h API and return the response. @@ -249,7 +249,7 @@ def _api_request(self, method, path, body=None, headers=None, stream=False): # **request_args, ) except ExternalRequestError as err: - raise HAPIError("Connecting to Hypothesis failed", err.response) from err + raise HAPIError("Connecting to Hypothesis failed", err.response) from err # noqa: EM101, TRY003 return response diff --git a/lms/services/http.py b/lms/services/http.py index 768d4300d0..2eed77b264 100644 --- a/lms/services/http.py +++ b/lms/services/http.py @@ -1,4 +1,4 @@ -from requests import RequestException, Response, Session +from requests import RequestException, Response, Session # noqa: A005 from lms.services.exceptions import ExternalRequestError @@ -7,7 +7,7 @@ class HTTPService: """Send HTTP requests with `requests` and receive the responses.""" # This is here mostly to let auto-spec know about it in the tests - session: Session = None # type: ignore + session: Session = None # type: ignore # noqa: PGH003 """The underlying requests Session.""" def __init__(self): diff --git a/lms/services/hubspot/_client.py b/lms/services/hubspot/_client.py index c736047eef..5c902a15a2 100644 --- a/lms/services/hubspot/_client.py +++ b/lms/services/hubspot/_client.py @@ -50,7 +50,7 @@ def import_billables(self, billables: list[tuple[str, int, int]], date_: date): files = [ { - "fileName": os.path.basename(csv_file.name), + "fileName": os.path.basename(csv_file.name), # noqa: PTH119 "fileFormat": "CSV", "fileImportPage": { "hasHeader": False, diff --git a/lms/services/hubspot/service.py b/lms/services/hubspot/service.py index ea9ea8c67b..1b292271a0 100644 --- a/lms/services/hubspot/service.py +++ b/lms/services/hubspot/service.py @@ -128,7 +128,7 @@ def date_or_timestamp(value: str | int | None) -> date | None: if not value: return None try: - return date.fromtimestamp(int(value) / 1000) + return date.fromtimestamp(int(value) / 1000) # noqa: DTZ012 except ValueError: # Date is already formatted, return it as a date object - return datetime.strptime(str(value), "%Y-%m-%d").date() + return datetime.strptime(str(value), "%Y-%m-%d").date() # noqa: DTZ007 diff --git a/lms/services/jstor/service.py b/lms/services/jstor/service.py index c6d3991f81..b782a98e67 100644 --- a/lms/services/jstor/service.py +++ b/lms/services/jstor/service.py @@ -21,7 +21,7 @@ class JSTORService: DEFAULT_DOI_PREFIX = "10.2307" """Used when no DOI prefix can be found.""" - def __init__(self, api_url, secret, enabled, site_code, headers=None): # noqa: PLR0913 + def __init__(self, api_url, secret, enabled, site_code, headers=None): """ Initialise the JSTOR service. @@ -63,8 +63,8 @@ def via_url(self, request, document_url): ).text if not s3_url.startswith("https://"): - raise ExternalRequestError( - f"Expected to get an S3 URL but got: '{s3_url}' instead" + raise ExternalRequestError( # noqa: TRY003 + f"Expected to get an S3 URL but got: '{s3_url}' instead" # noqa: EM102 ) return via_url( @@ -147,8 +147,8 @@ def thumbnail(self, article_id: str) -> str: raise if not data_uri.startswith("data:"): - raise ExternalRequestError( - f"Expected to get data URI but got '{data_uri}' instead" + raise ExternalRequestError( # noqa: TRY003 + f"Expected to get data URI but got '{data_uri}' instead" # noqa: EM102 ) return data_uri diff --git a/lms/services/jwt.py b/lms/services/jwt.py index 398bda234e..2bdba9c89b 100644 --- a/lms/services/jwt.py +++ b/lms/services/jwt.py @@ -52,9 +52,9 @@ def decode_with_secret(cls, jwt_str, secret) -> dict: leeway=cls.LEEWAY, ) except ExpiredSignatureError as err: - raise ExpiredJWTError() from err + raise ExpiredJWTError() from err # noqa: RSE102 except InvalidTokenError as err: - raise InvalidJWTError() from err + raise InvalidJWTError() from err # noqa: RSE102 del payload["exp"] return payload @@ -71,7 +71,7 @@ def encode_with_secret(cls, payload: dict, secret, lifetime): :return: the JWT string """ payload = copy.deepcopy(payload) - payload["exp"] = datetime.datetime.utcnow() + lifetime + payload["exp"] = datetime.datetime.utcnow() + lifetime # noqa: DTZ003 jwt_str = jwt.encode(payload, secret, algorithm="HS256") diff --git a/lms/services/jwt_oauth2_token.py b/lms/services/jwt_oauth2_token.py index bb17d40a66..183611426e 100644 --- a/lms/services/jwt_oauth2_token.py +++ b/lms/services/jwt_oauth2_token.py @@ -29,13 +29,16 @@ def save_token( self._db.add(token) token.access_token = access_token - token.received_at = datetime.now() - token.expires_at = datetime.now() + timedelta(seconds=expires_in) + token.received_at = datetime.now() # noqa: DTZ005 + token.expires_at = datetime.now() + timedelta(seconds=expires_in) # noqa: DTZ005 return token def get_token( - self, lti_registration, scopes: list[str], exclude_expired=True + self, + lti_registration, + scopes: list[str], + exclude_expired=True, # noqa: FBT002 ) -> JWTOAuth2Token | None: """ Get a token for the given registration and scopes if present in the DB. @@ -51,7 +54,7 @@ def get_token( if exclude_expired: query = query.filter( (JWTOAuth2Token.expires_at - timedelta(seconds=self.EXPIRATION_LEEWAY)) - >= datetime.now() + >= datetime.now() # noqa: DTZ005 ) return query.one_or_none() diff --git a/lms/services/launch_verifier.py b/lms/services/launch_verifier.py index dbde60af1f..9a65c49d85 100644 --- a/lms/services/launch_verifier.py +++ b/lms/services/launch_verifier.py @@ -78,7 +78,7 @@ def _verify(self): method = self._request.method if method != "POST": - raise LTIOAuthError("LTI launches should use POST") + raise LTIOAuthError("LTI launches should use POST") # noqa: EM101, TRY003 is_valid, _request = self._oauth1_endpoint.validate_request( # The docs for `validate_request` say to send the full URL with @@ -92,7 +92,7 @@ def _verify(self): ) if not is_valid: - raise LTIOAuthError("OAuth signature is not valid") + raise LTIOAuthError("OAuth signature is not valid") # noqa: EM101, TRY003 class _OAuthRequestValidator(RequestValidator): @@ -118,7 +118,7 @@ def check_nonce(self, nonce): # noqa: ARG002 return True - def validate_timestamp_and_nonce( # noqa: PLR0913, PLR0917 + def validate_timestamp_and_nonce( # noqa: PLR0913 # Not our design, we have to fit in with this API self, client_key, # noqa: ARG002 @@ -150,4 +150,4 @@ def get_client_secret(self, client_key, request): # noqa: ARG002 client_key ).shared_secret except ApplicationInstanceNotFound as err: - raise ConsumerKeyLaunchVerificationError() from err + raise ConsumerKeyLaunchVerificationError from err diff --git a/lms/services/lti_grading/_v11.py b/lms/services/lti_grading/_v11.py index 851852f4a8..717a111618 100644 --- a/lms/services/lti_grading/_v11.py +++ b/lms/services/lti_grading/_v11.py @@ -12,7 +12,7 @@ class LTI11GradingService(LTIGradingService): # See: LTI1.1 Outcomes https://www.imsglobal.org/specs/ltiomv1p0/specification - def __init__( # noqa: PLR0913 + def __init__( self, db, line_item_url, @@ -44,7 +44,7 @@ def read_result(self, grading_id) -> GradingResult: raise - try: + try: # noqa: SIM105 result.score = float( response["readResultResponse"]["result"]["resultScore"]["textString"] ) @@ -79,7 +79,7 @@ def sync_grade( LMSUserAssignmentMembership.lti_v11_lis_result_sourcedid.is_not(None), ) ).first() - assert ( + assert ( # noqa: S101, PT018 assignment_membership and assignment_membership.lti_v11_lis_result_sourcedid ), ( "Trying to grade a student without a membership or membership without lti_v11_lis_result_sourcedid" @@ -125,8 +125,9 @@ def _send_request( try: data = xmltodict.parse(response.text) except ExpatError as err: - raise ExternalRequestError( - "Unable to parse XML response from LTI Outcomes service", response + raise ExternalRequestError( # noqa: TRY003 + "Unable to parse XML response from LTI Outcomes service", # noqa: EM101 + response, ) from err try: @@ -146,7 +147,7 @@ def _get_body(cls, data): "imsx_codeMajor" ] except KeyError as err: - raise ExternalRequestError("Malformed LTI outcome response") from err + raise ExternalRequestError("Malformed LTI outcome response") from err # noqa: EM101, TRY003 if status != "success": raise ExternalRequestError(message="LTI outcome request failed") diff --git a/lms/services/lti_grading/_v13.py b/lms/services/lti_grading/_v13.py index aa04fb2d16..3b8543f6b9 100644 --- a/lms/services/lti_grading/_v13.py +++ b/lms/services/lti_grading/_v13.py @@ -15,7 +15,7 @@ class LTI13GradingService(LTIGradingService): # See: LTI1.3 Assignment and Grade Services https://www.imsglobal.org/spec/lti-ags/v2p0 - LTIA_SCOPES = [ + LTIA_SCOPES = [ # noqa: RUF012 "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", # Although this is implied by scope/lineitem according to the spec # Moodle requires this one explicitly for the GET @@ -24,7 +24,7 @@ class LTI13GradingService(LTIGradingService): "https://purl.imsglobal.org/spec/lti-ags/scope/score", ] - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, line_item_url, line_item_container_url, @@ -80,7 +80,7 @@ def read_result(self, grading_id) -> GradingResult: if not results: return result - try: + try: # noqa: SIM105 result.score = results[-1]["resultScore"] / results[-1]["resultMaximum"] except (TypeError, ZeroDivisionError, KeyError, IndexError): pass @@ -92,7 +92,7 @@ def read_result(self, grading_id) -> GradingResult: def get_score_maximum(self, resource_link_id) -> float | None: return self._read_grading_configuration(resource_link_id).get("scoreMaximum") - def sync_grade( # noqa: PLR0913 + def sync_grade( self, application_instance: ApplicationInstance, assignment: Assignment, @@ -106,7 +106,7 @@ def sync_grade( # noqa: PLR0913 This is very similar to `record_result` but not scoped to the request context, taking all the necessary information as parameters. """ - assert lms_user.lti_v13_user_id, ( + assert lms_user.lti_v13_user_id, ( # noqa: S101 "Trying to grade a student without lti_v13_user_id" ) payload = LTI13GradingService._record_score_payload( diff --git a/lms/services/lti_grading/interface.py b/lms/services/lti_grading/interface.py index b715940bf5..5523f4e18a 100644 --- a/lms/services/lti_grading/interface.py +++ b/lms/services/lti_grading/interface.py @@ -65,7 +65,7 @@ def read_result(self, grading_id) -> GradingResult: :param grading_id: The submission id :return: The score or `None` if no score has been submitted. """ - raise NotImplementedError() + raise NotImplementedError def record_result(self, grading_id, score=None, pre_record_hook=None, comment=None): """ @@ -85,9 +85,9 @@ def record_result(self, grading_id, score=None, pre_record_hook=None, comment=No :raise TypeError: if the given pre_record_hook returns a non-dict """ - raise NotImplementedError() + raise NotImplementedError - def sync_grade( # noqa: PLR0913 + def sync_grade( self, application_instance: ApplicationInstance, assignment: Assignment, @@ -101,7 +101,7 @@ def sync_grade( # noqa: PLR0913 This is very similar to `record_result` but not scoped to the request context, taking all the necessary information as parameters. """ - raise NotImplementedError() + raise NotImplementedError def create_line_item(self, resource_link_id, label): """ @@ -109,4 +109,4 @@ def create_line_item(self, resource_link_id, label): https://www.imsglobal.org/spec/lti-ags/v2p0#container-request-filters """ - raise NotImplementedError() + raise NotImplementedError diff --git a/lms/services/lti_names_roles.py b/lms/services/lti_names_roles.py index 1624915dfc..405549b247 100644 --- a/lms/services/lti_names_roles.py +++ b/lms/services/lti_names_roles.py @@ -25,7 +25,7 @@ class Member(TypedDict): class LTINamesRolesService: - LTIA_SCOPES = [ + LTIA_SCOPES = [ # noqa: RUF012 "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly" ] diff --git a/lms/services/lti_registration.py b/lms/services/lti_registration.py index cdbf480c92..09c05090a2 100644 --- a/lms/services/lti_registration.py +++ b/lms/services/lti_registration.py @@ -54,7 +54,7 @@ def _registration_search_query( return query - def create_registration( # noqa: PLR0913 + def create_registration( self, issuer: str, client_id: str, diff --git a/lms/services/lti_user.py b/lms/services/lti_user.py index 04537b4a4b..6ae2cdfea1 100644 --- a/lms/services/lti_user.py +++ b/lms/services/lti_user.py @@ -66,7 +66,7 @@ def serialize(lti_user: LTIUser) -> dict: def deserialize(self, **kwargs: dict) -> LTIUser: """Create an LTIUser based on kwargs.""" application_instance = self._application_instance_service.get_for_launch( - kwargs["application_instance_id"] # type: ignore + kwargs["application_instance_id"] # type: ignore # noqa: PGH003 ) lti_roles = self._lti_roles_service.get_roles(str(kwargs["roles"])) effective_lti_roles = ( @@ -86,7 +86,7 @@ def deserialize(self, **kwargs: dict) -> LTIUser: effective_lti_roles=effective_lti_roles, application_instance=application_instance, lti=lti, - **kwargs, # type: ignore + **kwargs, # type: ignore # noqa: PGH003 ) diff --git a/lms/services/ltia_http.py b/lms/services/ltia_http.py index 7fd7114848..27ad20837a 100644 --- a/lms/services/ltia_http.py +++ b/lms/services/ltia_http.py @@ -27,7 +27,7 @@ def __init__( self._plugin = plugin self._jwt_oauth2_token_service = jwt_oauth2_token_service - def request( # noqa: PLR0913 + def request( self, lti_registration: LTIRegistration, method, @@ -38,7 +38,7 @@ def request( # noqa: PLR0913 ): headers = headers or {} - assert "Authorization" not in headers + assert "Authorization" not in headers # noqa: S101 access_token = self._get_access_token(lti_registration, scopes) headers["Authorization"] = f"Bearer {access_token}" @@ -67,7 +67,7 @@ def _get_new_access_token( https://datatracker.ietf.org/doc/html/rfc7523 https://canvas.instructure.com/doc/api/file.oauth_endpoints.html#post-login-oauth2-token """ - now = datetime.utcnow() + now = datetime.utcnow() # noqa: DTZ003 signed_jwt = self._jwt_service.encode_with_private_key( { "exp": now + timedelta(hours=1), @@ -93,7 +93,7 @@ def _get_new_access_token( try: token_data = response.json() except JSONDecodeError: # pragma: no cover - LOG.error("Non-json response: %s", response.text) + LOG.error("Non-json response: %s", response.text) # noqa: TRY400 raise token = self._jwt_oauth2_token_service.save_token( diff --git a/lms/services/mailchimp.py b/lms/services/mailchimp.py index cdce8ba787..89b059a207 100644 --- a/lms/services/mailchimp.py +++ b/lms/services/mailchimp.py @@ -42,7 +42,7 @@ def __init__(self, db, api_key): self.db = db self.mailchimp_client = mailchimp_transactional.Client(api_key) - def send( # noqa: PLR0913, PLR0917 + def send( # noqa: PLR0913 self, template: str, sender: EmailSender, @@ -58,7 +58,7 @@ def send( # noqa: PLR0913, PLR0917 https://mailchimp.com/developer/transactional/api/messages/send-new-message/ """ - if task_done_key: + if task_done_key: # noqa: SIM102 if self.db.execute( select(TaskDone).filter_by(key=task_done_key) ).one_or_none(): @@ -99,7 +99,7 @@ def send( # noqa: PLR0913, PLR0917 try: self.mailchimp_client.messages.send(params) except Exception as exc: - raise MailchimpError() from exc + raise MailchimpError from exc if task_done_key: # Record the email send in the DB to avoid sending duplicates. diff --git a/lms/services/moodle.py b/lms/services/moodle.py index 321afa3a88..c5ea2e614a 100644 --- a/lms/services/moodle.py +++ b/lms/services/moodle.py @@ -167,7 +167,7 @@ def page(self, course_id, page_id) -> dict | None: } def list_pages(self, course_id: int): - root: File = { # type:ignore + root: File = { # type:ignore # noqa: PGH003 "type": "Folder", "display_name": "", "children": [], @@ -208,7 +208,7 @@ def list_pages(self, course_id: int): "display_name": module["name"], "lms_id": module["id"], "id": f"moodle://page/course/{course_id}/page_id/{module['id']}", - "updated_at": updated_at, # type: ignore + "updated_at": updated_at, # type: ignore # noqa: PGH003 } current_node["children"].append(file_node) @@ -263,7 +263,7 @@ def _construct_file_tree(course_id, files): "children": [], } folders[component] = new_folder - current_node["children"].append(new_folder) # type: ignore + current_node["children"].append(new_folder) # type: ignore # noqa: PGH003 current_node = folders[component] file_node = { @@ -274,7 +274,7 @@ def _construct_file_tree(course_id, files): "lms_id": file_data["url"], "updated_at": file_data["updated_at"], } - current_node["children"].append(file_node) # type: ignore + current_node["children"].append(file_node) # type: ignore # noqa: PGH003 return root["children"] @@ -283,7 +283,7 @@ def _api_url(self, function: Function) -> str: return url + f"&wsfunction={function.value}" - def _documents_for_storage( # noqa: PLR0913 + def _documents_for_storage( self, course_id, files, folder_type, document_type, parent_id=None ): for file in files: @@ -309,8 +309,8 @@ def _request(self, url: str, params: dict | None = None): # Moodle's API doesn't seem to use error codes (4xx, 5xx...) # so we have to inspect the response if isinstance(response, dict) and response.get("errorcode"): - raise ExternalRequestError( - "Moodle API error", + raise ExternalRequestError( # noqa: TRY003 + "Moodle API error", # noqa: EM101 validation_errors={ "errorcode": response.get("errorcode"), "message": response.get("message"), diff --git a/lms/services/oauth1.py b/lms/services/oauth1.py index 3c50a3afc2..07e156226e 100644 --- a/lms/services/oauth1.py +++ b/lms/services/oauth1.py @@ -48,7 +48,7 @@ def sign(self, application_instance, url: str, method: str, data: dict) -> dict: payload = { "oauth_version": "1.0", "oauth_nonce": uuid.uuid4().hex, - "oauth_timestamp": str(round(datetime.now().timestamp())), + "oauth_timestamp": str(round(datetime.now().timestamp())), # noqa: DTZ005 "oauth_consumer_key": client_key, "oauth_signature_method": "HMAC-SHA1", } diff --git a/lms/services/oauth2_token.py b/lms/services/oauth2_token.py index 40e5964d08..1d2d3e681c 100644 --- a/lms/services/oauth2_token.py +++ b/lms/services/oauth2_token.py @@ -45,9 +45,9 @@ def save(self, access_token, refresh_token, expires_in, service=Service.LMS): oauth2_token.access_token = access_token oauth2_token.refresh_token = refresh_token oauth2_token.expires_in = expires_in - oauth2_token.received_at = datetime.datetime.utcnow() + oauth2_token.received_at = datetime.datetime.utcnow() # noqa: DTZ003 - @lru_cache(maxsize=1) + @lru_cache(maxsize=1) # noqa: B019 def get(self, service=Service.LMS) -> OAuth2Token: """ Return the user's saved OAuth 2 token from the DB. @@ -65,8 +65,8 @@ def get(self, service=Service.LMS) -> OAuth2Token: .one() ) except NoResultFound as err: - raise OAuth2TokenError( - "We don't have an OAuth 2 token for this user" + raise OAuth2TokenError( # noqa: TRY003 + "We don't have an OAuth 2 token for this user" # noqa: EM101 ) from err def try_lock_for_refresh(self, service=Service.LMS): diff --git a/lms/services/oauth_http.py b/lms/services/oauth_http.py index dd28223259..2516c30f88 100644 --- a/lms/services/oauth_http.py +++ b/lms/services/oauth_http.py @@ -68,7 +68,7 @@ def request(self, method, url, headers=None, **kwargs): """ headers = headers or {} - assert "Authorization" not in headers + assert "Authorization" not in headers # noqa: S101 access_token = self._oauth2_token_service.get(service=self.service).access_token headers["Authorization"] = f"Bearer {access_token}" @@ -97,7 +97,11 @@ def get_access_token(self, token_url, redirect_uri, auth, authorization_code): ) def refresh_access_token( - self, token_url, redirect_uri, auth, prevent_concurrent_refreshes=True + self, + token_url, + redirect_uri, + auth, + prevent_concurrent_refreshes=True, # noqa: FBT002 ): """ Make a refresh token request and save the new token in the DB. @@ -117,7 +121,7 @@ def refresh_access_token( # If the "old" token is already current, just return immediately. if ( old_token.access_token - and datetime.utcnow() - old_token.received_at < timedelta(seconds=30) + and datetime.utcnow() - old_token.received_at < timedelta(seconds=30) # noqa: DTZ003 ): return old_token.access_token @@ -131,7 +135,7 @@ def refresh_access_token( # fails, the client should wait briefly and try again, at which # point it should find the refreshed token already available and # skip the refresh. - raise ConcurrentTokenRefreshError() from exc + raise ConcurrentTokenRefreshError() from exc # noqa: RSE102 try: return self._token_request( @@ -151,7 +155,7 @@ def refresh_access_token( else: if error_dict["error"] == "invalid_grant": # Looks like our refresh token has expired or been revoked. - raise OAuth2TokenError() from err + raise OAuth2TokenError() from err # noqa: RSE102 raise diff --git a/lms/services/organization.py b/lms/services/organization.py index feb2d702cc..39f9e9083d 100644 --- a/lms/services/organization.py +++ b/lms/services/organization.py @@ -52,8 +52,8 @@ def get_by_public_id(self, public_id: str) -> Organization | None: parts = public_id.split(".") if not len(parts) == 4: - raise InvalidPublicId( - f"Malformed public id: '{public_id}'. Expected 4 dot separated parts." + raise InvalidPublicId( # noqa: TRY003 + f"Malformed public id: '{public_id}'. Expected 4 dot separated parts." # noqa: EM102 ) return self._organization_search_query(public_id=public_id).one_or_none() @@ -77,11 +77,11 @@ def get_hierarchy_root(self, id_: int) -> Organization: .order_by(Organization.parent_id) ).all() - assert not hierarchy[-1].parent_id, "The root item should have no parent" + assert not hierarchy[-1].parent_id, "The root item should have no parent" # noqa: S101 return hierarchy[-1] - def search( # noqa: PLR0913 + def search( self, id_=None, public_id=None, name=None, guid=None, limit=100 ) -> list[Organization]: """ @@ -157,7 +157,9 @@ def auto_assign_organization( # Add a note to indicate the application instance was automatically # allocated to an organization application_instance.settings.set( - "hypothesis", "auto_assigned_to_org", True + "hypothesis", + "auto_assigned_to_org", + True, # noqa: FBT003 ) if ( @@ -177,7 +179,7 @@ def auto_assign_organization( org = self.create_organization() # Add a note to indicate the organization was automatically # created instead of going through our normal process - org.settings.set("hypothesis", "auto_created", True) + org.settings.set("hypothesis", "auto_created", True) # noqa: FBT003 # Fill out missing names if not org.name and (name := application_instance.tool_consumer_instance_name): @@ -206,7 +208,7 @@ def _generate_public_id(self): # is URL safe. The other option is '~'. return f"{self._region_code}.lms.org.{id_}" - def update_organization( # noqa: PLR0913 + def update_organization( self, organization: Organization, name=None, @@ -252,29 +254,29 @@ def _move_organization_parent(self, organization: Organization, parent_public_id # This would be caught by the next check, but doing it here means we # can give a more sensible error message if parent_public_id == organization.public_id: - raise InvalidOrganizationParent( - "Cannot set an organization to be it's own parent" + raise InvalidOrganizationParent( # noqa: TRY003 + "Cannot set an organization to be it's own parent" # noqa: EM101 ) parent = self._organization_search_query( public_id=parent_public_id ).one_or_none() if not parent: - raise InvalidOrganizationParent( - f"Could not find parent organization: '{parent_public_id}'" + raise InvalidOrganizationParent( # noqa: TRY003 + f"Could not find parent organization: '{parent_public_id}'" # noqa: EM102 ) # Get a list including our self and all are children etc. if parent.id in self.get_hierarchy_ids(organization.id, include_parents=False): - raise InvalidOrganizationParent( - f"Cannot use '{parent_public_id}' as a parent as it a " + raise InvalidOrganizationParent( # noqa: TRY003 + f"Cannot use '{parent_public_id}' as a parent as it a " # noqa: EM102 "child of this organization" ) organization.parent = parent organization.parent_id = parent.id - def get_hierarchy_ids(self, id_, include_parents=False) -> list[int]: + def get_hierarchy_ids(self, id_, include_parents=False) -> list[int]: # noqa: FBT002 """ Get an organization and it's children's ids order not guaranteed. @@ -304,7 +306,7 @@ def get_hierarchy_ids(self, id_, include_parents=False) -> list[int]: recursive_case = self._db_session.query(*cols).join(base_case, join_condition) # This will recurse until no new rows are added - rows = self._db_session.query(base_case.union(recursive_case)).all() # type: ignore + rows = self._db_session.query(base_case.union(recursive_case)).all() # type: ignore # noqa: PGH003 return [row[0] for row in rows] def is_member(self, organization: Organization, user: User) -> bool: diff --git a/lms/services/organization_usage_report.py b/lms/services/organization_usage_report.py index 6d6bc11e63..4c119cc397 100644 --- a/lms/services/organization_usage_report.py +++ b/lms/services/organization_usage_report.py @@ -72,7 +72,7 @@ def monthly_report_dates( reports_dates = [] # First report, based on current date - until = date.today() + until = date.today() # noqa: DTZ011 for _ in range(reports): # Always calculate until the start of the previous month @@ -98,7 +98,7 @@ def generate_usage_report( ): """Generate and store an usage report for one organization.""" organization = self._organization_service.get_by_id(organization_id) - assert organization + assert organization # noqa: S101 report_key = OrganizationUsageReport.generate_key( organization, tag, since, until ) @@ -147,7 +147,7 @@ def usage_report(self, organization: Organization, since: date, until: date): ).all() if not groups_from_org: - raise ValueError(f"No courses found for {organization.public_id}") + raise ValueError(f"No courses found for {organization.public_id}") # noqa: EM102, TRY003 LOG.info( "Generating report for %s based on %d candidate groups.", @@ -164,8 +164,8 @@ def usage_report(self, organization: Organization, since: date, until: date): ) ] if not groups_with_annos: - raise ValueError( - f"No courses with activity found for {organization.public_id}" + raise ValueError( # noqa: TRY003 + f"No courses with activity found for {organization.public_id}" # noqa: EM102 ) # Based on those groups generate the usage report based on the definition of unique user: diff --git a/lms/services/roster.py b/lms/services/roster.py index 83245fbe11..ca572ae32e 100644 --- a/lms/services/roster.py +++ b/lms/services/roster.py @@ -154,7 +154,7 @@ def _get_roster( def fetch_course_roster(self, lms_course: LMSCourse) -> None: """Fetch the roster information for a course from the LMS.""" - assert lms_course.lti_context_memberships_url, ( + assert lms_course.lti_context_memberships_url, ( # noqa: S101 "Trying fetch roster for course without service URL." ) lti_registration = self._get_lti_registration(lms_course) @@ -184,7 +184,7 @@ def fetch_course_roster(self, lms_course: LMSCourse) -> None: lti_user_id = member.get("lti11_legacy_user_id") or member["user_id"] # Now, for every user + role, insert a row in the roster table for role in member["roles"]: - roster_upsert_elements.append( + roster_upsert_elements.append( # noqa: PERF401 { "lms_course_id": lms_course.id, "lms_user_id": lms_users_by_lti_user_id[lti_user_id].id, @@ -211,10 +211,10 @@ def fetch_course_roster(self, lms_course: LMSCourse) -> None: def fetch_assignment_roster(self, assignment: Assignment) -> None: """Fetch the roster information for an assignment from the LMS.""" - assert assignment.lti_v13_resource_link_id, ( + assert assignment.lti_v13_resource_link_id, ( # noqa: S101 "Trying fetch roster for an assignment without LTI1.3 ID." ) - assert assignment.course, ( + assert assignment.course, ( # noqa: S101 "Trying fetch roster for an assignment without a course." ) @@ -225,7 +225,7 @@ def fetch_assignment_roster(self, assignment: Assignment) -> None: ) ).one() - assert lms_course.lti_context_memberships_url, ( + assert lms_course.lti_context_memberships_url, ( # noqa: S101 "Trying fetch roster for course without service URL." ) lti_registration = self._get_lti_registration(lms_course) @@ -244,7 +244,7 @@ def fetch_assignment_roster(self, assignment: Assignment) -> None: # Canvas, assignment deleted in the LMS or "Requested ResourceLink was not found" in err.response_body ): - LOG.error("Fetching assignment roster failed: %s", err.response_body) + LOG.error("Fetching assignment roster failed: %s", err.response_body) # noqa: TRY400 # We ignore this type of error, just stop here. return @@ -271,7 +271,7 @@ def fetch_assignment_roster(self, assignment: Assignment) -> None: lti_user_id = member.get("lti11_legacy_user_id") or member["user_id"] # Now, for every user + role, insert a row in the roster table for role in member["roles"]: - roster_upsert_elements.append( + roster_upsert_elements.append( # noqa: PERF401 { "assignment_id": assignment.id, "lms_user_id": lms_users_by_lti_user_id[lti_user_id].id, @@ -298,10 +298,10 @@ def fetch_assignment_roster(self, assignment: Assignment) -> None: def fetch_canvas_group_roster(self, canvas_group: LMSSegment) -> None: """Fetch the roster information for a canvas group from the LMS.""" - assert canvas_group.type == "canvas_group" + assert canvas_group.type == "canvas_group" # noqa: S101 lms_course = canvas_group.lms_course - assert lms_course.lti_context_memberships_url, ( + assert lms_course.lti_context_memberships_url, ( # noqa: S101 "Trying fetch roster for course without service URL." ) @@ -343,7 +343,7 @@ def fetch_canvas_group_roster(self, canvas_group: LMSSegment) -> None: lti_user_id = member.get("lti11_legacy_user_id") or member["user_id"] # Now, for every user + role, insert a row in the roster table for role in member["roles"]: - roster_upsert_elements.append( + roster_upsert_elements.append( # noqa: PERF401 { "lms_segment_id": canvas_group.id, "lms_user_id": lms_users_by_lti_user_id[lti_user_id].id, @@ -556,7 +556,7 @@ def _get_application_instance(self, lms_course) -> ApplicationInstance: def _get_lti_registration(self, lms_course) -> LTIRegistration: ai = self._get_application_instance(lms_course) - assert ai.lti_registration, "No LTI registration found for LMSCourse." + assert ai.lti_registration, "No LTI registration found for LMSCourse." # noqa: S101 return ai.lti_registration def _get_course_instructor(self, lms_course: LMSCourse) -> LMSUser | None: @@ -577,7 +577,7 @@ def _get_canvas_sections( canvas_api_client: CanvasAPIClient, oauth2_token_service: OAuth2TokenService, lms_course: LMSCourse, - with_refresh_token=False, + with_refresh_token=False, # noqa: FBT002 ) -> list[dict]: try: return canvas_api_client.course_sections( diff --git a/lms/services/rsa_key.py b/lms/services/rsa_key.py index 6c9c2d7511..6055edce30 100644 --- a/lms/services/rsa_key.py +++ b/lms/services/rsa_key.py @@ -40,7 +40,7 @@ def rotate( This method is meant to be called periodically by a celery task. """ - now = datetime.now() + now = datetime.now() # noqa: DTZ005 new_keys = target_keys for key in self._db.query(RSAKey).all(): @@ -60,7 +60,7 @@ def rotate( # Assert the expected result, if we find something different the transition will be aborted # and we'd be back at the initial state - assert self._db.query(RSAKey).filter_by(expired=False).count() == target_keys, ( + assert self._db.query(RSAKey).filter_by(expired=False).count() == target_keys, ( # noqa: S101 "The number of active RSAKey doesn't match the target" ) diff --git a/lms/services/upsert.py b/lms/services/upsert.py index c02754e229..03a516a83d 100644 --- a/lms/services/upsert.py +++ b/lms/services/upsert.py @@ -28,7 +28,7 @@ def bulk_upsert( # This would be worse than pointless: it would actually crash in # some cases. This SQLAlchemy code: # - # insert(MyModel).values([]) + # insert(MyModel).values([]) # noqa: ERA001 # # produces this SQL: # @@ -41,7 +41,7 @@ def bulk_upsert( # # We do a wasteful query here to maintain # the same return type in all branches. - return db.query(model_class).filter(False) + return db.query(model_class).filter(False) # noqa: FBT003 index_elements_columns = [getattr(model_class, c) for c in index_elements] diff --git a/lms/services/user.py b/lms/services/user.py index 96cce01b04..1d7732bb38 100644 --- a/lms/services/user.py +++ b/lms/services/user.py @@ -122,7 +122,7 @@ def upsert_lms_user(self, user: User, lti_params: LTIParams) -> LMSUser: ) return lms_user - @lru_cache(maxsize=128) + @lru_cache(maxsize=128) # noqa: B019 def get(self, application_instance, user_id: str) -> User: """ Get a User that belongs to `application_instance` with the given id. @@ -140,7 +140,7 @@ def get(self, application_instance, user_id: str) -> User: ).scalar_one() except NoResultFound as err: - raise UserNotFound() from err + raise UserNotFound from err return existing_user @@ -255,7 +255,7 @@ def get_users_for_segments( return query.order_by(LMSUser.display_name, LMSUser.id) - def get_users_for_organization( # noqa: PLR0913 + def get_users_for_organization( self, role_scope: RoleScope, role_type: RoleType, @@ -291,7 +291,7 @@ def get_users_for_organization( # noqa: PLR0913 return query.order_by(LMSUser.display_name, LMSUser.id) - def get_users( # noqa: PLR0913, PLR0917 + def get_users( # noqa: PLR0913 self, role_scope: RoleScope, role_type: RoleType, diff --git a/lms/services/vitalsource/_client.py b/lms/services/vitalsource/_client.py index 39506274e7..e9ac7926c2 100644 --- a/lms/services/vitalsource/_client.py +++ b/lms/services/vitalsource/_client.py @@ -42,7 +42,7 @@ def __init__(self, api_key: str): :raises ValueError: If `api_key` is missing """ if not api_key: - raise ValueError("VitalSource credentials are missing") + raise ValueError("VitalSource credentials are missing") # noqa: EM101, TRY003 self._http_session = HTTPService() @@ -153,7 +153,7 @@ def get_sso_redirect(self, user_reference, url) -> str: # This is used in `_VSUserAuth` authentication mechanism below. We want to # cache this so that repeated calls for the same user are only issued once. - @lru_cache(1) + @lru_cache(1) # noqa: B019 def get_user_credentials(self, user_reference: str) -> dict: """ Get user credentials that can be used with user-specific queries. @@ -186,8 +186,8 @@ def _handle_book_errors(cls, book_id: str, err: ExternalRequestError): # VS need to do "something" to configure it. This shouldn't happen # at random, only when onboarding new customers. if "Catalog not found" in json_errors: - raise VitalSourceConfigurationError( - "The catalog has not been initialized" + raise VitalSourceConfigurationError( # noqa: TRY003 + "The catalog has not been initialized" # noqa: EM101 ) if err.status_code == 404: diff --git a/lms/services/vitalsource/model.py b/lms/services/vitalsource/model.py index 938531660d..bbb39e81ff 100644 --- a/lms/services/vitalsource/model.py +++ b/lms/services/vitalsource/model.py @@ -53,7 +53,7 @@ def from_document_url(cls, document_url): parsed = urlparse(document_url) if parsed.scheme != "vitalsource": - raise ValueError("URL is not a valid vitalsource:// URL") + raise ValueError("URL is not a valid vitalsource:// URL") # noqa: EM101, TRY003 # `vitalsource://` URLs were not designed with URL parsing in mind # originally (:facepalm:), so they were structured in such a way that @@ -64,14 +64,14 @@ def from_document_url(cls, document_url): path_match = cls._PATH_REGEX.search(path) if path_match is None: - raise ValueError("URL is not a valid vitalsource:// URL") + raise ValueError("URL is not a valid vitalsource:// URL") # noqa: EM101, TRY003 book_id = path_match["book_id"] loc_type = path_match["loc_type"] loc = path_match["loc"] if loc_type not in {"cfi", "page"}: - raise ValueError("Invalid book location specifier") + raise ValueError("Invalid book location specifier") # noqa: EM101, TRY003 if loc_type == "page": loc = unquote_plus(loc) diff --git a/lms/services/vitalsource/service.py b/lms/services/vitalsource/service.py index 3a0fbfcf47..1a8ef3ac23 100644 --- a/lms/services/vitalsource/service.py +++ b/lms/services/vitalsource/service.py @@ -11,21 +11,21 @@ class VitalSourceService: """A high-level interface for dealing with VitalSource.""" - H_SKUS = ["HYPOTHESISLMSAPP", "HYPOTHESISLMSAPPR180"] + H_SKUS = ["HYPOTHESISLMSAPP", "HYPOTHESISLMSAPPR180"] # noqa: RUF012 """ SKU of the H app in the VitalSource store. Student pay schools will check students have a license for this SKU before they can use the H LMS app. """ - def __init__( # noqa: PLR0913, PLR0917 + def __init__( # noqa: PLR0913 self, - enabled: bool = False, + enabled: bool = False, # noqa: FBT001, FBT002 global_client: VitalSourceClient | None = None, customer_client: VitalSourceClient | None = None, user_lti_param: str | None = None, user_lti_pattern: str | None = None, - student_pay_enabled: bool = False, + student_pay_enabled: bool = False, # noqa: FBT001, FBT002 ): """ Initialise the service. @@ -66,17 +66,17 @@ def sso_enabled(self) -> bool: def get_book_info(self, book_id: str) -> dict: """Get details of a book.""" - assert self._metadata_client + assert self._metadata_client # noqa: S101 return self._metadata_client.get_book_info(book_id) def get_table_of_contents(self, book_id: str) -> list[dict]: """Get the table of contents for a book.""" - assert self._metadata_client + assert self._metadata_client # noqa: S101 return self._metadata_client.get_table_of_contents(book_id) - def get_document_url( # noqa: PLR0913 + def get_document_url( self, book_id: str, page: str | None = None, @@ -98,9 +98,9 @@ def get_document_url( # noqa: PLR0913 url = urlparse(url) params = parse_qs(url.query) if end_page: - params["end_page"] = end_page # type: ignore + params["end_page"] = end_page # type: ignore # noqa: PGH003 if end_cfi: - params["end_cfi"] = end_cfi # type: ignore + params["end_cfi"] = end_cfi # type: ignore # noqa: PGH003 url = url._replace(query=urlencode(params, doseq=True)) return urlunparse(url) @@ -163,7 +163,7 @@ def get_sso_redirect(self, document_url, user_reference: str) -> str: :param user_reference: The user reference (you can use `get_user_reference()` to help you with this) """ - assert self._sso_client + assert self._sso_client # noqa: S101 return self._sso_client.get_sso_redirect( user_reference, self.get_book_reader_url(document_url) ) @@ -214,12 +214,11 @@ def check_h_license( if lti_user.is_learner: # Do the actual license check, only for students user_reference = self.get_user_reference(lti_params) - assert self._sso_client + assert self._sso_client # noqa: S101 for sku in self.H_SKUS: if self._sso_client.get_user_book_license(user_reference, sku): return None - else: - return ErrorCode.VITALSOURCE_STUDENT_PAY_NO_LICENSE + return ErrorCode.VITALSOURCE_STUDENT_PAY_NO_LICENSE return None @@ -241,8 +240,9 @@ def compile_user_lti_pattern(pattern: str | None) -> re.Pattern | None: raise VitalSourceMalformedRegex(str(err), pattern=pattern) from err if compiled_pattern.groups != 1: - raise VitalSourceMalformedRegex( - "The user regex must have one capture group (brackets)", pattern=pattern + raise VitalSourceMalformedRegex( # noqa: TRY003 + "The user regex must have one capture group (brackets)", # noqa: EM101 + pattern=pattern, ) return compiled_pattern diff --git a/lms/services/youtube.py b/lms/services/youtube.py index f9b789d993..dc7dcd1b7f 100644 --- a/lms/services/youtube.py +++ b/lms/services/youtube.py @@ -15,7 +15,7 @@ def __init__(self, video_id): class YouTubeService: """An interface for dealing with YouTube API.""" - def __init__(self, enabled: bool, api_key: str, http: HTTPService): + def __init__(self, enabled: bool, api_key: str, http: HTTPService): # noqa: FBT001 """ Initialise the YouTube service. diff --git a/lms/tasks/celery.py b/lms/tasks/celery.py index 45d421338c..a14fd394c9 100644 --- a/lms/tasks/celery.py +++ b/lms/tasks/celery.py @@ -86,7 +86,7 @@ def bootstrap_worker(sender, **_kwargs): # pragma: no cover # Put some common handy things around for tasks try: - lms = create_app(None, **{}) + lms = create_app(None) except Exception: # noqa: BLE001 # If we don't bail out here ourselves, Celery just hides the error @@ -134,7 +134,7 @@ def add_task_name_and_id_to_log_messages( root_loggers_handler.setFormatter( logging.Formatter( - "[%(asctime)s: %(levelname)s/%(processName)s] " + "[%(asctime)s: %(levelname)s/%(processName)s] " # noqa: ISC003 + f"{task.name}[{task_id}] " + "%(message)s" ) diff --git a/lms/tasks/email_digests.py b/lms/tasks/email_digests.py index a4d3db7da8..3e922b8081 100644 --- a/lms/tasks/email_digests.py +++ b/lms/tasks/email_digests.py @@ -49,7 +49,7 @@ def send_instructor_email_digest_tasks(): year=now.year, month=now.month, day=now.day, hour=5, tzinfo=UTC ) - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: candidate_courses = ( select(Event.course_id) @@ -144,14 +144,14 @@ def send_instructor_email_digest( """ # Caution: datetime.fromisoformat() doesn't support all ISO 8601 strings! # This only works for the subset of ISO 8601 produced by datetime.isoformat(). - created_before: datetime = datetime.fromisoformat(created_before) # type: ignore + created_before: datetime = datetime.fromisoformat(created_before) # type: ignore # noqa: PGH003 if created_after is None: - created_after = created_before - timedelta(days=7) # type: ignore + created_after = created_before - timedelta(days=7) # type: ignore # noqa: PGH003 else: - created_after: datetime = datetime.fromisoformat(created_after) # type: ignore + created_after: datetime = datetime.fromisoformat(created_after) # type: ignore # noqa: PGH003 - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: task_done_data = _get_task_done_data(request.db, h_userid) @@ -160,7 +160,7 @@ def send_instructor_email_digest( datetime.fromisoformat(task_done_data["created_before"]).replace( tzinfo=UTC ), - created_after.replace(tzinfo=UTC), # type:ignore + created_after.replace(tzinfo=UTC), # type:ignore # noqa: PGH003 ) digest_service = request.find_service(DigestService) diff --git a/lms/tasks/event.py b/lms/tasks/event.py index 4cd678353e..7ac5522b7b 100644 --- a/lms/tasks/event.py +++ b/lms/tasks/event.py @@ -16,9 +16,9 @@ @app.task def insert_event(event: dict) -> None: - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: - from lms.services.event import EventService # noqa: PLC0415 + from lms.services.event import EventService request.find_service(EventService).insert_event( BaseEvent(request=request, **event) @@ -27,7 +27,7 @@ def insert_event(event: dict) -> None: @app.task def purge_launch_data(*, max_age_days=30) -> None: - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: events_with_old_lti_params = ( select(Event.id) diff --git a/lms/tasks/grading.py b/lms/tasks/grading.py index 0efee640dd..0726421e00 100644 --- a/lms/tasks/grading.py +++ b/lms/tasks/grading.py @@ -13,7 +13,7 @@ @app.task() def sync_grades(): """Start processing pending (scheduled) GradingSync.""" - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: scheduled_syncs = request.db.scalars( select(GradingSync) @@ -37,7 +37,7 @@ def sync_grades(): ) def sync_grade(*, grading_sync_grade_id: int): """Send one particular grade to the LMS.""" - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: grading_sync_grade = request.db.get(GradingSyncGrade, grading_sync_grade_id) grading_sync = grading_sync_grade.grading_sync @@ -46,7 +46,7 @@ def sync_grade(*, grading_sync_grade_id: int): grading_service = service_factory(None, request, application_instance) try: - assert assignment.lis_outcome_service_url, ( + assert assignment.lis_outcome_service_url, ( # noqa: S101 "Assignment without grading URL" ) @@ -77,7 +77,7 @@ def sync_grade(*, grading_sync_grade_id: int): @app.task() def sync_grades_complete(*, grading_sync_id): """Summarize a GradingSync status based on the state of its children GradingSyncGrade.""" - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: grading_sync = request.db.get(GradingSync, grading_sync_id) diff --git a/lms/tasks/hubspot.py b/lms/tasks/hubspot.py index 96756da31f..0a585bd764 100644 --- a/lms/tasks/hubspot.py +++ b/lms/tasks/hubspot.py @@ -7,7 +7,7 @@ @app.task def refresh_hubspot_data(): """Refresh the HubSpot companies, scheduled daily in h-periodic.""" - with app.request_context() as request: # pylint: disable=no-member + with app.request_context() as request: # pylint: disable=no-member # noqa: SIM117 with request.tm: hs = request.find_service(HubSpotService) @@ -16,8 +16,8 @@ def refresh_hubspot_data(): @app.task def export_companies_contract_billables(): - with app.request_context() as request: # pylint: disable=no-member + with app.request_context() as request: # pylint: disable=no-member # noqa: SIM117 with request.tm: hs = request.find_service(HubSpotService) - hs.export_companies_contract_billables(date.today()) + hs.export_companies_contract_billables(date.today()) # noqa: DTZ011 diff --git a/lms/tasks/organization.py b/lms/tasks/organization.py index 52b692502e..1e5d856a57 100644 --- a/lms/tasks/organization.py +++ b/lms/tasks/organization.py @@ -17,7 +17,7 @@ def generate_usage_report( organization_id: int, tag: str, since: str, until: str ) -> None: - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: request.find_service(OrganizationUsageReportService).generate_usage_report( organization_id, @@ -37,14 +37,14 @@ def schedule_monthly_deal_report(limit: int, backfill: int = 0) -> None: """ reports_scheduled = 0 - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: usage_service = request.find_service(OrganizationUsageReportService) hubspot_service = request.find_service(HubSpotService) companies_with_active_deals = hubspot_service.get_companies_with_active_deals( # Get active deals, now and a few days ago to account for the last billing period - date.today() - timedelta(days=30) + date.today() - timedelta(days=30) # noqa: DTZ011 ) for company in companies_with_active_deals: organization = company.organization diff --git a/lms/tasks/roster.py b/lms/tasks/roster.py index f531017084..6846e3a5e0 100644 --- a/lms/tasks/roster.py +++ b/lms/tasks/roster.py @@ -39,7 +39,7 @@ def schedule_fetching_course_rosters() -> None: """Schedule fetching course rosters based on their last lunches and the most recent roster fetch.""" # We use the python version (and not func.now()) for easier mocking during tests - now = datetime.now() + now = datetime.now() # noqa: DTZ005 # Only fetch roster for courses for which we haven't schedule a fetch recently no_recent_scheduled_roster_fetch_clause = ~exists( @@ -66,7 +66,7 @@ def schedule_fetching_course_rosters() -> None: ) ) - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: query = ( select(LMSCourse.id) @@ -90,7 +90,7 @@ def schedule_fetching_course_rosters() -> None: TaskDone( key=f"roster::course::scheduled::{lms_course_id}", data=None, - expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, + expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, # noqa: DTZ005 ) ) @@ -99,7 +99,7 @@ def schedule_fetching_assignment_rosters() -> None: """Schedule fetching assignment rosters based on their last lunches and the most recent roster fetch.""" # We use the python version (and not func.now()) for easier mocking during tests - now = datetime.now() + now = datetime.now() # noqa: DTZ005 no_recent_roster_clause = ~exists( select(AssignmentRoster).where( @@ -123,7 +123,7 @@ def schedule_fetching_assignment_rosters() -> None: ) ) - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: query = ( select(Assignment.id) @@ -154,7 +154,7 @@ def schedule_fetching_assignment_rosters() -> None: TaskDone( key=f"roster::assignment::scheduled::{assignment_id}", data=None, - expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, + expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, # noqa: DTZ005 ) ) @@ -163,7 +163,7 @@ def schedule_fetching_segment_rosters() -> None: """Schedule fetching segment rosters based on their last lunches and the most recent roster fetch.""" # We use the python version (and not func.now()) for easier mocking during tests - now = datetime.now() + now = datetime.now() # noqa: DTZ005 # Only fetch roster for segments for which we haven't schedule a fetch recently no_recent_scheduled_roster_fetch_clause = ~exists( @@ -190,7 +190,7 @@ def schedule_fetching_segment_rosters() -> None: ) ) - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: query = ( select(LMSSegment.id) @@ -217,7 +217,7 @@ def schedule_fetching_segment_rosters() -> None: TaskDone( key=f"roster::segment::scheduled::{lms_segment_id}", data=None, - expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, + expires_at=datetime.now() + ROSTER_REFRESH_WINDOW, # noqa: DTZ005 ) ) diff --git a/lms/tasks/task_done.py b/lms/tasks/task_done.py index 1ee9a89973..dcf5c046d4 100644 --- a/lms/tasks/task_done.py +++ b/lms/tasks/task_done.py @@ -17,8 +17,8 @@ def delete_expired_rows(): This is intended to be called periodically. """ - with app.request_context() as request: + with app.request_context() as request: # noqa: SIM117 with request.tm: request.db.execute( - delete(TaskDone).where(TaskDone.expires_at < datetime.utcnow()) + delete(TaskDone).where(TaskDone.expires_at < datetime.utcnow()) # noqa: DTZ003 ) diff --git a/lms/validation/__init__.py b/lms/validation/__init__.py index 6d93013b4b..525eaa8fdd 100644 --- a/lms/validation/__init__.py +++ b/lms/validation/__init__.py @@ -98,5 +98,5 @@ def wrapper_view(context, request): def includeme(config): - _validated_view.options = ["schema"] # type: ignore + _validated_view.options = ["schema"] # type: ignore # noqa: PGH003 config.add_view_deriver(_validated_view) diff --git a/lms/validation/_base.py b/lms/validation/_base.py index 5bc3345601..66e3325245 100644 --- a/lms/validation/_base.py +++ b/lms/validation/_base.py @@ -113,8 +113,8 @@ def check_content_type(self, data, **_): content_type = self.context["request"].content_type if content_type != "application/json": - raise HTTPUnsupportedMediaType( - f"Unexpected content type. Expected 'application/json' but found '{content_type}'", + raise HTTPUnsupportedMediaType( # noqa: TRY003 + f"Unexpected content type. Expected 'application/json' but found '{content_type}'", # noqa: EM102 ) return data @@ -153,8 +153,8 @@ def _pre_load(self, response, **_kwargs): try: return response.json() except (AttributeError, ValueError) as err: - raise marshmallow.ValidationError( - "response doesn't have a valid JSON body" + raise marshmallow.ValidationError( # noqa: TRY003 + "response doesn't have a valid JSON body" # noqa: EM101 ) from err diff --git a/lms/validation/_exceptions.py b/lms/validation/_exceptions.py index 0aac848c4f..81f0e9363e 100644 --- a/lms/validation/_exceptions.py +++ b/lms/validation/_exceptions.py @@ -3,7 +3,7 @@ from pyramid import httpexceptions from pyramid.httpexceptions import HTTPFound -__all__ = ["ValidationError", "LTIToolRedirect"] +__all__ = ["LTIToolRedirect", "ValidationError"] class ValidationError(httpexceptions.HTTPUnprocessableEntity): @@ -73,14 +73,14 @@ def _add_lti_message_to_url(cls, location, message): @classmethod def _messages_to_string(cls, messages): if not isinstance(messages, dict): - raise ValueError("Messages must be a dict of lists: field -> [errors]") + raise ValueError("Messages must be a dict of lists: field -> [errors]") # noqa: EM101, TRY003, TRY004 parts = [] for field, errors in messages.items(): if not isinstance(errors, list): - raise ValueError("Messages must be a dict of lists: field -> [errors]") + raise ValueError("Messages must be a dict of lists: field -> [errors]") # noqa: EM101, TRY003, TRY004 for error in errors: - parts.append(f"Field '{field}': {error}") + parts.append(f"Field '{field}': {error}") # noqa: PERF401 return ", ".join(parts) diff --git a/lms/validation/_lti_launch_params.py b/lms/validation/_lti_launch_params.py index de57f29055..d5e57906be 100644 --- a/lms/validation/_lti_launch_params.py +++ b/lms/validation/_lti_launch_params.py @@ -69,7 +69,7 @@ def validate_consumer_key(self, data, **_kwargs): not data.get("oauth_consumer_key", None) and data["lti_version"] == "LTI-1p0" ): - raise ValidationError("Required for LTI1.1", "oauth_consumer_key") + raise ValidationError("Required for LTI1.1", "oauth_consumer_key") # noqa: EM101, TRY003 class BasicLTILaunchSchema(_CommonLTILaunchSchema): @@ -98,7 +98,7 @@ class Meta: # If we have an error in one of these fields we should redirect back to # the calling LMS if possible - lti_redirect_fields = { + lti_redirect_fields = { # noqa: RUF012 "resource_link_id", "lti_version", "lti_message_type", @@ -134,7 +134,7 @@ def handle_error(self, error, data, *, many, **kwargs): # ``err.messages``, but without overwriting any of the existing # error messages already present in ``messages``. for field in err.messages: - messages.setdefault(field, []).extend(err.messages[field]) # type:ignore + messages.setdefault(field, []).extend(err.messages[field]) # type:ignore # noqa: PGH003 return_url = None if return_url: @@ -198,8 +198,9 @@ def _load_auto_grading_config(self, data, **_kwargs): try: data["auto_grading_config"] = json.loads(auto_grading_config) except json.decoder.JSONDecodeError as exc: - raise ValidationError( - "Invalid json for nested field", "auto_grading_config" + raise ValidationError( # noqa: TRY003 + "Invalid json for nested field", # noqa: EM101 + "auto_grading_config", ) from exc return data diff --git a/lms/validation/authentication/_bearer_token.py b/lms/validation/authentication/_bearer_token.py index 61d0aa5c18..2ae9906d47 100644 --- a/lms/validation/authentication/_bearer_token.py +++ b/lms/validation/authentication/_bearer_token.py @@ -120,6 +120,5 @@ def _decode_authorization(self, data, **_kwargs): jwt = data["authorization"] # Some locations (cookies) don't allow the leading "Bearer " so we don't include it. # Remove it in all other cases - if jwt.startswith("Bearer "): - jwt = jwt[len("Bearer ") :] + jwt = jwt.removeprefix("Bearer ") return {"authorization": jwt} diff --git a/lms/validation/authentication/_exceptions.py b/lms/validation/authentication/_exceptions.py index 0f83791b72..2547296f3c 100644 --- a/lms/validation/authentication/_exceptions.py +++ b/lms/validation/authentication/_exceptions.py @@ -3,9 +3,9 @@ # ValidationError has a large hierarchy, but we need to inherit from it __all__ = [ - "MissingStateParamError", "ExpiredStateParamError", "InvalidStateParamError", + "MissingStateParamError", ] diff --git a/lms/validation/authentication/_lti.py b/lms/validation/authentication/_lti.py index 2c197ba757..33abb7d45d 100644 --- a/lms/validation/authentication/_lti.py +++ b/lms/validation/authentication/_lti.py @@ -78,7 +78,7 @@ def _verify_oauth_1(self, _data, **_kwargs): try: self._launch_verifier.verify() except LTILaunchVerificationError as err: - raise marshmallow.ValidationError("Invalid OAuth 1 signature.") from err + raise marshmallow.ValidationError("Invalid OAuth 1 signature.") from err # noqa: EM101, TRY003 class LTI13AuthSchema(LTIV11CoreSchema): diff --git a/lms/validation/authentication/_oauth.py b/lms/validation/authentication/_oauth.py index 4305ee607a..dd52d66687 100644 --- a/lms/validation/authentication/_oauth.py +++ b/lms/validation/authentication/_oauth.py @@ -113,7 +113,7 @@ def lti_user(self, state=None) -> LTIUser: try: state = request.params["state"] except KeyError as err: - raise MissingStateParamError() from err + raise MissingStateParamError() from err # noqa: RSE102 decoded_user = self._decode_state(state)["user"] return self._lti_user_service.deserialize(**decoded_user) @@ -126,16 +126,16 @@ def validate_state(self, state): payload = self._decode_state(state) if payload["csrf"] != request.session.pop("oauth2_csrf", None): - raise marshmallow.ValidationError("Invalid CSRF token") + raise marshmallow.ValidationError("Invalid CSRF token") # noqa: EM101, TRY003 def _decode_state(self, state): """Decode the given state JWT and return its payload or raise.""" try: return self._jwt_service.decode_with_secret(state, self._secret) except ExpiredJWTError as err: - raise ExpiredStateParamError() from err + raise ExpiredStateParamError() from err # noqa: RSE102 except InvalidJWTError as err: - raise InvalidStateParamError() from err + raise InvalidStateParamError() from err # noqa: RSE102 class OAuthTokenResponseSchema(RequestsResponseSchema): diff --git a/lms/views/admin/_schemas.py b/lms/views/admin/_schemas.py index cadd576c8b..2fcfd1b246 100644 --- a/lms/views/admin/_schemas.py +++ b/lms/views/admin/_schemas.py @@ -16,10 +16,10 @@ class EmptyStringNoneMixin: """ def deserialize(self, value, attr, data, **kwargs): - if value == missing or value.strip() == "": # noqa: PLC1901 + if value == missing or value.strip() == "": return None - return super().deserialize(value, attr, data, **kwargs) # type:ignore + return super().deserialize(value, attr, data, **kwargs) # type:ignore # noqa: PGH003 -class EmptyStringInt(EmptyStringNoneMixin, fields.Int): # type: ignore +class EmptyStringInt(EmptyStringNoneMixin, fields.Int): # type: ignore # noqa: PGH003 """Allow empty string as "missing value" instead of failing integer validation.""" diff --git a/lms/views/admin/application_instance/_core.py b/lms/views/admin/application_instance/_core.py index 4c18285f17..308c95111c 100644 --- a/lms/views/admin/application_instance/_core.py +++ b/lms/views/admin/application_instance/_core.py @@ -2,7 +2,7 @@ from lms.models import ApplicationInstance from lms.services import ApplicationInstanceNotFound -from lms.services.application_instance import ApplicationInstanceService +from lms.services.application_instance import ApplicationInstanceService # noqa: TC001 class BaseApplicationInstanceView: @@ -25,7 +25,7 @@ def application_instance(self) -> ApplicationInstance: ) except ApplicationInstanceNotFound as err: - raise HTTPNotFound() from err + raise HTTPNotFound() from err # noqa: RSE102 def _redirect(self, route_name, **kwargs): return HTTPFound(location=self.request.route_url(route_name, **kwargs)) diff --git a/lms/views/admin/application_instance/update.py b/lms/views/admin/application_instance/update.py index 3dec731cfe..ff69593dc8 100644 --- a/lms/views/admin/application_instance/update.py +++ b/lms/views/admin/application_instance/update.py @@ -92,7 +92,7 @@ def update_instance_settings(self): ai.settings.set_secret(self._aes_service, field.group, field.key, value) else: - assert field.format is str + assert field.format is str # noqa: S101 ai.settings.set(field.group, field.key, value) self.request.session.flash( diff --git a/lms/views/admin/application_instance/upgrade.py b/lms/views/admin/application_instance/upgrade.py index 9d45711077..2f30f32724 100644 --- a/lms/views/admin/application_instance/upgrade.py +++ b/lms/views/admin/application_instance/upgrade.py @@ -38,7 +38,7 @@ def upgrade_instance_start(self): ) else: # This shouldn't really happen, but belt and braces - raise HTTPClientError("`lti_registration_id` is required for an upgrade") + raise HTTPClientError("`lti_registration_id` is required for an upgrade") # noqa: EM101, TRY003 return dict(self.request.params, lti_registration=lti_registration) diff --git a/lms/views/admin/assignment.py b/lms/views/admin/assignment.py index 923a355fcf..b15017d6a2 100644 --- a/lms/views/admin/assignment.py +++ b/lms/views/admin/assignment.py @@ -51,7 +51,7 @@ def assignment_dashboard(self): ) # We can only navigate to assignments via their course, assigmentl.course won't be null - assert assignment.course + assert assignment.course # noqa: S101 response = HTTPFound( location=self.request.route_url( "dashboard.organization.assignment", @@ -67,4 +67,4 @@ def _get_or_404(self) -> Assignment: ): return assignment - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 diff --git a/lms/views/admin/course.py b/lms/views/admin/course.py index b08aca054c..ded604c33d 100644 --- a/lms/views/admin/course.py +++ b/lms/views/admin/course.py @@ -103,7 +103,7 @@ def search(self): if org_public_id := self.request.params.get("org_public_id", "").strip(): try: organization = self.organization_service.get_by_public_id(org_public_id) - assert organization + assert organization # noqa: S101 organization_ids = self.organization_service.get_hierarchy_ids( organization.id, include_parents=False ) @@ -126,4 +126,4 @@ def _get_course_or_404(self, id_) -> Course: if course := self.course_service.get_by_id(id_=id_): return course - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 diff --git a/lms/views/admin/email.py b/lms/views/admin/email.py index d1d9d79bf7..70718c5520 100644 --- a/lms/views/admin/email.py +++ b/lms/views/admin/email.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta # noqa: A005 from pyramid.httpexceptions import HTTPBadRequest, HTTPFound from pyramid.renderers import render @@ -30,36 +30,36 @@ def post(self): until = self.request.POST["until"].strip() if not to_email: - raise HTTPBadRequest( - "You must enter an email address to send the test email(s) to." + raise HTTPBadRequest( # noqa: TRY003 + "You must enter an email address to send the test email(s) to." # noqa: EM101 ) if not to_email.endswith("@hypothes.is"): - raise HTTPBadRequest( - "Test emails can only be sent to @hypothes.is addresses." + raise HTTPBadRequest( # noqa: TRY003 + "Test emails can only be sent to @hypothes.is addresses." # noqa: EM101 ) if len(h_userids) > 3: - raise HTTPBadRequest( - "Test emails can only be sent for up to 3 users at once." + raise HTTPBadRequest( # noqa: TRY003 + "Test emails can only be sent for up to 3 users at once." # noqa: EM101 ) try: since = datetime.fromisoformat(since) until = datetime.fromisoformat(until) except ValueError as exc: - raise HTTPBadRequest( - "Times must be in ISO 8601 format, for example: '2023-02-27T00:00:00'." + raise HTTPBadRequest( # noqa: TRY003 + "Times must be in ISO 8601 format, for example: '2023-02-27T00:00:00'." # noqa: EM101 ) from exc if until <= since: - raise HTTPBadRequest( - "The 'since' time must be earlier than the 'until' time." + raise HTTPBadRequest( # noqa: TRY003 + "The 'since' time must be earlier than the 'until' time." # noqa: EM101 ) if (until - since) > timedelta(days=30): - raise HTTPBadRequest( - "The 'since' and 'until' times must be less than 30 days apart." + raise HTTPBadRequest( # noqa: TRY003 + "The 'since' and 'until' times must be less than 30 days apart." # noqa: EM101 ) for h_userid in h_userids: diff --git a/lms/views/admin/lti_registration.py b/lms/views/admin/lti_registration.py index e7a4923702..cf2ec568ae 100644 --- a/lms/views/admin/lti_registration.py +++ b/lms/views/admin/lti_registration.py @@ -25,7 +25,7 @@ class LTIRegistrationBaseSchema(PyramidRequestSchema): def validate_issuer(self, value): if value.endswith("/"): # Prevent confusion with application instance LMS URL. - raise ValidationError("Issuer can't end with '/'") + raise ValidationError("Issuer can't end with '/'") # noqa: EM101, TRY003 class SearchLTIRegistrationSchema(PyramidRequestSchema): @@ -229,4 +229,4 @@ def _get_registration_or_404(self, id_) -> LTIRegistration: if org := self.lti_registration_service.get_by_id(id_): return org - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 diff --git a/lms/views/admin/organization.py b/lms/views/admin/organization.py index c6525e7d82..bcb47c4f35 100644 --- a/lms/views/admin/organization.py +++ b/lms/views/admin/organization.py @@ -156,7 +156,7 @@ def toggle_organization_enabled(self): request_org_id = self.request.matchdict["id_"] for org_id in self.organization_service.get_hierarchy_ids(request_org_id): org = self.organization_service.get_by_id(org_id) - assert org, "Organization {org_id} not found" + assert org, "Organization {org_id} not found" # noqa: S101 self.organization_service.update_organization( org, enabled=self.request.params.get("enabled", "") == "on" ) @@ -207,17 +207,17 @@ def usage(self): since = datetime.fromisoformat(self.request.params["since"]) until = datetime.fromisoformat(self.request.params["until"]) except ValueError as exc: - raise HTTPBadRequest( - "Times must be in ISO 8601 format, for example: '2023-02-27T00:00:00'." + raise HTTPBadRequest( # noqa: TRY003 + "Times must be in ISO 8601 format, for example: '2023-02-27T00:00:00'." # noqa: EM101 ) from exc if until <= since: - raise HTTPBadRequest( - "The 'since' time must be earlier than the 'until' time." + raise HTTPBadRequest( # noqa: TRY003 + "The 'since' time must be earlier than the 'until' time." # noqa: EM101 ) - if since < datetime(2023, 1, 1): - raise HTTPBadRequest("Usage reports can only be generated since 2023") + if since < datetime(2023, 1, 1): # noqa: DTZ001 + raise HTTPBadRequest("Usage reports can only be generated since 2023") # noqa: EM101, TRY003 try: report = self.organization_usage_report_service.usage_report( @@ -315,4 +315,4 @@ def _get_org_or_404(self, id_) -> Organization: if org := self.organization_service.get_by_id(id_): return org - raise HTTPNotFound() + raise HTTPNotFound() # noqa: RSE102 diff --git a/lms/views/admin/role.py b/lms/views/admin/role.py index 0e8530ef42..b02dc2f92b 100644 --- a/lms/views/admin/role.py +++ b/lms/views/admin/role.py @@ -3,7 +3,7 @@ from lms.events import AuditTrailEvent from lms.security import Permissions -from lms.services.application_instance import ApplicationInstanceService +from lms.services.application_instance import ApplicationInstanceService # noqa: TC001 from lms.services.lti_role_service import LTIRoleService diff --git a/lms/views/api/blackboard/files.py b/lms/views/api/blackboard/files.py index a683ec7a1f..1c1f31f55c 100644 --- a/lms/views/api/blackboard/files.py +++ b/lms/views/api/blackboard/files.py @@ -76,13 +76,14 @@ def via_url(self): document_url = self.request.params["document_url"] document_url_match = DOCUMENT_URL_REGEX.search(document_url) - assert document_url_match + assert document_url_match # noqa: S101 file_id = course.get_mapped_file_id(document_url_match["file_id"]) try: - if self.request.lti_user.is_instructor: + if self.request.lti_user.is_instructor: # noqa: SIM102 if not self.course_copy_plugin.is_file_in_course(course_id, file_id): - raise FileNotFoundInCourse( - "blackboard_file_not_found_in_course", file_id + raise FileNotFoundInCourse( # noqa: TRY301 + "blackboard_file_not_found_in_course", # noqa: EM101 + file_id, ) public_url = self.blackboard_api_client.public_url(course_id, file_id) diff --git a/lms/views/api/canvas/authorize.py b/lms/views/api/canvas/authorize.py index 6b52aeee54..aa754f3517 100644 --- a/lms/views/api/canvas/authorize.py +++ b/lms/views/api/canvas/authorize.py @@ -72,7 +72,7 @@ def authorize(request): # If the instance could add a new course with sections... application_instance.settings.get("canvas", "sections_enabled") # ... or any of it's existing courses have sections - or course_service.any_with_setting("canvas", "sections_enabled", True) + or course_service.any_with_setting("canvas", "sections_enabled", True) # noqa: FBT003 ): scopes += SCOPES["sections"] @@ -115,7 +115,7 @@ def oauth2_redirect(request): try: canvas_api_client.get_token(authorization_code) except CanvasAPIServerError as err: - raise HTTPInternalServerError("Authorizing with the Canvas API failed") from err + raise HTTPInternalServerError("Authorizing with the Canvas API failed") from err # noqa: EM101, TRY003 return {} @@ -152,7 +152,7 @@ def oauth2_redirect_error(request): ) if error_description := request.params.get("error_description"): - kwargs["error_details"] = {"error_description": error_description} # type: ignore + kwargs["error_details"] = {"error_description": error_description} # type: ignore # noqa: PGH003 request.context.js_config.enable_oauth2_redirect_error_mode(**kwargs) diff --git a/lms/views/api/canvas/files.py b/lms/views/api/canvas/files.py index a8a4a3ea17..cc25c86f05 100644 --- a/lms/views/api/canvas/files.py +++ b/lms/views/api/canvas/files.py @@ -46,7 +46,7 @@ def via_url(self): ) document_url_match = DOCUMENT_URL_REGEX.search(assignment.document_url) - assert document_url_match + assert document_url_match # noqa: S101 public_url = self.canvas.public_url_for_file( assignment, document_url_match["file_id"], diff --git a/lms/views/api/canvas/pages.py b/lms/views/api/canvas/pages.py index 59e7017076..0ea16d4a67 100644 --- a/lms/views/api/canvas/pages.py +++ b/lms/views/api/canvas/pages.py @@ -95,7 +95,8 @@ def via_url(self): current_course_id, ) raise PageNotFoundInCourse( - "canvas_page_not_found_in_course", document_page_id + "canvas_page_not_found_in_course", # noqa: EM101 + document_page_id, ) # Store a mapping so we don't have to re-search next time. @@ -116,7 +117,8 @@ def via_url(self): _ = self.canvas.api.pages.page(current_course_id, effective_page_id) except CanvasAPIError as err: raise PageNotFoundInCourse( - "canvas_page_not_found_in_course", effective_page_id + "canvas_page_not_found_in_course", # noqa: EM101 + effective_page_id, ) from err # We build a token to authorize the view that fetches the actual @@ -168,7 +170,7 @@ def proxy(self): @staticmethod def _parse_document_url(document_url): document_url_match = DOCUMENT_URL_REGEX.search(document_url) - assert document_url_match + assert document_url_match # noqa: S101 course_id = document_url_match["course_id"] page_id = document_url_match["page_id"] diff --git a/lms/views/api/canvas_studio.py b/lms/views/api/canvas_studio.py index 91c6d055fd..687a465500 100644 --- a/lms/views/api/canvas_studio.py +++ b/lms/views/api/canvas_studio.py @@ -149,7 +149,8 @@ def via_url(request): media_id = CanvasStudioService.media_id_from_url(document_url) if not media_id: raise CanvasStudioLaunchError( - "canvas_studio_media_not_found", "Unable to get Canvas Studio media ID" + "canvas_studio_media_not_found", # noqa: EM101 + "Unable to get Canvas Studio media ID", ) svc = request.find_service(CanvasStudioService) @@ -163,14 +164,14 @@ def via_url(request): download_url = svc.get_video_download_url(media_id) if not download_url: raise CanvasStudioLaunchError( - "canvas_studio_download_unavailable", + "canvas_studio_download_unavailable", # noqa: EM101 "Hypothesis was unable to fetch the video", ) transcript_url = svc.get_transcript_url(media_id) if not transcript_url: raise CanvasStudioLaunchError( - "canvas_studio_transcript_unavailable", + "canvas_studio_transcript_unavailable", # noqa: EM101 "This video does not have a published transcript", ) diff --git a/lms/views/api/d2l/authorize.py b/lms/views/api/d2l/authorize.py index 04ffd27819..22006b692d 100644 --- a/lms/views/api/d2l/authorize.py +++ b/lms/views/api/d2l/authorize.py @@ -26,7 +26,7 @@ def authorize(request): scopes += GROUPS_SCOPES if not scopes: - raise NotImplementedError("Trying to get a D2L without any scopes") + raise NotImplementedError("Trying to get a D2L without any scopes") # noqa: EM101 application_instance = request.lti_user.application_instance state = OAuthCallbackSchema(request).state_param() diff --git a/lms/views/api/d2l/files.py b/lms/views/api/d2l/files.py index 8138f72cf5..6420e03203 100644 --- a/lms/views/api/d2l/files.py +++ b/lms/views/api/d2l/files.py @@ -41,13 +41,14 @@ def via_url(_context, request): ) document_url_match = DOCUMENT_URL_REGEX.search(document_url) - assert document_url_match + assert document_url_match # noqa: S101 file_id = course.get_mapped_file_id(document_url_match["file_id"]) try: - if request.lti_user.is_instructor: + if request.lti_user.is_instructor: # noqa: SIM102 if not course_copy_plugin.is_file_in_course(course_id, file_id): - raise FileNotFoundInCourse( - "d2l_file_not_found_in_course_instructor", file_id + raise FileNotFoundInCourse( # noqa: TRY301 + "d2l_file_not_found_in_course_instructor", # noqa: EM101 + file_id, ) public_url = api_client.public_url(course_id, file_id) diff --git a/lms/views/api/exceptions.py b/lms/views/api/exceptions.py index 263267562e..b00967cbbb 100644 --- a/lms/views/api/exceptions.py +++ b/lms/views/api/exceptions.py @@ -290,8 +290,8 @@ def __json__(self, request): if refresh_route: path = request.route_path(refresh_route) else: - raise ValueError( - f"No OAuth 2 refresh API for {request.product.family}" + raise ValueError( # noqa: TRY003 + f"No OAuth 2 refresh API for {request.product.family}" # noqa: EM102 ) body["refresh"] = {"method": "POST", "path": path} diff --git a/lms/views/api/grading.py b/lms/views/api/grading.py index 8bcc10063f..7c541fece4 100644 --- a/lms/views/api/grading.py +++ b/lms/views/api/grading.py @@ -140,7 +140,7 @@ def record_canvas_speedgrader_submission(self): error_code=ErrorCode.CANVAS_SUBMISSION_COURSE_NOT_AVAILABLE ) from err - raise err + raise err # noqa: TRY201 self.request.registry.notify( LTIEvent.from_request(request=self.request, type_=LTIEvent.Type.SUBMISSION) diff --git a/lms/views/api/moodle/files.py b/lms/views/api/moodle/files.py index c07fcddaad..261464cb23 100644 --- a/lms/views/api/moodle/files.py +++ b/lms/views/api/moodle/files.py @@ -45,7 +45,7 @@ def via_url(_context, request): document_url = request.params["document_url"] document_url_match = DOCUMENT_URL_REGEX.search(document_url) - assert document_url_match + assert document_url_match # noqa: S101 document_course_id = document_url_match["course_id"] document_file_id = document_url_match["url"] effective_file_id = _effective_file_id( @@ -54,7 +54,7 @@ def via_url(_context, request): # Try to access the file, we have to check that we have indeed access to this file. if not api.file_exists(effective_file_id): - raise FileNotFoundInCourse("moodle_file_not_found_in_course", document_url) + raise FileNotFoundInCourse("moodle_file_not_found_in_course", document_url) # noqa: EM101 return { "via_url": helpers.via_url( @@ -93,7 +93,7 @@ def _effective_file_id( document_url, course.lms_id, ) - raise FileNotFoundInCourse("moodle_file_not_found_in_course", document_url) + raise FileNotFoundInCourse("moodle_file_not_found_in_course", document_url) # noqa: EM101 # Store a mapping so we don't have to re-search next time. LOG.debug( "Via URL for page, found page in the new course. Document: %s, course: %s, new page id: %s", diff --git a/lms/views/api/moodle/pages.py b/lms/views/api/moodle/pages.py index 979343e2c8..f6b690bd2d 100644 --- a/lms/views/api/moodle/pages.py +++ b/lms/views/api/moodle/pages.py @@ -59,7 +59,7 @@ def via_url(self): # We don't need the result of this exact call but # we can check that we have indeed access to this page. if not self.api.page(current_course.lms_id, effective_page_id): - raise PageNotFoundInCourse("moodle_page_not_found_in_course", document_url) + raise PageNotFoundInCourse("moodle_page_not_found_in_course", document_url) # noqa: EM101 # We build a token to authorize the view that fetches the actual # pages content as the user making this request. @@ -113,7 +113,7 @@ def proxy(self): @staticmethod def _parse_document_url(document_url): document_url_match = DOCUMENT_URL_REGEX.search(document_url) - assert document_url_match + assert document_url_match # noqa: S101 course_id = document_url_match["course_id"] page_id = document_url_match["page_id"] @@ -149,7 +149,7 @@ def _effective_page_id( document_url, course.lms_id, ) - raise PageNotFoundInCourse("moodle_page_not_found_in_course", document_page_id) + raise PageNotFoundInCourse("moodle_page_not_found_in_course", document_page_id) # noqa: EM101 # Store a mapping so we don't have to re-search next time. course.set_mapped_page_id(document_page_id, found_page.lms_id) diff --git a/lms/views/dashboard/api/grading.py b/lms/views/dashboard/api/grading.py index 516919501e..d0b7f246d4 100644 --- a/lms/views/dashboard/api/grading.py +++ b/lms/views/dashboard/api/grading.py @@ -8,7 +8,7 @@ from lms.models.grading_sync import AutoGradingSyncStatus from lms.security import Permissions from lms.services import AutoGradingService -from lms.services.dashboard import DashboardService +from lms.services.dashboard import DashboardService # noqa: TC001 from lms.tasks.grading import sync_grades from lms.validation._base import JSONPyramidRequestSchema diff --git a/lms/views/dashboard/pagination.py b/lms/views/dashboard/pagination.py index 38325e2708..d347e79195 100644 --- a/lms/views/dashboard/pagination.py +++ b/lms/views/dashboard/pagination.py @@ -56,7 +56,7 @@ def get_page( else: # In the non-null case we can sort by both columns items_query = items_query.where( - tuple(cursor_columns) > tuple(cursor_values) # type: ignore + tuple(cursor_columns) > tuple(cursor_values) # type: ignore # noqa: PGH003 ) limit = min(MAX_ITEMS_PER_PAGE, request.parsed_params["limit"]) @@ -91,15 +91,15 @@ def decode_cursor(self, in_data: dict, **_kwargs) -> dict: try: in_data["cursor"] = json.loads(cursor) except ValueError as exc: - raise ValidationError("Invalid value for pagination cursor.") from exc + raise ValidationError("Invalid value for pagination cursor.") from exc # noqa: EM101, TRY003 if not isinstance(in_data["cursor"], list) or len(in_data["cursor"]) != 2: - raise ValidationError( - "Invalid value for pagination cursor. Cursor must be a list of at least two values." + raise ValidationError( # noqa: TRY003 + "Invalid value for pagination cursor. Cursor must be a list of at least two values." # noqa: EM101 ) if [type(v) for v in in_data["cursor"]] not in [[str, int], [NoneType, int]]: - raise ValidationError( - "Invalid value for pagination cursor. Cursor must be a [str | None, int] list." + raise ValidationError( # noqa: TRY003 + "Invalid value for pagination cursor. Cursor must be a [str | None, int] list." # noqa: EM101 ) return in_data diff --git a/lms/views/email.py b/lms/views/email.py index f965d9a45c..b9c8e1da1b 100644 --- a/lms/views/email.py +++ b/lms/views/email.py @@ -1,4 +1,4 @@ -import logging +import logging # noqa: A005 from pyramid.httpexceptions import HTTPFound from pyramid.security import remember diff --git a/lms/views/favicon.py b/lms/views/favicon.py index 77d3989c96..7d7282710e 100644 --- a/lms/views/favicon.py +++ b/lms/views/favicon.py @@ -6,6 +6,6 @@ @view_config(route_name="favicon") def favicon(request): - here = os.path.dirname(__file__) - icon = os.path.join(here, "..", "static", "images", "favicons", "favicon.ico") + here = os.path.dirname(__file__) # noqa: PTH120 + icon = os.path.join(here, "..", "static", "images", "favicons", "favicon.ico") # noqa: PTH118 return FileResponse(icon, request=request) diff --git a/lms/views/helpers/_via.py b/lms/views/helpers/_via.py index 2e067a1957..9866a16e66 100644 --- a/lms/views/helpers/_via.py +++ b/lms/views/helpers/_via.py @@ -20,7 +20,7 @@ def _common_via_params(request) -> dict: } -def via_url( # noqa: PLR0913, PLR0917 +def via_url( # noqa: PLR0913 request, document_url, content_type=None, options=None, headers=None, query=None ): """ diff --git a/lms/views/lti/basic_launch.py b/lms/views/lti/basic_launch.py index e9fa00d22b..f855d67984 100644 --- a/lms/views/lti/basic_launch.py +++ b/lms/views/lti/basic_launch.py @@ -18,10 +18,10 @@ from lms.events import LTIEvent from lms.models import Assignment -from lms.product.plugin.misc import MiscPlugin +from lms.product.plugin.misc import MiscPlugin # noqa: TC001 from lms.security import Permissions from lms.services import LTIGradingService, UserService, VitalSourceService -from lms.services.assignment import AssignmentService +from lms.services.assignment import AssignmentService # noqa: TC001 from lms.validation import BasicLTILaunchSchema, ConfigureAssignmentSchema LOG = logging.getLogger(__name__) diff --git a/lms/views/lti/deep_linking.py b/lms/views/lti/deep_linking.py index 4ce2943758..72cc154af9 100644 --- a/lms/views/lti/deep_linking.py +++ b/lms/views/lti/deep_linking.py @@ -46,7 +46,7 @@ from webargs import fields from lms.events import LTIEvent -from lms.product.plugin.misc import MiscPlugin +from lms.product.plugin.misc import MiscPlugin # noqa: TC001 from lms.security import Permissions from lms.services import JWTService, UserService from lms.validation import DeepLinkingLTILaunchSchema @@ -145,7 +145,7 @@ def file_picker_to_form_fields_v13(self): if title := assignment_configuration.get("title"): content_item["title"] = title - now = datetime.utcnow() + now = datetime.utcnow() # noqa: DTZ003 message = { "exp": now + timedelta(hours=1), "iat": now, @@ -271,6 +271,6 @@ def _get_assignment_configuration(request) -> dict: if content["type"] == "url": params["url"] = content["url"] else: - raise ValueError(f"Unknown content type: '{content['type']}'") + raise ValueError(f"Unknown content type: '{content['type']}'") # noqa: EM102, TRY003 return params diff --git a/lms/views/status.py b/lms/views/status.py index c9f402c218..56a4f639d5 100644 --- a/lms/views/status.py +++ b/lms/views/status.py @@ -14,7 +14,7 @@ def status(request): request.db.execute(text("SELECT 1")) except Exception as err: LOG.exception("Executing a simple database query failed:") - raise HTTPInternalServerError("Database connection failed") from err + raise HTTPInternalServerError("Database connection failed") from err # noqa: EM101, TRY003 if "sentry" in request.params: capture_message("Test message from LMS's status view") diff --git a/pyproject.toml b/pyproject.toml index 24055819b9..838b2e4af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,49 +17,9 @@ target-version = "py311" [tool.ruff.lint] select = ["ALL"] ignore = [ - "UP", # pyupgrade - "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info "ANN", # flake8-annotations (checks for absence of type annotations on functions) - "ASYNC", # flake8-async (checks for asyncio-related problems) - "S", # flake8-bandit (checks for security issues) - "FBT", # flake8-boolean-trap (checks for the "boolean trap" anti-pattern) - "B", # flake8-bugbear (checks for bugs and design problems) - "A", # flake8-builtins (checks for builtins being overridden) "CPY", # flake8-copyright (checks for missing copyright notices) - "C4", # flake8-comprehensions (helps write better list/set/dict comprehensions) - "DTZ", # flake8-datetimez (checks for usages of unsafe naive datetime class) - "T10", # flake8-debugger (checks for set traces etc) - "EM", # flake8-errmsg (checks for error message formatting issues) - "EXE", # flake8-executable (checks for incorrect executable permissions and shebangs) - "FA", # flake8-future-annotations (checks for missing from __future__ import annotations) - "ISC", # flake8-implicit-str-concat (checks for style problems with string literal concatenation) - "ICN", # flake8-import-conventions (checks for unconventional imports and aliases) - "LOG", # flake8-logging (checks for issues with using the logging module) - "G", # flake8-logging-format (enforce usage of `extra` in logging calls) - "INP", # flake8-no-pep420 (checks for missing __init__.py files) - "PIE", # flake8-pie (miscellaneous) - "T20", # flake8-print (checks for print and pprint statements) - "PT", # flake8-pytest-style (checks for common pytest style and consistency issues) - "RSE", # flake8-raise (checks for issues with raising exceptions) - "RET", # flake8-return (checks for issues with return values) - "SLOT", # flake8-slots (requires __slots__ in subclasses of immutable types) - "SIM", # flake8-simplify (lots of code simplification checks) - "TID", # flake8-tidy-imports (checks for issues with imports) - "TC", # flake8-type-checking (checks for type checking imports that aren't in TYPE_CHECKING blocks) - "ARG", # flake8-unused-arguments (checks for unused arguments) - "PTH", # flake8-use-pathlib (checks for cases with pathlib could be used but isn't) - "TD", # flake8-todos (enforces good style for "# TODO" comments) - "FIX", # flake8-fixme (checks for FIXMEs, TODOs, HACKs, etc) - "ERA", # eradicate (checks for commented-out code) - "PGH", # pygrep-hooks (miscellaneous) - "PL", # pylint (miscellaneous rules from pylint) - "TRY", # tryceratops (various try/except-related checks) - "FLY", # flynt (checks for old-style %-formatted strings) - "PERF", # perflint (checks for performance anti-patterns) - "FURB", # refurb (various "refurbishing and modernizing" checks) - "DOC", # pydoclint (docstring checks) - "RUF", # Ruff-specific rules - "COM", # flake8-commas (we used a code formatter so we don't need a linter to check this) + "COM", # flake8-commas (we use a code formatter so we don't need a linter to check this) "D100","D101","D102","D103","D104","D105","D106","D107", # Missing docstrings. "D202", # "No blank lines allowed after function docstring" conflicts with the Ruff code formatter. # "Multi-line docstring summary should start at the first line" (D212) @@ -91,10 +51,23 @@ ignore = [ "PLR0913", # Too many arguments. Tests often have lots of arguments. "PLR0917", # Too many positional arguments. Tests often have lots of arguments. "PLR0904", # Too many public methods. Test classes often have lots of test methods. + "S101", # Use of `assert` detected. + "PT006", # Enforces a consistent style for the type of the `argnames` parameter to + # pytest.mark.parametrize. We have too many pre-existing violations of + # this. + "PT007", # Enforces a consistent style for the type of the `argvalues` parameter to + # pytest.mark.parametrize. We have too many pre-existing violations of + # this. ] "__init__.py" = [ "F401", # Ignore unused import errors on __init__ files to avoid having to add either a noqa stament or an __all__ declaration. ] +"lms/migrations/*" = [ + "INP001", +] +"bin/*" = [ + "INP001", +] [tool.coverage.run] branch = true diff --git a/tests/factories/oauth2_token.py b/tests/factories/oauth2_token.py index a7f967df50..5e99933984 100644 --- a/tests/factories/oauth2_token.py +++ b/tests/factories/oauth2_token.py @@ -15,6 +15,6 @@ access_token=ACCESS_TOKEN, # This is intentionally an "old" time and not a token that was very recently # received. - received_at=datetime(2023, 12, 1), + received_at=datetime(2023, 12, 1), # noqa: DTZ001 refresh_token=REFRESH_TOKEN, ) diff --git a/tests/functional/bin/run_data_task_test.py b/tests/functional/bin/run_data_task_test.py index 1b12d9ed40..0692d230ad 100644 --- a/tests/functional/bin/run_data_task_test.py +++ b/tests/functional/bin/run_data_task_test.py @@ -4,7 +4,7 @@ import pytest from importlib_resources import files -from pytest import fixture +from pytest import fixture # noqa: PT013 from tests.functional.conftest import TEST_ENVIRONMENT @@ -29,7 +29,7 @@ def test_reporting_tasks(self, environ): self.run_task(environ, task_name) def run_task(self, environ, task_name): - result = check_output( + result = check_output( # noqa: S603 [ sys.executable, "bin/run_data_task.py", @@ -43,8 +43,8 @@ def run_task(self, environ, task_name): assert result - print(f"Task {task_name} OK!") - print(result.decode("utf-8")) + print(f"Task {task_name} OK!") # noqa: T201 + print(result.decode("utf-8")) # noqa: T201 @fixture def environ(self): @@ -59,7 +59,7 @@ def environ(self): def run_in_root(self): # A context manager to ensure we work from the root, but return the # path to where it was before - current_dir = os.getcwd() + current_dir = os.getcwd() # noqa: PTH109 os.chdir(str(files("lms") / "..")) yield diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index f1c6646371..49b021445b 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -142,7 +142,7 @@ def intercept_http_calls_to_h(): # Catch URLs we aren't expecting or have failed to mock def error_response(request, uri, _response_headers): - raise NotImplementedError(f"Unexpected call to URL: {request.method} {uri}") + raise NotImplementedError(f"Unexpected call to URL: {request.method} {uri}") # noqa: EM102 with httpretty.enabled(): # Mock all calls to the H API diff --git a/tests/functional/lti_certification/v13/conftest.py b/tests/functional/lti_certification/v13/conftest.py index d0f2642e76..e11077cdbc 100644 --- a/tests/functional/lti_certification/v13/conftest.py +++ b/tests/functional/lti_certification/v13/conftest.py @@ -5,7 +5,7 @@ import importlib_resources import jwt import pytest -from pytest import register_assert_rewrite +from pytest import register_assert_rewrite # noqa: PT013 from tests import factories @@ -49,7 +49,7 @@ def lti_registration(db_session): # noqa: ARG001 issuer="https://ltiadvantagevalidator.imsglobal.org", client_id="imstester_4ba76ab", key_set_url="https://oauth2server.imsglobal.org/jwks", - token_url="https://ltiadvantagevalidator.imsglobal.org/ltitool/authcodejwt.html", + token_url="https://ltiadvantagevalidator.imsglobal.org/ltitool/authcodejwt.html", # noqa: S106 ) @@ -113,7 +113,7 @@ def teacher_payload(common_payload): @pytest.fixture def common_payload(): # This is the parts of the payload common to both teacher and student - now = int(datetime.timestamp(datetime.now())) + now = int(datetime.timestamp(datetime.now())) # noqa: DTZ005 return { "exp": now + 60, diff --git a/tests/functional/lti_certification/v13/core/test_bad_payloads.py b/tests/functional/lti_certification/v13/core/test_bad_payloads.py index 09be1efda2..44019fd169 100644 --- a/tests/functional/lti_certification/v13/core/test_bad_payloads.py +++ b/tests/functional/lti_certification/v13/core/test_bad_payloads.py @@ -119,7 +119,7 @@ def test_user_claim_missing(self, test_payload, assert_missing_claim): def test_payload(self, request): """Get an OAuthToken or None based on the fixture params.""" - yield request.getfixturevalue(request.param) + return request.getfixturevalue(request.param) @pytest.fixture(autouse=True) def debug_log_capture(self, caplog): diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 07f0ea7991..3ec253fdc6 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -292,8 +292,8 @@ def application_instance(db_session): organization=factories.Organization(), ) - application_instance.settings.set("canvas", "sections_enabled", True) - application_instance.settings.set("canvas", "groups_enabled", False) + application_instance.settings.set("canvas", "sections_enabled", True) # noqa: FBT003 + application_instance.settings.set("canvas", "groups_enabled", False) # noqa: FBT003 # Force flush to get a non None application_instance.id db_session.flush() diff --git a/tests/unit/lms/config_test.py b/tests/unit/lms/config_test.py index 4f9cb4accf..c3c16191cb 100644 --- a/tests/unit/lms/config_test.py +++ b/tests/unit/lms/config_test.py @@ -6,7 +6,7 @@ class TestConfigure: # These are settings with no special tricks - NORMAL_SETTINGS = { + NORMAL_SETTINGS = { # noqa: RUF012 setting for setting in SETTINGS if not setting.value_mapper and setting.read_from == setting.name @@ -75,7 +75,7 @@ def test_it_gets_aes_secret(self, environ, lms_secret, aes_secret): assert configure({}).registry.settings["aes_secret"] == aes_secret def test_it_aes_secret_raises_for_non_ascii(self, environ): - environ["LMS_SECRET"] = "\u2119" + environ["LMS_SECRET"] = "\u2119" # noqa: S105 with pytest.raises(SettingError): configure({}) @@ -87,5 +87,5 @@ def config_file(self): @pytest.fixture(autouse=True) def environ(self, patch): os = patch("lms.config.os") - os.environ = {setting.read_from.upper(): "env" for setting in SETTINGS} + os.environ = {setting.read_from.upper(): "env" for setting in SETTINGS} # noqa: B003 return os.environ diff --git a/tests/unit/lms/data_tasks/report/create_from_scratch/test_01_functions/test_01_date_functions.py b/tests/unit/lms/data_tasks/report/create_from_scratch/test_01_functions/test_01_date_functions.py index 387cada462..072fe97446 100644 --- a/tests/unit/lms/data_tasks/report/create_from_scratch/test_01_functions/test_01_date_functions.py +++ b/tests/unit/lms/data_tasks/report/create_from_scratch/test_01_functions/test_01_date_functions.py @@ -9,8 +9,8 @@ class TestDateFunctions: - ONE_YEAR_AGO = datetime.now().replace(year=datetime.now().year - 1).date() - TWO_YEARS_AGO = datetime.now().replace(year=datetime.now().year - 2).date() + ONE_YEAR_AGO = datetime.now().replace(year=datetime.now().year - 1).date() # noqa: DTZ005 + TWO_YEARS_AGO = datetime.now().replace(year=datetime.now().year - 2).date() # noqa: DTZ005 @pytest.mark.usefixtures("with_date_functions") @pytest.mark.parametrize( diff --git a/tests/unit/lms/extensions/feature_flags/_helpers_test.py b/tests/unit/lms/extensions/feature_flags/_helpers_test.py index d05852072f..e4e3bed342 100644 --- a/tests/unit/lms/extensions/feature_flags/_helpers_test.py +++ b/tests/unit/lms/extensions/feature_flags/_helpers_test.py @@ -143,5 +143,5 @@ def test_that_set_and_get_work_together(self, pyramid_request): @pytest.fixture(autouse=True) def pyramid_config(self, pyramid_config): - pyramid_config.registry.settings["feature_flags_cookie_secret"] = "test_secret" + pyramid_config.registry.settings["feature_flags_cookie_secret"] = "test_secret" # noqa: S105 return pyramid_config diff --git a/tests/unit/lms/extensions/feature_flags/_providers_test.py b/tests/unit/lms/extensions/feature_flags/_providers_test.py index 1423b02b39..f7e683ea02 100644 --- a/tests/unit/lms/extensions/feature_flags/_providers_test.py +++ b/tests/unit/lms/extensions/feature_flags/_providers_test.py @@ -40,7 +40,7 @@ def test_it(self, pyramid_request, envvars, result, os): @pytest.fixture(autouse=True) def os(self, patch): os = patch("lms.extensions.feature_flags._providers.os") - os.environ = {} + os.environ = {} # noqa: B003 return os diff --git a/tests/unit/lms/models/application_instance_test.py b/tests/unit/lms/models/application_instance_test.py index 78aa7da825..40916139d6 100644 --- a/tests/unit/lms/models/application_instance_test.py +++ b/tests/unit/lms/models/application_instance_test.py @@ -79,7 +79,7 @@ def test_lms_host(self, application_instance): def test_lms_host_raises_ValueError(self, application_instance, lms_url): application_instance.lms_url = lms_url - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 application_instance.lms_host() def test_decrypted_developer_secret_returns_the_decrypted_developer_secret( diff --git a/tests/unit/lms/models/assignment_grouping_test.py b/tests/unit/lms/models/assignment_grouping_test.py index d97105dadc..0ce7b48df4 100644 --- a/tests/unit/lms/models/assignment_grouping_test.py +++ b/tests/unit/lms/models/assignment_grouping_test.py @@ -25,7 +25,7 @@ def test_it_does_not_allow_duplicates(self, db_session, assignment, grouping): db_session.add(AssignmentGrouping(assignment=assignment, grouping=grouping)) db_session.commit() - with pytest.raises(IntegrityError): + with pytest.raises(IntegrityError): # noqa: PT012 db_session.add(AssignmentGrouping(assignment=assignment, grouping=grouping)) db_session.commit() diff --git a/tests/unit/lms/models/assignment_membership_test.py b/tests/unit/lms/models/assignment_membership_test.py index 620e7c8ee0..314923c913 100644 --- a/tests/unit/lms/models/assignment_membership_test.py +++ b/tests/unit/lms/models/assignment_membership_test.py @@ -31,7 +31,7 @@ def test_it_does_not_allow_duplicates(self, db_session, user, assignment, lti_ro ) db_session.commit() - with pytest.raises(IntegrityError): + with pytest.raises(IntegrityError): # noqa: PT012 db_session.add( AssignmentMembership( user=user, assignment=assignment, lti_role=lti_role diff --git a/tests/unit/lms/models/group_info_test.py b/tests/unit/lms/models/group_info_test.py index 1e23a8e574..d620211ae8 100644 --- a/tests/unit/lms/models/group_info_test.py +++ b/tests/unit/lms/models/group_info_test.py @@ -39,7 +39,7 @@ def test_upsert_instructor_when_existing_instructors( group_info.upsert_instructor(new_instructor) - assert group_info.instructors == existing_instructors + [new_instructor] + assert group_info.instructors == existing_instructors + [new_instructor] # noqa: RUF005 def test_upsert_instructor_when_existing_matching_instructor( self, group_info, existing_instructors diff --git a/tests/unit/lms/models/lti_params_test.py b/tests/unit/lms/models/lti_params_test.py index 55fa04a5d1..78a3d2c74d 100644 --- a/tests/unit/lms/models/lti_params_test.py +++ b/tests/unit/lms/models/lti_params_test.py @@ -158,6 +158,6 @@ def test_it_sets_lti_jwt(self, configurator): LTIParams.from_request, name="lti_params", property=True, reify=True ) - @pytest.fixture() + @pytest.fixture def configurator(self): return create_autospec(Configurator, spec_set=True, instance=True) diff --git a/tests/unit/lms/models/lti_role_test.py b/tests/unit/lms/models/lti_role_test.py index 7862190d25..ed255ab2cf 100644 --- a/tests/unit/lms/models/lti_role_test.py +++ b/tests/unit/lms/models/lti_role_test.py @@ -229,6 +229,6 @@ def test_it_converts_type_to_an_enum(self): assert lti_role.type == RoleType.LEARNER def test_it_raises_LookupError_for_invalid_types(self, db_session): - with pytest.raises(StatementError): + with pytest.raises(StatementError): # noqa: PT012 db_session.add(LTIRole(type="INVALID")) db_session.flush() diff --git a/tests/unit/lms/models/lti_user_test.py b/tests/unit/lms/models/lti_user_test.py index 8b7cc8be14..1ce5de6f79 100644 --- a/tests/unit/lms/models/lti_user_test.py +++ b/tests/unit/lms/models/lti_user_test.py @@ -18,7 +18,7 @@ def test_h_user(self, HUser): "role,is_instructor", [ (Role(value="r", scope=RoleScope.COURSE, type=RoleType.ADMIN), True), - (Role(value="r", scope=RoleScope.COURSE, type=RoleType.ADMIN), True), + (Role(value="r", scope=RoleScope.COURSE, type=RoleType.ADMIN), True), # noqa: PT014 (Role(value="r", scope=RoleScope.COURSE, type=RoleType.INSTRUCTOR), True), ( Role(value="r", scope=RoleScope.INSTITUTION, type=RoleType.INSTRUCTOR), diff --git a/tests/unit/lms/models/oauth2_token_test.py b/tests/unit/lms/models/oauth2_token_test.py index 49afd55f7f..ffa1f2e9b6 100644 --- a/tests/unit/lms/models/oauth2_token_test.py +++ b/tests/unit/lms/models/oauth2_token_test.py @@ -9,15 +9,15 @@ class TestOAuth2Token: def test_persist_and_retrieve_all_attrs(self, application_instance, db_session): - now = datetime.datetime.utcnow() + now = datetime.datetime.utcnow() # noqa: DTZ003 # Add a token with the default "lms" service db_session.add( OAuth2Token( user_id="test_user_id", application_instance_id=application_instance.id, - access_token="test_access_token", - refresh_token="test_refresh_token", + access_token="test_access_token", # noqa: S106 + refresh_token="test_refresh_token", # noqa: S106 expires_in=3600, received_at=now, service=Service.LMS, @@ -28,8 +28,8 @@ def test_persist_and_retrieve_all_attrs(self, application_instance, db_session): assert token.user_id == "test_user_id" assert token.application_instance_id == application_instance.id assert token.application_instance == application_instance - assert token.access_token == "test_access_token" - assert token.refresh_token == "test_refresh_token" + assert token.access_token == "test_access_token" # noqa: S105 + assert token.refresh_token == "test_refresh_token" # noqa: S105 assert token.expires_in == 3600 assert token.received_at == now assert token.service == Service.LMS @@ -39,8 +39,8 @@ def test_persist_and_retrieve_all_attrs(self, application_instance, db_session): OAuth2Token( user_id="test_user_id", application_instance_id=application_instance.id, - access_token="test_access_token", - refresh_token="test_refresh_token", + access_token="test_access_token", # noqa: S106 + refresh_token="test_refresh_token", # noqa: S106 expires_in=3600, received_at=now, service=Service.CANVAS_STUDIO, diff --git a/tests/unit/lms/product/__init___test.py b/tests/unit/lms/product/__init___test.py index 8c9a2bebcd..5659e25991 100644 --- a/tests/unit/lms/product/__init___test.py +++ b/tests/unit/lms/product/__init___test.py @@ -15,6 +15,6 @@ def test_it_sets_request_method(self, configurator): get_product_from_request, name="product", property=True, reify=True ) - @pytest.fixture() + @pytest.fixture def configurator(self): return create_autospec(Configurator, spec_set=True, instance=True) diff --git a/tests/unit/lms/product/factory_test.py b/tests/unit/lms/product/factory_test.py index 07642abbc9..3968279d9c 100644 --- a/tests/unit/lms/product/factory_test.py +++ b/tests/unit/lms/product/factory_test.py @@ -9,7 +9,7 @@ @pytest.mark.usefixtures("application_instance_service") class TestGetProductFromRequest: PRODUCTS = (Product, Blackboard, Canvas) - PRODUCT_MAP = [ + PRODUCT_MAP = [ # noqa: RUF012 # Products with a specific implementation ("BlackboardLearn", Product.Family.BLACKBOARD, Blackboard), ("canvas", Product.Family.CANVAS, Canvas), diff --git a/tests/unit/lms/product/family_test.py b/tests/unit/lms/product/family_test.py index 9f104d22af..f028ff6513 100644 --- a/tests/unit/lms/product/family_test.py +++ b/tests/unit/lms/product/family_test.py @@ -5,7 +5,7 @@ class TestFromLaunch: - PRODUCT_MAP = [ + PRODUCT_MAP = [ # noqa: RUF012 ("BlackboardLearn", Product.Family.BLACKBOARD), ("canvas", Product.Family.CANVAS), ("BlackbaudK12", Product.Family.BLACKBAUD), diff --git a/tests/unit/lms/product/moodle/_plugin/grouping_test.py b/tests/unit/lms/product/moodle/_plugin/grouping_test.py index e4871b3807..bcbbaaee54 100644 --- a/tests/unit/lms/product/moodle/_plugin/grouping_test.py +++ b/tests/unit/lms/product/moodle/_plugin/grouping_test.py @@ -142,4 +142,4 @@ def plugin(self, moodle_api_client, lti_user, group_set_service): @pytest.fixture def course(self): - return factories.Course(lms_id=random.randint(1, 1000)) + return factories.Course(lms_id=random.randint(1, 1000)) # noqa: S311 diff --git a/tests/unit/lms/renderers_test.py b/tests/unit/lms/renderers_test.py index ec8beb7a01..48e167f46e 100644 --- a/tests/unit/lms/renderers_test.py +++ b/tests/unit/lms/renderers_test.py @@ -11,7 +11,7 @@ "time,expected", [ # No timezone, UTC is assumed - (datetime(2024, 1, 1), "2024-01-01T00:00:00+00:00"), + (datetime(2024, 1, 1), "2024-01-01T00:00:00+00:00"), # noqa: DTZ001 # UTC, UTC is left intact (datetime(2024, 1, 1, tzinfo=UTC), "2024-01-01T00:00:00+00:00"), # Non-UTC, timezone is also left intact diff --git a/tests/unit/lms/security_test.py b/tests/unit/lms/security_test.py index 92eb84f187..2a3b6f61e6 100644 --- a/tests/unit/lms/security_test.py +++ b/tests/unit/lms/security_test.py @@ -242,7 +242,7 @@ def test_remember(self, policy, pyramid_request, AuthTktCookieHelper): @pytest.fixture def policy(self, email_preferences_service): return EmailPreferencesSecurityPolicy( - secret="test_email_preferences_secret", + secret="test_email_preferences_secret", # noqa: S106 domain="example.com", email_preferences_service=email_preferences_service, use_secure_cookie=True, @@ -515,7 +515,7 @@ def test_get_policy_email_preferences( sub_policy = policy.get_policy(pyramid_request) EmailPreferencesSecurityPolicy.assert_called_once_with( - secret="test_email_preferences_secret", + secret="test_email_preferences_secret", # noqa: S106 domain="example.com", email_preferences_service=email_preferences_service, use_secure_cookie=True, diff --git a/tests/unit/lms/services/application_instance_test.py b/tests/unit/lms/services/application_instance_test.py index 9cdad7829e..671b1c62e4 100644 --- a/tests/unit/lms/services/application_instance_test.py +++ b/tests/unit/lms/services/application_instance_test.py @@ -111,7 +111,7 @@ def test_update_application_instance( lms_url="http://example.com", deployment_id="DEPLOYMENT_ID", developer_key="DEVELOPER_KEY", - developer_secret="DEVELOPER_SECRET", + developer_secret="DEVELOPER_SECRET", # noqa: S106 organization_public_id=mock.sentinel.org_id, ) @@ -269,7 +269,7 @@ def test_search_by_organization_id(self, service, db_session): assert instances == [ai] def test_search_order(self, service): - now, before = datetime.now(), datetime.now() - timedelta(hours=1) + now, before = datetime.now(), datetime.now() - timedelta(hours=1) # noqa: DTZ005 # These are not in the right order on purpose factories.ApplicationInstance.create(name="b", last_launched=before) @@ -312,7 +312,7 @@ def test_update_from_lti_params( application_instance ) assert application_instance == Any.object.with_attrs(lms_data) - assert application_instance.last_launched == datetime(year=2022, month=4, day=4) + assert application_instance.last_launched == datetime(year=2022, month=4, day=4) # noqa: DTZ001 def test_update_from_lti_params_no_guid_doesnt_change_values( self, service, organization_service, application_instance diff --git a/tests/unit/lms/services/assignment_test.py b/tests/unit/lms/services/assignment_test.py index 6f2b17eb8d..75dcff4444 100644 --- a/tests/unit/lms/services/assignment_test.py +++ b/tests/unit/lms/services/assignment_test.py @@ -179,7 +179,7 @@ def test_get_assignment_for_launch_existing( svc, misc_plugin, get_assignment, - _get_copied_from_assignment, + _get_copied_from_assignment, # noqa: PT019 course, ): misc_plugin.get_assignment_configuration.return_value = { @@ -221,7 +221,7 @@ def test_get_assignment_creates_assignment( svc, misc_plugin, get_assignment, - _get_copied_from_assignment, + _get_copied_from_assignment, # noqa: PT019 create_assignment, group_set_id, course, @@ -252,7 +252,7 @@ def test_get_assignment_created_assignments_point_to_copy( svc, misc_plugin, get_assignment, - _get_copied_from_assignment, + _get_copied_from_assignment, # noqa: PT019 create_assignment, course, ): @@ -477,10 +477,11 @@ def svc(self, db_session, misc_plugin): @pytest.fixture(autouse=True) def assignment(self): return factories.Assignment( - created=datetime(2000, 1, 1), updated=datetime(2000, 1, 1) + created=datetime(2000, 1, 1), # noqa: DTZ001 + updated=datetime(2000, 1, 1), # noqa: DTZ001 ) - @pytest.fixture() + @pytest.fixture def instructor_in_assignment(self, assignment): user = factories.User() lti_role = factories.LTIRole(scope=RoleScope.COURSE, type=RoleType.INSTRUCTOR) @@ -490,7 +491,7 @@ def instructor_in_assignment(self, assignment): return user - @pytest.fixture() + @pytest.fixture def course(self, db_session, application_instance): course = factories.Course(application_instance=application_instance) db_session.flush() diff --git a/tests/unit/lms/services/async_oauth_http_test.py b/tests/unit/lms/services/async_oauth_http_test.py index 1fdb7c3bad..9d5a8a1557 100644 --- a/tests/unit/lms/services/async_oauth_http_test.py +++ b/tests/unit/lms/services/async_oauth_http_test.py @@ -17,7 +17,7 @@ def test_request_success( ): responses = svc.request("GET", urls) - for url, response in zip(urls, responses): + for url, response in zip(urls, responses, strict=False): assert response.status == 200 assert response.headers["url"] == url assert response.sync_text == '["ASYNC RESPONSE"]' diff --git a/tests/unit/lms/services/auto_grading_test.py b/tests/unit/lms/services/auto_grading_test.py index f4f9d2f4da..befb78034e 100644 --- a/tests/unit/lms/services/auto_grading_test.py +++ b/tests/unit/lms/services/auto_grading_test.py @@ -27,10 +27,14 @@ def test_get_in_progress_sync(self, svc, db_session): def test_get_last_sync(self, svc, db_session, assignment): factories.GradingSync( - assignment=assignment, created=datetime(2020, 1, 1), status="finished" + assignment=assignment, + created=datetime(2020, 1, 1), # noqa: DTZ001 + status="finished", ) new = factories.GradingSync( - assignment=assignment, created=datetime(2024, 1, 1), status="finished" + assignment=assignment, + created=datetime(2024, 1, 1), # noqa: DTZ001 + status="finished", ) db_session.flush() @@ -39,7 +43,7 @@ def test_get_last_sync(self, svc, db_session, assignment): def test_create_grade_sync(self, svc, db_session, assignment): creator = factories.LMSUser() lms_users = factories.LMSUser.create_batch(5) - grades = {lms_user: random.random() for lms_user in lms_users} + grades = {lms_user: random.random() for lms_user in lms_users} # noqa: S311 db_session.flush() grading_sync = svc.create_grade_sync(assignment, creator, grades) @@ -74,7 +78,7 @@ def test_get_last_grades(self, svc, db_session, assignment): grading_sync=sync_1, lms_user=student_1, success=False, - updated=datetime(2025, 1, 1), + updated=datetime(2025, 1, 1), # noqa: DTZ001 grade=1, ) # Old @@ -82,14 +86,14 @@ def test_get_last_grades(self, svc, db_session, assignment): grading_sync=sync_2, lms_user=student_1, success=True, - updated=datetime(2023, 1, 1), + updated=datetime(2023, 1, 1), # noqa: DTZ001 grade=2, ) factories.GradingSyncGrade( grading_sync=sync_3, lms_user=student_1, success=True, - updated=datetime(2024, 1, 1), + updated=datetime(2024, 1, 1), # noqa: DTZ001 grade=3, ) # Other student in another sync @@ -97,7 +101,7 @@ def test_get_last_grades(self, svc, db_session, assignment): grading_sync=older_sync, lms_user=student_2, success=True, - updated=datetime(2024, 1, 1), + updated=datetime(2024, 1, 1), # noqa: DTZ001 grade=4, ) @@ -148,7 +152,7 @@ def test_calculate_grade( assert grade == expected_grade def test_calculate_grade_bad_config(self, svc): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 svc.calculate_grade( AutoGradingConfig( activity_calculation="RANDOM", diff --git a/tests/unit/lms/services/blackboard_api/_basic_test.py b/tests/unit/lms/services/blackboard_api/_basic_test.py index a9a31e6bd0..2cac1593c5 100644 --- a/tests/unit/lms/services/blackboard_api/_basic_test.py +++ b/tests/unit/lms/services/blackboard_api/_basic_test.py @@ -56,7 +56,7 @@ def test_get_token(self, basic_client, oauth_http_service): basic_client.get_token(sentinel.authorization_code) oauth_http_service.get_access_token.assert_called_once_with( - token_url="https://blackboard.example.com/learn/api/public/v1/oauth2/token", + token_url="https://blackboard.example.com/learn/api/public/v1/oauth2/token", # noqa: S106 redirect_uri=sentinel.redirect_uri, auth=(sentinel.client_id, sentinel.client_secret), authorization_code=sentinel.authorization_code, diff --git a/tests/unit/lms/services/blackboard_api/client_test.py b/tests/unit/lms/services/blackboard_api/client_test.py index 935533d9fa..927df67e73 100644 --- a/tests/unit/lms/services/blackboard_api/client_test.py +++ b/tests/unit/lms/services/blackboard_api/client_test.py @@ -85,7 +85,7 @@ def test_it_with_pagination(self, svc, basic_client, blackboard_list_files_schem # Each response from the Blackboard API includes the path to the next # page in the JSON body. This is the whole path to the next page, # including limit and offset query params, as a string. For example: - # "/learn/api/public/v1/courses/uuid:/resources?limit=200&offset=200" + # "/learn/api/public/v1/courses/uuid:/resources?limit=200&offset=200" # noqa: ERA001 # basic_client.request.side_effect = [ factories.requests.Response( diff --git a/tests/unit/lms/services/canvas_api/_authenticated_test.py b/tests/unit/lms/services/canvas_api/_authenticated_test.py index 5e5bff4128..5b3295cf2c 100644 --- a/tests/unit/lms/services/canvas_api/_authenticated_test.py +++ b/tests/unit/lms/services/canvas_api/_authenticated_test.py @@ -55,7 +55,7 @@ def test_get_token( ): token = authenticated_client.get_token("authorization_code") - assert token == "new_access_token" + assert token == "new_access_token" # noqa: S105 basic_client.send.assert_called_once_with( "POST", @@ -84,7 +84,7 @@ def test_get_refreshed_token( ): token = authenticated_client.get_refreshed_token("refresh_token") - assert token == "new_access_token" + assert token == "new_access_token" # noqa: S105 oauth2_token_service.try_lock_for_refresh.assert_called_once() diff --git a/tests/unit/lms/services/canvas_api/client_test.py b/tests/unit/lms/services/canvas_api/client_test.py index 7258634e6d..715ca8b0de 100644 --- a/tests/unit/lms/services/canvas_api/client_test.py +++ b/tests/unit/lms/services/canvas_api/client_test.py @@ -571,7 +571,7 @@ def test_methods_raise_CanvasAPIServerError_if_the_response_is_invalid_json( with pytest.raises(CanvasAPIServerError): data_method() - methods = { + methods = { # noqa: RUF012 "authenticated_users_sections": ["course_id"], "course_sections": ["course_id"], "course_group_categories": ["course_id"], diff --git a/tests/unit/lms/services/canvas_studio_test.py b/tests/unit/lms/services/canvas_studio_test.py index b0d7f820d6..596591ba5c 100644 --- a/tests/unit/lms/services/canvas_studio_test.py +++ b/tests/unit/lms/services/canvas_studio_test.py @@ -341,7 +341,7 @@ def admin_oauth_http_service(self): svc.get.side_effect = self.get_request_handler(is_admin=True) return svc - def get_request_handler(self, collections=None, is_admin=False): # noqa: C901, PLR0915 + def get_request_handler(self, collections=None, is_admin=False): # noqa: C901, FBT002, PLR0915 """ Create a handler for `GET` requests to the Canvas Studio API. @@ -352,7 +352,7 @@ def get_request_handler(self, collections=None, is_admin=False): # noqa: C901, def make_collection(id_, name, type_, created_at): return {"id": id_, "name": name, "type": type_, "created_at": created_at} - def make_file(id_, title, created_at, with_thumbnail=False): + def make_file(id_, title, created_at, with_thumbnail=False): # noqa: FBT002 file = { "id": id_, "title": title, @@ -373,7 +373,7 @@ def make_file(id_, title, created_at, with_thumbnail=False): make_collection(10, None, "course_wide", "2024-03-01"), ] - def handler(url, allow_redirects=True): # noqa: C901, PLR0912, PLR0915 + def handler(url, allow_redirects=True): # noqa: C901, FBT002, PLR0912, PLR0915 api_prefix = "/api/public/v1/" parsed_url = urlparse(url) @@ -471,7 +471,7 @@ def collection_data(items_field, items): json_data = {} case _: # pragma: nocover - raise ValueError(f"Unexpected URL {url}") + raise ValueError(f"Unexpected URL {url}") # noqa: EM102, TRY003 # This mirrors how HTTPService handles responses based on status code. response = None @@ -480,7 +480,7 @@ def collection_data(items_field, items): status_code=status_code, json_data=json_data ) response.raise_for_status() - return response + return response # noqa: TRY300 except RequestException as err: raise ExternalRequestError( request=err.request, response=response diff --git a/tests/unit/lms/services/course_test.py b/tests/unit/lms/services/course_test.py index 5f62fc2b97..42da685d1d 100644 --- a/tests/unit/lms/services/course_test.py +++ b/tests/unit/lms/services/course_test.py @@ -275,7 +275,7 @@ def test_upsert_course_sets_canvas_sections_enabled_based_on_legacy_rows( db_session.add( CourseGroupsExportedFromH( authority_provided_id=grouping_service.get_authority_provided_id.return_value, - created=datetime.utcnow(), + created=datetime.utcnow(), # noqa: DTZ003 ) ) application_instance.settings = ApplicationSettings({}) diff --git a/tests/unit/lms/services/dashboard_test.py b/tests/unit/lms/services/dashboard_test.py index 7584d1dd54..f4737d09be 100644 --- a/tests/unit/lms/services/dashboard_test.py +++ b/tests/unit/lms/services/dashboard_test.py @@ -392,7 +392,7 @@ def test_get_segment_roster( == roster_service.get_segments_roster.return_value.order_by.return_value ) - @pytest.fixture() + @pytest.fixture def svc( self, assignment_service, @@ -420,14 +420,14 @@ def organization_service(self, organization_service, organization): organization_service.get_hierarchy_ids.return_value = [] return organization_service - @pytest.fixture() + @pytest.fixture def course(self, db_session, application_instance): course = factories.Course(application_instance=application_instance) course.lms_course = factories.LMSCourse(course=course) db_session.flush() return course - @pytest.fixture() + @pytest.fixture def get_request_admin_organizations(self, svc): with patch.object( svc, "get_request_admin_organizations" diff --git a/tests/unit/lms/services/digest_test.py b/tests/unit/lms/services/digest_test.py index 4b1127d846..b98432da88 100644 --- a/tests/unit/lms/services/digest_test.py +++ b/tests/unit/lms/services/digest_test.py @@ -332,7 +332,7 @@ def test_assignment_infos(self, db_session): for annotation in annotations ] courses = factories.Course.create_batch(size=2) - for assignment, course in zip(assignments, courses): + for assignment, course in zip(assignments, courses, strict=False): factories.AssignmentGrouping(assignment=assignment, grouping=course) context = DigestContext(db_session, sentinel.h_userid, annotations) @@ -347,7 +347,7 @@ def test_assignment_infos(self, db_session): assignment.title, course.authority_provided_id, ) - for assignment, course in zip(assignments, courses) + for assignment, course in zip(assignments, courses, strict=False) ] ) @@ -412,12 +412,12 @@ def test_user_info_ignores_duplicate_userids(self, db_session): factories.User.build( h_userid="id", email="email@example.com", - updated=datetime(year=2024, month=1, day=2), + updated=datetime(year=2024, month=1, day=2), # noqa: DTZ001 ), factories.User.build( h_userid="id", email="older@example.com", - updated=datetime(year=2024, month=1, day=1), + updated=datetime(year=2024, month=1, day=1), # noqa: DTZ001 ), ], "email@example.com", @@ -433,12 +433,12 @@ def test_user_info_ignores_duplicate_userids(self, db_session): factories.User.build( h_userid="id", email=None, - updated=datetime(year=2024, month=1, day=2), + updated=datetime(year=2024, month=1, day=2), # noqa: DTZ001 ), factories.User.build( h_userid="id", email="email@example.com", - updated=datetime(year=2024, month=1, day=1), + updated=datetime(year=2024, month=1, day=1), # noqa: DTZ001 ), ], "email@example.com", @@ -463,12 +463,12 @@ def test_user_info_email(self, db_session, users, expected_email): factories.User.build( h_userid="id", display_name="most_recent", - updated=datetime(year=2024, month=1, day=2), + updated=datetime(year=2024, month=1, day=2), # noqa: DTZ001 ), factories.User.build( h_userid="id", display_name="older", - updated=datetime(year=2024, month=1, day=1), + updated=datetime(year=2024, month=1, day=1), # noqa: DTZ001 ), ], "most_recent", @@ -484,12 +484,12 @@ def test_user_info_email(self, db_session, users, expected_email): factories.User.build( h_userid="id", display_name=None, - updated=datetime(year=2024, month=1, day=2), + updated=datetime(year=2024, month=1, day=2), # noqa: DTZ001 ), factories.User.build( h_userid="id", display_name="display_name", - updated=datetime(year=2024, month=1, day=1), + updated=datetime(year=2024, month=1, day=1), # noqa: DTZ001 ), ], "display_name", @@ -596,12 +596,12 @@ def test_course_infos_title(self, db_session): factories.Course.build( authority_provided_id="id", lms_name="most_recent", - updated=datetime(year=2024, month=1, day=2), + updated=datetime(year=2024, month=1, day=2), # noqa: DTZ001 ), factories.Course.build( authority_provided_id="id", lms_name="older", - updated=datetime(year=2024, month=1, day=1), + updated=datetime(year=2024, month=1, day=1), # noqa: DTZ001 ), ] db_session.add_all(courses) diff --git a/tests/unit/lms/services/email_preferences_test.py b/tests/unit/lms/services/email_preferences_test.py index ea69741709..32143bd190 100644 --- a/tests/unit/lms/services/email_preferences_test.py +++ b/tests/unit/lms/services/email_preferences_test.py @@ -201,7 +201,7 @@ def test_it( EmailPreferencesService.assert_called_once_with( db_session, - secret="test_secret", + secret="test_secret", # noqa: S106 route_url=pyramid_request.route_url, jwt_service=jwt_service, user_preferences_service=user_preferences_service, diff --git a/tests/unit/lms/services/exceptions_test.py b/tests/unit/lms/services/exceptions_test.py index f685ddf98d..9d62bfff21 100644 --- a/tests/unit/lms/services/exceptions_test.py +++ b/tests/unit/lms/services/exceptions_test.py @@ -82,7 +82,7 @@ class TestExternalAsyncRequestError: (None, False, None), (Mock(request_info=Mock(url="response-url")), False, "response-url"), (None, True, "exception-URL"), - (Mock(request_info=Mock(url="response-url")), False, "response-url"), + (Mock(request_info=Mock(url="response-url")), False, "response-url"), # noqa: PT014 ], ) def test_url(self, response, exception, expected): diff --git a/tests/unit/lms/services/file_test.py b/tests/unit/lms/services/file_test.py index 14592e1a57..6769201dc6 100644 --- a/tests/unit/lms/services/file_test.py +++ b/tests/unit/lms/services/file_test.py @@ -114,7 +114,7 @@ def test_upsert(self, db_session, svc, application_instance): assert file.name == f"update_file_{i}" for i, file in enumerate(insert_files): - file = db_session.query(File).filter_by(lms_id=file.lms_id).one() + file = db_session.query(File).filter_by(lms_id=file.lms_id).one() # noqa: PLW2901 assert file.size == i * 100 assert file.name == f"insert_file_{i}" @@ -126,7 +126,7 @@ def noise(self, application_instance): def svc(self, application_instance, db_session): return FileService(application_instance, db_session) - @pytest.fixture() + @pytest.fixture def file(self, application_instance): return factories.File( application_instance=application_instance, diff --git a/tests/unit/lms/services/grant_token_test.py b/tests/unit/lms/services/grant_token_test.py index 6f8e5a2b4d..0ed0beb7ae 100644 --- a/tests/unit/lms/services/grant_token_test.py +++ b/tests/unit/lms/services/grant_token_test.py @@ -12,7 +12,7 @@ class TestGrantTokenService: def test_it_generates_valid_jwt_token(self, svc): - before = int(datetime.datetime.now().timestamp()) + before = int(datetime.datetime.now().timestamp()) # noqa: DTZ005 user = HUser(username="abcdef123") grant_token = svc.generate_token(user) diff --git a/tests/unit/lms/services/grouping/service_test.py b/tests/unit/lms/services/grouping/service_test.py index da9d95dcdb..f60886042d 100644 --- a/tests/unit/lms/services/grouping/service_test.py +++ b/tests/unit/lms/services/grouping/service_test.py @@ -298,7 +298,7 @@ def make_grouping( factory=factories.CanvasSection, parent=None, group_set_id=42, - membership=True, + membership=True, # noqa: FBT002 ): parent = parent or course diff --git a/tests/unit/lms/services/h_api_test.py b/tests/unit/lms/services/h_api_test.py index bc8a7a37b3..3fcbd67d32 100644 --- a/tests/unit/lms/services/h_api_test.py +++ b/tests/unit/lms/services/h_api_test.py @@ -37,7 +37,7 @@ def test_bulk_action_process_commands_correctly(self, h_api, BulkAPI): ] ) - def test_bulk_action_calls_h_correctly(self, h_api, BulkAPI, _api_request): + def test_bulk_action_calls_h_correctly(self, h_api, BulkAPI, _api_request): # noqa: PT019 h_api.execute_bulk([sentinel.command]) _api_request.assert_called_once_with( @@ -49,7 +49,7 @@ def test_bulk_action_calls_h_correctly(self, h_api, BulkAPI, _api_request): ), ) - def test_get_user_works(self, h_api, _api_request): + def test_get_user_works(self, h_api, _api_request): # noqa: PT019 _api_request.return_value.json.return_value = { "display_name": sentinel.display_name } @@ -98,7 +98,7 @@ def test_get_annotations( result = h_api.get_annotations( h_userid="acct:name@lms.hypothes.is", - created_after=datetime(2001, 2, 3, 4, 5, 6), + created_after=datetime(2001, 2, 3, 4, 5, 6), # noqa: DTZ001 created_before=datetime(2002, 2, 3, 4, 5, 6, tzinfo=UTC), ) @@ -203,7 +203,7 @@ def test_get_groups(self, h_api, http_service): result = h_api.get_groups( groups=["group_1", "group_2"], - annotations_created_after=datetime(2001, 2, 3, 4, 5, 6), + annotations_created_after=datetime(2001, 2, 3, 4, 5, 6), # noqa: DTZ001 annotations_created_before=datetime(2002, 2, 3, 4, 5, 6, tzinfo=UTC), batch_size=1, ) @@ -306,7 +306,7 @@ def test__api_request_raises_HAPIError_for_request_errors( def test__api_request_raises_other_exceptions_normally(self, h_api, http_service): http_service.request.side_effect = OSError() - with pytest.raises(OSError): + with pytest.raises(OSError): # noqa: PT011 h_api._api_request(sentinel.method, "dummy-path") # noqa: SLF001 def test_get_userid(self, h_api): @@ -316,7 +316,7 @@ def test_get_username(self, h_api): assert h_api.get_username("acct:username@lms.hypothes.is") == "username" def test_get_username_raises_if_username_is_invalid(self, h_api): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 h_api.get_username("invalid_userid") @pytest.fixture @@ -328,7 +328,7 @@ def h_api(self, http_service): return HAPI( authority="lms.hypothes.is", client_id="TEST_CLIENT_ID", - client_secret="TEST_CLIENT_SECRET", + client_secret="TEST_CLIENT_SECRET", # noqa: S106 h_private_url="https://h.example.com/private/api/", http_service=http_service, ) diff --git a/tests/unit/lms/services/http_test.py b/tests/unit/lms/services/http_test.py index 9d427d0999..dce90ade52 100644 --- a/tests/unit/lms/services/http_test.py +++ b/tests/unit/lms/services/http_test.py @@ -56,7 +56,7 @@ def test_it_raises_if_the_response_is_an_error(self, svc): assert exc_info.value.request == sentinel.err_request assert exc_info.value.response == response - @pytest.fixture() + @pytest.fixture def passed_args(self): return { "headers": sentinel.headers, diff --git a/tests/unit/lms/services/hubspot/service_test.py b/tests/unit/lms/services/hubspot/service_test.py index 97b92f6566..6322843f01 100644 --- a/tests/unit/lms/services/hubspot/service_test.py +++ b/tests/unit/lms/services/hubspot/service_test.py @@ -106,7 +106,7 @@ def test_export_companies_contract_billables( [ (None, None), ("2024-12-31", date(2024, 12, 31)), - (datetime(2024, 12, 31).timestamp() * 1000, date(2024, 12, 31)), + (datetime(2024, 12, 31).timestamp() * 1000, date(2024, 12, 31)), # noqa: DTZ001 ], ) def test_date_or_timestamp(self, expected, value): @@ -115,7 +115,7 @@ def test_date_or_timestamp(self, expected, value): def test_factory(self, pyramid_request, db_session, HubSpotClient, HubSpot): svc = HubSpotService.factory(sentinel.context, pyramid_request) - HubSpot.assert_called_once_with(access_token="HUBSPOT_API_KEY") + HubSpot.assert_called_once_with(access_token="HUBSPOT_API_KEY") # noqa: S106 HubSpotClient.assert_called_once_with(HubSpot.return_value) assert svc._db == db_session # noqa: SLF001 assert svc._region_code == "us" # noqa: SLF001 diff --git a/tests/unit/lms/services/jwt_oauth2_token_test.py b/tests/unit/lms/services/jwt_oauth2_token_test.py index a1c63e6f26..eb11ba055e 100644 --- a/tests/unit/lms/services/jwt_oauth2_token_test.py +++ b/tests/unit/lms/services/jwt_oauth2_token_test.py @@ -21,8 +21,8 @@ def test_it(self, svc, lti_registration, scopes): def test_save_new_token(self, svc, lti_registration, scopes): token = svc.save_token(lti_registration, scopes, "ACCESS_TOKEN", 3600) - assert token.access_token == "ACCESS_TOKEN" - assert token.received_at == datetime(2022, 4, 4, 0) + assert token.access_token == "ACCESS_TOKEN" # noqa: S105 + assert token.received_at == datetime(2022, 4, 4, 0) # noqa: DTZ001 assert token.expires_at == token.received_at + timedelta(seconds=3600) @freeze_time("2022-04-04") @@ -30,21 +30,21 @@ def test_save_existing_token(self, svc, lti_registration, scopes): existing_token = factories.JWTOAuth2Token( lti_registration=lti_registration, scopes=" ".join(scopes), - expires_at=datetime.now(), + expires_at=datetime.now(), # noqa: DTZ005 ) token = svc.save_token(lti_registration, scopes, "ACCESS_TOKEN", 3600) assert existing_token == token - assert token.received_at == datetime(2022, 4, 4, 0) - assert token.expires_at == datetime(2022, 4, 4, 1) + assert token.received_at == datetime(2022, 4, 4, 0) # noqa: DTZ001 + assert token.expires_at == datetime(2022, 4, 4, 1) # noqa: DTZ001 @freeze_time("2022-04-04") def test_get(self, svc, lti_registration, db_session, scopes): existing_token = factories.JWTOAuth2Token( lti_registration=lti_registration, scopes=" ".join(scopes), - expires_at=datetime.now() + timedelta(hours=1), + expires_at=datetime.now() + timedelta(hours=1), # noqa: DTZ005 ) db_session.flush() @@ -57,7 +57,7 @@ def test_get_doesnt_return_expired(self, svc, lti_registration, db_session, scop factories.JWTOAuth2Token( lti_registration=lti_registration, scopes=" ".join(scopes), - expires_at=datetime.now() - timedelta(hours=1), + expires_at=datetime.now() - timedelta(hours=1), # noqa: DTZ005 ) db_session.flush() @@ -72,7 +72,7 @@ def test_get_return_expired_with_flag( expired_token = factories.JWTOAuth2Token( lti_registration=lti_registration, scopes=" ".join(scopes), - expires_at=datetime.now() - timedelta(hours=1), + expires_at=datetime.now() - timedelta(hours=1), # noqa: DTZ005 ) db_session.flush() diff --git a/tests/unit/lms/services/jwt_test.py b/tests/unit/lms/services/jwt_test.py index a74f5e087d..65edd55a93 100644 --- a/tests/unit/lms/services/jwt_test.py +++ b/tests/unit/lms/services/jwt_test.py @@ -9,7 +9,7 @@ from freezegun import freeze_time from jwt.exceptions import InvalidTokenError from pyramid.config import Configurator -from pytest import param +from pytest import param # noqa: PT013 from lms.services.exceptions import ExpiredJWTError, InvalidJWTError from lms.services.jwt import ( @@ -64,7 +64,7 @@ def test_encode_with_secret(self, svc, jwt): ) jwt.encode.assert_called_once_with( - dict(payload, exp=datetime.datetime.utcnow() + datetime.timedelta(hours=1)), + dict(payload, exp=datetime.datetime.utcnow() + datetime.timedelta(hours=1)), # noqa: DTZ003 "secret", algorithm="HS256", ) @@ -85,7 +85,11 @@ def test_encode_with_private_key(self, svc, jwt, rsa_key_service): assert encoded_jwt == jwt.encode.return_value def test_decode_lti_token( - self, svc, jwt, _RequestsPyJWKClient, lti_registration_service + self, + svc, + jwt, + _RequestsPyJWKClient, # noqa: PT019 + lti_registration_service, ): registration = factories.LTIRegistration(key_set_url="http://jwk.com") lti_registration_service.get.return_value = registration @@ -148,7 +152,7 @@ def test_decode_lti_token_with_invalid_jwt(self, svc, jwt): @staticmethod def encode_jwt( payload, - secret="test_secret", + secret="test_secret", # noqa: S107 algorithm="HS256", headers=None, lifetime=None, @@ -157,7 +161,7 @@ def encode_jwt( payload = copy.deepcopy(payload) if lifetime: - payload["exp"] = datetime.datetime.utcnow() + lifetime + payload["exp"] = datetime.datetime.utcnow() + lifetime # noqa: DTZ003 return jwt.encode(payload, secret, algorithm=algorithm, headers=headers) @@ -167,11 +171,11 @@ def jwk_endpoint(self): httpretty.register_uri("GET", "http://jwk.com", body=json.dumps(keys)) - @pytest.fixture() + @pytest.fixture def jwt(self, patch): return patch("lms.services.jwt.jwt") - @pytest.fixture() + @pytest.fixture def _RequestsPyJWKClient(self, patch): return patch("lms.services.jwt._RequestsPyJWKClient") @@ -215,7 +219,7 @@ def JWTService(self, patch): class TestGetLTIJWT: def test_it(self, jwt_service, pyramid_request): - pyramid_request.params["id_token"] = "JWT" + pyramid_request.params["id_token"] = "JWT" # noqa: S105 jwt_params = _get_lti_jwt(pyramid_request) @@ -233,6 +237,6 @@ def test_it_sets_lti_jwt(self, configurator): _get_lti_jwt, name="lti_jwt", property=True, reify=True ) - @pytest.fixture() + @pytest.fixture def configurator(self): return create_autospec(Configurator, spec_set=True, instance=True) diff --git a/tests/unit/lms/services/lti_grading/_v13_test.py b/tests/unit/lms/services/lti_grading/_v13_test.py index c8f07c9ccc..b4dd97f04d 100644 --- a/tests/unit/lms/services/lti_grading/_v13_test.py +++ b/tests/unit/lms/services/lti_grading/_v13_test.py @@ -4,7 +4,7 @@ import pytest from freezegun import freeze_time from h_matchers import Any -from pytest import param +from pytest import param # noqa: PT013 from lms.product.family import Family from lms.services.exceptions import ExternalRequestError, StudentNotInCourse @@ -168,7 +168,7 @@ def test_sync_grade( response = svc.sync_grade( lti_v13_application_instance, assignment, - datetime(2022, 4, 4).isoformat(), + datetime(2022, 4, 4).isoformat(), # noqa: DTZ001 lms_user, sentinel.grade, ) diff --git a/tests/unit/lms/services/lti_role_service_test.py b/tests/unit/lms/services/lti_role_service_test.py index aad529e80a..9c817ac071 100644 --- a/tests/unit/lms/services/lti_role_service_test.py +++ b/tests/unit/lms/services/lti_role_service_test.py @@ -163,7 +163,7 @@ def existing_overrides(self, existing_roles, application_instance): @staticmethod def random_enum_excluding(enum_, excluding): - return random.choice(list(set(enum_) - {excluding})) + return random.choice(list(set(enum_) - {excluding})) # noqa: S311 class TestServiceFactory: diff --git a/tests/unit/lms/services/ltia_http_test.py b/tests/unit/lms/services/ltia_http_test.py index fe60a2c00a..58766a8175 100644 --- a/tests/unit/lms/services/ltia_http_test.py +++ b/tests/unit/lms/services/ltia_http_test.py @@ -29,8 +29,8 @@ def test_request_with_new_token( jwt_service.encode_with_private_key.assert_called_once_with( { "aud": misc_plugin.get_ltia_aud_claim.return_value, - "exp": datetime(2022, 4, 4, 1, 0), - "iat": datetime(2022, 4, 4, 0, 0), + "exp": datetime(2022, 4, 4, 1, 0), # noqa: DTZ001 + "iat": datetime(2022, 4, 4, 0, 0), # noqa: DTZ001 "iss": lti_registration.client_id, "sub": lti_registration.client_id, "jti": uuid.uuid4.return_value.hex, diff --git a/tests/unit/lms/services/oauth2_token_test.py b/tests/unit/lms/services/oauth2_token_test.py index 6f68cf91d7..046ac3f91f 100644 --- a/tests/unit/lms/services/oauth2_token_test.py +++ b/tests/unit/lms/services/oauth2_token_test.py @@ -3,7 +3,7 @@ import pytest from h_matchers import Any -from pytest import param +from pytest import param # noqa: PT013 from lms.db import LockType from lms.models import OAuth2Token @@ -22,8 +22,8 @@ class TestOAuth2TokenService: @pytest.mark.parametrize("service", [Service.LMS, Service.CANVAS_STUDIO]) def test_save(self, db_session, application_instance, lti_user, svc, service): svc.save( - access_token="access_token", - refresh_token="refresh_token", + access_token="access_token", # noqa: S106 + refresh_token="refresh_token", # noqa: S106 expires_in=1234, service=service, ) diff --git a/tests/unit/lms/services/oauth_http_test.py b/tests/unit/lms/services/oauth_http_test.py index cfd2c901a0..59e394b51b 100644 --- a/tests/unit/lms/services/oauth_http_test.py +++ b/tests/unit/lms/services/oauth_http_test.py @@ -280,7 +280,7 @@ def test_refresh_token_skips_if_token_is_current( http_service, ): token = oauth2_token_service.get(Service.LMS) - token.received_at = datetime.datetime.utcnow() + token.received_at = datetime.datetime.utcnow() # noqa: DTZ003 svc.refresh_access_token( sentinel.token_url, sentinel.redirect_uri, sentinel.auth diff --git a/tests/unit/lms/services/organization_usage_report_test.py b/tests/unit/lms/services/organization_usage_report_test.py index 0565cdabb1..6d44c22d75 100644 --- a/tests/unit/lms/services/organization_usage_report_test.py +++ b/tests/unit/lms/services/organization_usage_report_test.py @@ -113,8 +113,8 @@ def test_generate_usage_report_existing_report( ) def test_usage_report(self, svc, org_with_parent, h_api, organization_service): - since = datetime(2023, 1, 1, 0, 0, 0, 0) - until = datetime(2023, 12, 31, 23, 59, 59, 999999) + since = datetime(2023, 1, 1, 0, 0, 0, 0) # noqa: DTZ001 + until = datetime(2023, 12, 31, 23, 59, 59, 999999) # noqa: DTZ001 ai_root_org = factories.ApplicationInstance(organization=org_with_parent.parent) ai_child_org = factories.ApplicationInstance(organization=org_with_parent) @@ -190,10 +190,10 @@ def test_usage_report(self, svc, org_with_parent, h_api, organization_service): assert report == Any.list.containing(expected) def test_usage_report_with_no_courses(self, svc, org_with_parent): - since = datetime(2023, 1, 1) - until = datetime(2023, 12, 31) + since = datetime(2023, 1, 1) # noqa: DTZ001 + until = datetime(2023, 12, 31) # noqa: DTZ001 - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError) as error: # noqa: PT011 svc.usage_report(org_with_parent.parent, since, until) assert "no courses found" in str(error.value).lower() @@ -206,8 +206,8 @@ def test_usage_report_with_no_activity( org_with_parent.id, ] - since = datetime(2023, 1, 1) - until = datetime(2023, 12, 31) + since = datetime(2023, 1, 1) # noqa: DTZ001 + until = datetime(2023, 12, 31) # noqa: DTZ001 ai_root_org = factories.ApplicationInstance(organization=org_with_parent.parent) factories.Course( @@ -215,7 +215,7 @@ def test_usage_report_with_no_activity( ) h_api.get_groups.return_value = [] - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError) as error: # noqa: PT011 svc.usage_report(org_with_parent.parent, since, until) assert "no courses with activity" in str(error.value).lower() diff --git a/tests/unit/lms/services/roster_test.py b/tests/unit/lms/services/roster_test.py index 38e9e4e055..ddddd73ade 100644 --- a/tests/unit/lms/services/roster_test.py +++ b/tests/unit/lms/services/roster_test.py @@ -22,7 +22,7 @@ class TestRosterService: @pytest.mark.parametrize( "create_roster,expected", - [(True, datetime(2021, 1, 1)), (False, None)], + [(True, datetime(2021, 1, 1)), (False, None)], # noqa: DTZ001 ) def test_assignment_roster_last_updated( self, svc, assignment, db_session, create_roster, expected @@ -32,7 +32,7 @@ def test_assignment_roster_last_updated( if create_roster: factories.AssignmentRoster( - updated=datetime(2021, 1, 1), + updated=datetime(2021, 1, 1), # noqa: DTZ001 lms_user=lms_user, assignment=assignment, lti_role=lti_role, @@ -44,7 +44,7 @@ def test_assignment_roster_last_updated( @pytest.mark.parametrize( "create_roster,expected", - [(True, datetime(2021, 1, 1)), (False, None)], + [(True, datetime(2021, 1, 1)), (False, None)], # noqa: DTZ001 ) def test_segment_roster_last_updated( self, svc, lms_segment, db_session, create_roster, expected @@ -54,7 +54,7 @@ def test_segment_roster_last_updated( if create_roster: factories.LMSSegmentRoster( - updated=datetime(2021, 1, 1), + updated=datetime(2021, 1, 1), # noqa: DTZ001 lms_user=lms_user, lms_segment=lms_segment, lti_role=lti_role, @@ -66,7 +66,7 @@ def test_segment_roster_last_updated( @pytest.mark.parametrize( "create_roster,expected", - [(True, datetime(2021, 1, 1)), (False, None)], + [(True, datetime(2021, 1, 1)), (False, None)], # noqa: DTZ001 ) def test_course_roster_last_updated( self, svc, lms_course, db_session, create_roster, expected @@ -76,7 +76,7 @@ def test_course_roster_last_updated( if create_roster: factories.CourseRoster( - updated=datetime(2021, 1, 1), + updated=datetime(2021, 1, 1), # noqa: DTZ001 lms_user=lms_user, lms_course=lms_course, lti_role=lti_role, diff --git a/tests/unit/lms/services/rsa_key_service_test.py b/tests/unit/lms/services/rsa_key_service_test.py index 01e65276ce..2fc46cc10c 100644 --- a/tests/unit/lms/services/rsa_key_service_test.py +++ b/tests/unit/lms/services/rsa_key_service_test.py @@ -113,7 +113,7 @@ def valid_keys(self): return factories.RSAKey.create_batch( size=3, public_key='{"key": "value"}', - created=datetime(2022, 1, 10), + created=datetime(2022, 1, 10), # noqa: DTZ001 ) @pytest.fixture @@ -122,7 +122,7 @@ def expired_keys(self): size=3, expired=True, public_key='{"key": "value"}', - created=datetime(2022, 1, 10), + created=datetime(2022, 1, 10), # noqa: DTZ001 ) @pytest.fixture diff --git a/tests/unit/lms/services/upsert_test.py b/tests/unit/lms/services/upsert_test.py index 0590bca4af..106a9fb046 100644 --- a/tests/unit/lms/services/upsert_test.py +++ b/tests/unit/lms/services/upsert_test.py @@ -8,8 +8,8 @@ @pytest.mark.xdist_group("TableWithBulkUpsert") class TestBulkAction: - INDEX_ELEMENTS = ["id"] - UPDATE_COLUMNS = ["name"] + INDEX_ELEMENTS = ["id"] # noqa: RUF012 + UPDATE_COLUMNS = ["name"] # noqa: RUF012 class TableWithBulkUpsert(Base): __tablename__ = "test_table_with_bulk_upsert" diff --git a/tests/unit/lms/services/vitalsource/_client_test.py b/tests/unit/lms/services/vitalsource/_client_test.py index 3f59907eef..aa56f4e513 100644 --- a/tests/unit/lms/services/vitalsource/_client_test.py +++ b/tests/unit/lms/services/vitalsource/_client_test.py @@ -28,7 +28,7 @@ def test_init(self): } def test_init_raises_if_launch_credentials_invalid(self): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 VitalSourceClient(api_key=None) def test_get_book_info(self, client, http_service): @@ -166,7 +166,11 @@ def test_book_method_errors( ), ) def test_get_user_book_license( - self, client, http_service, response_xml, _VSUserAuth + self, + client, + http_service, + response_xml, + _VSUserAuth, # noqa: PT019 ): http_service.request.return_value = factories.requests.Response( status_code=200, raw=response_xml @@ -204,7 +208,7 @@ def test_get_user_book_license_with_no_license(self, client, http_service): is None ) - def test_get_sso_redirect(self, client, http_service, _VSUserAuth): + def test_get_sso_redirect(self, client, http_service, _VSUserAuth): # noqa: PT019 http_service.request.return_value = factories.requests.Response( status_code=200, # There are many more attributes in real results than these, but diff --git a/tests/unit/lms/services/vitalsource/model_test.py b/tests/unit/lms/services/vitalsource/model_test.py index d402dae370..d7182e7b76 100644 --- a/tests/unit/lms/services/vitalsource/model_test.py +++ b/tests/unit/lms/services/vitalsource/model_test.py @@ -7,7 +7,7 @@ class TestVSBookLocation: - TEST_CASES = [ + TEST_CASES = [ # noqa: RUF012 # Book ID + CFI ("vitalsource://book/bookID/book-id/cfi//abc", "book-id", "/abc", None), ("vitalsource://book/bookID/book-id/cfi/abc", "book-id", "abc", None), @@ -62,7 +62,7 @@ def test_from_document_url(self, document_url, book_id, cfi, page): ], ) def test_from_document_url_invalid(self, document_url, expected): - with pytest.raises(ValueError) as exc_info: + with pytest.raises(ValueError) as exc_info: # noqa: PT011 VSBookLocation.from_document_url(document_url) assert str(exc_info.value) == expected diff --git a/tests/unit/lms/tasks/email_digests_test.py b/tests/unit/lms/tasks/email_digests_test.py index d145ec6bd6..58185225aa 100644 --- a/tests/unit/lms/tasks/email_digests_test.py +++ b/tests/unit/lms/tasks/email_digests_test.py @@ -116,7 +116,9 @@ def participating_instances(self): for instance in instances: instance.settings.set( - "hypothesis", "instructor_email_digests_enabled", True + "hypothesis", + "instructor_email_digests_enabled", + True, # noqa: FBT003 ) return instances @@ -128,7 +130,7 @@ def participating_instructors(self, participating_instances, make_instructors): for instance in participating_instances: for _ in range(2): - users.append(factories.User(application_instance=instance)) + users.append(factories.User(application_instance=instance)) # noqa: PERF401 make_instructors(users, participating_instances[0], with_launch=True) @@ -143,7 +145,7 @@ def participating_instructors_with_no_launches( for instance in participating_instances: for _ in range(2): - users.append(factories.User(application_instance=instance)) + users.append(factories.User(application_instance=instance)) # noqa: PERF401 make_instructors(users, participating_instances[0], with_launch=False) @@ -153,7 +155,7 @@ def participating_instructors_with_no_launches( def non_participating_instance(self): """Return an instance that doesn't have the feature enabled.""" instance = factories.ApplicationInstance() - instance.settings.set("hypothesis", "instructor_email_digests_enabled", False) + instance.settings.set("hypothesis", "instructor_email_digests_enabled", False) # noqa: FBT003 return instance @pytest.fixture @@ -176,7 +178,7 @@ def non_instructor(self, participating_instances, make_learner): def make_instructors(self, db_session): instructor_role = factories.LTIRole(value="Instructor") - def make_instructors(users, application_instance, with_launch=True): + def make_instructors(users, application_instance, with_launch=True): # noqa: FBT002 """Make the given user instructors for an assignment.""" course = factories.Course() assignment = factories.Assignment() @@ -185,7 +187,7 @@ def make_instructors(users, application_instance, with_launch=True): if with_launch: # Create a launch for this course/assignment factories.Event( - timestamp=datetime(2023, 3, 8, 22), + timestamp=datetime(2023, 3, 8, 22), # noqa: DTZ001 application_instance=application_instance, course=course, assignment=assignment, diff --git a/tests/unit/lms/tasks/event_test.py b/tests/unit/lms/tasks/event_test.py index 999bd6c4d6..4a70c611c3 100644 --- a/tests/unit/lms/tasks/event_test.py +++ b/tests/unit/lms/tasks/event_test.py @@ -18,15 +18,15 @@ def test_insert_event(event_service, BaseEvent, pyramid_request): @freeze_time("2024-1-25") def test_purge_launch_data(): recent_data = factories.EventData( - event=factories.Event(timestamp=datetime(2024, 1, 20)), + event=factories.Event(timestamp=datetime(2024, 1, 20)), # noqa: DTZ001 data={"lti_params": {"some": "data"}}, ) old_data = factories.EventData( - event=factories.Event(timestamp=datetime(2024, 1, 10)), + event=factories.Event(timestamp=datetime(2024, 1, 10)), # noqa: DTZ001 data={"lti_params": {"some": "data"}}, ) old_data_no_launch = factories.EventData( - event=factories.Event(timestamp=datetime(2024, 1, 10)), + event=factories.Event(timestamp=datetime(2024, 1, 10)), # noqa: DTZ001 data={"some_other_data": {"some": "data"}}, ) diff --git a/tests/unit/lms/tasks/grading_test.py b/tests/unit/lms/tasks/grading_test.py index be7dd265ab..39f263c6d9 100644 --- a/tests/unit/lms/tasks/grading_test.py +++ b/tests/unit/lms/tasks/grading_test.py @@ -55,7 +55,7 @@ def test_sync_grade_raises( grading_service.sync_grade.side_effect = Exception sync_grade.max_retries = 2 - with pytest.raises(Exception): + with pytest.raises(Exception): # noqa: B017, PT011 sync_grade( grading_sync_grade_id=grading_sync.grades[0].id, ) diff --git a/tests/unit/lms/tasks/roster_test.py b/tests/unit/lms/tasks/roster_test.py index 2aa35875f5..ab07c9a211 100644 --- a/tests/unit/lms/tasks/roster_test.py +++ b/tests/unit/lms/tasks/roster_test.py @@ -157,7 +157,7 @@ def assignment_with_no_recent_launch(self, lms_course_with_recent_launch): ) factories.Event( assignment=assignment, - timestamp=datetime(2024, 1, 1), + timestamp=datetime(2024, 1, 1), # noqa: DTZ001 ) return assignment @@ -166,7 +166,7 @@ def lms_course_with_recent_launch(self): course = factories.Course() factories.Event( course=course, - timestamp=datetime(2024, 8, 28), + timestamp=datetime(2024, 8, 28), # noqa: DTZ001 ) return factories.LMSCourse( @@ -180,7 +180,7 @@ def lms_course_with_no_recent_launch(self): course = factories.Course() factories.Event( course=course, - timestamp=datetime(2024, 1, 1), + timestamp=datetime(2024, 1, 1), # noqa: DTZ001 ) return factories.LMSCourse( @@ -194,7 +194,7 @@ def lms_course_with_recent_launch_and_task_done_row(self, db_session): course = factories.Course() factories.Event( course=course, - timestamp=datetime(2024, 8, 28), + timestamp=datetime(2024, 8, 28), # noqa: DTZ001 ) lms_course = factories.LMSCourse( lti_context_memberships_url="URL", @@ -212,7 +212,7 @@ def assignment_with_recent_launch(self, lms_course_with_recent_launch): ) factories.Event( assignment=assignment, - timestamp=datetime(2024, 8, 28), + timestamp=datetime(2024, 8, 28), # noqa: DTZ001 ) return assignment @@ -225,7 +225,7 @@ def assignment_with_recent_launch_and_task_done_row( ) factories.Event( assignment=assignment, - timestamp=datetime(2024, 8, 28), + timestamp=datetime(2024, 8, 28), # noqa: DTZ001 ) db_session.flush() # Make sure we have an ID for the assignment factories.TaskDone(key=f"roster::assignment::scheduled::{assignment.id}") @@ -244,7 +244,7 @@ def lms_course_with_launch_and_recent_roster(self): lms_user=factories.LMSUser(), lti_role=factories.LTIRole(), active=True, - updated=datetime(2024, 8, 25), + updated=datetime(2024, 8, 25), # noqa: DTZ001 ) return lms_course @@ -256,14 +256,14 @@ def assignment_with_launch_and_recent_roster(self, lms_course_with_recent_launch ) factories.Event( assignment=assignment, - timestamp=datetime(2024, 8, 28), + timestamp=datetime(2024, 8, 28), # noqa: DTZ001 ) factories.AssignmentRoster( assignment=assignment, lms_user=factories.LMSUser(), lti_role=factories.LTIRole(), active=True, - updated=datetime(2024, 8, 25), + updated=datetime(2024, 8, 25), # noqa: DTZ001 ) return assignment @@ -308,7 +308,7 @@ def lms_segment_with_launch_and_recent_roster(self, lms_course_with_recent_launc lms_user=factories.LMSUser(), lti_role=factories.LTIRole(), active=True, - updated=datetime(2024, 8, 25), + updated=datetime(2024, 8, 25), # noqa: DTZ001 ) return lms_segment diff --git a/tests/unit/lms/tweens_test.py b/tests/unit/lms/tweens_test.py index 0c480f9abd..090e9980ae 100644 --- a/tests/unit/lms/tweens_test.py +++ b/tests/unit/lms/tweens_test.py @@ -20,7 +20,7 @@ def test_it_calls_db_rollback_on_exception(self, handler, pyramid_request): tween = rollback_db_session_tween_factory(handler, pyramid_request.registry) - with pytest.raises(IOError): + with pytest.raises(IOError): # noqa: PT011 tween(pyramid_request) handler.assert_called_once_with(pyramid_request) diff --git a/tests/unit/lms/validation/__init___test.py b/tests/unit/lms/validation/__init___test.py index e791968155..1f5f4b322e 100644 --- a/tests/unit/lms/validation/__init___test.py +++ b/tests/unit/lms/validation/__init___test.py @@ -74,7 +74,7 @@ def info(self, Schema): """ return mock.MagicMock(options={"schema": Schema}) - @pytest.fixture() + @pytest.fixture def parsed_params(self, Schema): """Return the parsed params as returned by the schema.""" return Schema.return_value.parse.return_value @@ -91,7 +91,7 @@ def view(self): class TestIncludeMe: - def test_it_registers_the_view_deriver(self, pyramid_config, _validated_view): + def test_it_registers_the_view_deriver(self, pyramid_config, _validated_view): # noqa: PT019 includeme(pyramid_config) assert _validated_view.options == ["schema"] diff --git a/tests/unit/lms/validation/_exceptions_test.py b/tests/unit/lms/validation/_exceptions_test.py index 8c620014fa..45a949a757 100644 --- a/tests/unit/lms/validation/_exceptions_test.py +++ b/tests/unit/lms/validation/_exceptions_test.py @@ -42,7 +42,7 @@ def test_we_produce_a_string_message(self, redirect): ], ) def test_it_requires_well_formatted_messages(self, messages): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 _ = LTIToolRedirect("http://example.com", messages) @pytest.fixture diff --git a/tests/unit/lms/validation/_lti_launch_params_test.py b/tests/unit/lms/validation/_lti_launch_params_test.py index 3d5bab8bb1..0695331121 100644 --- a/tests/unit/lms/validation/_lti_launch_params_test.py +++ b/tests/unit/lms/validation/_lti_launch_params_test.py @@ -31,7 +31,7 @@ class ExampleSchema(LTIV11CoreSchema): @pytest.fixture def pyramid_request(self, pyramid_request): - pyramid_request.POST["id_token"] = "JWT" + pyramid_request.POST["id_token"] = "JWT" # noqa: S105 pyramid_request.params = {"extra": "value"} pyramid_request.lti_jwt = sentinel.lti_jwt return pyramid_request diff --git a/tests/unit/lms/views/admin/application_instance/create_test.py b/tests/unit/lms/views/admin/application_instance/create_test.py index d332b9958b..aee693678f 100644 --- a/tests/unit/lms/views/admin/application_instance/create_test.py +++ b/tests/unit/lms/views/admin/application_instance/create_test.py @@ -50,7 +50,7 @@ def test_create_callback_v13(self, views, application_instance_service): email="test@example.com", deployment_id="22222", developer_key="DEVELOPER_KEY", - developer_secret="DEVELOPER_SECRET", + developer_secret="DEVELOPER_SECRET", # noqa: S106 organization_public_id="us.lms.org.ID", lti_registration_id=54321, ) @@ -79,7 +79,7 @@ def test_create_callback_with_errors( assert response == REDIRECT_TO_CREATE_AI - _V11_NEW_AI_BAD_FIELDS = [ + _V11_NEW_AI_BAD_FIELDS = [ # noqa: RUF012 ("lms_url", "not a url"), ("email", "not an email"), ("organization_public_id", None), @@ -87,7 +87,8 @@ def test_create_callback_with_errors( ] @pytest.mark.parametrize( - "param,bad_value", _V11_NEW_AI_BAD_FIELDS + [("deployment_id", None)] + "param,bad_value", + _V11_NEW_AI_BAD_FIELDS + [("deployment_id", None)], # noqa: RUF005 ) def test_create_callback_v13_required_fields( self, views, create_ai_params_v13, param, bad_value diff --git a/tests/unit/lms/views/admin/application_instance/update_test.py b/tests/unit/lms/views/admin/application_instance/update_test.py index ae120f81da..969991233d 100644 --- a/tests/unit/lms/views/admin/application_instance/update_test.py +++ b/tests/unit/lms/views/admin/application_instance/update_test.py @@ -52,7 +52,7 @@ def test_update_application_instance( lms_url="http://example.com", deployment_id="DEPLOYMENT_ID", developer_key="DEVELOPER KEY", - developer_secret="DEVELOPER SECRET", + developer_secret="DEVELOPER SECRET", # noqa: S106 ) assert ai_from_matchdict.settings.get(setting, sub_setting) == expected diff --git a/tests/unit/lms/views/admin/lti_registration_test.py b/tests/unit/lms/views/admin/lti_registration_test.py index 9eb2225893..e38e6c3292 100644 --- a/tests/unit/lms/views/admin/lti_registration_test.py +++ b/tests/unit/lms/views/admin/lti_registration_test.py @@ -89,7 +89,7 @@ def test_new_registration_callback( client_id="CLIENT_ID", auth_login_url="http://auth-login-url.com", key_set_url="http://key-set-url.com", - token_url="http://token-url.com", + token_url="http://token-url.com", # noqa: S106 ) assert response == temporary_redirect_to( diff --git a/tests/unit/lms/views/admin/organization_test.py b/tests/unit/lms/views/admin/organization_test.py index a75eaac8fc..26e4e23617 100644 --- a/tests/unit/lms/views/admin/organization_test.py +++ b/tests/unit/lms/views/admin/organization_test.py @@ -156,7 +156,7 @@ def test_toggle_organization_enabled( pyramid_request.matchdict["id_"] = sentinel.id_ pyramid_request.params["enabled"] = value - organization_service.get_by_id.side_effect = [organization] + children + organization_service.get_by_id.side_effect = [organization] + children # noqa: RUF005 organization_service.get_hierarchy_ids.return_value = [organization.id] + [ org.id for org in children ] @@ -249,8 +249,8 @@ def test_usage_flashes_if_service_raises( self, views, organization_service, organization_usage_report_service ): organization_usage_report_service.usage_report.side_effect = ValueError - since = datetime(2023, 1, 1) - until = datetime(2023, 12, 31) + since = datetime(2023, 1, 1) # noqa: DTZ001 + until = datetime(2023, 12, 31) # noqa: DTZ001 result = views.usage() @@ -261,8 +261,8 @@ def test_usage_flashes_if_service_raises( def test_usage( self, organization_service, views, organization_usage_report_service ): - since = datetime(2023, 1, 1) - until = datetime(2023, 12, 31) + since = datetime(2023, 1, 1) # noqa: DTZ001 + until = datetime(2023, 12, 31) # noqa: DTZ001 result = views.usage() diff --git a/tests/unit/lms/views/api/canvas/authorize_test.py b/tests/unit/lms/views/api/canvas/authorize_test.py index b3bdfe99de..b83a11a99d 100644 --- a/tests/unit/lms/views/api/canvas/authorize_test.py +++ b/tests/unit/lms/views/api/canvas/authorize_test.py @@ -127,7 +127,9 @@ def sections_not_supported(self, application_instance): @pytest.fixture def sections_disabled(self, pyramid_request): pyramid_request.lti_user.application_instance.settings.set( - "canvas", "sections_enabled", False + "canvas", + "sections_enabled", + False, # noqa: FBT003 ) @pytest.fixture diff --git a/tests/unit/lms/views/api/canvas_studio_test.py b/tests/unit/lms/views/api/canvas_studio_test.py index c5e27ac5ac..feaeb97304 100644 --- a/tests/unit/lms/views/api/canvas_studio_test.py +++ b/tests/unit/lms/views/api/canvas_studio_test.py @@ -139,7 +139,7 @@ def test_it_raises_if_download_not_available( @pytest.fixture def via_video_url(self, patch): - yield patch("lms.views.api.canvas_studio.via_video_url") + return patch("lms.views.api.canvas_studio.via_video_url") @pytest.fixture def assignment_service(self, assignment_service): diff --git a/tests/unit/lms/views/api/exceptions_test.py b/tests/unit/lms/views/api/exceptions_test.py index 3c4bab8adb..a22403a5c2 100644 --- a/tests/unit/lms/views/api/exceptions_test.py +++ b/tests/unit/lms/views/api/exceptions_test.py @@ -279,7 +279,7 @@ def test_json_includes_refresh_info_with_custom_refresh_route( def test_json_raises_if_no_refresh_route(self, pyramid_request): pyramid_request.product.route = Routes() - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 ErrorBody().__json__(pyramid_request) @pytest.mark.usefixtures("with_refreshable_exception") diff --git a/tests/unit/lms/views/api/moodle/files_test.py b/tests/unit/lms/views/api/moodle/files_test.py index 86979fad7b..d1a86f25fa 100644 --- a/tests/unit/lms/views/api/moodle/files_test.py +++ b/tests/unit/lms/views/api/moodle/files_test.py @@ -23,7 +23,7 @@ def test_via_url( lti_user, course_service, ): - type(moodle_api_client).token = "TOKEN" + type(moodle_api_client).token = "TOKEN" # noqa: S105 lti_user.lti.course_id = course_service.get_by_context_id.return_value.lms_id = ( "COURSE_ID" ) @@ -44,7 +44,7 @@ def test_via_url( def test_via_url_deleted_file( moodle_api_client, pyramid_request, lti_user, course_service ): - type(moodle_api_client).token = "TOKEN" + type(moodle_api_client).token = "TOKEN" # noqa: S105 lti_user.lti.course_id = course_service.get_by_context_id.return_value.lms_id = ( "COURSE_ID" ) @@ -64,7 +64,7 @@ def test_via_url_copied_with_mapped_id( moodle_api_client, ): pyramid_request.params["document_url"] = "moodle://file/course/COURSE_ID/url/URL" - type(moodle_api_client).token = "TOKEN" + type(moodle_api_client).token = "TOKEN" # noqa: S105 lti_user.lti.course_id = course_service.get_by_context_id.return_value.lms_id = ( "OTHER_COURSE_ID" ) @@ -115,7 +115,7 @@ def test_via_url_copied_found_page( lti_user, moodle_api_client, ): - type(moodle_api_client).token = "TOKEN" + type(moodle_api_client).token = "TOKEN" # noqa: S105 lti_user.lti.course_id = course_service.get_by_context_id.return_value.lms_id = ( "OTHER_COURSE_ID" ) diff --git a/tests/unit/lms/views/dashboard/api/assignment_test.py b/tests/unit/lms/views/dashboard/api/assignment_test.py index f95e8b7d95..fa454de561 100644 --- a/tests/unit/lms/views/dashboard/api/assignment_test.py +++ b/tests/unit/lms/views/dashboard/api/assignment_test.py @@ -229,7 +229,7 @@ def test_course_assignments( "annotation_metrics": { "annotations": 4, "replies": sentinel.replies, - "last_activity": datetime(2024, 1, 1), + "last_activity": datetime(2024, 1, 1), # noqa: DTZ001 }, }, { diff --git a/tests/unit/lms/views/dashboard/api/user_test.py b/tests/unit/lms/views/dashboard/api/user_test.py index af41c8f776..276fc6c8a0 100644 --- a/tests/unit/lms/views/dashboard/api/user_test.py +++ b/tests/unit/lms/views/dashboard/api/user_test.py @@ -26,7 +26,7 @@ def test_get_students( views, get_page, segment_authority_provided_ids, - _students_query, + _students_query, # noqa: PT019 ): pyramid_request.parsed_params = { "course_ids": [sentinel.course_id_1, sentinel.course_id_2], @@ -106,7 +106,7 @@ def test_students_metrics( ] ) ) - .add_columns(True), + .add_columns(True), # noqa: FBT003 ) else: @@ -126,7 +126,7 @@ def test_students_metrics( ] ) ) - .add_columns(True), + .add_columns(True), # noqa: FBT003 ) db_session.flush() @@ -155,7 +155,7 @@ def test_students_metrics( "annotation_metrics": { "annotations": 4, "replies": sentinel.replies, - "last_activity": datetime(2024, 1, 1), + "last_activity": datetime(2024, 1, 1), # noqa: DTZ001 }, }, { @@ -229,7 +229,7 @@ def test_students_metrics_with_auto_grading( ] ) ) - .add_columns(True), + .add_columns(True), # noqa: FBT003 ) dashboard_service.get_request_assignment.return_value = assignment h_api.get_annotation_counts.return_value = annotation_counts_response @@ -258,7 +258,7 @@ def test_students_metrics_with_auto_grading( "annotation_metrics": { "annotations": 4, "replies": sentinel.replies, - "last_activity": datetime(2024, 1, 1), + "last_activity": datetime(2024, 1, 1), # noqa: DTZ001 }, }, { diff --git a/tests/unit/lms/views/dashboard/pagination_test.py b/tests/unit/lms/views/dashboard/pagination_test.py index 4007e64ccd..a7009f0e14 100644 --- a/tests/unit/lms/views/dashboard/pagination_test.py +++ b/tests/unit/lms/views/dashboard/pagination_test.py @@ -25,7 +25,7 @@ def test_when_no_next_page(self, pyramid_request, db_session): def test_when_empty(self, pyramid_request): pyramid_request.parsed_params = {"limit": 100} - query = select(Course).where(False) + query = select(Course).where(False) # noqa: FBT003 items, pagination = get_page(pyramid_request, query, (Course.id,)) diff --git a/tests/unit/lms/views/lti/basic_launch_test.py b/tests/unit/lms/views/lti/basic_launch_test.py index 0af4901510..5f5e0f5279 100644 --- a/tests/unit/lms/views/lti/basic_launch_test.py +++ b/tests/unit/lms/views/lti/basic_launch_test.py @@ -54,7 +54,12 @@ def test___init___( ) def test_configure_assignment_callback( - self, svc, pyramid_request, _show_document, assignment_service, course_service + self, + svc, + pyramid_request, + _show_document, # noqa: PT019 + assignment_service, + course_service, ): pyramid_request.parsed_params = { "document_url": sentinel.document_url, @@ -84,7 +89,7 @@ def test_edit_assignment_callback( self, svc, pyramid_request, - _show_document, + _show_document, # noqa: PT019 assignment_service, LTIEvent, ): @@ -119,7 +124,7 @@ def test_lti_launch_configured( svc, assignment_service, pyramid_request, - _show_document, + _show_document, # noqa: PT019 LTIEvent, course_service, ): diff --git a/tests/unit/lms/views/lti/deep_linking_test.py b/tests/unit/lms/views/lti/deep_linking_test.py index 53286dba2e..01a86d5230 100644 --- a/tests/unit/lms/views/lti/deep_linking_test.py +++ b/tests/unit/lms/views/lti/deep_linking_test.py @@ -137,7 +137,7 @@ def test_it_for_v13( LTIEvent, pyramid_request, misc_plugin, - _get_assignment_configuration, + _get_assignment_configuration, # noqa: PT019 title, ): if title: @@ -146,8 +146,8 @@ def test_it_for_v13( fields = views.file_picker_to_form_fields_v13() message = { - "exp": datetime(2022, 4, 4, 1, 0), - "iat": datetime(2022, 4, 4, 0, 0), + "exp": datetime(2022, 4, 4, 1, 0), # noqa: DTZ001 + "iat": datetime(2022, 4, 4, 0, 0), # noqa: DTZ001 "iss": application_instance.lti_registration.client_id, "sub": application_instance.lti_registration.client_id, "aud": application_instance.lti_registration.issuer, @@ -202,7 +202,7 @@ def test_it_for_v13_missing_deep_linking_settings_data( def test_it_for_v11( self, views, - _get_assignment_configuration, + _get_assignment_configuration, # noqa: PT019 pyramid_request, LTIEvent, misc_plugin, @@ -298,7 +298,7 @@ def test__get_assignment_configuration( def test_it_with_unknown_file_type(self, pyramid_request): pyramid_request.parsed_params.update({"content": {"type": "other"}}) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 DeepLinkingFieldsViews(pyramid_request).file_picker_to_form_fields_v13() @pytest.fixture diff --git a/tests/unit/services.py b/tests/unit/services.py index 7053586117..eb5a74091e 100644 --- a/tests/unit/services.py +++ b/tests/unit/services.py @@ -58,7 +58,7 @@ from lms.services.youtube import YouTubeService from tests import factories -__all__ = ( +__all__ = ( # noqa: RUF022 # Meta fixture for creating service fixtures "mock_service", # Individual services