canvas 0.14.0__py3-none-any.whl → 0.15.0__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 (68) hide show
  1. {canvas-0.14.0.dist-info → canvas-0.15.0.dist-info}/METADATA +1 -1
  2. {canvas-0.14.0.dist-info → canvas-0.15.0.dist-info}/RECORD +67 -46
  3. canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -1
  4. canvas_generated/messages/effects_pb2.py +2 -2
  5. canvas_generated/messages/effects_pb2.pyi +4 -0
  6. canvas_generated/messages/events_pb2.py +2 -2
  7. canvas_generated/messages/events_pb2.pyi +8 -0
  8. canvas_sdk/commands/tests/protocol/tests.py +3 -1
  9. canvas_sdk/commands/tests/test_utils.py +76 -18
  10. canvas_sdk/effects/banner_alert/tests.py +41 -20
  11. canvas_sdk/events/base.py +1 -3
  12. canvas_sdk/handlers/action_button.py +5 -2
  13. canvas_sdk/handlers/application.py +1 -1
  14. canvas_sdk/handlers/cron_task.py +1 -1
  15. canvas_sdk/protocols/clinical_quality_measure.py +1 -1
  16. canvas_sdk/v1/apps.py +7 -0
  17. canvas_sdk/v1/data/__init__.py +75 -4
  18. canvas_sdk/v1/data/allergy_intolerance.py +3 -7
  19. canvas_sdk/v1/data/billing.py +2 -5
  20. canvas_sdk/v1/data/command.py +7 -9
  21. canvas_sdk/v1/data/condition.py +3 -7
  22. canvas_sdk/v1/data/detected_issue.py +4 -9
  23. canvas_sdk/v1/data/device.py +4 -8
  24. canvas_sdk/v1/data/imaging.py +12 -17
  25. canvas_sdk/v1/data/lab.py +16 -29
  26. canvas_sdk/v1/data/medication.py +3 -7
  27. canvas_sdk/v1/data/note.py +7 -14
  28. canvas_sdk/v1/data/observation.py +4 -11
  29. canvas_sdk/v1/data/organization.py +1 -2
  30. canvas_sdk/v1/data/patient.py +0 -1
  31. canvas_sdk/v1/data/practicelocation.py +2 -4
  32. canvas_sdk/v1/data/protocol_override.py +3 -6
  33. canvas_sdk/v1/data/questionnaire.py +5 -15
  34. canvas_sdk/v1/data/staff.py +5 -7
  35. canvas_sdk/v1/data/task.py +5 -11
  36. canvas_sdk/v1/data/user.py +0 -1
  37. canvas_sdk/v1/models.py +4 -0
  38. plugin_runner/plugin_installer.py +1 -1
  39. plugin_runner/plugin_runner.py +5 -25
  40. plugin_runner/sandbox.py +105 -9
  41. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +38 -0
  42. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +11 -0
  43. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
  44. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +33 -0
  45. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +3 -0
  46. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +6 -0
  47. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +5 -0
  48. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +4 -0
  49. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +29 -0
  50. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +12 -0
  51. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
  52. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +10 -0
  53. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
  54. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +18 -0
  55. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +29 -0
  56. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +12 -0
  57. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
  58. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +10 -0
  59. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
  60. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +18 -0
  61. plugin_runner/tests/test_application.py +9 -9
  62. plugin_runner/tests/test_plugin_installer.py +12 -1
  63. plugin_runner/tests/test_plugin_runner.py +171 -32
  64. plugin_runner/tests/test_sandbox.py +5 -13
  65. settings.py +3 -1
  66. canvas_sdk/models/__init__.py +0 -8
  67. {canvas-0.14.0.dist-info → canvas-0.15.0.dist-info}/WHEEL +0 -0
  68. {canvas-0.14.0.dist-info → canvas-0.15.0.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@ from canvas_sdk.events import Event, EventRequest, EventType
6
6
  from canvas_sdk.handlers.application import Application
7
7
 
8
8
 
9
- class TestApplication(Application):
9
+ class ExampleApplication(Application):
10
10
  """A concrete implementation of the Application class for testing."""
11
11
 
12
12
  def on_open(self) -> Effect:
@@ -15,9 +15,9 @@ class TestApplication(Application):
15
15
 
16
16
 
17
17
  @pytest.fixture
18
- def app_instance(event: Event) -> TestApplication:
18
+ def app_instance(event: Event) -> ExampleApplication:
19
19
  """Provide an instance of the TestApplication with a mocked event."""
20
- app = TestApplication(event)
20
+ app = ExampleApplication(event)
21
21
  return app
22
22
 
23
23
 
@@ -25,7 +25,7 @@ def test_compute_event_not_targeted() -> None:
25
25
  """Test that compute filters out events not targeted for the app."""
26
26
  request = EventRequest(type=EventType.APPLICATION__ON_OPEN, target="some_identifier")
27
27
  event = Event(request)
28
- app = TestApplication(event)
28
+ app = ExampleApplication(event)
29
29
 
30
30
  result = app.compute()
31
31
 
@@ -36,10 +36,10 @@ def test_compute_event_targeted() -> None:
36
36
  """Test that compute processes events targeted for the app."""
37
37
  request = EventRequest(
38
38
  type=EventType.APPLICATION__ON_OPEN,
39
- target=f"{TestApplication.__module__}:{TestApplication.__qualname__}",
39
+ target=f"{ExampleApplication.__module__}:{ExampleApplication.__qualname__}",
40
40
  )
41
41
  event = Event(request)
42
- app = TestApplication(event)
42
+ app = ExampleApplication(event)
43
43
  result = app.compute()
44
44
 
45
45
  assert len(result) == 1, "Expected a single effect if the event target is the app identifier"
@@ -48,13 +48,13 @@ def test_compute_event_targeted() -> None:
48
48
 
49
49
  def test_identifier_property() -> None:
50
50
  """Test the identifier property of the Application class."""
51
- expected_identifier = f"{TestApplication.__module__}:{TestApplication.__qualname__}"
51
+ expected_identifier = f"{ExampleApplication.__module__}:{ExampleApplication.__qualname__}"
52
52
  request = EventRequest(
53
53
  type=EventType.APPLICATION__ON_OPEN,
54
- target=f"{TestApplication.__module__}:{TestApplication.__qualname__}",
54
+ target=f"{ExampleApplication.__module__}:{ExampleApplication.__qualname__}",
55
55
  )
56
56
  event = Event(request)
57
- app = TestApplication(event)
57
+ app = ExampleApplication(event)
58
58
 
59
59
  assert app.identifier == expected_identifier, "The identifier property is incorrect"
60
60
 
@@ -83,8 +83,19 @@ def test_plugin_installation_from_tarball(mocker: MockerFixture) -> None:
83
83
  tarball_2 = _create_tarball("plugin2")
84
84
 
85
85
  mocker.patch("plugin_runner.plugin_installer.enabled_plugins", return_value=mock_plugins)
86
+
87
+ def mock_download_plugin(package: str) -> MagicMock:
88
+ mock_context = mocker.Mock()
89
+ if package == "plugins/plugin1.tar.gz":
90
+ mock_context.__enter__ = mocker.Mock(return_value=tarball_1)
91
+ elif package == "plugins/plugin2.tar":
92
+ mock_context.__enter__ = mocker.Mock(return_value=tarball_2)
93
+ mock_context.__exit__ = mocker.Mock(return_value=None)
94
+ return mock_context
95
+
86
96
  mocker.patch(
87
- "plugin_runner.plugin_installer.download_plugin", side_effect=[tarball_1, tarball_2]
97
+ "plugin_runner.plugin_installer.download_plugin",
98
+ side_effect=mock_download_plugin,
88
99
  )
89
100
 
90
101
  install_plugins()
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import shutil
2
3
  from collections.abc import Generator
3
4
  from pathlib import Path
@@ -7,18 +8,18 @@ import pytest
7
8
 
8
9
  from canvas_generated.messages.effects_pb2 import EffectType
9
10
  from canvas_generated.messages.plugins_pb2 import ReloadPluginsRequest
10
- from canvas_sdk.events import EventRequest, EventType
11
+ from canvas_sdk.events import Event, EventRequest, EventType
11
12
  from plugin_runner.plugin_runner import (
12
13
  EVENT_HANDLER_MAP,
13
14
  LOADED_PLUGINS,
14
15
  PluginRunner,
16
+ load_or_reload_plugin,
15
17
  load_plugins,
16
- sandbox_from_package,
17
18
  )
18
19
 
19
20
 
20
21
  @pytest.fixture
21
- def setup_test_plugin(request: pytest.FixtureRequest) -> Generator[Path, None, None]:
22
+ def install_test_plugin(request: pytest.FixtureRequest) -> Generator[Path, None, None]:
22
23
  """Copies a specified plugin from the fixtures directory to the data directory
23
24
  and removes it after the test.
24
25
 
@@ -51,6 +52,17 @@ def setup_test_plugin(request: pytest.FixtureRequest) -> Generator[Path, None, N
51
52
  shutil.rmtree(dest_plugin_path)
52
53
 
53
54
 
55
+ @pytest.fixture
56
+ def load_test_plugins() -> Generator[None, None, None]:
57
+ """Manages the lifecycle of test plugins by loading and unloading them."""
58
+ try:
59
+ load_plugins()
60
+ yield
61
+ finally:
62
+ LOADED_PLUGINS.clear()
63
+ EVENT_HANDLER_MAP.clear()
64
+
65
+
54
66
  @pytest.fixture
55
67
  def plugin_runner() -> PluginRunner:
56
68
  """Fixture to initialize PluginRunner with mocks."""
@@ -59,11 +71,9 @@ def plugin_runner() -> PluginRunner:
59
71
  return runner
60
72
 
61
73
 
62
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
63
- def test_load_plugins_with_valid_plugin(setup_test_plugin: Path) -> None:
74
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
75
+ def test_load_plugins_with_valid_plugin(install_test_plugin: Path, load_test_plugins: None) -> None:
64
76
  """Test loading plugins with a valid plugin."""
65
- load_plugins()
66
-
67
77
  assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" in LOADED_PLUGINS
68
78
  assert (
69
79
  LOADED_PLUGINS["example_plugin:example_plugin.protocols.my_protocol:Protocol"]["active"]
@@ -72,12 +82,11 @@ def test_load_plugins_with_valid_plugin(setup_test_plugin: Path) -> None:
72
82
 
73
83
 
74
84
  @pytest.mark.asyncio
75
- @pytest.mark.parametrize("setup_test_plugin", ["test_module_imports_plugin"], indirect=True)
85
+ @pytest.mark.parametrize("install_test_plugin", ["test_module_imports_plugin"], indirect=True)
76
86
  async def test_load_plugins_with_plugin_that_imports_other_modules_within_plugin_package(
77
- setup_test_plugin: Path, plugin_runner: PluginRunner
87
+ install_test_plugin: Path, plugin_runner: PluginRunner, load_test_plugins: None
78
88
  ) -> None:
79
89
  """Test loading plugins with a valid plugin that imports other modules within the current plugin package."""
80
- load_plugins()
81
90
  assert (
82
91
  "test_module_imports_plugin:test_module_imports_plugin.protocols.my_protocol:Protocol"
83
92
  in LOADED_PLUGINS
@@ -102,7 +111,7 @@ async def test_load_plugins_with_plugin_that_imports_other_modules_within_plugin
102
111
 
103
112
 
104
113
  @pytest.mark.parametrize(
105
- "setup_test_plugin",
114
+ "install_test_plugin",
106
115
  [
107
116
  "test_module_imports_outside_plugin_v1",
108
117
  "test_module_imports_outside_plugin_v2",
@@ -111,18 +120,108 @@ async def test_load_plugins_with_plugin_that_imports_other_modules_within_plugin
111
120
  indirect=True,
112
121
  )
113
122
  def test_load_plugins_with_plugin_that_imports_other_modules_outside_plugin_package(
114
- setup_test_plugin: Path,
123
+ install_test_plugin: Path, caplog: pytest.LogCaptureFixture
115
124
  ) -> None:
116
125
  """Test loading plugins with an invalid plugin that imports other modules outside the current plugin package."""
117
- with pytest.raises(ImportError, match="is not an allowed import"):
118
- sandbox_from_package(setup_test_plugin)
126
+ with caplog.at_level(logging.ERROR):
127
+ load_or_reload_plugin(install_test_plugin)
128
+
129
+ assert any(
130
+ "Error importing module" in record.message for record in caplog.records
131
+ ), "log.error() was not called with the expected message."
132
+
133
+
134
+ @pytest.mark.parametrize(
135
+ "install_test_plugin",
136
+ [
137
+ "test_module_forbidden_imports_plugin",
138
+ ],
139
+ indirect=True,
140
+ )
141
+ def test_load_plugins_with_plugin_that_imports_forbidden_modules(
142
+ install_test_plugin: Path, caplog: pytest.LogCaptureFixture
143
+ ) -> None:
144
+ """Test loading plugins with an invalid plugin that imports forbidden modules."""
145
+ with caplog.at_level(logging.ERROR):
146
+ load_or_reload_plugin(install_test_plugin)
147
+
148
+ assert any(
149
+ "Error importing module" in record.message for record in caplog.records
150
+ ), "log.error() was not called with the expected message."
151
+
152
+
153
+ @pytest.mark.parametrize(
154
+ "install_test_plugin",
155
+ [
156
+ "test_module_forbidden_imports_runtime_plugin",
157
+ ],
158
+ indirect=True,
159
+ )
160
+ def test_load_plugins_with_plugin_that_imports_forbidden_modules_at_runtime(
161
+ install_test_plugin: Path,
162
+ ) -> None:
163
+ """Test loading plugins with an invalid plugin that imports forbidden modules at runtime."""
164
+ with pytest.raises(ImportError, match="is not an allowed import."):
165
+ load_or_reload_plugin(install_test_plugin)
166
+ class_handler = LOADED_PLUGINS[
167
+ "test_module_forbidden_imports_runtime_plugin:test_module_forbidden_imports_runtime_plugin.protocols.my_protocol:Protocol"
168
+ ]["class"]
169
+ class_handler(Event(EventRequest(type=EventType.UNKNOWN))).compute()
170
+
171
+
172
+ @pytest.mark.parametrize(
173
+ "install_test_plugin",
174
+ [
175
+ "test_implicit_imports_plugin",
176
+ ],
177
+ indirect=True,
178
+ )
179
+ def test_plugin_that_implicitly_imports_allowed_modules(
180
+ install_test_plugin: Path, caplog: pytest.LogCaptureFixture
181
+ ) -> None:
182
+ """Test loading plugins with a plugin that implicitly imports allowed modules."""
183
+ with caplog.at_level(logging.INFO):
184
+ load_or_reload_plugin(install_test_plugin)
185
+ class_handler = LOADED_PLUGINS[
186
+ "test_implicit_imports_plugin:test_implicit_imports_plugin.protocols.my_protocol:Allowed"
187
+ ]["class"]
188
+ class_handler(Event(EventRequest(type=EventType.UNKNOWN))).compute()
189
+
190
+ assert any(
191
+ "Hello, World!" in record.message for record in caplog.records
192
+ ), "log.info() with Template.render() was not called."
119
193
 
120
194
 
121
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
122
- def test_reload_plugin(setup_test_plugin: Path) -> None:
195
+ @pytest.mark.parametrize(
196
+ "install_test_plugin",
197
+ [
198
+ "test_implicit_imports_plugin",
199
+ ],
200
+ indirect=True,
201
+ )
202
+ def test_plugin_that_implicitly_imports_forbidden_modules(
203
+ install_test_plugin: Path, caplog: pytest.LogCaptureFixture
204
+ ) -> None:
205
+ """Test loading plugins with an invalid plugin that implicitly imports forbidden modules."""
206
+ with (
207
+ caplog.at_level(logging.INFO),
208
+ pytest.raises(ImportError, match="'os' is not an allowed import."),
209
+ ):
210
+ load_or_reload_plugin(install_test_plugin)
211
+ class_handler = LOADED_PLUGINS[
212
+ "test_implicit_imports_plugin:test_implicit_imports_plugin.protocols.my_protocol:Forbidden"
213
+ ]["class"]
214
+ class_handler(Event(EventRequest(type=EventType.UNKNOWN))).compute()
215
+
216
+ assert (
217
+ any("os list dir" in record.message for record in caplog.records) is False
218
+ ), "log.info() with os.listdir() was called."
219
+
220
+
221
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
222
+ def test_reload_plugin(install_test_plugin: Path, load_test_plugins: None) -> None:
123
223
  """Test reloading a plugin."""
124
224
  load_plugins()
125
- load_plugins()
126
225
 
127
226
  assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" in LOADED_PLUGINS
128
227
  assert (
@@ -131,18 +230,22 @@ def test_reload_plugin(setup_test_plugin: Path) -> None:
131
230
  )
132
231
 
133
232
 
134
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
135
- def test_remove_plugin_should_be_removed_from_loaded_plugins(setup_test_plugin: Path) -> None:
233
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
234
+ def test_remove_plugin_should_be_removed_from_loaded_plugins(
235
+ install_test_plugin: Path, load_test_plugins: None
236
+ ) -> None:
136
237
  """Test removing a plugin."""
137
- load_plugins()
138
238
  assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" in LOADED_PLUGINS
139
- shutil.rmtree(setup_test_plugin)
239
+ shutil.rmtree(install_test_plugin)
140
240
  load_plugins()
141
241
  assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" not in LOADED_PLUGINS
142
242
 
143
243
 
144
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
145
- def test_load_plugins_should_refresh_event_protocol_map(setup_test_plugin: Path) -> None:
244
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
245
+ @pytest.mark.parametrize("load_test_plugins", [None], indirect=True)
246
+ def test_load_plugins_should_refresh_event_protocol_map(
247
+ load_test_plugins: None, install_test_plugin: Path
248
+ ) -> None:
146
249
  """Test that the event protocol map is refreshed when loading plugins."""
147
250
  assert EVENT_HANDLER_MAP == {}
148
251
  load_plugins()
@@ -153,13 +256,11 @@ def test_load_plugins_should_refresh_event_protocol_map(setup_test_plugin: Path)
153
256
 
154
257
 
155
258
  @pytest.mark.asyncio
156
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
259
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
157
260
  async def test_handle_plugin_event_returns_expected_result(
158
- setup_test_plugin: Path, plugin_runner: PluginRunner
261
+ install_test_plugin: Path, plugin_runner: PluginRunner, load_test_plugins: None
159
262
  ) -> None:
160
263
  """Test that HandleEvent successfully calls the relevant plugins and returns the expected result."""
161
- load_plugins()
162
-
163
264
  event = EventRequest(type=EventType.UNKNOWN)
164
265
 
165
266
  result = []
@@ -174,11 +275,11 @@ async def test_handle_plugin_event_returns_expected_result(
174
275
 
175
276
 
176
277
  @pytest.mark.asyncio
177
- @pytest.mark.parametrize("setup_test_plugin", ["example_plugin"], indirect=True)
178
- async def test_reload_plugins_event_handler_successfully_loads_plugins(
179
- setup_test_plugin: Path, plugin_runner: PluginRunner
278
+ @pytest.mark.parametrize("install_test_plugin", ["example_plugin"], indirect=True)
279
+ async def test_reload_plugins_event_handler_successfully_publishes_message(
280
+ install_test_plugin: Path, plugin_runner: PluginRunner
180
281
  ) -> None:
181
- """Test ReloadPlugins Event handler successfully loads plugins."""
282
+ """Test ReloadPlugins Event handler successfully publishes a message with restart action."""
182
283
  with patch("plugin_runner.plugin_runner.publish_message", MagicMock()) as mock_publish_message:
183
284
  request = ReloadPluginsRequest()
184
285
 
@@ -190,4 +291,42 @@ async def test_reload_plugins_event_handler_successfully_loads_plugins(
190
291
 
191
292
  assert len(result) == 1
192
293
  assert result[0].success is True
193
- assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" in LOADED_PLUGINS
294
+
295
+
296
+ @pytest.mark.asyncio
297
+ @pytest.mark.parametrize("install_test_plugin", ["test_module_imports_plugin"], indirect=True)
298
+ async def test_changes_to_plugin_modules_should_be_reflected_after_reload(
299
+ install_test_plugin: Path, load_test_plugins: None, plugin_runner: PluginRunner
300
+ ) -> None:
301
+ """Test that changes to plugin modules are reflected after reloading the plugin."""
302
+ event = EventRequest(type=EventType.UNKNOWN)
303
+
304
+ result = []
305
+ async for response in plugin_runner.HandleEvent(event, None):
306
+ result.append(response)
307
+
308
+ assert len(result) == 1
309
+ assert result[0].success is True
310
+ assert len(result[0].effects) == 1
311
+ assert result[0].effects[0].type == EffectType.LOG
312
+ assert result[0].effects[0].payload == "Successfully imported!"
313
+
314
+ NEW_CODE = """
315
+ def import_me() -> str:
316
+ return "Successfully changed!"
317
+ """
318
+ file_path = install_test_plugin / "other_module" / "base.py"
319
+ file_path.write_text(NEW_CODE, encoding="utf-8")
320
+
321
+ # Reload the plugin
322
+ load_plugins()
323
+
324
+ result = []
325
+ async for response in plugin_runner.HandleEvent(event, None):
326
+ result.append(response)
327
+
328
+ assert len(result) == 1
329
+ assert result[0].success is True
330
+ assert len(result[0].effects) == 1
331
+ assert result[0].effects[0].type == EffectType.LOG
332
+ assert result[0].effects[0].payload == "Successfully changed!"
@@ -28,9 +28,9 @@ CODE_WITH_FORBIDDEN_FUNC_NAME = """
28
28
  builtins = {}
29
29
  """
30
30
 
31
- SOURCE_CODE_MODULE_OS = """
32
- import os
33
- result = os.listdir('.')
31
+ SOURCE_CODE_MODULE = """
32
+ import module.b
33
+ result = module.b
34
34
  """
35
35
 
36
36
 
@@ -110,16 +110,8 @@ print("Hello, Sandbox!")
110
110
  assert "Hello, Sandbox!" in scope["_print"].txt, "Print output should be captured."
111
111
 
112
112
 
113
- def test_sandbox_module_name_imports_within_package() -> None:
114
- """Test that modules within the same package can be imported."""
115
- sandbox_module_a = Sandbox(source_code=SOURCE_CODE_MODULE_OS, namespace="os.a")
116
- result = sandbox_module_a.execute()
117
-
118
- assert "os" in result
119
-
120
-
121
113
  def test_sandbox_denies_module_name_import_outside_package() -> None:
122
114
  """Test that modules outside the root package cannot be imported."""
123
- sandbox_module_a = Sandbox(source_code=SOURCE_CODE_MODULE_OS, namespace="module.a")
124
- with pytest.raises(ImportError, match="os' is not an allowed import."):
115
+ sandbox_module_a = Sandbox(source_code=SOURCE_CODE_MODULE, namespace="other_module.a")
116
+ with pytest.raises(ImportError, match="module.b' is not an allowed import."):
125
117
  sandbox_module_a.execute()
settings.py CHANGED
@@ -19,7 +19,9 @@ INTEGRATION_TEST_CLIENT_SECRET = os.getenv("INTEGRATION_TEST_CLIENT_SECRET")
19
19
 
20
20
  GRAPHQL_ENDPOINT = os.getenv("GRAPHQL_ENDPOINT", "http://localhost:8000/plugins-graphql")
21
21
 
22
- INSTALLED_APPS = ["canvas_sdk"]
22
+ INSTALLED_APPS = [
23
+ "canvas_sdk.v1",
24
+ ]
23
25
 
24
26
  SECRET_KEY = os.getenv(
25
27
  "SECRET_KEY",
@@ -1,8 +0,0 @@
1
- # ruff: noqa
2
- # register all models in the app so they can be used with apps.get_model()
3
- from canvas_sdk.v1.data.command import Command
4
- from canvas_sdk.v1.data.condition import Condition
5
- from canvas_sdk.v1.data.note import Note
6
- from canvas_sdk.v1.data.patient import Patient
7
- from canvas_sdk.v1.data.user import CanvasUser
8
- from canvas_sdk.v1.data.questionnaire import Interview