diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2553cdbd..af091830 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,25 +42,13 @@ repos: args: [--no-update] files: ^(.*/)?(poetry\.lock|pyproject\.toml)$ - - repo: https://github.com/pycqa/pydocstyle - rev: 6.3.0 - hooks: - - id: pydocstyle - exclude: "generated/|tests.py" - additional_dependencies: [tomli] - - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - name: isort (python) - exclude: *generated - - repo: https://github.com/psf/black - rev: 24.10.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 hooks: - - id: black - args: ["--config=pyproject.toml"] + - id: ruff + args: [ --fix ] + - id: ruff-format exclude: *generated - repo: https://github.com/rtts/djhtml diff --git a/canvas_cli/apps/auth/tests.py b/canvas_cli/apps/auth/tests.py index a214b3a1..0f19b416 100644 --- a/canvas_cli/apps/auth/tests.py +++ b/canvas_cli/apps/auth/tests.py @@ -8,6 +8,8 @@ @pytest.fixture def valid_token_response() -> Any: + """Returns a valid token response.""" + class TokenResponse: status_code = 200 @@ -19,6 +21,8 @@ def json(self) -> dict: @pytest.fixture def error_token_response() -> Any: + """Returns an error token response.""" + class TokenResponse: status_code = 500 @@ -27,6 +31,8 @@ class TokenResponse: @pytest.fixture def expired_token_response() -> Any: + """Returns an expired token response.""" + class TokenResponse: status_code = 200 @@ -45,6 +51,7 @@ def test_get_or_request_api_token_uses_stored_token( mock_get_password: MagicMock, valid_token_response: Any, ) -> None: + """Test that get_or_request_api_token uses a stored token if it is valid.""" mock_is_token_valid.return_value = True mock_get_password.return_value = "a-stored-valid-token" mock_post.return_value = valid_token_response @@ -66,6 +73,7 @@ def test_get_or_request_api_token_requests_token_if_none_stored( mock_set_password: MagicMock, valid_token_response: Any, ) -> None: + """Test that get_or_request_api_token requests a new token if none is stored.""" mock_client_credentials.return_value = "client_id=id&client_secret=secret" mock_get_password.return_value = None mock_post.return_value = valid_token_response @@ -95,6 +103,7 @@ def test_get_or_request_api_token_raises_exception_if_error_token_response( mock_get_password: MagicMock, error_token_response: Any, ) -> None: + """Test that get_or_request_api_token raises an exception if an error token response is received.""" mock_client_credentials.return_value = "client_id=id&client_secret=secret" mock_get_password.return_value = None mock_post.return_value = error_token_response @@ -123,6 +132,7 @@ def test_get_or_request_api_token_raises_exception_if_expired_token( mock_get_password: MagicMock, expired_token_response: Any, ) -> None: + """Test that get_or_request_api_token raises an exception if an expired token is received.""" mock_client_credentials.return_value = "client_id=id&client_secret=secret" mock_get_password.return_value = None mock_post.return_value = expired_token_response diff --git a/canvas_cli/apps/emit/emit.py b/canvas_cli/apps/emit/emit.py index 8b99308e..ab567408 100644 --- a/canvas_cli/apps/emit/emit.py +++ b/canvas_cli/apps/emit/emit.py @@ -7,7 +7,6 @@ import typer from canvas_generated.messages.events_pb2 import Event as PluginRunnerEvent -from canvas_generated.messages.events_pb2 import EventType as PluginRunnerEventType from canvas_generated.services.plugin_runner_pb2_grpc import PluginRunnerStub diff --git a/canvas_cli/apps/logs/logs.py b/canvas_cli/apps/logs/logs.py index 4079b456..46ce6cb7 100644 --- a/canvas_cli/apps/logs/logs.py +++ b/canvas_cli/apps/logs/logs.py @@ -1,5 +1,5 @@ import json -from typing import Optional, cast +from typing import cast from urllib.parse import urlparse import typer @@ -31,9 +31,9 @@ def _on_open(ws: websocket.WebSocket) -> None: def logs( - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None - ) + ), ) -> None: """Listens and prints log streams from the instance.""" if not host: @@ -62,4 +62,4 @@ def logs( ws.run_forever() except KeyboardInterrupt: - raise typer.Exit(0) + raise typer.Exit(0) from None diff --git a/canvas_cli/apps/plugin/plugin.py b/canvas_cli/apps/plugin/plugin.py index bb0863e0..08629c3b 100644 --- a/canvas_cli/apps/plugin/plugin.py +++ b/canvas_cli/apps/plugin/plugin.py @@ -2,8 +2,9 @@ import json import tarfile import tempfile +from collections.abc import Iterable from pathlib import Path -from typing import Any, List, Optional, cast +from typing import Any, cast from urllib.parse import urljoin import requests @@ -53,14 +54,15 @@ def _build_package(package: Path) -> Path: def _get_name_from_metadata(host: str, token: str, package: Path) -> str | None: """Extract metadata from a provided package and return the package name if it exists in the metadata.""" try: - metadata_response = requests.post( - plugin_url(host, "extract-metadata"), - headers={"Authorization": f"Bearer {token}"}, - files={"package": open(package, "rb")}, - ) + with open(package) as package_file: + metadata_response = requests.post( + plugin_url(host, "extract-metadata"), + headers={"Authorization": f"Bearer {token}"}, + files={"package": package_file}, + ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if metadata_response.status_code != requests.codes.ok: print(f"Status code {metadata_response.status_code}: {metadata_response.text}") @@ -116,8 +118,8 @@ def _get_meta_properties(protocol_path: Path, classname: str) -> dict[str, str]: def _get_protocols_with_new_cqm_properties( - protocol_classes: List[dict[str, Any]], plugin: Path -) -> List[dict[str, Any]] | None: + protocol_classes: Iterable[dict[str, Any]], plugin: Path +) -> Iterable[dict[str, Any]] | None: """Extract the meta properties of any ClinicalQualityMeasure Protocols included in the plugin if they have changed.""" has_updates = False protocol_props = [] @@ -146,14 +148,14 @@ def init() -> None: try: project_dir = cookiecutter(str(template)) except OutputDirExistsException: - raise typer.BadParameter(f"The supplied directory already exists") + raise typer.BadParameter("The supplied directory already exists") from None print(f"Project created in {project_dir}") def install( plugin_name: Path = typer.Argument(..., help="Path to plugin to install"), - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, @@ -181,15 +183,16 @@ def install( print(f"Posting {built_package_path.absolute()} to {url}") try: - r = requests.post( - url, - data={"is_enabled": True}, - files={"package": open(built_package_path, "rb")}, - headers={"Authorization": f"Bearer {token}"}, - ) + with open(built_package_path, "rb") as package: + r = requests.post( + url, + data={"is_enabled": True}, + files={"package": package}, + headers={"Authorization": f"Bearer {token}"}, + ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.status_code == requests.codes.created: print(f"Plugin {plugin_name} successfully installed!") @@ -207,7 +210,7 @@ def install( def uninstall( name: str = typer.Argument(..., help="Plugin name to uninstall"), - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, @@ -232,7 +235,7 @@ def uninstall( ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.status_code == requests.codes.no_content: print(f"Plugin {name} successfully uninstalled!") @@ -243,7 +246,7 @@ def uninstall( def enable( name: str = typer.Argument(..., help="Plugin name to enable"), - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, @@ -269,7 +272,7 @@ def enable( ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.ok: print(f"Plugin {name} successfully enabled!") @@ -280,7 +283,7 @@ def enable( def disable( name: str = typer.Argument(..., help="Plugin name to disable"), - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, @@ -306,7 +309,7 @@ def disable( ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.ok: print(f"Plugin {name} successfully disabled!") @@ -316,11 +319,11 @@ def disable( def list( - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, - ) + ), ) -> None: """List all plugins from a Canvas instance.""" if not host: @@ -337,7 +340,7 @@ def list( ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.status_code == requests.codes.ok: plugins = r.json().get("results", []) @@ -382,7 +385,7 @@ def validate_manifest( except json.JSONDecodeError: print("There was a problem loading the manifest file, please ensure it's valid JSON") - raise typer.Abort() + raise typer.Abort() from None validate_manifest_file(manifest_json) @@ -391,14 +394,14 @@ def validate_manifest( def update( name: str = typer.Argument(..., help="Plugin name to update"), - package: Optional[Path] = typer.Option( + package_path: Path | None = typer.Option( help="Path to a wheel or sdist file containing the python package to install", default=None, ), - is_enabled: Optional[bool] = typer.Option( + is_enabled: bool | None = typer.Option( None, "--enable/--disable", show_default=False, help="Enable/disable the plugin" ), - host: Optional[str] = typer.Option( + host: str | None = typer.Option( callback=get_default_host, help="Canvas instance to connect to", default=None, @@ -408,27 +411,36 @@ def update( if not host: raise typer.BadParameter("Please specify a host or set a default via the `auth` command") - if package: - validate_package(package) + if package_path: + validate_package(package_path) token = get_or_request_api_token(host) - print(f"Updating plugin {name} from {host} with {is_enabled=}, {package=}") - - binary_package = {"package": open(package, "rb")} if package else None + print(f"Updating plugin {name} from {host} with {is_enabled=}, {package_path=}") url = plugin_url(host, name) try: - r = requests.patch( - url, - data={"is_enabled": is_enabled} if is_enabled is not None else {}, - files=binary_package, - headers={"Authorization": f"Bearer {token}"}, - ) + data = {"is_enabled": is_enabled} if is_enabled is not None else {} + headers = {"Authorization": f"Bearer {token}"} + + if package_path: + with open(package_path, "rb") as package: + r = requests.patch( + url, + data=data, + headers=headers, + files={"package": package}, + ) + else: + r = requests.patch( + url, + data=data, + headers=headers, + ) except requests.exceptions.RequestException: print(f"Failed to connect to {host}") - raise typer.Exit(1) + raise typer.Exit(1) from None if r.status_code == requests.codes.ok: print("Plugin successfully updated!") diff --git a/canvas_cli/apps/plugin/tests.py b/canvas_cli/apps/plugin/tests.py index b9954881..84291c28 100644 --- a/canvas_cli/apps/plugin/tests.py +++ b/canvas_cli/apps/plugin/tests.py @@ -1,7 +1,8 @@ import shutil +from collections.abc import Generator from datetime import datetime from pathlib import Path -from typing import Any, Generator +from typing import Any import pytest import typer @@ -39,12 +40,13 @@ def test_validate_package_valid_file(tmp_path: Path) -> None: @pytest.fixture(scope="session") def init_plugin_name() -> str: - """The plugin name to be used for the canvas cli init test""" + """The plugin name to be used for the canvas cli init test.""" return f"testing_init-{datetime.now().timestamp()}".replace(".", "") @pytest.fixture(autouse=True, scope="session") def clean_up_plugin(init_plugin_name: str) -> Generator[Any, Any, Any]: + """Cleans up the plugin directory after the test.""" yield if Path(f"./{init_plugin_name}").exists(): shutil.rmtree(Path(f"./{init_plugin_name}")) diff --git a/canvas_cli/conftest.py b/canvas_cli/conftest.py index 037c6ded..32f0fdc4 100644 --- a/canvas_cli/conftest.py +++ b/canvas_cli/conftest.py @@ -19,6 +19,7 @@ def app_dir() -> str: @pytest.fixture(autouse=True) def reset_context_variables() -> None: """Reset the context properties to their default value. + This is needed because we cannot build a `reset` method in the CLIContext class, because `load_from_file` loads properties dynamically. Also since this is a CLI, it's not expected to keep the global context in memory for more than a run, diff --git a/canvas_cli/main.py b/canvas_cli/main.py index db908479..1f1d99a0 100644 --- a/canvas_cli/main.py +++ b/canvas_cli/main.py @@ -1,6 +1,5 @@ import importlib.metadata from pathlib import Path -from typing import Optional import typer @@ -62,7 +61,7 @@ def get_or_create_config_file() -> Path: @app.callback() def main( - version: Optional[bool] = typer.Option( + version: bool | None = typer.Option( None, "--version", callback=version_callback, is_eager=True ), ) -> None: diff --git a/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py b/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py index a414c379..8a555647 100644 --- a/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +++ b/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py @@ -50,5 +50,4 @@ def compute(self) -> list[Effect]: # Return zero, one, or many effects. # Example: - # return [Effect(type=EffectType.LOG, payload=json.dumps(payload))] - return [] + return [Effect(type=EffectType.LOG, payload=json.dumps(payload))] diff --git a/canvas_cli/tests.py b/canvas_cli/tests.py index 1d508a7c..1b32441e 100644 --- a/canvas_cli/tests.py +++ b/canvas_cli/tests.py @@ -1,8 +1,9 @@ import os import shutil +from collections.abc import Callable, Generator from datetime import datetime from pathlib import Path -from typing import Any, Callable, Generator, cast +from typing import Any, cast from unittest.mock import MagicMock, patch from urllib.parse import urlparse @@ -20,14 +21,13 @@ @pytest.fixture(scope="session") def plugin_name() -> str: - """The plugin name to be used for the canvas cli test""" + """The plugin name to be used for the canvas cli test.""" return f"cli-{datetime.now().timestamp()}".replace(".", "") @pytest.fixture(scope="session") def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator[None, None, None]: """Creates the necessary config file for auth before performing cli tests.""" - if not settings.INTEGRATION_TEST_URL: raise ImproperlyConfigured("INTEGRATION_TEST_URL is not set") @@ -45,19 +45,27 @@ def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator temp_path = path.parent / "temp_credentials.ini" - original_content = open(path, "r").read() - open(path, "w").writelines( - [ - f"[{host}]\n", - f"client_id={client_id}\n", - f"client_secret={client_secret}\n", - ] - ) - open(temp_path, "a").write(original_content) + # Read the original content + with open(path) as original_file: + original_content = original_file.read() + + # Write new content to the original file + with open(path, "w") as original_file: + original_file.writelines( + [ + f"[{host}]\n", + f"client_id={client_id}\n", + f"client_secret={client_secret}\n", + ] + ) + + # Append original content to the temp file + with open(temp_path, "a") as temp_file: + temp_file.write(original_content) yield - with open(temp_path, "r") as temp: + with open(temp_path) as temp: original_content = temp.read() with open(path, "w") as f: f.write(original_content) @@ -66,9 +74,10 @@ def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator @pytest.fixture(autouse=True, scope="session") def write_plugin(plugin_name: str) -> Generator[Any, Any, Any]: + """Writes a plugin to the file system.""" runner.invoke(app, "init", input=plugin_name) - protocol = open(f"./{plugin_name}/protocols/my_protocol.py", "w") - p = """ + + protocol_code = """ from canvas_sdk.events import EventType from canvas_sdk.protocols import BaseProtocol from logger import log @@ -81,8 +90,9 @@ def compute(self): log.info(self.NARRATIVE_STRING) return [] """ - protocol.write(p) - protocol.close() + + with open(f"./{plugin_name}/protocols/my_protocol.py", "w") as protocol: + protocol.write(protocol_code) yield @@ -91,12 +101,12 @@ def compute(self): def list_empty_plugins(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 1 - list all plugins""" + """Step 1 - list all plugins.""" return ("list", 0, [], [f"{plugin_name}"]) def install_new_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 2 - install a new plugin""" + """Step 2 - install a new plugin.""" return ( f"install {plugin_name}", 0, @@ -111,12 +121,12 @@ def install_new_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str] def list_newly_installed_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 3 - list all plugins, including newly installed one""" + """Step 3 - list all plugins, including newly installed one.""" return ("list", 0, [f"{plugin_name}@0.0.1 enabled"], []) def disable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 4 - disable plugin""" + """Step 4 - disable plugin.""" return ( f"disable {plugin_name}", 0, @@ -126,12 +136,12 @@ def disable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: def list_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 5 - list disabled plugin""" + """Step 5 - list disabled plugin.""" return ("list", 0, [f"{plugin_name}@0.0.1 disabled"], []) def enable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 6 - enable the disabled plugin""" + """Step 6 - enable the disabled plugin.""" return ( f"enable {plugin_name}", 0, @@ -141,7 +151,7 @@ def enable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: def uninstall_enabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 7 - try to uninstall the enabled plugin""" + """Step 7 - try to uninstall the enabled plugin.""" return ( f"uninstall {plugin_name}", 1, @@ -154,7 +164,7 @@ def uninstall_enabled_plugin(plugin_name: str) -> tuple[str, int, list[str], lis def uninstall_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]: - """Step 8 - disable and then uninstall the plugin""" + """Step 8 - disable and then uninstall the plugin.""" runner.invoke(app, f"disable {plugin_name}") return ( @@ -192,7 +202,7 @@ def test_canvas_list_install_disable_enable_uninstall( create_or_update_config_auth_file_for_testing: None, step: Callable, ) -> None: - """Tests that the Canvas CLI can list, install, disable, enable, and uninstall a plugin""" + """Tests that the Canvas CLI can list, install, disable, enable, and uninstall a plugin.""" mock_get_password.return_value = None mock_set_password.return_value = None diff --git a/canvas_cli/utils/context/context.py b/canvas_cli/utils/context/context.py index a357b7e4..36d88af2 100644 --- a/canvas_cli/utils/context/context.py +++ b/canvas_cli/utils/context/context.py @@ -1,7 +1,8 @@ import functools import json +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable, TypeVar, cast +from typing import Any, TypeVar, cast import typer @@ -12,6 +13,7 @@ class CLIContext: """Class that handles configuration across the CLI. + Includes methods for: * Loading a JSON file with configuration keys into memory. * Making a property transient (default, value is not persisted) or persistent (via decorators) @@ -118,7 +120,7 @@ def load_from_file(self, file: Path) -> None: success=False, path=str(file), ) - raise typer.Abort() + raise typer.Abort() from None self._config_file_path = file diff --git a/canvas_cli/utils/context/tests.py b/canvas_cli/utils/context/tests.py index d16920dd..9ca1e15b 100644 --- a/canvas_cli/utils/context/tests.py +++ b/canvas_cli/utils/context/tests.py @@ -1,7 +1,7 @@ import json import os +from collections.abc import Generator from pathlib import Path -from typing import Generator import pytest import typer diff --git a/canvas_cli/utils/print/tests.py b/canvas_cli/utils/print/tests.py index f28b0544..96185ea6 100644 --- a/canvas_cli/utils/print/tests.py +++ b/canvas_cli/utils/print/tests.py @@ -18,7 +18,7 @@ def test_print_json_outputs_valid_json(message: Any, capfd: pytest.CaptureFixtur try: json.loads(output) except ValueError as exc: - assert False, f"{output} is not valid json: {exc}" + assert AssertionError(f"{output} is not valid json: {exc}") def test_print_json_outputs_kwargs(capfd: pytest.CaptureFixture[str]) -> None: @@ -33,7 +33,7 @@ def test_print_json_outputs_kwargs(capfd: pytest.CaptureFixture[str]) -> None: assert json_dict.get("a_dict") == {"one": 2} except ValueError as exc: - assert False, f"{output} is not valid json: {exc}" + assert AssertionError(f"{output} is not valid json: {exc}") def test_print_overrides_default(capfd: pytest.CaptureFixture[str]) -> None: diff --git a/canvas_cli/utils/validators/tests.py b/canvas_cli/utils/validators/tests.py index bb85d025..d5c1ecde 100644 --- a/canvas_cli/utils/validators/tests.py +++ b/canvas_cli/utils/validators/tests.py @@ -5,6 +5,7 @@ @pytest.fixture def protocol_manifest_example() -> dict: + """Return a valid protocol manifest example.""" return { "sdk_version": "0.3.1", "plugin_version": "1.0.1", @@ -32,5 +33,5 @@ def protocol_manifest_example() -> dict: def test_manifest_file_schema(protocol_manifest_example: dict) -> None: - """Test that no exception raised when a valid manifest file is validated""" + """Test that no exception raised when a valid manifest file is validated.""" validate_manifest_file(protocol_manifest_example) diff --git a/canvas_sdk/base.py b/canvas_sdk/base.py index 35ceb73f..83f2541b 100644 --- a/canvas_sdk/base.py +++ b/canvas_sdk/base.py @@ -24,7 +24,7 @@ class Meta: ) def _get_effect_method_required_fields(self, method: Any) -> tuple: - return getattr(self.Meta, f"{method}_required_fields", tuple()) + return getattr(self.Meta, f"{method}_required_fields", ()) def _create_error_detail(self, type: str, message: str, value: Any) -> InitErrorDetails: return InitErrorDetails({"type": PydanticCustomError(type, message), "input": value}) diff --git a/canvas_sdk/commands/base.py b/canvas_sdk/commands/base.py index fd0f8260..7658a5d9 100644 --- a/canvas_sdk/commands/base.py +++ b/canvas_sdk/commands/base.py @@ -2,11 +2,11 @@ import re from enum import EnumType from types import NoneType, UnionType -from typing import Any, Literal, Tuple, Union, cast, get_args, get_origin +from typing import Union, get_args, get_origin from canvas_sdk.base import Model from canvas_sdk.commands.constants import Coding -from canvas_sdk.effects import Effect, EffectType +from canvas_sdk.effects import Effect from canvas_sdk.effects.protocol_card import Recommendation @@ -26,9 +26,7 @@ def constantized_key(self) -> str: command_uuid: str | None = None def _get_effect_method_required_fields(self, method: str) -> tuple: - base_required_fields: tuple = getattr( - _BaseCommand.Meta, f"{method}_required_fields", tuple() - ) + base_required_fields: tuple = getattr(_BaseCommand.Meta, f"{method}_required_fields", ()) command_required_fields = super()._get_effect_method_required_fields(method) return tuple(set(base_required_fields) | set(command_required_fields)) @@ -72,7 +70,7 @@ def command_schema(cls) -> dict: """The schema of the command.""" base_properties = {"note_uuid", "command_uuid"} schema = cls.model_json_schema() - required_fields: tuple = getattr(cls.Meta, "commit_required_fields", tuple()) + required_fields: tuple = getattr(cls.Meta, "commit_required_fields", ()) return { definition.get("commands_api_name", name): { "required": name in required_fields, diff --git a/canvas_sdk/commands/commands/prescribe.py b/canvas_sdk/commands/commands/prescribe.py index d292b190..c8e21e13 100644 --- a/canvas_sdk/commands/commands/prescribe.py +++ b/canvas_sdk/commands/commands/prescribe.py @@ -1,7 +1,5 @@ -from dataclasses import dataclass from decimal import Decimal from enum import Enum -from typing import TypeVar from pydantic import Field, conlist @@ -29,7 +27,9 @@ class Substitutions(Enum): NOT_ALLOWED = "not_allowed" fdb_code: str | None = Field(default=None, json_schema_extra={"commands_api_name": "prescribe"}) - icd10_codes: conlist(str, max_length=2) = Field([], json_schema_extra={"commands_api_name": "indications"}) # type: ignore[valid-type] + icd10_codes: conlist(str, max_length=2) = Field( # type: ignore[valid-type] + [], json_schema_extra={"commands_api_name": "indications"} + ) sig: str = "" days_supply: int | None = None quantity_to_dispense: Decimal | float | int | None = None diff --git a/canvas_sdk/commands/commands/reason_for_visit.py b/canvas_sdk/commands/commands/reason_for_visit.py index c8523ee4..31eed6b8 100644 --- a/canvas_sdk/commands/commands/reason_for_visit.py +++ b/canvas_sdk/commands/commands/reason_for_visit.py @@ -24,7 +24,7 @@ def _get_error_details( if self.structured and not self.coding: errors.append( self._create_error_detail( - "value", f"Structured RFV should have a coding.", self.coding + "value", "Structured RFV should have a coding.", self.coding ) ) return errors diff --git a/canvas_sdk/commands/commands/task.py b/canvas_sdk/commands/commands/task.py index d25922d3..d1b0959b 100644 --- a/canvas_sdk/commands/commands/task.py +++ b/canvas_sdk/commands/commands/task.py @@ -1,7 +1,8 @@ from datetime import date -from enum import Enum, StrEnum +from enum import StrEnum +from typing import NotRequired -from typing_extensions import NotRequired, TypedDict +from typing_extensions import TypedDict from canvas_sdk.commands.base import _BaseCommand as BaseCommand diff --git a/canvas_sdk/commands/commands/vitals.py b/canvas_sdk/commands/commands/vitals.py index 1623b95e..1d0e7d78 100644 --- a/canvas_sdk/commands/commands/vitals.py +++ b/canvas_sdk/commands/commands/vitals.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Optional from pydantic import conint, constr diff --git a/canvas_sdk/commands/constants.py b/canvas_sdk/commands/constants.py index 7692efb3..34876736 100644 --- a/canvas_sdk/commands/constants.py +++ b/canvas_sdk/commands/constants.py @@ -1,4 +1,6 @@ -from typing_extensions import NotRequired, TypedDict +from typing import NotRequired + +from typing_extensions import TypedDict class Coding(TypedDict): diff --git a/canvas_sdk/commands/tests/protocol/tests.py b/canvas_sdk/commands/tests/protocol/tests.py index b9f19db6..c90d158b 100644 --- a/canvas_sdk/commands/tests/protocol/tests.py +++ b/canvas_sdk/commands/tests/protocol/tests.py @@ -1,5 +1,5 @@ +from collections.abc import Generator from datetime import datetime -from typing import Any, Generator import pytest @@ -18,16 +18,19 @@ @pytest.fixture(scope="session") def token() -> MaskedValue: + """Get a valid token.""" return get_token() @pytest.fixture(scope="session") def new_note(token: MaskedValue) -> dict: + """Create a new note.""" return create_new_note(token) @pytest.fixture(scope="session") def plugin_name() -> str: + """The plugin name to be used.""" return f"commands{datetime.now().timestamp()}".replace(".", "") @@ -35,6 +38,7 @@ def plugin_name() -> str: def write_and_install_protocol_and_clean_up( plugin_name: str, token: MaskedValue, new_note: dict ) -> Generator[None, None, None]: + """Write the protocol code, install the plugin, and clean up after the test.""" write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS) install_plugin(plugin_name, token) @@ -47,6 +51,7 @@ def write_and_install_protocol_and_clean_up( def test_protocol_that_inserts_every_command( write_and_install_protocol_and_clean_up: None, token: MaskedValue, new_note: dict ) -> None: + """Test that the protocol inserts every command.""" trigger_plugin_event(token) commands_in_body = get_original_note_body_commands(new_note["id"], token) diff --git a/canvas_sdk/commands/tests/schema/tests.py b/canvas_sdk/commands/tests/schema/tests.py index 1d32fd65..2f394fd6 100644 --- a/canvas_sdk/commands/tests/schema/tests.py +++ b/canvas_sdk/commands/tests/schema/tests.py @@ -18,16 +18,19 @@ @pytest.fixture(scope="session") def token() -> MaskedValue: + """Get a valid token.""" return get_token() @pytest.fixture(scope="session") def new_note(token: MaskedValue) -> dict: + """Create a new note.""" return create_new_note(token) @pytest.fixture def command_type_map() -> dict[str, type]: + """Map of command field types to their corresponding Python types.""" return { "AutocompleteField": str, "MultiLineTextField": str, @@ -51,6 +54,7 @@ def test_command_schema_matches_command_api( new_note: dict, Command: _BaseCommand, ) -> None: + """Test that the command schema matches the command API.""" # first create the command in the new note data = {"noteKey": new_note["externallyExposableId"], "schemaKey": Command.Meta.key} headers = {"Authorization": f"Bearer {token.value}"} @@ -90,7 +94,7 @@ def test_command_schema_matches_command_api( # this condition initially created for Prescribe.indications, # but could apply to other AutocompleteField fields that are lists # making the assumption here that if the field ends in 's' (like indications), it is a list - assert get_origin(expected_type) == list + assert get_origin(expected_type) is list else: assert expected_type == actual_type diff --git a/canvas_sdk/commands/tests/test_utils.py b/canvas_sdk/commands/tests/test_utils.py index dbf56f7c..a76e005f 100644 --- a/canvas_sdk/commands/tests/test_utils.py +++ b/canvas_sdk/commands/tests/test_utils.py @@ -35,6 +35,8 @@ class MaskedValue: + """A class to mask sensitive values in tests.""" + def __init__(self, value: str) -> None: self.value = value @@ -46,6 +48,7 @@ def __str___(self) -> str: def get_field_type_unformatted(field_props: dict[str, Any]) -> str: + """Get the unformatted field type from the field properties.""" if t := field_props.get("type"): return field_props.get("format") or t @@ -56,14 +59,17 @@ def get_field_type_unformatted(field_props: dict[str, Any]) -> str: def get_field_type(field_props: dict) -> str: + """Get the field type from the field properties.""" return get_field_type_unformatted(field_props).replace("-", "").replace("array", "list") def random_string() -> str: + """Generate a random string.""" return "".join(random.choices(string.ascii_uppercase + string.digits, k=7)) def fake(field_props: dict, Command: type[_BaseCommand]) -> Any: + """Generate a fake value for a field.""" t = get_field_type(field_props) match t: case "string": @@ -89,13 +95,14 @@ def fake(field_props: dict, Command: type[_BaseCommand]) -> Any: case "ClinicalQuantity": return ClinicalQuantity(representative_ndc="ndc", ncpdp_quantity_qualifier_code="code") if t[0].isupper(): - return random.choice([e for e in getattr(Command, t)]) + return random.choice(list(getattr(Command, t))) def raises_wrong_type_error( Command: type[_BaseCommand], field: str, ) -> None: + """Test that the correct error is raised when the wrong type is passed to a field.""" field_props = Command.model_json_schema()["properties"][field] field_type = get_field_type(field_props) wrong_field_type = "integer" if field_type == "string" else "string" @@ -119,8 +126,8 @@ def raises_wrong_type_error( "dictionary" if field_type == "Coding" or field_type == "ClinicalQuantity" else field_type ) if field_type == "number": - assert f"Input should be an instance of Decimal" in err_msg1 - assert f"Input should be an instance of Decimal" in err_msg2 + assert "Input should be an instance of Decimal" in err_msg1 + assert "Input should be an instance of Decimal" in err_msg2 elif field_type[0].isupper(): assert f"Input should be an instance of {Command.__name__}.{field_type}" in err_msg1 assert f"Input should be an instance of {Command.__name__}.{field_type}" in err_msg2 @@ -133,6 +140,7 @@ def raises_none_error_for_effect_method( Command: type[_BaseCommand], method: str, ) -> None: + """Test that the correct error is raised when a required field is None for an effect method.""" cmd_name = Command.__name__ cmd_name_article = "an" if cmd_name.startswith(("A", "E", "I", "O", "U")) else "a" @@ -155,6 +163,7 @@ def raises_none_error_for_effect_method( def write_protocol_code( note_uuid: str, plugin_name: str, commands: list[type[_BaseCommand]] ) -> None: + """Test that the protocol code is written correctly.""" imports = ", ".join([c.__name__ for c in commands]) effects = ", ".join([f"{c.__name__}(note_uuid='{note_uuid}').originate()" for c in commands]) @@ -171,21 +180,23 @@ def compute(self): with chdir(Path("./custom-plugins")): runner.invoke(app, "init", input=plugin_name) - protocol = open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") - protocol.write(protocol_code) - protocol.close() + with open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") as protocol: + protocol.write(protocol_code) def install_plugin(plugin_name: str, token: MaskedValue) -> None: - requests.post( - plugin_url(cast(str, settings.INTEGRATION_TEST_URL)), - data={"is_enabled": True}, - files={"package": open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb")}, - headers={"Authorization": f"Bearer {token.value}"}, - ) + """Install a plugin.""" + with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package: + requests.post( + plugin_url(cast(str, settings.INTEGRATION_TEST_URL)), + data={"is_enabled": True}, + files={"package": package}, + headers={"Authorization": f"Bearer {token.value}"}, + ) def trigger_plugin_event(token: MaskedValue) -> None: + """Trigger a plugin event.""" requests.post( f"{settings.INTEGRATION_TEST_URL}/api/Note/", headers={ @@ -204,6 +215,7 @@ def trigger_plugin_event(token: MaskedValue) -> None: def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> list[str]: + """Get the commands from the original note body.""" original_note = requests.get( f"{settings.INTEGRATION_TEST_URL}/api/Note/{new_note_id}", headers={ @@ -225,6 +237,7 @@ def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> lis def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None: + """Clean up the files and plugins.""" # clean up if Path(f"./custom-plugins/{plugin_name}").exists(): shutil.rmtree(Path(f"./custom-plugins/{plugin_name}")) @@ -261,6 +274,7 @@ def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None: def create_new_note(token: MaskedValue) -> dict: + """Create a new note.""" headers = { "Authorization": f"Bearer {token.value}", "Content-Type": "application/json", @@ -279,6 +293,7 @@ def create_new_note(token: MaskedValue) -> dict: def get_token() -> MaskedValue: + """Get a valid token.""" return MaskedValue( requests.post( f"{settings.INTEGRATION_TEST_URL}/auth/token/", diff --git a/canvas_sdk/commands/tests/unit/tests.py b/canvas_sdk/commands/tests/unit/tests.py index 5ca0ad6e..432658a6 100644 --- a/canvas_sdk/commands/tests/unit/tests.py +++ b/canvas_sdk/commands/tests/unit/tests.py @@ -82,6 +82,7 @@ def test_command_raises_generic_error_when_kwarg_given_incorrect_type( Command: type[_BaseCommand], fields_to_test: tuple[str], ) -> None: + """Test that Command raises a generic error when a kwarg is given an incorrect type.""" for field in fields_to_test: raises_wrong_type_error(Command, field) @@ -184,6 +185,7 @@ def test_command_raises_specific_error_when_kwarg_given_incorrect_type( err_msg: str, valid_kwargs: dict, ) -> None: + """Test that Command raises a specific error when a kwarg is given an incorrect type.""" with pytest.raises(ValidationError) as e1: cmd = Command(**err_kwargs) cmd.originate() @@ -258,6 +260,7 @@ def test_command_allows_kwarg_with_correct_type( Command: type[_BaseCommand], fields_to_test: tuple[str], ) -> None: + """Test that Command allows a kwarg with the correct type.""" schema = Command.model_json_schema() for field in fields_to_test: diff --git a/canvas_sdk/effects/__init__.py b/canvas_sdk/effects/__init__.py index ec60f1fd..e1ca7dfb 100644 --- a/canvas_sdk/effects/__init__.py +++ b/canvas_sdk/effects/__init__.py @@ -1 +1,3 @@ from canvas_generated.messages.effects_pb2 import Effect, EffectType + +__all__ = ("Effect", "EffectType") diff --git a/canvas_sdk/effects/banner_alert/__init__.py b/canvas_sdk/effects/banner_alert/__init__.py index 743f5693..236cb000 100644 --- a/canvas_sdk/effects/banner_alert/__init__.py +++ b/canvas_sdk/effects/banner_alert/__init__.py @@ -1,2 +1,4 @@ from canvas_sdk.effects.banner_alert.add_banner_alert import AddBannerAlert from canvas_sdk.effects.banner_alert.remove_banner_alert import RemoveBannerAlert + +__all__ = ("AddBannerAlert", "RemoveBannerAlert") diff --git a/canvas_sdk/effects/banner_alert/tests.py b/canvas_sdk/effects/banner_alert/tests.py index 966da14f..5a46ab6c 100644 --- a/canvas_sdk/effects/banner_alert/tests.py +++ b/canvas_sdk/effects/banner_alert/tests.py @@ -1,8 +1,9 @@ import shutil +from collections.abc import Generator from contextlib import chdir from datetime import datetime from pathlib import Path -from typing import Any, Generator +from typing import Any import pytest import requests @@ -21,6 +22,7 @@ @pytest.fixture(scope="session") def token() -> MaskedValue: + """Get a valid token.""" return MaskedValue( requests.post( f"{settings.INTEGRATION_TEST_URL}/auth/token/", @@ -36,6 +38,7 @@ def token() -> MaskedValue: @pytest.fixture(scope="session") def first_patient_id(token: MaskedValue) -> dict: + """Get the first patient id.""" headers = { "Authorization": f"Bearer {token.value}", "Content-Type": "application/json", @@ -47,6 +50,7 @@ def first_patient_id(token: MaskedValue) -> dict: @pytest.fixture(scope="session") def plugin_name() -> str: + """Get the plugin name to be used.""" return f"addbanneralert{datetime.now().timestamp()}".replace(".", "") @@ -54,7 +58,7 @@ def plugin_name() -> str: def write_and_install_protocol_and_clean_up( first_patient_id: str, plugin_name: str, token: MaskedValue ) -> Generator[Any, Any, Any]: - + """Write the protocol code, install the plugin, and clean up after the test.""" if not settings.INTEGRATION_TEST_URL: raise ImproperlyConfigured("INTEGRATION_TEST_URL is not set") @@ -62,9 +66,8 @@ def write_and_install_protocol_and_clean_up( with chdir(Path("./custom-plugins")): runner.invoke(app, "init", input=plugin_name) - protocol = open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") - protocol.write( - f"""from canvas_sdk.effects.banner_alert import AddBannerAlert + protocol_code = f""" +from canvas_sdk.effects.banner_alert import AddBannerAlert from canvas_sdk.events import EventType from canvas_sdk.protocols import BaseProtocol @@ -81,16 +84,18 @@ def compute(self): ).apply() ] """ - ) - protocol.close() - # install the plugin - requests.post( - plugin_url(settings.INTEGRATION_TEST_URL), - data={"is_enabled": True}, - files={"package": open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb")}, - headers={"Authorization": f"Bearer {token.value}"}, - ) + with open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") as protocol: + protocol.write(protocol_code) + + with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package: + # install the plugin + requests.post( + plugin_url(settings.INTEGRATION_TEST_URL), + data={"is_enabled": True}, + files={"package": package}, + headers={"Authorization": f"Bearer {token.value}"}, + ) yield @@ -132,6 +137,7 @@ def test_protocol_that_adds_banner_alert( plugin_name: str, first_patient_id: str, ) -> None: + """Test that the protocol adds a banner alert.""" # trigger the event requests.post( f"{settings.INTEGRATION_TEST_URL}/api/Note/", @@ -206,6 +212,7 @@ def test_protocol_that_adds_banner_alert( def test_banner_alert_apply_method_succeeds_with_all_required_fields( Effect: type[AddBannerAlert] | type[RemoveBannerAlert], params: dict, expected_payload: str ) -> None: + """Test that the apply method succeeds with all required fields.""" b = Effect() for k, v in params.items(): setattr(b, k, v) @@ -240,6 +247,7 @@ def test_banner_alert_apply_method_succeeds_with_all_required_fields( def test_banner_alert_apply_method_raises_error_without_required_fields( Effect: type[AddBannerAlert] | type[RemoveBannerAlert], expected_err_msgs: str ) -> None: + """Test that the apply method raises an error when missing required fields.""" b = Effect() with pytest.raises(ValidationError) as e: b.apply() diff --git a/canvas_sdk/effects/protocol_card/__init__.py b/canvas_sdk/effects/protocol_card/__init__.py index 1abcdecd..c42d091a 100644 --- a/canvas_sdk/effects/protocol_card/__init__.py +++ b/canvas_sdk/effects/protocol_card/__init__.py @@ -1 +1,3 @@ from canvas_sdk.effects.protocol_card.protocol_card import ProtocolCard, Recommendation + +__all__ = ("ProtocolCard", "Recommendation") diff --git a/canvas_sdk/effects/protocol_card/tests.py b/canvas_sdk/effects/protocol_card/tests.py index 3083e632..aa8dc1d9 100644 --- a/canvas_sdk/effects/protocol_card/tests.py +++ b/canvas_sdk/effects/protocol_card/tests.py @@ -21,6 +21,7 @@ def test_apply_method_succeeds_with_patient_id_and_key() -> None: + """Test that the apply method succeeds with all required fields.""" p = ProtocolCard(patient_id="uuid", key="something-unique") applied = p.apply() assert ( @@ -30,6 +31,7 @@ def test_apply_method_succeeds_with_patient_id_and_key() -> None: def test_apply_method_raises_error_without_patient_id_and_key() -> None: + """Test that the apply method raises an error when missing required fields.""" p = ProtocolCard() with pytest.raises(ValidationError) as e: @@ -109,6 +111,7 @@ def test_apply_method_raises_error_without_patient_id_and_key() -> None: def test_add_recommendations( init_params: dict[Any, Any], rec1_params: dict[Any, Any], rec2_params: dict[Any, Any] ) -> None: + """Test that the add_recommendation method adds recommendations to the ProtocolCard.""" p = ProtocolCard(**init_params) p.add_recommendation(**rec1_params) p.recommendations.append(Recommendation(**rec2_params)) @@ -117,17 +120,17 @@ def test_add_recommendations( "narrative": init_params["narrative"], "recommendations": [ { - "title": rec1_params.get("title", None), - "button": rec1_params.get("button", None), - "href": rec1_params.get("href", None), + "title": rec1_params.get("title"), + "button": rec1_params.get("button"), + "href": rec1_params.get("href"), "command": {"type": rec1_params["command"]} if "command" in rec1_params else {}, "context": rec1_params.get("context", {}), "key": 0, }, { - "title": rec2_params.get("title", None), - "button": rec2_params.get("button", None), - "href": rec2_params.get("href", None), + "title": rec2_params.get("title"), + "button": rec2_params.get("button"), + "href": rec2_params.get("href"), "command": {"type": rec2_params["command"]} if "command" in rec2_params else {}, "context": rec2_params.get("context", {}), "key": 1, @@ -158,6 +161,7 @@ def test_add_recommendations( def test_add_recommendations_from_commands( Command: type[_BaseCommand], init_params: dict[str, str] ) -> None: + """Test that the add_recommendation method adds recommendations from commands to the ProtocolCard.""" cmd = Command(**init_params) p = ProtocolCard(patient_id="uuid", key="commands") p.recommendations.append(cmd.recommend()) diff --git a/canvas_sdk/events/__init__.py b/canvas_sdk/events/__init__.py index 46c14e08..c9adbaed 100644 --- a/canvas_sdk/events/__init__.py +++ b/canvas_sdk/events/__init__.py @@ -1 +1,3 @@ from canvas_generated.messages.events_pb2 import Event, EventResponse, EventType + +__all__ = ("Event", "EventResponse", "EventType") diff --git a/canvas_sdk/handlers/__init__.py b/canvas_sdk/handlers/__init__.py index b50b603d..188968c7 100644 --- a/canvas_sdk/handlers/__init__.py +++ b/canvas_sdk/handlers/__init__.py @@ -1 +1,3 @@ from canvas_sdk.handlers.base import BaseHandler + +__all__ = ("BaseHandler",) diff --git a/canvas_sdk/protocols/__init__.py b/canvas_sdk/protocols/__init__.py index 97a777ca..d5c869b4 100644 --- a/canvas_sdk/protocols/__init__.py +++ b/canvas_sdk/protocols/__init__.py @@ -1,2 +1,4 @@ from canvas_sdk.protocols.base import BaseProtocol from canvas_sdk.protocols.clinical_quality_measure import ClinicalQualityMeasure + +__all__ = ("BaseProtocol", "ClinicalQualityMeasure") diff --git a/canvas_sdk/protocols/base.py b/canvas_sdk/protocols/base.py index f4cb6261..303c330b 100644 --- a/canvas_sdk/protocols/base.py +++ b/canvas_sdk/protocols/base.py @@ -1,5 +1,3 @@ -from typing import Any - from canvas_sdk.handlers.base import BaseHandler diff --git a/canvas_sdk/protocols/clinical_quality_measure.py b/canvas_sdk/protocols/clinical_quality_measure.py index 881218f3..d651d479 100644 --- a/canvas_sdk/protocols/clinical_quality_measure.py +++ b/canvas_sdk/protocols/clinical_quality_measure.py @@ -88,7 +88,8 @@ def patient_id(model: type[Model]) -> str: case EventType.CONDITION_CREATED | EventType.CONDITION_UPDATED: self._patient_id = patient_id(Condition) case ( - EventType.MEDICATION_LIST_ITEM_CREATED | EventType.MEDICATION_LIST_ITEM_UPDATED + EventType.MEDICATION_LIST_ITEM_CREATED + | EventType.MEDICATION_LIST_ITEM_UPDATED ): self._patient_id = patient_id(Medication) case _: diff --git a/canvas_sdk/utils/http.py b/canvas_sdk/utils/http.py index 83db77bc..7e374a7b 100644 --- a/canvas_sdk/utils/http.py +++ b/canvas_sdk/utils/http.py @@ -1,6 +1,7 @@ import time +from collections.abc import Callable, Mapping from functools import wraps -from typing import Any, Callable, Mapping, TypeVar, cast +from typing import Any, TypeVar, cast import requests import statsd diff --git a/canvas_sdk/utils/tests.py b/canvas_sdk/utils/tests.py index ecb0a99f..d3b19a38 100644 --- a/canvas_sdk/utils/tests.py +++ b/canvas_sdk/utils/tests.py @@ -5,6 +5,7 @@ @patch("requests.Session.get") def test_http_get(mock_get: MagicMock) -> None: + """Test that the Http.get method calls requests.get with the correct arguments.""" http = Http() http.get("https://www.canvasmedical.com/", headers={"Authorization": "Bearer as;ldkfjdkj"}) mock_get.assert_called_once_with( @@ -14,6 +15,7 @@ def test_http_get(mock_get: MagicMock) -> None: @patch("requests.Session.post") def test_http_post(mock_post: MagicMock) -> None: + """Test that the Http.post method calls requests.post with the correct arguments.""" http = Http() http.post( "https://www.canvasmedical.com/", @@ -31,6 +33,7 @@ def test_http_post(mock_post: MagicMock) -> None: @patch("requests.Session.put") def test_http_put(mock_put: MagicMock) -> None: + """Test that the Http.put method calls requests.put with the correct arguments.""" http = Http() http.put( "https://www.canvasmedical.com/", @@ -48,6 +51,7 @@ def test_http_put(mock_put: MagicMock) -> None: @patch("requests.Session.patch") def test_http_patch(mock_patch: MagicMock) -> None: + """Test that the Http.patch method calls requests.patch with the correct arguments.""" http = Http() http.patch( "https://www.canvasmedical.com/", diff --git a/canvas_sdk/v1/data/__init__.py b/canvas_sdk/v1/data/__init__.py index 0b76f978..a656778a 100644 --- a/canvas_sdk/v1/data/__init__.py +++ b/canvas_sdk/v1/data/__init__.py @@ -4,3 +4,16 @@ from .patient import Patient from .staff import Staff from .task import Task, TaskComment, TaskLabel + +__all__ = ( + "BillingLineItem", + "Condition", + "ConditionCoding", + "Medication", + "MedicationCoding", + "Patient", + "Staff", + "Task", + "TaskComment", + "TaskLabel", +) diff --git a/canvas_sdk/v1/data/allergy_intolerance.py b/canvas_sdk/v1/data/allergy_intolerance.py index a8480cf5..8961695c 100644 --- a/canvas_sdk/v1/data/allergy_intolerance.py +++ b/canvas_sdk/v1/data/allergy_intolerance.py @@ -1,4 +1,3 @@ -from django.contrib.postgres.fields import ArrayField from django.db import models from canvas_sdk.v1.data.base import CommittableModelManager, ValueSetLookupQuerySet diff --git a/canvas_sdk/v1/data/base.py b/canvas_sdk/v1/data/base.py index dde016e8..437770f7 100644 --- a/canvas_sdk/v1/data/base.py +++ b/canvas_sdk/v1/data/base.py @@ -1,6 +1,6 @@ from abc import abstractmethod from collections.abc import Container -from typing import TYPE_CHECKING, Any, Protocol, Self, Type, cast +from typing import TYPE_CHECKING, Any, Protocol, Self, cast from django.db import models from django.db.models import Q @@ -50,7 +50,7 @@ class ValueSetLookupQuerySetProtocol(QuerySetProtocol): @staticmethod @abstractmethod - def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]: + def codings(value_set: type["ValueSet"]) -> tuple[tuple[str, set[str]]]: """A protocol method for defining codings.""" raise NotImplementedError @@ -64,7 +64,7 @@ def q_object(system: str, codes: Container[str]) -> Q: class ValueSetLookupQuerySetMixin(ValueSetLookupQuerySetProtocol): """A QuerySet mixin that can filter objects based on a ValueSet.""" - def find(self, value_set: Type["ValueSet"]) -> models.QuerySet[Any]: + def find(self, value_set: type["ValueSet"]) -> models.QuerySet[Any]: """ Filters conditions, medications, etc. to those found in the inherited ValueSet class that is passed. @@ -84,7 +84,7 @@ def find(self, value_set: Type["ValueSet"]) -> models.QuerySet[Any]: return self.filter(q_filter).distinct() @staticmethod - def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]: + def codings(value_set: type["ValueSet"]) -> tuple[tuple[str, set[str]]]: """Provide a sequence of tuples where each tuple is a code system URL and a set of codes.""" values_dict = cast(dict, value_set.values) return cast( @@ -113,7 +113,7 @@ class ValueSetLookupByNameQuerySetMixin(ValueSetLookupQuerySetMixin): """ @staticmethod - def codings(value_set: Type["ValueSet"]) -> tuple[tuple[str, set[str]]]: + def codings(value_set: type["ValueSet"]) -> tuple[tuple[str, set[str]]]: """ Provide a sequence of tuples where each tuple is a code system name and a set of codes. """ diff --git a/canvas_sdk/v1/data/billing.py b/canvas_sdk/v1/data/billing.py index bc7f2763..7d755754 100644 --- a/canvas_sdk/v1/data/billing.py +++ b/canvas_sdk/v1/data/billing.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Type +from typing import TYPE_CHECKING from django.db import models @@ -14,7 +14,7 @@ class BillingLineItemQuerySet(ValueSetTimeframeLookupQuerySet): """A class that adds functionality to filter BillingLineItem objects.""" - def find(self, value_set: Type["ValueSet"]) -> models.QuerySet: + def find(self, value_set: type["ValueSet"]) -> models.QuerySet: """ This method is overridden to use for BillingLineItem CPT codes. The codes are saved as string values in the BillingLineItem.cpt field, diff --git a/canvas_sdk/v1/data/common.py b/canvas_sdk/v1/data/common.py index a8db9bd2..8e26db6d 100644 --- a/canvas_sdk/v1/data/common.py +++ b/canvas_sdk/v1/data/common.py @@ -1,4 +1,3 @@ -from django.contrib.postgres.fields import ArrayField from django.db import models diff --git a/canvas_sdk/v1/data/patient.py b/canvas_sdk/v1/data/patient.py index e5c753cc..f1f15dbb 100644 --- a/canvas_sdk/v1/data/patient.py +++ b/canvas_sdk/v1/data/patient.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Self +from typing import Self import arrow from django.db import models diff --git a/canvas_sdk/v1/data/questionnaire.py b/canvas_sdk/v1/data/questionnaire.py index d955e23b..7f728c54 100644 --- a/canvas_sdk/v1/data/questionnaire.py +++ b/canvas_sdk/v1/data/questionnaire.py @@ -153,7 +153,8 @@ class Meta: note_id = models.BigIntegerField() appointment_id = models.BigIntegerField() questionnaires = models.ManyToManyField( # type: ignore[var-annotated] - Questionnaire, through="canvas_sdk.InterviewQuestionnaireMap" # type: ignore[misc] + Questionnaire, + through="canvas_sdk.InterviewQuestionnaireMap", # type: ignore[misc] ) progress_status = models.CharField() created = models.DateTimeField() diff --git a/canvas_sdk/value_set/custom.py b/canvas_sdk/value_set/custom.py index 9e6ac532..a472ecd7 100644 --- a/canvas_sdk/value_set/custom.py +++ b/canvas_sdk/value_set/custom.py @@ -115,7 +115,6 @@ class Antiarrhythmics(ValueSet): "230155", "237183", "243776", - "243776", "248491", "248829", "250272", @@ -129,9 +128,6 @@ class Antiarrhythmics(ValueSet): "265785", "274471", "278255", - "278255", - "278255", - "278255", "280333", "281153", "283306", @@ -139,8 +135,6 @@ class Antiarrhythmics(ValueSet): "291187", "296991", "444249", - "444249", - "444944", "444944", "449494", "449496", @@ -157,12 +151,10 @@ class Antiarrhythmics(ValueSet): "454207", "454371", "545231", - "545231", "545232", "545233", "545238", "545239", - "545239", "558741", "558745", "559416", @@ -177,8 +169,6 @@ class Antiarrhythmics(ValueSet): "565069", "573523", "583982", - "583982", - "583985", "583985", "590326", "590375", diff --git a/canvas_sdk/value_set/tests/test_value_sets.py b/canvas_sdk/value_set/tests/test_value_sets.py index b66bc8d4..e0562779 100644 --- a/canvas_sdk/value_set/tests/test_value_sets.py +++ b/canvas_sdk/value_set/tests/test_value_sets.py @@ -8,12 +8,14 @@ def test_value_set_class_values_property() -> None: + """Test that the value_set returns the correct values.""" value_set = DisordersOfTheImmuneSystem assert value_set.values["ICD10CM"] == DisordersOfTheImmuneSystem.ICD10CM assert value_set.values["SNOMEDCT"] == DisordersOfTheImmuneSystem.SNOMEDCT def test_value_set_class_pipe_operator_with_two_value_sets() -> None: + """Test that the pipe operator returns the correct values.""" combined_value_set: CombinedValueSet = ( DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination ) @@ -30,6 +32,7 @@ def test_value_set_class_pipe_operator_with_two_value_sets() -> None: def test_value_set_class_pipe_operator_with_three_value_sets() -> None: + """Test that the pipe operator returns the correct values with multiple operands.""" combined_value_set: CombinedValueSet = ( DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination | Rhabdomyolysis ) @@ -46,6 +49,7 @@ def test_value_set_class_pipe_operator_with_three_value_sets() -> None: def test_value_set_class_pipe_operator_with_two_combined_value_sets() -> None: + """Test that the pipe operator returns the correct values with two combined value_sets.""" combined_value_set_1: CombinedValueSet = ( DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination ) diff --git a/canvas_sdk/value_set/v2022/individual_characteristic.py b/canvas_sdk/value_set/v2022/individual_characteristic.py index 8482de22..332e85f0 100644 --- a/canvas_sdk/value_set/v2022/individual_characteristic.py +++ b/canvas_sdk/value_set/v2022/individual_characteristic.py @@ -2,7 +2,8 @@ class Ethnicity(ValueSet): - """ + """Ethnicity value set. + **Clinical Focus:** **Data Element Scope:** @@ -26,7 +27,8 @@ class Ethnicity(ValueSet): class OncAdministrativeSex(ValueSet): - """ + """ONC Administrative Sex value set. + **Clinical Focus:** Gender identity restricted to only Male and Female used in administrative situations requiring a restriction to these two categories. **Data Element Scope:** Gender @@ -50,7 +52,8 @@ class OncAdministrativeSex(ValueSet): class Payer(ValueSet): - """ + """Payer value set. + **Clinical Focus:** Categories of types of health care payor entities as defined by the US Public Health Data Consortium SOP code system **Data Element Scope:** @code in CCDA r2.1 template Planned Coverage [act: identifier urn:oid:2.16.840.1.113883.10.20.22.4.129 (open)] DYNAMIC @@ -234,7 +237,8 @@ class Payer(ValueSet): class Race(ValueSet): - """ + """Race value set. + **Clinical Focus:** **Data Element Scope:** @@ -262,7 +266,8 @@ class Race(ValueSet): class Female(ValueSet): - """ + """Female Gender value set. + **Clinical Focus:** Concepts that represent Female when assessing quality measures **Data Element Scope:** Gender @@ -285,7 +290,8 @@ class Female(ValueSet): class White(ValueSet): - """ + """White Race value set. + **Clinical Focus:** The purpose of this value set is to represent concepts for the patient characteristic of white race. **Data Element Scope:** This value set may use a model element related to Race. diff --git a/canvas_sdk/value_set/v2022/procedure.py b/canvas_sdk/value_set/v2022/procedure.py index fccb5029..e878b3c3 100644 --- a/canvas_sdk/value_set/v2022/procedure.py +++ b/canvas_sdk/value_set/v2022/procedure.py @@ -1563,7 +1563,8 @@ class PeritonealDialysis(ValueSet): class ProceduresUsedToIndicateSexualActivity(ValueSet): - """ + """Procedures Used to Indicate Sexual Activity Value Set. + **Clinical Focus:** The purpose of this value set is to represent procedures indicating history of sexual activity **Data Element Scope:** This value set may use a model element related to Procedure. @@ -2514,7 +2515,8 @@ class CabgSurgeries(ValueSet): class Cabg_PciProcedure(ValueSet): - """ + """CABG, PCI Procedure Value Set. + **Clinical Focus:** CABG and PCI procedures **Data Element Scope:** CABG and PCI procedures diff --git a/canvas_sdk/value_set/value_set.py b/canvas_sdk/value_set/value_set.py index 0bfb3185..2c5849f2 100644 --- a/canvas_sdk/value_set/value_set.py +++ b/canvas_sdk/value_set/value_set.py @@ -107,6 +107,6 @@ def values(cls) -> dict[str, set[str]]: """A property that returns a dictionary of code systems and their associated values.""" return { system: getattr(cls, system) - for system in cls.CODE_SYSTEM_MAPPING.keys() + for system in cls.CODE_SYSTEM_MAPPING if hasattr(cls, system) } diff --git a/plugin_runner/plugin_runner.py b/plugin_runner/plugin_runner.py index ba396b93..6ec6cd47 100644 --- a/plugin_runner/plugin_runner.py +++ b/plugin_runner/plugin_runner.py @@ -8,8 +8,9 @@ import time import traceback from collections import defaultdict +from collections.abc import AsyncGenerator from types import FrameType -from typing import Any, AsyncGenerator, Optional, TypedDict +from typing import Any, TypedDict import grpc import statsd @@ -181,7 +182,7 @@ async def ReloadPlugins( yield ReloadPluginsResponse(success=True) -def handle_hup_cb(_signum: int, _frame: Optional[FrameType]) -> None: +def handle_hup_cb(_signum: int, _frame: FrameType | None) -> None: """handle_hup_cb.""" log.info("Received SIGHUP, reloading plugins...") load_plugins() @@ -191,7 +192,7 @@ def find_modules(base_path: pathlib.Path, prefix: str | None = None) -> list[str """Find all modules in the specified package path.""" modules: list[str] = [] - for file_finder, module_name, is_pkg in pkgutil.iter_modules( + for _, module_name, is_pkg in pkgutil.iter_modules( [base_path.as_posix()], ): if is_pkg: diff --git a/plugin_runner/sandbox.py b/plugin_runner/sandbox.py index d0b62789..a4c0f33a 100644 --- a/plugin_runner/sandbox.py +++ b/plugin_runner/sandbox.py @@ -141,13 +141,12 @@ def check_name( ): self.warn( node, - '"{name}" is an invalid variable name because it ' - 'starts with "_"'.format(name=name), + f'"{name}" is an invalid variable name because it ' 'starts with "_"', ) elif name.endswith("__roles__"): self.error( node, - '"%s" is an invalid variable name because ' 'it ends with "__roles__".' % name, + f'"{name}" is an invalid variable name because ' 'it ends with "__roles__".', ) elif name in FORBIDDEN_FUNC_NAMES: self.error(node, f'"{name}" is a reserved name.') @@ -166,15 +165,14 @@ def visit_Attribute(self, node: ast.Attribute) -> ast.AST: if node.attr.startswith("_") and node.attr != "_": self.warn( node, - '"{name}" is an invalid attribute name because it starts ' - 'with "_".'.format(name=node.attr), + f'"{node.attr}" is an invalid attribute name because it starts ' 'with "_".', ) if node.attr.endswith("__roles__"): self.error( node, - '"{name}" is an invalid attribute name because it ends ' - 'with "__roles__".'.format(name=node.attr), + f'"{node.attr}" is an invalid attribute name because it ends ' + 'with "__roles__".', ) if isinstance(node.ctx, ast.Load): @@ -188,7 +186,7 @@ def visit_Attribute(self, node: ast.Attribute) -> ast.AST: copy_locations(new_node, node) return new_node - elif isinstance(node.ctx, (ast.Store, ast.Del)): + elif isinstance(node.ctx, ast.Store | ast.Del): node = self.node_contents_visit(node) new_value = ast.Call( func=ast.Name("_write_", ast.Load()), args=[node.value], keywords=[] diff --git a/plugin_runner/tests/test_plugin_runner.py b/plugin_runner/tests/test_plugin_runner.py index 566f505e..5abf9756 100644 --- a/plugin_runner/tests/test_plugin_runner.py +++ b/plugin_runner/tests/test_plugin_runner.py @@ -1,6 +1,6 @@ import shutil +from collections.abc import Generator from pathlib import Path -from typing import Generator from unittest.mock import MagicMock, patch import pytest @@ -179,7 +179,6 @@ async def test_reload_plugins_event_handler_successfully_loads_plugins( setup_test_plugin: Path, plugin_runner: PluginRunner ) -> None: """Test ReloadPlugins Event handler successfully loads plugins.""" - with patch("plugin_runner.plugin_runner.publish_message", MagicMock()) as mock_publish_message: request = ReloadPluginsRequest() diff --git a/poetry.lock b/poetry.lock index a0350202..05eea6fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -121,50 +121,6 @@ files = [ [package.dependencies] chardet = ">=3.0.2" -[[package]] -name = "black" -version = "24.10.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -files = [ - {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, - {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, - {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, - {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, - {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, - {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, - {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, - {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, - {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, - {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, - {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, - {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, - {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, - {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, - {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, - {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, - {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, - {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, - {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, - {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, - {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, - {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2024.7.4" @@ -1334,17 +1290,6 @@ files = [ qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pexpect" version = "4.9.0" @@ -2286,6 +2231,33 @@ files = [ {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, ] +[[package]] +name = "ruff" +version = "0.8.1" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, + {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, + {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, + {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, + {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, + {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, + {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, +] + [[package]] name = "secretstorage" version = "3.3.3" @@ -2635,4 +2607,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "3db8e4bffdc9fc647e6e4f3057b3afa3517c419c136f717be269c69ce3fd065f" +content-hash = "6433f4a7d3d9629aadad36679acf380479a2b8c951588a86bf301faa4d298730" diff --git a/pyproject.toml b/pyproject.toml index 5544ea4b..a3900337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,15 +2,9 @@ build-backend = "poetry.core.masonry.api" requires = ["poetry-core"] -[tool.black] -line-length = 100 - [tool.django-stubs] django_settings_module = "settings" -[tool.isort] -profile = "black" - [tool.mypy] check_untyped_defs = true color_output = true @@ -82,7 +76,6 @@ typing-extensions = ">=4.8,<4.13" websocket-client = "^1.7.0" [tool.poetry.group.dev.dependencies] -black = "^24.8.0" grpcio-tools = "^1.60.1" mypy = "*" pre-commit = "*" @@ -91,6 +84,7 @@ pytest-asyncio = "^0.24.0" pytest-mock = "*" python-semantic-release = "*" requests-mock = "*" +ruff = "^0.8.0" types-protobuf = "*" types-requests = "*" @@ -105,10 +99,6 @@ priority = "primary" init_forbid_extra = true init_typed = true -[tool.pydocstyle] -add_ignore = "D100,D104,D105,D106,D107,D200,D203,D205,D212,D400" -convention = "google" - [tool.pytest.ini_options] addopts = "--durations=10" asyncio_default_fixture_loop_scope = "session" @@ -131,6 +121,33 @@ norecursedirs = [ ] python_files = ["*_tests.py", "test_*.py", "tests.py"] +[tool.ruff] +exclude = [ + "canvas_generated/" +] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +ignore = ["D100", "D104", "D105", "D106", "D107", "D200", "D205", "D212", "E501"] +select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "D", # pydocstyle + "E", # pycodestyle errors + "F", # Pyflakes + "I", # isort + "SIM", # flake8-simplify + "UP", # pyupgrade + "W" # pycodestyle warnings +] + +[tool.ruff.lint.flake8-bugbear] +extend-immutable-calls = ["typer.Argument", "typer.Option"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + [tool.semantic_release] build_command = "poetry build" commit_message = "chore: v{version}\n\nSee the changelog for changes." diff --git a/settings.py b/settings.py index 2c5dfd77..abd04fe7 100644 --- a/settings.py +++ b/settings.py @@ -57,7 +57,9 @@ ( "/plugin-runner/custom-plugins" if IS_PRODUCTION - else "./plugin_runner/tests/data/plugins" if IS_TESTING else "./custom-plugins" + else "./plugin_runner/tests/data/plugins" + if IS_TESTING + else "./custom-plugins" ), )