canvas 0.4.0__py3-none-any.whl → 0.5.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 (49) hide show
  1. {canvas-0.4.0.dist-info → canvas-0.5.0.dist-info}/METADATA +3 -2
  2. {canvas-0.4.0.dist-info → canvas-0.5.0.dist-info}/RECORD +49 -12
  3. canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +2 -6
  4. canvas_generated/messages/events_pb2.py +2 -2
  5. canvas_generated/messages/events_pb2.pyi +108 -0
  6. canvas_sdk/effects/patient_chart_summary_configuration.py +1 -0
  7. canvas_sdk/v1/data/detected_issue.py +52 -0
  8. canvas_sdk/v1/data/protocol_override.py +58 -0
  9. canvas_sdk/value_set/__init__.py +0 -0
  10. canvas_sdk/value_set/v2022/__init__.py +0 -0
  11. plugin_runner/authentication.py +3 -7
  12. plugin_runner/plugin_runner.py +48 -26
  13. plugin_runner/sandbox.py +22 -8
  14. plugin_runner/tests/__init__.py +0 -0
  15. plugin_runner/tests/data/plugins/.gitkeep +0 -0
  16. plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json +29 -0
  17. plugin_runner/tests/fixtures/plugins/example_plugin/README.md +12 -0
  18. plugin_runner/tests/fixtures/plugins/example_plugin/__init__.py +0 -0
  19. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/__init__.py +0 -0
  20. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/my_protocol.py +18 -0
  21. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/CANVAS_MANIFEST.json +29 -0
  22. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/README.md +12 -0
  23. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/__init__.py +0 -0
  24. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/base.py +3 -0
  25. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/__init__.py +0 -0
  26. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/my_protocol.py +18 -0
  27. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/CANVAS_MANIFEST.json +29 -0
  28. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/README.md +12 -0
  29. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/__init__.py +0 -0
  30. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/base.py +6 -0
  31. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/__init__.py +0 -0
  32. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/my_protocol.py +18 -0
  33. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/CANVAS_MANIFEST.json +29 -0
  34. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/README.md +12 -0
  35. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/__init__.py +0 -0
  36. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/base.py +8 -0
  37. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/__init__.py +0 -0
  38. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/my_protocol.py +18 -0
  39. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/CANVAS_MANIFEST.json +29 -0
  40. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/README.md +12 -0
  41. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/__init__.py +0 -0
  42. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/base.py +3 -0
  43. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py +0 -0
  44. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py +18 -0
  45. plugin_runner/tests/test_plugin_runner.py +208 -0
  46. plugin_runner/tests/test_sandbox.py +113 -0
  47. settings.py +23 -0
  48. {canvas-0.4.0.dist-info → canvas-0.5.0.dist-info}/WHEEL +0 -0
  49. {canvas-0.4.0.dist-info → canvas-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -1,15 +1,15 @@
1
1
  import asyncio
2
- import importlib.util
3
2
  import json
4
3
  import os
5
4
  import pathlib
5
+ import pkgutil
6
6
  import signal
7
7
  import sys
8
8
  import time
9
9
  import traceback
10
10
  from collections import defaultdict
11
11
  from types import FrameType
12
- from typing import Any, AsyncGenerator, Optional, TypedDict, cast
12
+ from typing import Any, AsyncGenerator, Optional, TypedDict
13
13
 
14
14
  import grpc
15
15
  import statsd
@@ -30,17 +30,7 @@ from logger import log
30
30
  from plugin_runner.authentication import token_for_plugin
31
31
  from plugin_runner.plugin_synchronizer import publish_message
32
32
  from plugin_runner.sandbox import Sandbox
33
-
34
- ENV = os.getenv("ENV", "development")
35
-
36
- IS_PRODUCTION = ENV == "production"
37
-
38
- MANIFEST_FILE_NAME = "CANVAS_MANIFEST.json"
39
-
40
- SECRETS_FILE_NAME = "SECRETS.json"
41
-
42
- # specify a local plugin directory for development
43
- PLUGIN_DIRECTORY = "/plugin-runner/custom-plugins" if IS_PRODUCTION else "./custom-plugins"
33
+ from settings import MANIFEST_FILE_NAME, PLUGIN_DIRECTORY, SECRETS_FILE_NAME
44
34
 
45
35
  # when we import plugins we'll use the module name directly so we need to add the plugin
46
36
  # directory to the path
@@ -51,7 +41,7 @@ sys.path.append(PLUGIN_DIRECTORY)
51
41
  LOADED_PLUGINS: dict = {}
52
42
 
53
43
  # a global dictionary of events to protocol class names
54
- EVENT_PROTOCOL_MAP: dict = {}
44
+ EVENT_PROTOCOL_MAP: dict[str, list] = defaultdict(list)
55
45
 
56
46
 
57
47
  class DataAccess(TypedDict):
@@ -113,7 +103,7 @@ class PluginRunner(PluginRunnerServicer):
113
103
  event_start_time = time.time()
114
104
  event_type = request.type
115
105
  event_name = EventType.Name(event_type)
116
- relevant_plugins = EVENT_PROTOCOL_MAP.get(event_name, [])
106
+ relevant_plugins = EVENT_PROTOCOL_MAP[event_name]
117
107
 
118
108
  if event_type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
119
109
  plugin_name = request.target
@@ -197,18 +187,50 @@ def handle_hup_cb(_signum: int, _frame: Optional[FrameType]) -> None:
197
187
  load_plugins()
198
188
 
199
189
 
200
- def sandbox_from_module_name(module_name: str) -> Any:
190
+ def find_modules(base_path: pathlib.Path, prefix: str | None = None) -> list[str]:
191
+ """Find all modules in the specified package path."""
192
+ modules: list[str] = []
193
+
194
+ for file_finder, module_name, is_pkg in pkgutil.iter_modules(
195
+ [base_path.as_posix()],
196
+ ):
197
+ if is_pkg:
198
+ modules = modules + find_modules(
199
+ base_path / module_name,
200
+ prefix=f"{prefix}.{module_name}" if prefix else module_name,
201
+ )
202
+ else:
203
+ modules.append(f"{prefix}.{module_name}" if prefix else module_name)
204
+
205
+ return modules
206
+
207
+
208
+ def sandbox_from_package(package_path: pathlib.Path) -> dict[str, Any]:
209
+ """Sandbox the code execution."""
210
+ package_name = package_path.name
211
+ available_modules = find_modules(package_path)
212
+ sandboxes = {}
213
+
214
+ for module_name in available_modules:
215
+ result = sandbox_from_module(package_path, module_name)
216
+ full_module_name = f"{package_name}.{module_name}"
217
+ sandboxes[full_module_name] = result
218
+
219
+ return sandboxes
220
+
221
+
222
+ def sandbox_from_module(package_path: pathlib.Path, module_name: str) -> Any:
201
223
  """Sandbox the code execution."""
202
- spec = importlib.util.find_spec(module_name)
224
+ module_path = package_path / str(module_name.replace(".", "/") + ".py")
203
225
 
204
- if not spec or not spec.origin:
205
- raise Exception(f'Could not load plugin "{module_name}"')
226
+ if not module_path.exists():
227
+ raise ModuleNotFoundError(f'Could not load module "{module_name}"')
206
228
 
207
- origin = pathlib.Path(spec.origin)
208
- source_code = origin.read_text()
229
+ source_code = module_path.read_text()
209
230
 
210
- sandbox = Sandbox(source_code)
231
+ full_module_name = f"{package_path.name}.{module_name}"
211
232
 
233
+ sandbox = Sandbox(source_code, module_name=full_module_name)
212
234
  return sandbox.execute()
213
235
 
214
236
 
@@ -240,6 +262,7 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
240
262
  # TODO add existing schema validation from Michela here
241
263
  try:
242
264
  protocols = manifest_json["components"]["protocols"]
265
+ results = sandbox_from_package(path)
243
266
  except Exception as e:
244
267
  log.error(f'Unable to load plugin "{name}": {str(e)}')
245
268
  return
@@ -258,7 +281,7 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
258
281
  if name_and_class in LOADED_PLUGINS:
259
282
  log.info(f"Reloading plugin '{name_and_class}'")
260
283
 
261
- result = sandbox_from_module_name(protocol_module)
284
+ result = results[protocol_module]
262
285
 
263
286
  LOADED_PLUGINS[name_and_class]["active"] = True
264
287
 
@@ -268,7 +291,7 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
268
291
  else:
269
292
  log.info(f"Loading plugin '{name_and_class}'")
270
293
 
271
- result = sandbox_from_module_name(protocol_module)
294
+ result = results[protocol_module]
272
295
 
273
296
  LOADED_PLUGINS[name_and_class] = {
274
297
  "active": True,
@@ -285,8 +308,7 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
285
308
 
286
309
  def refresh_event_type_map() -> None:
287
310
  """Ensure the event subscriptions are up to date."""
288
- global EVENT_PROTOCOL_MAP
289
- EVENT_PROTOCOL_MAP = defaultdict(list)
311
+ EVENT_PROTOCOL_MAP.clear()
290
312
 
291
313
  for name, plugin in LOADED_PLUGINS.items():
292
314
  if hasattr(plugin["class"], "RESPONDS_TO"):
plugin_runner/sandbox.py CHANGED
@@ -75,12 +75,6 @@ def _is_known_module(name: str) -> bool:
75
75
  return any(name.startswith(m) for m in ALLOWED_MODULES)
76
76
 
77
77
 
78
- def _safe_import(name: str, *args: Any, **kwargs: Any) -> Any:
79
- if not _is_known_module(name):
80
- raise ImportError(f"{name!r} is not an allowed import.")
81
- return __import__(name, *args, **kwargs)
82
-
83
-
84
78
  def _unrestricted(_ob: Any, *args: Any, **kwargs: Any) -> Any:
85
79
  """Return the given object, unmodified."""
86
80
  return _ob
@@ -96,6 +90,7 @@ class Sandbox:
96
90
 
97
91
  source_code: str
98
92
  namespace: str
93
+ module_name: str | None
99
94
 
100
95
  class Transformer(RestrictingNodeTransformer):
101
96
  """A node transformer for customizing the sandbox compiler."""
@@ -204,12 +199,20 @@ class Sandbox:
204
199
  # Impossible Case only ctx Load, Store and Del are defined in ast.
205
200
  raise NotImplementedError(f"Unknown ctx type: {type(node.ctx)}")
206
201
 
207
- def __init__(self, source_code: str, namespace: str | None = None) -> None:
202
+ def __init__(
203
+ self, source_code: str, namespace: str | None = None, module_name: str | None = None
204
+ ) -> None:
208
205
  if source_code is None:
209
206
  raise TypeError("source_code may not be None")
207
+ self.module_name = module_name
210
208
  self.namespace = namespace or "protocols"
211
209
  self.source_code = source_code
212
210
 
211
+ @cached_property
212
+ def package_name(self) -> str | None:
213
+ """Return the root package name."""
214
+ return self.module_name.split(".")[0] if self.module_name else None
215
+
213
216
  @cached_property
214
217
  def scope(self) -> dict[str, Any]:
215
218
  """Return the scope used for evaluation."""
@@ -217,7 +220,7 @@ class Sandbox:
217
220
  "__builtins__": {
218
221
  **safe_builtins.copy(),
219
222
  **utility_builtins.copy(),
220
- "__import__": _safe_import,
223
+ "__import__": self._safe_import,
221
224
  "classmethod": builtins.classmethod,
222
225
  "staticmethod": builtins.staticmethod,
223
226
  "any": builtins.any,
@@ -263,6 +266,17 @@ class Sandbox:
263
266
  """Return warnings encountered when compiling the source code."""
264
267
  return cast(tuple[str, ...], self.compile_result.warnings)
265
268
 
269
+ def _is_known_module(self, name: str) -> bool:
270
+ return bool(
271
+ _is_known_module(name)
272
+ or (self.package_name and name.split(".")[0] == self.package_name)
273
+ )
274
+
275
+ def _safe_import(self, name: str, *args: Any, **kwargs: Any) -> Any:
276
+ if not (self._is_known_module(name)):
277
+ raise ImportError(f"{name!r} is not an allowed import.")
278
+ return __import__(name, *args, **kwargs)
279
+
266
280
  def execute(self) -> dict:
267
281
  """Execute the given code in a restricted sandbox."""
268
282
  if self.errors:
File without changes
File without changes
@@ -0,0 +1,29 @@
1
+ {
2
+ "sdk_version": "0.1.4",
3
+ "plugin_version": "0.0.1",
4
+ "name": "example_plugin",
5
+ "description": "Edit the description in CANVAS_MANIFEST.json",
6
+ "components": {
7
+ "protocols": [
8
+ {
9
+ "class": "example_plugin.protocols.my_protocol:Protocol",
10
+ "description": "A protocol that does xyz...",
11
+ "data_access": {
12
+ "event": "",
13
+ "read": [],
14
+ "write": []
15
+ }
16
+ }
17
+ ],
18
+ "commands": [],
19
+ "content": [],
20
+ "effects": [],
21
+ "views": []
22
+ },
23
+ "secrets": [],
24
+ "tags": {},
25
+ "references": [],
26
+ "license": "",
27
+ "diagram": false,
28
+ "readme": "./README.md"
29
+ }
@@ -0,0 +1,12 @@
1
+ ==============
2
+ example_plugin
3
+ ==============
4
+
5
+ ## Description
6
+
7
+ A description of this plugin
8
+
9
+ ### Important Note!
10
+
11
+ The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
12
+ gets updated if you add, remove, or rename protocols.
@@ -0,0 +1,18 @@
1
+ from canvas_sdk.effects import Effect, EffectType
2
+ from canvas_sdk.events import EventType
3
+ from canvas_sdk.protocols import BaseProtocol
4
+
5
+
6
+ class Protocol(BaseProtocol):
7
+ """
8
+ You should put a helpful description of this protocol's behavior here.
9
+ """
10
+
11
+ # Name the event type you wish to run in response to
12
+ RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
13
+
14
+ NARRATIVE_STRING = "I was inserted from my plugin's protocol."
15
+
16
+ def compute(self) -> list[Effect]:
17
+ """This method gets called when an event of the type RESPONDS_TO is fired."""
18
+ return [Effect(type=EffectType.LOG, payload="Hello, world!")]
@@ -0,0 +1,29 @@
1
+ {
2
+ "sdk_version": "0.1.4",
3
+ "plugin_version": "0.0.1",
4
+ "name": "test_module_imports_outside_plugin_v1",
5
+ "description": "Edit the description in CANVAS_MANIFEST.json",
6
+ "components": {
7
+ "protocols": [
8
+ {
9
+ "class": "test_module_imports_outside_plugin_v1.protocols.my_protocol:Protocol",
10
+ "description": "A protocol that does xyz...",
11
+ "data_access": {
12
+ "event": "",
13
+ "read": [],
14
+ "write": []
15
+ }
16
+ }
17
+ ],
18
+ "commands": [],
19
+ "content": [],
20
+ "effects": [],
21
+ "views": []
22
+ },
23
+ "secrets": [],
24
+ "tags": {},
25
+ "references": [],
26
+ "license": "",
27
+ "diagram": false,
28
+ "readme": "./README.md"
29
+ }
@@ -0,0 +1,12 @@
1
+ ==========================
2
+ test_module_imports_outside_plugin_v1
3
+ ==========================
4
+
5
+ ## Description
6
+
7
+ A description of this plugin
8
+
9
+ ### Important Note!
10
+
11
+ The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
12
+ gets updated if you add, remove, or rename protocols.
@@ -0,0 +1,3 @@
1
+ def import_me() -> str:
2
+ """Test method."""
3
+ return "Successfully imported!"
@@ -0,0 +1,18 @@
1
+ from test_module_imports_plugin.other_module.base import import_me
2
+
3
+ from canvas_sdk.effects import Effect, EffectType
4
+ from canvas_sdk.events import EventType
5
+ from canvas_sdk.protocols import BaseProtocol
6
+
7
+
8
+ class Protocol(BaseProtocol):
9
+ """
10
+ You should put a helpful description of this protocol's behavior here.
11
+ """
12
+
13
+ # Name the event type you wish to run in response to
14
+ RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
15
+
16
+ def compute(self) -> list[Effect]:
17
+ """This method gets called when an event of the type RESPONDS_TO is fired."""
18
+ return [Effect(type=EffectType.LOG, payload=import_me())]
@@ -0,0 +1,29 @@
1
+ {
2
+ "sdk_version": "0.1.4",
3
+ "plugin_version": "0.0.1",
4
+ "name": "test_module_imports_outside_plugin_v2",
5
+ "description": "Edit the description in CANVAS_MANIFEST.json",
6
+ "components": {
7
+ "protocols": [
8
+ {
9
+ "class": "test_module_imports_outside_plugin_v2.protocols.my_protocol:Protocol",
10
+ "description": "A protocol that does xyz...",
11
+ "data_access": {
12
+ "event": "",
13
+ "read": [],
14
+ "write": []
15
+ }
16
+ }
17
+ ],
18
+ "commands": [],
19
+ "content": [],
20
+ "effects": [],
21
+ "views": []
22
+ },
23
+ "secrets": [],
24
+ "tags": {},
25
+ "references": [],
26
+ "license": "",
27
+ "diagram": false,
28
+ "readme": "./README.md"
29
+ }
@@ -0,0 +1,12 @@
1
+ ==========================
2
+ test_module_imports_outside_plugin_v2
3
+ ==========================
4
+
5
+ ## Description
6
+
7
+ A description of this plugin
8
+
9
+ ### Important Note!
10
+
11
+ The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
12
+ gets updated if you add, remove, or rename protocols.
@@ -0,0 +1,6 @@
1
+ import os
2
+
3
+
4
+ def import_me() -> list[str]:
5
+ """Test method."""
6
+ return os.listdir(".")
@@ -0,0 +1,18 @@
1
+ from test_module_imports_outside_plugin_v2.other_module.base import import_me
2
+
3
+ from canvas_sdk.effects import Effect, EffectType
4
+ from canvas_sdk.events import EventType
5
+ from canvas_sdk.protocols import BaseProtocol
6
+
7
+
8
+ class Protocol(BaseProtocol):
9
+ """
10
+ You should put a helpful description of this protocol's behavior here.
11
+ """
12
+
13
+ # Name the event type you wish to run in response to
14
+ RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
15
+
16
+ def compute(self) -> list[Effect]:
17
+ """This method gets called when an event of the type RESPONDS_TO is fired."""
18
+ return [Effect(type=EffectType.LOG, payload=import_me())]
@@ -0,0 +1,29 @@
1
+ {
2
+ "sdk_version": "0.1.4",
3
+ "plugin_version": "0.0.1",
4
+ "name": "test_module_imports_outside_plugin_v3",
5
+ "description": "Edit the description in CANVAS_MANIFEST.json",
6
+ "components": {
7
+ "protocols": [
8
+ {
9
+ "class": "test_module_imports_outside_plugin_v3.protocols.my_protocol:Protocol",
10
+ "description": "A protocol that does xyz...",
11
+ "data_access": {
12
+ "event": "",
13
+ "read": [],
14
+ "write": []
15
+ }
16
+ }
17
+ ],
18
+ "commands": [],
19
+ "content": [],
20
+ "effects": [],
21
+ "views": []
22
+ },
23
+ "secrets": [],
24
+ "tags": {},
25
+ "references": [],
26
+ "license": "",
27
+ "diagram": false,
28
+ "readme": "./README.md"
29
+ }
@@ -0,0 +1,12 @@
1
+ ==========================
2
+ test_module_imports_outside_plugin_v3
3
+ ==========================
4
+
5
+ ## Description
6
+
7
+ A description of this plugin
8
+
9
+ ### Important Note!
10
+
11
+ The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
12
+ gets updated if you add, remove, or rename protocols.
@@ -0,0 +1,8 @@
1
+ from ...test_module_imports_outside_plugin_v2.other_module.base import (
2
+ import_me as other_module_import_me,
3
+ )
4
+
5
+
6
+ def import_me() -> list[str]:
7
+ """Test method."""
8
+ return other_module_import_me()
@@ -0,0 +1,18 @@
1
+ from test_module_imports_outside_plugin_v3.other_module.base import import_me
2
+
3
+ from canvas_sdk.effects import Effect, EffectType
4
+ from canvas_sdk.events import EventType
5
+ from canvas_sdk.protocols import BaseProtocol
6
+
7
+
8
+ class Protocol(BaseProtocol):
9
+ """
10
+ You should put a helpful description of this protocol's behavior here.
11
+ """
12
+
13
+ # Name the event type you wish to run in response to
14
+ RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
15
+
16
+ def compute(self) -> list[Effect]:
17
+ """This method gets called when an event of the type RESPONDS_TO is fired."""
18
+ return [Effect(type=EffectType.LOG, payload=import_me())]
@@ -0,0 +1,29 @@
1
+ {
2
+ "sdk_version": "0.1.4",
3
+ "plugin_version": "0.0.1",
4
+ "name": "test_module_imports_plugin",
5
+ "description": "Edit the description in CANVAS_MANIFEST.json",
6
+ "components": {
7
+ "protocols": [
8
+ {
9
+ "class": "test_module_imports_plugin.protocols.my_protocol:Protocol",
10
+ "description": "A protocol that does xyz...",
11
+ "data_access": {
12
+ "event": "",
13
+ "read": [],
14
+ "write": []
15
+ }
16
+ }
17
+ ],
18
+ "commands": [],
19
+ "content": [],
20
+ "effects": [],
21
+ "views": []
22
+ },
23
+ "secrets": [],
24
+ "tags": {},
25
+ "references": [],
26
+ "license": "",
27
+ "diagram": false,
28
+ "readme": "./README.md"
29
+ }
@@ -0,0 +1,12 @@
1
+ ==========================
2
+ test_module_imports_plugin
3
+ ==========================
4
+
5
+ ## Description
6
+
7
+ A description of this plugin
8
+
9
+ ### Important Note!
10
+
11
+ The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
12
+ gets updated if you add, remove, or rename protocols.
@@ -0,0 +1,3 @@
1
+ def import_me() -> str:
2
+ """Test method."""
3
+ return "Successfully imported!"
@@ -0,0 +1,18 @@
1
+ from test_module_imports_plugin.other_module.base import import_me
2
+
3
+ from canvas_sdk.effects import Effect, EffectType
4
+ from canvas_sdk.events import EventType
5
+ from canvas_sdk.protocols import BaseProtocol
6
+
7
+
8
+ class Protocol(BaseProtocol):
9
+ """
10
+ You should put a helpful description of this protocol's behavior here.
11
+ """
12
+
13
+ # Name the event type you wish to run in response to
14
+ RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
15
+
16
+ def compute(self) -> list[Effect]:
17
+ """This method gets called when an event of the type RESPONDS_TO is fired."""
18
+ return [Effect(type=EffectType.LOG, payload=import_me())]