canvas 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of canvas might be problematic. Click here for more details.

Files changed (56) hide show
  1. {canvas-0.8.0.dist-info → canvas-0.8.2.dist-info}/METADATA +2 -2
  2. {canvas-0.8.0.dist-info → canvas-0.8.2.dist-info}/RECORD +56 -56
  3. canvas_cli/apps/auth/tests.py +10 -0
  4. canvas_cli/apps/emit/emit.py +0 -1
  5. canvas_cli/apps/logs/logs.py +4 -4
  6. canvas_cli/apps/plugin/plugin.py +55 -43
  7. canvas_cli/apps/plugin/tests.py +4 -2
  8. canvas_cli/conftest.py +1 -0
  9. canvas_cli/main.py +1 -2
  10. canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -2
  11. canvas_cli/tests.py +36 -26
  12. canvas_cli/utils/context/context.py +4 -2
  13. canvas_cli/utils/context/tests.py +1 -1
  14. canvas_cli/utils/print/tests.py +2 -2
  15. canvas_cli/utils/validators/tests.py +2 -1
  16. canvas_sdk/base.py +1 -1
  17. canvas_sdk/commands/base.py +4 -6
  18. canvas_sdk/commands/commands/prescribe.py +3 -3
  19. canvas_sdk/commands/commands/reason_for_visit.py +1 -1
  20. canvas_sdk/commands/commands/task.py +3 -2
  21. canvas_sdk/commands/commands/vitals.py +0 -1
  22. canvas_sdk/commands/constants.py +3 -1
  23. canvas_sdk/commands/tests/protocol/tests.py +6 -1
  24. canvas_sdk/commands/tests/schema/tests.py +5 -1
  25. canvas_sdk/commands/tests/test_utils.py +27 -12
  26. canvas_sdk/commands/tests/unit/tests.py +3 -0
  27. canvas_sdk/effects/__init__.py +2 -0
  28. canvas_sdk/effects/banner_alert/__init__.py +2 -0
  29. canvas_sdk/effects/banner_alert/tests.py +22 -14
  30. canvas_sdk/effects/protocol_card/__init__.py +2 -0
  31. canvas_sdk/effects/protocol_card/tests.py +10 -6
  32. canvas_sdk/events/__init__.py +2 -0
  33. canvas_sdk/handlers/__init__.py +2 -0
  34. canvas_sdk/protocols/__init__.py +2 -0
  35. canvas_sdk/protocols/base.py +0 -2
  36. canvas_sdk/protocols/clinical_quality_measure.py +2 -1
  37. canvas_sdk/utils/http.py +2 -1
  38. canvas_sdk/utils/tests.py +4 -0
  39. canvas_sdk/v1/data/__init__.py +13 -0
  40. canvas_sdk/v1/data/allergy_intolerance.py +0 -1
  41. canvas_sdk/v1/data/base.py +5 -5
  42. canvas_sdk/v1/data/billing.py +2 -2
  43. canvas_sdk/v1/data/common.py +0 -1
  44. canvas_sdk/v1/data/patient.py +1 -1
  45. canvas_sdk/v1/data/questionnaire.py +2 -1
  46. canvas_sdk/value_set/custom.py +0 -10
  47. canvas_sdk/value_set/tests/test_value_sets.py +4 -0
  48. canvas_sdk/value_set/v2022/individual_characteristic.py +12 -6
  49. canvas_sdk/value_set/v2022/procedure.py +4 -2
  50. canvas_sdk/value_set/value_set.py +1 -1
  51. plugin_runner/plugin_runner.py +4 -3
  52. plugin_runner/sandbox.py +6 -8
  53. plugin_runner/tests/test_plugin_runner.py +1 -2
  54. settings.py +3 -1
  55. {canvas-0.8.0.dist-info → canvas-0.8.2.dist-info}/WHEEL +0 -0
  56. {canvas-0.8.0.dist-info → canvas-0.8.2.dist-info}/entry_points.txt +0 -0
canvas_cli/tests.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import os
2
2
  import shutil
3
+ from collections.abc import Callable, Generator
3
4
  from datetime import datetime
4
5
  from pathlib import Path
5
- from typing import Any, Callable, Generator, cast
6
+ from typing import Any, cast
6
7
  from unittest.mock import MagicMock, patch
7
8
  from urllib.parse import urlparse
8
9
 
@@ -20,14 +21,13 @@ runner = CliRunner()
20
21
 
21
22
  @pytest.fixture(scope="session")
22
23
  def plugin_name() -> str:
23
- """The plugin name to be used for the canvas cli test"""
24
+ """The plugin name to be used for the canvas cli test."""
24
25
  return f"cli-{datetime.now().timestamp()}".replace(".", "")
25
26
 
26
27
 
27
28
  @pytest.fixture(scope="session")
28
29
  def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator[None, None, None]:
29
30
  """Creates the necessary config file for auth before performing cli tests."""
30
-
31
31
  if not settings.INTEGRATION_TEST_URL:
32
32
  raise ImproperlyConfigured("INTEGRATION_TEST_URL is not set")
33
33
 
@@ -45,19 +45,27 @@ def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator
45
45
 
46
46
  temp_path = path.parent / "temp_credentials.ini"
47
47
 
48
- original_content = open(path, "r").read()
49
- open(path, "w").writelines(
50
- [
51
- f"[{host}]\n",
52
- f"client_id={client_id}\n",
53
- f"client_secret={client_secret}\n",
54
- ]
55
- )
56
- open(temp_path, "a").write(original_content)
48
+ # Read the original content
49
+ with open(path) as original_file:
50
+ original_content = original_file.read()
51
+
52
+ # Write new content to the original file
53
+ with open(path, "w") as original_file:
54
+ original_file.writelines(
55
+ [
56
+ f"[{host}]\n",
57
+ f"client_id={client_id}\n",
58
+ f"client_secret={client_secret}\n",
59
+ ]
60
+ )
61
+
62
+ # Append original content to the temp file
63
+ with open(temp_path, "a") as temp_file:
64
+ temp_file.write(original_content)
57
65
 
58
66
  yield
59
67
 
60
- with open(temp_path, "r") as temp:
68
+ with open(temp_path) as temp:
61
69
  original_content = temp.read()
62
70
  with open(path, "w") as f:
63
71
  f.write(original_content)
@@ -66,9 +74,10 @@ def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator
66
74
 
67
75
  @pytest.fixture(autouse=True, scope="session")
68
76
  def write_plugin(plugin_name: str) -> Generator[Any, Any, Any]:
77
+ """Writes a plugin to the file system."""
69
78
  runner.invoke(app, "init", input=plugin_name)
70
- protocol = open(f"./{plugin_name}/protocols/my_protocol.py", "w")
71
- p = """
79
+
80
+ protocol_code = """
72
81
  from canvas_sdk.events import EventType
73
82
  from canvas_sdk.protocols import BaseProtocol
74
83
  from logger import log
@@ -81,8 +90,9 @@ class Protocol(BaseProtocol):
81
90
  log.info(self.NARRATIVE_STRING)
82
91
  return []
83
92
  """
84
- protocol.write(p)
85
- protocol.close()
93
+
94
+ with open(f"./{plugin_name}/protocols/my_protocol.py", "w") as protocol:
95
+ protocol.write(protocol_code)
86
96
 
87
97
  yield
88
98
 
@@ -91,12 +101,12 @@ class Protocol(BaseProtocol):
91
101
 
92
102
 
93
103
  def list_empty_plugins(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
94
- """Step 1 - list all plugins"""
104
+ """Step 1 - list all plugins."""
95
105
  return ("list", 0, [], [f"{plugin_name}"])
96
106
 
97
107
 
98
108
  def install_new_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
99
- """Step 2 - install a new plugin"""
109
+ """Step 2 - install a new plugin."""
100
110
  return (
101
111
  f"install {plugin_name}",
102
112
  0,
@@ -111,12 +121,12 @@ def install_new_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]
111
121
 
112
122
 
113
123
  def list_newly_installed_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
114
- """Step 3 - list all plugins, including newly installed one"""
124
+ """Step 3 - list all plugins, including newly installed one."""
115
125
  return ("list", 0, [f"{plugin_name}@0.0.1 enabled"], [])
116
126
 
117
127
 
118
128
  def disable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
119
- """Step 4 - disable plugin"""
129
+ """Step 4 - disable plugin."""
120
130
  return (
121
131
  f"disable {plugin_name}",
122
132
  0,
@@ -126,12 +136,12 @@ def disable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
126
136
 
127
137
 
128
138
  def list_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
129
- """Step 5 - list disabled plugin"""
139
+ """Step 5 - list disabled plugin."""
130
140
  return ("list", 0, [f"{plugin_name}@0.0.1 disabled"], [])
131
141
 
132
142
 
133
143
  def enable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
134
- """Step 6 - enable the disabled plugin"""
144
+ """Step 6 - enable the disabled plugin."""
135
145
  return (
136
146
  f"enable {plugin_name}",
137
147
  0,
@@ -141,7 +151,7 @@ def enable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
141
151
 
142
152
 
143
153
  def uninstall_enabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
144
- """Step 7 - try to uninstall the enabled plugin"""
154
+ """Step 7 - try to uninstall the enabled plugin."""
145
155
  return (
146
156
  f"uninstall {plugin_name}",
147
157
  1,
@@ -154,7 +164,7 @@ def uninstall_enabled_plugin(plugin_name: str) -> tuple[str, int, list[str], lis
154
164
 
155
165
 
156
166
  def uninstall_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
157
- """Step 8 - disable and then uninstall the plugin"""
167
+ """Step 8 - disable and then uninstall the plugin."""
158
168
  runner.invoke(app, f"disable {plugin_name}")
159
169
 
160
170
  return (
@@ -192,7 +202,7 @@ def test_canvas_list_install_disable_enable_uninstall(
192
202
  create_or_update_config_auth_file_for_testing: None,
193
203
  step: Callable,
194
204
  ) -> None:
195
- """Tests that the Canvas CLI can list, install, disable, enable, and uninstall a plugin"""
205
+ """Tests that the Canvas CLI can list, install, disable, enable, and uninstall a plugin."""
196
206
  mock_get_password.return_value = None
197
207
  mock_set_password.return_value = None
198
208
 
@@ -1,7 +1,8 @@
1
1
  import functools
2
2
  import json
3
+ from collections.abc import Callable
3
4
  from pathlib import Path
4
- from typing import Any, Callable, TypeVar, cast
5
+ from typing import Any, TypeVar, cast
5
6
 
6
7
  import typer
7
8
 
@@ -12,6 +13,7 @@ F = TypeVar("F", bound=Callable)
12
13
 
13
14
  class CLIContext:
14
15
  """Class that handles configuration across the CLI.
16
+
15
17
  Includes methods for:
16
18
  * Loading a JSON file with configuration keys into memory.
17
19
  * Making a property transient (default, value is not persisted) or persistent (via decorators)
@@ -118,7 +120,7 @@ class CLIContext:
118
120
  success=False,
119
121
  path=str(file),
120
122
  )
121
- raise typer.Abort()
123
+ raise typer.Abort() from None
122
124
 
123
125
  self._config_file_path = file
124
126
 
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import os
3
+ from collections.abc import Generator
3
4
  from pathlib import Path
4
- from typing import Generator
5
5
 
6
6
  import pytest
7
7
  import typer
@@ -18,7 +18,7 @@ def test_print_json_outputs_valid_json(message: Any, capfd: pytest.CaptureFixtur
18
18
  try:
19
19
  json.loads(output)
20
20
  except ValueError as exc:
21
- assert False, f"{output} is not valid json: {exc}"
21
+ assert AssertionError(f"{output} is not valid json: {exc}")
22
22
 
23
23
 
24
24
  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:
33
33
  assert json_dict.get("a_dict") == {"one": 2}
34
34
 
35
35
  except ValueError as exc:
36
- assert False, f"{output} is not valid json: {exc}"
36
+ assert AssertionError(f"{output} is not valid json: {exc}")
37
37
 
38
38
 
39
39
  def test_print_overrides_default(capfd: pytest.CaptureFixture[str]) -> None:
@@ -5,6 +5,7 @@ from canvas_cli.utils.validators import validate_manifest_file
5
5
 
6
6
  @pytest.fixture
7
7
  def protocol_manifest_example() -> dict:
8
+ """Return a valid protocol manifest example."""
8
9
  return {
9
10
  "sdk_version": "0.3.1",
10
11
  "plugin_version": "1.0.1",
@@ -32,5 +33,5 @@ def protocol_manifest_example() -> dict:
32
33
 
33
34
 
34
35
  def test_manifest_file_schema(protocol_manifest_example: dict) -> None:
35
- """Test that no exception raised when a valid manifest file is validated"""
36
+ """Test that no exception raised when a valid manifest file is validated."""
36
37
  validate_manifest_file(protocol_manifest_example)
canvas_sdk/base.py CHANGED
@@ -24,7 +24,7 @@ class Model(BaseModel):
24
24
  )
25
25
 
26
26
  def _get_effect_method_required_fields(self, method: Any) -> tuple:
27
- return getattr(self.Meta, f"{method}_required_fields", tuple())
27
+ return getattr(self.Meta, f"{method}_required_fields", ())
28
28
 
29
29
  def _create_error_detail(self, type: str, message: str, value: Any) -> InitErrorDetails:
30
30
  return InitErrorDetails({"type": PydanticCustomError(type, message), "input": value})
@@ -2,11 +2,11 @@ import json
2
2
  import re
3
3
  from enum import EnumType
4
4
  from types import NoneType, UnionType
5
- from typing import Any, Literal, Tuple, Union, cast, get_args, get_origin
5
+ from typing import Union, get_args, get_origin
6
6
 
7
7
  from canvas_sdk.base import Model
8
8
  from canvas_sdk.commands.constants import Coding
9
- from canvas_sdk.effects import Effect, EffectType
9
+ from canvas_sdk.effects import Effect
10
10
  from canvas_sdk.effects.protocol_card import Recommendation
11
11
 
12
12
 
@@ -26,9 +26,7 @@ class _BaseCommand(Model):
26
26
  command_uuid: str | None = None
27
27
 
28
28
  def _get_effect_method_required_fields(self, method: str) -> tuple:
29
- base_required_fields: tuple = getattr(
30
- _BaseCommand.Meta, f"{method}_required_fields", tuple()
31
- )
29
+ base_required_fields: tuple = getattr(_BaseCommand.Meta, f"{method}_required_fields", ())
32
30
  command_required_fields = super()._get_effect_method_required_fields(method)
33
31
  return tuple(set(base_required_fields) | set(command_required_fields))
34
32
 
@@ -72,7 +70,7 @@ class _BaseCommand(Model):
72
70
  """The schema of the command."""
73
71
  base_properties = {"note_uuid", "command_uuid"}
74
72
  schema = cls.model_json_schema()
75
- required_fields: tuple = getattr(cls.Meta, "commit_required_fields", tuple())
73
+ required_fields: tuple = getattr(cls.Meta, "commit_required_fields", ())
76
74
  return {
77
75
  definition.get("commands_api_name", name): {
78
76
  "required": name in required_fields,
@@ -1,7 +1,5 @@
1
- from dataclasses import dataclass
2
1
  from decimal import Decimal
3
2
  from enum import Enum
4
- from typing import TypeVar
5
3
 
6
4
  from pydantic import Field, conlist
7
5
 
@@ -29,7 +27,9 @@ class PrescribeCommand(_BaseCommand):
29
27
  NOT_ALLOWED = "not_allowed"
30
28
 
31
29
  fdb_code: str | None = Field(default=None, json_schema_extra={"commands_api_name": "prescribe"})
32
- icd10_codes: conlist(str, max_length=2) = Field([], json_schema_extra={"commands_api_name": "indications"}) # type: ignore[valid-type]
30
+ icd10_codes: conlist(str, max_length=2) = Field( # type: ignore[valid-type]
31
+ [], json_schema_extra={"commands_api_name": "indications"}
32
+ )
33
33
  sig: str = ""
34
34
  days_supply: int | None = None
35
35
  quantity_to_dispense: Decimal | float | int | None = None
@@ -24,7 +24,7 @@ class ReasonForVisitCommand(_BaseCommand):
24
24
  if self.structured and not self.coding:
25
25
  errors.append(
26
26
  self._create_error_detail(
27
- "value", f"Structured RFV should have a coding.", self.coding
27
+ "value", "Structured RFV should have a coding.", self.coding
28
28
  )
29
29
  )
30
30
  return errors
@@ -1,7 +1,8 @@
1
1
  from datetime import date
2
- from enum import Enum, StrEnum
2
+ from enum import StrEnum
3
+ from typing import NotRequired
3
4
 
4
- from typing_extensions import NotRequired, TypedDict
5
+ from typing_extensions import TypedDict
5
6
 
6
7
  from canvas_sdk.commands.base import _BaseCommand as BaseCommand
7
8
 
@@ -1,5 +1,4 @@
1
1
  from enum import Enum
2
- from typing import Optional
3
2
 
4
3
  from pydantic import conint, constr
5
4
 
@@ -1,4 +1,6 @@
1
- from typing_extensions import NotRequired, TypedDict
1
+ from typing import NotRequired
2
+
3
+ from typing_extensions import TypedDict
2
4
 
3
5
 
4
6
  class Coding(TypedDict):
@@ -1,5 +1,5 @@
1
+ from collections.abc import Generator
1
2
  from datetime import datetime
2
- from typing import Any, Generator
3
3
 
4
4
  import pytest
5
5
 
@@ -18,16 +18,19 @@ from canvas_sdk.commands.tests.test_utils import (
18
18
 
19
19
  @pytest.fixture(scope="session")
20
20
  def token() -> MaskedValue:
21
+ """Get a valid token."""
21
22
  return get_token()
22
23
 
23
24
 
24
25
  @pytest.fixture(scope="session")
25
26
  def new_note(token: MaskedValue) -> dict:
27
+ """Create a new note."""
26
28
  return create_new_note(token)
27
29
 
28
30
 
29
31
  @pytest.fixture(scope="session")
30
32
  def plugin_name() -> str:
33
+ """The plugin name to be used."""
31
34
  return f"commands{datetime.now().timestamp()}".replace(".", "")
32
35
 
33
36
 
@@ -35,6 +38,7 @@ def plugin_name() -> str:
35
38
  def write_and_install_protocol_and_clean_up(
36
39
  plugin_name: str, token: MaskedValue, new_note: dict
37
40
  ) -> Generator[None, None, None]:
41
+ """Write the protocol code, install the plugin, and clean up after the test."""
38
42
  write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS)
39
43
  install_plugin(plugin_name, token)
40
44
 
@@ -47,6 +51,7 @@ def write_and_install_protocol_and_clean_up(
47
51
  def test_protocol_that_inserts_every_command(
48
52
  write_and_install_protocol_and_clean_up: None, token: MaskedValue, new_note: dict
49
53
  ) -> None:
54
+ """Test that the protocol inserts every command."""
50
55
  trigger_plugin_event(token)
51
56
 
52
57
  commands_in_body = get_original_note_body_commands(new_note["id"], token)
@@ -18,16 +18,19 @@ from canvas_sdk.commands.tests.test_utils import (
18
18
 
19
19
  @pytest.fixture(scope="session")
20
20
  def token() -> MaskedValue:
21
+ """Get a valid token."""
21
22
  return get_token()
22
23
 
23
24
 
24
25
  @pytest.fixture(scope="session")
25
26
  def new_note(token: MaskedValue) -> dict:
27
+ """Create a new note."""
26
28
  return create_new_note(token)
27
29
 
28
30
 
29
31
  @pytest.fixture
30
32
  def command_type_map() -> dict[str, type]:
33
+ """Map of command field types to their corresponding Python types."""
31
34
  return {
32
35
  "AutocompleteField": str,
33
36
  "MultiLineTextField": str,
@@ -51,6 +54,7 @@ def test_command_schema_matches_command_api(
51
54
  new_note: dict,
52
55
  Command: _BaseCommand,
53
56
  ) -> None:
57
+ """Test that the command schema matches the command API."""
54
58
  # first create the command in the new note
55
59
  data = {"noteKey": new_note["externallyExposableId"], "schemaKey": Command.Meta.key}
56
60
  headers = {"Authorization": f"Bearer {token.value}"}
@@ -90,7 +94,7 @@ def test_command_schema_matches_command_api(
90
94
  # this condition initially created for Prescribe.indications,
91
95
  # but could apply to other AutocompleteField fields that are lists
92
96
  # making the assumption here that if the field ends in 's' (like indications), it is a list
93
- assert get_origin(expected_type) == list
97
+ assert get_origin(expected_type) is list
94
98
 
95
99
  else:
96
100
  assert expected_type == actual_type
@@ -35,6 +35,8 @@ runner = CliRunner()
35
35
 
36
36
 
37
37
  class MaskedValue:
38
+ """A class to mask sensitive values in tests."""
39
+
38
40
  def __init__(self, value: str) -> None:
39
41
  self.value = value
40
42
 
@@ -46,6 +48,7 @@ class MaskedValue:
46
48
 
47
49
 
48
50
  def get_field_type_unformatted(field_props: dict[str, Any]) -> str:
51
+ """Get the unformatted field type from the field properties."""
49
52
  if t := field_props.get("type"):
50
53
  return field_props.get("format") or t
51
54
 
@@ -56,14 +59,17 @@ def get_field_type_unformatted(field_props: dict[str, Any]) -> str:
56
59
 
57
60
 
58
61
  def get_field_type(field_props: dict) -> str:
62
+ """Get the field type from the field properties."""
59
63
  return get_field_type_unformatted(field_props).replace("-", "").replace("array", "list")
60
64
 
61
65
 
62
66
  def random_string() -> str:
67
+ """Generate a random string."""
63
68
  return "".join(random.choices(string.ascii_uppercase + string.digits, k=7))
64
69
 
65
70
 
66
71
  def fake(field_props: dict, Command: type[_BaseCommand]) -> Any:
72
+ """Generate a fake value for a field."""
67
73
  t = get_field_type(field_props)
68
74
  match t:
69
75
  case "string":
@@ -89,13 +95,14 @@ def fake(field_props: dict, Command: type[_BaseCommand]) -> Any:
89
95
  case "ClinicalQuantity":
90
96
  return ClinicalQuantity(representative_ndc="ndc", ncpdp_quantity_qualifier_code="code")
91
97
  if t[0].isupper():
92
- return random.choice([e for e in getattr(Command, t)])
98
+ return random.choice(list(getattr(Command, t)))
93
99
 
94
100
 
95
101
  def raises_wrong_type_error(
96
102
  Command: type[_BaseCommand],
97
103
  field: str,
98
104
  ) -> None:
105
+ """Test that the correct error is raised when the wrong type is passed to a field."""
99
106
  field_props = Command.model_json_schema()["properties"][field]
100
107
  field_type = get_field_type(field_props)
101
108
  wrong_field_type = "integer" if field_type == "string" else "string"
@@ -119,8 +126,8 @@ def raises_wrong_type_error(
119
126
  "dictionary" if field_type == "Coding" or field_type == "ClinicalQuantity" else field_type
120
127
  )
121
128
  if field_type == "number":
122
- assert f"Input should be an instance of Decimal" in err_msg1
123
- assert f"Input should be an instance of Decimal" in err_msg2
129
+ assert "Input should be an instance of Decimal" in err_msg1
130
+ assert "Input should be an instance of Decimal" in err_msg2
124
131
  elif field_type[0].isupper():
125
132
  assert f"Input should be an instance of {Command.__name__}.{field_type}" in err_msg1
126
133
  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(
133
140
  Command: type[_BaseCommand],
134
141
  method: str,
135
142
  ) -> None:
143
+ """Test that the correct error is raised when a required field is None for an effect method."""
136
144
  cmd_name = Command.__name__
137
145
  cmd_name_article = "an" if cmd_name.startswith(("A", "E", "I", "O", "U")) else "a"
138
146
 
@@ -155,6 +163,7 @@ def raises_none_error_for_effect_method(
155
163
  def write_protocol_code(
156
164
  note_uuid: str, plugin_name: str, commands: list[type[_BaseCommand]]
157
165
  ) -> None:
166
+ """Test that the protocol code is written correctly."""
158
167
  imports = ", ".join([c.__name__ for c in commands])
159
168
  effects = ", ".join([f"{c.__name__}(note_uuid='{note_uuid}').originate()" for c in commands])
160
169
 
@@ -171,21 +180,23 @@ class Protocol(BaseProtocol):
171
180
  with chdir(Path("./custom-plugins")):
172
181
  runner.invoke(app, "init", input=plugin_name)
173
182
 
174
- protocol = open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w")
175
- protocol.write(protocol_code)
176
- protocol.close()
183
+ with open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") as protocol:
184
+ protocol.write(protocol_code)
177
185
 
178
186
 
179
187
  def install_plugin(plugin_name: str, token: MaskedValue) -> None:
180
- requests.post(
181
- plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
182
- data={"is_enabled": True},
183
- files={"package": open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb")},
184
- headers={"Authorization": f"Bearer {token.value}"},
185
- )
188
+ """Install a plugin."""
189
+ with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
190
+ requests.post(
191
+ plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
192
+ data={"is_enabled": True},
193
+ files={"package": package},
194
+ headers={"Authorization": f"Bearer {token.value}"},
195
+ )
186
196
 
187
197
 
188
198
  def trigger_plugin_event(token: MaskedValue) -> None:
199
+ """Trigger a plugin event."""
189
200
  requests.post(
190
201
  f"{settings.INTEGRATION_TEST_URL}/api/Note/",
191
202
  headers={
@@ -204,6 +215,7 @@ def trigger_plugin_event(token: MaskedValue) -> None:
204
215
 
205
216
 
206
217
  def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> list[str]:
218
+ """Get the commands from the original note body."""
207
219
  original_note = requests.get(
208
220
  f"{settings.INTEGRATION_TEST_URL}/api/Note/{new_note_id}",
209
221
  headers={
@@ -225,6 +237,7 @@ def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> lis
225
237
 
226
238
 
227
239
  def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
240
+ """Clean up the files and plugins."""
228
241
  # clean up
229
242
  if Path(f"./custom-plugins/{plugin_name}").exists():
230
243
  shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
@@ -261,6 +274,7 @@ COMMANDS: list[type[_BaseCommand]] = [
261
274
 
262
275
 
263
276
  def create_new_note(token: MaskedValue) -> dict:
277
+ """Create a new note."""
264
278
  headers = {
265
279
  "Authorization": f"Bearer {token.value}",
266
280
  "Content-Type": "application/json",
@@ -279,6 +293,7 @@ def create_new_note(token: MaskedValue) -> dict:
279
293
 
280
294
 
281
295
  def get_token() -> MaskedValue:
296
+ """Get a valid token."""
282
297
  return MaskedValue(
283
298
  requests.post(
284
299
  f"{settings.INTEGRATION_TEST_URL}/auth/token/",
@@ -82,6 +82,7 @@ def test_command_raises_generic_error_when_kwarg_given_incorrect_type(
82
82
  Command: type[_BaseCommand],
83
83
  fields_to_test: tuple[str],
84
84
  ) -> None:
85
+ """Test that Command raises a generic error when a kwarg is given an incorrect type."""
85
86
  for field in fields_to_test:
86
87
  raises_wrong_type_error(Command, field)
87
88
 
@@ -184,6 +185,7 @@ def test_command_raises_specific_error_when_kwarg_given_incorrect_type(
184
185
  err_msg: str,
185
186
  valid_kwargs: dict,
186
187
  ) -> None:
188
+ """Test that Command raises a specific error when a kwarg is given an incorrect type."""
187
189
  with pytest.raises(ValidationError) as e1:
188
190
  cmd = Command(**err_kwargs)
189
191
  cmd.originate()
@@ -258,6 +260,7 @@ def test_command_allows_kwarg_with_correct_type(
258
260
  Command: type[_BaseCommand],
259
261
  fields_to_test: tuple[str],
260
262
  ) -> None:
263
+ """Test that Command allows a kwarg with the correct type."""
261
264
  schema = Command.model_json_schema()
262
265
 
263
266
  for field in fields_to_test:
@@ -1 +1,3 @@
1
1
  from canvas_generated.messages.effects_pb2 import Effect, EffectType
2
+
3
+ __all__ = ("Effect", "EffectType")
@@ -1,2 +1,4 @@
1
1
  from canvas_sdk.effects.banner_alert.add_banner_alert import AddBannerAlert
2
2
  from canvas_sdk.effects.banner_alert.remove_banner_alert import RemoveBannerAlert
3
+
4
+ __all__ = ("AddBannerAlert", "RemoveBannerAlert")