canvas 0.14.0__py3-none-any.whl → 0.16.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.
- {canvas-0.14.0.dist-info → canvas-0.16.0.dist-info}/METADATA +2 -2
- {canvas-0.14.0.dist-info → canvas-0.16.0.dist-info}/RECORD +87 -52
- canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json +6 -3
- canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/my_application.py +4 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -1
- canvas_cli/utils/validators/manifest_schema.py +9 -2
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +18 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +48 -0
- canvas_sdk/commands/tests/protocol/tests.py +3 -1
- canvas_sdk/commands/tests/test_utils.py +76 -18
- canvas_sdk/effects/banner_alert/tests.py +41 -20
- canvas_sdk/effects/launch_modal.py +14 -3
- canvas_sdk/events/base.py +1 -3
- canvas_sdk/handlers/action_button.py +33 -13
- canvas_sdk/handlers/application.py +1 -1
- canvas_sdk/handlers/cron_task.py +1 -1
- canvas_sdk/protocols/clinical_quality_measure.py +1 -1
- canvas_sdk/templates/__init__.py +3 -0
- canvas_sdk/templates/tests/__init__.py +0 -0
- canvas_sdk/templates/tests/test_utils.py +43 -0
- canvas_sdk/templates/utils.py +44 -0
- canvas_sdk/v1/apps.py +7 -0
- canvas_sdk/v1/data/__init__.py +98 -5
- canvas_sdk/v1/data/allergy_intolerance.py +25 -9
- canvas_sdk/v1/data/appointment.py +56 -0
- canvas_sdk/v1/data/assessment.py +40 -0
- canvas_sdk/v1/data/base.py +35 -22
- canvas_sdk/v1/data/billing.py +4 -7
- canvas_sdk/v1/data/care_team.py +60 -0
- canvas_sdk/v1/data/command.py +8 -10
- canvas_sdk/v1/data/common.py +53 -0
- canvas_sdk/v1/data/condition.py +22 -10
- canvas_sdk/v1/data/coverage.py +294 -0
- canvas_sdk/v1/data/detected_issue.py +5 -9
- canvas_sdk/v1/data/device.py +4 -8
- canvas_sdk/v1/data/imaging.py +12 -17
- canvas_sdk/v1/data/lab.py +41 -31
- canvas_sdk/v1/data/medication.py +16 -10
- canvas_sdk/v1/data/note.py +11 -14
- canvas_sdk/v1/data/observation.py +19 -14
- canvas_sdk/v1/data/organization.py +1 -2
- canvas_sdk/v1/data/patient.py +140 -2
- canvas_sdk/v1/data/practicelocation.py +2 -4
- canvas_sdk/v1/data/protocol_override.py +21 -8
- canvas_sdk/v1/data/questionnaire.py +20 -17
- canvas_sdk/v1/data/staff.py +5 -7
- canvas_sdk/v1/data/task.py +5 -11
- canvas_sdk/v1/data/user.py +0 -1
- canvas_sdk/v1/models.py +4 -0
- canvas_sdk/value_set/hcc2018.py +55369 -0
- plugin_runner/plugin_installer.py +1 -1
- plugin_runner/plugin_runner.py +5 -25
- plugin_runner/sandbox.py +133 -9
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +38 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +11 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +33 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +3 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +6 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +5 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +4 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +29 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +12 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +10 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +18 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +29 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +12 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +10 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +18 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/CANVAS_MANIFEST.json +47 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/README.md +11 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py +43 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html +10 -0
- plugin_runner/tests/test_application.py +9 -9
- plugin_runner/tests/test_plugin_installer.py +12 -1
- plugin_runner/tests/test_plugin_runner.py +159 -66
- plugin_runner/tests/test_sandbox.py +26 -14
- settings.py +13 -1
- canvas_sdk/models/__init__.py +0 -8
- {canvas-0.14.0.dist-info → canvas-0.16.0.dist-info}/WHEEL +0 -0
- {canvas-0.14.0.dist-info → canvas-0.16.0.dist-info}/entry_points.txt +0 -0
|
@@ -97,7 +97,7 @@ def _extract_rows_to_dict(rows: list) -> dict[str, PluginAttributes]:
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
@contextmanager
|
|
100
|
-
def download_plugin(plugin_package: str) -> Generator:
|
|
100
|
+
def download_plugin(plugin_package: str) -> Generator[Path, None, None]:
|
|
101
101
|
"""Download the plugin package from the S3 bucket."""
|
|
102
102
|
method = "GET"
|
|
103
103
|
host = f"s3-{settings.AWS_REGION}.amazonaws.com"
|
plugin_runner/plugin_runner.py
CHANGED
|
@@ -261,32 +261,15 @@ def find_modules(base_path: pathlib.Path, prefix: str | None = None) -> list[str
|
|
|
261
261
|
return modules
|
|
262
262
|
|
|
263
263
|
|
|
264
|
-
def
|
|
264
|
+
def sandbox_from_module(base_path: pathlib.Path, module_name: str) -> Any:
|
|
265
265
|
"""Sandbox the code execution."""
|
|
266
|
-
|
|
267
|
-
available_modules = find_modules(package_path)
|
|
268
|
-
sandboxes = {}
|
|
269
|
-
|
|
270
|
-
for module_name in available_modules:
|
|
271
|
-
result = sandbox_from_module(package_path, module_name)
|
|
272
|
-
full_module_name = f"{package_name}.{module_name}"
|
|
273
|
-
sandboxes[full_module_name] = result
|
|
274
|
-
|
|
275
|
-
return sandboxes
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def sandbox_from_module(package_path: pathlib.Path, module_name: str) -> Any:
|
|
279
|
-
"""Sandbox the code execution."""
|
|
280
|
-
module_path = package_path / str(module_name.replace(".", "/") + ".py")
|
|
266
|
+
module_path = base_path / str(module_name.replace(".", "/") + ".py")
|
|
281
267
|
|
|
282
268
|
if not module_path.exists():
|
|
283
269
|
raise ModuleNotFoundError(f'Could not load module "{module_name}"')
|
|
284
270
|
|
|
285
|
-
|
|
271
|
+
sandbox = Sandbox(module_path, namespace=module_name)
|
|
286
272
|
|
|
287
|
-
full_module_name = f"{package_path.name}.{module_name}"
|
|
288
|
-
|
|
289
|
-
sandbox = Sandbox(source_code, namespace=full_module_name)
|
|
290
273
|
return sandbox.execute()
|
|
291
274
|
|
|
292
275
|
|
|
@@ -320,7 +303,6 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
|
|
|
320
303
|
handlers = manifest_json["components"].get("protocols", []) + manifest_json[
|
|
321
304
|
"components"
|
|
322
305
|
].get("applications", [])
|
|
323
|
-
results = sandbox_from_package(path)
|
|
324
306
|
except Exception as e:
|
|
325
307
|
log.error(f'Unable to load plugin "{name}": {str(e)}')
|
|
326
308
|
return
|
|
@@ -336,11 +318,11 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
|
|
|
336
318
|
continue
|
|
337
319
|
|
|
338
320
|
try:
|
|
321
|
+
result = sandbox_from_module(path.parent, handler_module)
|
|
322
|
+
|
|
339
323
|
if name_and_class in LOADED_PLUGINS:
|
|
340
324
|
log.info(f"Reloading plugin '{name_and_class}'")
|
|
341
325
|
|
|
342
|
-
result = results[handler_module]
|
|
343
|
-
|
|
344
326
|
LOADED_PLUGINS[name_and_class]["active"] = True
|
|
345
327
|
|
|
346
328
|
LOADED_PLUGINS[name_and_class]["class"] = result[handler_class]
|
|
@@ -349,8 +331,6 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
|
|
|
349
331
|
else:
|
|
350
332
|
log.info(f"Loading plugin '{name_and_class}'")
|
|
351
333
|
|
|
352
|
-
result = results[handler_module]
|
|
353
|
-
|
|
354
334
|
LOADED_PLUGINS[name_and_class] = {
|
|
355
335
|
"active": True,
|
|
356
336
|
"class": result[handler_class],
|
plugin_runner/sandbox.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import builtins
|
|
3
|
+
import importlib
|
|
4
|
+
import sys
|
|
3
5
|
from _ast import AnnAssign
|
|
4
6
|
from functools import cached_property
|
|
7
|
+
from pathlib import Path
|
|
5
8
|
from typing import Any, cast
|
|
6
9
|
|
|
7
10
|
from RestrictedPython import (
|
|
@@ -31,6 +34,7 @@ from RestrictedPython.transformer import (
|
|
|
31
34
|
#
|
|
32
35
|
ALLOWED_MODULES = frozenset(
|
|
33
36
|
[
|
|
37
|
+
"__future__",
|
|
34
38
|
"_strptime",
|
|
35
39
|
"arrow",
|
|
36
40
|
"base64",
|
|
@@ -42,12 +46,15 @@ ALLOWED_MODULES = frozenset(
|
|
|
42
46
|
"canvas_sdk.handlers",
|
|
43
47
|
"canvas_sdk.protocols",
|
|
44
48
|
"canvas_sdk.utils",
|
|
49
|
+
"canvas_sdk.templates",
|
|
45
50
|
"canvas_sdk.v1",
|
|
46
51
|
"canvas_sdk.value_set",
|
|
47
52
|
"canvas_sdk.views",
|
|
48
53
|
"contextlib",
|
|
54
|
+
"dataclasses",
|
|
49
55
|
"datetime",
|
|
50
56
|
"dateutil",
|
|
57
|
+
"decimal",
|
|
51
58
|
"django.db.models",
|
|
52
59
|
"django.utils.functional",
|
|
53
60
|
"enum",
|
|
@@ -60,6 +67,7 @@ ALLOWED_MODULES = frozenset(
|
|
|
60
67
|
"math",
|
|
61
68
|
"operator",
|
|
62
69
|
"pickletools",
|
|
70
|
+
"pydantic",
|
|
63
71
|
"random",
|
|
64
72
|
"rapidfuzz",
|
|
65
73
|
"re",
|
|
@@ -74,6 +82,14 @@ ALLOWED_MODULES = frozenset(
|
|
|
74
82
|
)
|
|
75
83
|
|
|
76
84
|
|
|
85
|
+
##
|
|
86
|
+
# FORBIDDEN_ASSIGNMENTS
|
|
87
|
+
#
|
|
88
|
+
# The names in this list are forbidden to be assigned to in a sandboxed runtime.
|
|
89
|
+
#
|
|
90
|
+
FORBIDDEN_ASSIGNMENTS = frozenset(["__name__", "__is_plugin__"])
|
|
91
|
+
|
|
92
|
+
|
|
77
93
|
def _is_known_module(name: str) -> bool:
|
|
78
94
|
return any(name.startswith(m) for m in ALLOWED_MODULES)
|
|
79
95
|
|
|
@@ -88,6 +104,20 @@ def _apply(_ob: Any, *args: Any, **kwargs: Any) -> Any:
|
|
|
88
104
|
return _ob(*args, **kwargs)
|
|
89
105
|
|
|
90
106
|
|
|
107
|
+
def _find_folder_in_path(file_path: Path, target_folder_name: str) -> Path | None:
|
|
108
|
+
"""Recursively search for a folder with the specified name in the hierarchy of the given file path."""
|
|
109
|
+
file_path = file_path.resolve()
|
|
110
|
+
|
|
111
|
+
if file_path.name == target_folder_name:
|
|
112
|
+
return file_path
|
|
113
|
+
|
|
114
|
+
# If we've reached the root of the file system, return None
|
|
115
|
+
if file_path.parent == file_path:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
return _find_folder_in_path(file_path.parent, target_folder_name)
|
|
119
|
+
|
|
120
|
+
|
|
91
121
|
class Sandbox:
|
|
92
122
|
"""A restricted sandbox for safely executing arbitrary Python code."""
|
|
93
123
|
|
|
@@ -119,7 +149,10 @@ class Sandbox:
|
|
|
119
149
|
return self.node_contents_visit(node)
|
|
120
150
|
|
|
121
151
|
def check_name(
|
|
122
|
-
self,
|
|
152
|
+
self,
|
|
153
|
+
node: ast.ImportFrom,
|
|
154
|
+
name: str | None,
|
|
155
|
+
allow_magic_methods: bool = False,
|
|
123
156
|
) -> None:
|
|
124
157
|
"""Check names if they are allowed.
|
|
125
158
|
|
|
@@ -150,6 +183,24 @@ class Sandbox:
|
|
|
150
183
|
elif name in FORBIDDEN_FUNC_NAMES:
|
|
151
184
|
self.error(node, f'"{name}" is a reserved name.')
|
|
152
185
|
|
|
186
|
+
def visit_Assign(self, node: ast.Assign) -> ast.AST:
|
|
187
|
+
"""Check for forbidden assignments."""
|
|
188
|
+
for target in node.targets:
|
|
189
|
+
if isinstance(target, ast.Name) and target.id in FORBIDDEN_ASSIGNMENTS:
|
|
190
|
+
self.error(node, f"Assignments to '{target.id}' are not allowed.")
|
|
191
|
+
elif isinstance(target, ast.Tuple | ast.List):
|
|
192
|
+
self.check_for_name_in_iterable(target)
|
|
193
|
+
|
|
194
|
+
return super().visit_Assign(node)
|
|
195
|
+
|
|
196
|
+
def check_for_name_in_iterable(self, iterable_node: ast.Tuple | ast.List) -> None:
|
|
197
|
+
"""Check if any element of an iterable is a forbidden assignment."""
|
|
198
|
+
for elt in iterable_node.elts:
|
|
199
|
+
if isinstance(elt, ast.Name) and elt.id in FORBIDDEN_ASSIGNMENTS:
|
|
200
|
+
self.error(iterable_node, f"Assignments to '{elt.id}' are not allowed.")
|
|
201
|
+
elif isinstance(elt, ast.Tuple | ast.List):
|
|
202
|
+
self.check_for_name_in_iterable(elt)
|
|
203
|
+
|
|
153
204
|
def visit_Attribute(self, node: ast.Attribute) -> ast.AST:
|
|
154
205
|
"""Checks and mutates attribute access/assignment.
|
|
155
206
|
|
|
@@ -199,16 +250,28 @@ class Sandbox:
|
|
|
199
250
|
# Impossible Case only ctx Load, Store and Del are defined in ast.
|
|
200
251
|
raise NotImplementedError(f"Unknown ctx type: {type(node.ctx)}")
|
|
201
252
|
|
|
202
|
-
def __init__(
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
source_code: str | Path,
|
|
256
|
+
namespace: str | None = None,
|
|
257
|
+
evaluated_modules: dict[str, bool] | None = None,
|
|
258
|
+
) -> None:
|
|
203
259
|
if source_code is None:
|
|
204
260
|
raise TypeError("source_code may not be None")
|
|
205
|
-
self.namespace = namespace or "protocols"
|
|
206
|
-
self.source_code = source_code
|
|
207
261
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
262
|
+
self.namespace = namespace or "protocols"
|
|
263
|
+
self.package_name = self.namespace.split(".")[0]
|
|
264
|
+
|
|
265
|
+
if isinstance(source_code, Path):
|
|
266
|
+
if not source_code.exists():
|
|
267
|
+
raise FileNotFoundError(f"File not found: {source_code}")
|
|
268
|
+
self.source_code = source_code.read_text()
|
|
269
|
+
package_path = _find_folder_in_path(source_code, self.package_name)
|
|
270
|
+
self.base_path = package_path.parent if package_path else None
|
|
271
|
+
self._evaluated_modules: dict[str, bool] = evaluated_modules or {}
|
|
272
|
+
else:
|
|
273
|
+
self.source_code = source_code
|
|
274
|
+
self.base_path = None
|
|
212
275
|
|
|
213
276
|
@cached_property
|
|
214
277
|
def scope(self) -> dict[str, Any]:
|
|
@@ -236,6 +299,7 @@ class Sandbox:
|
|
|
236
299
|
},
|
|
237
300
|
"__metaclass__": type,
|
|
238
301
|
"__name__": self.namespace,
|
|
302
|
+
"__is_plugin__": True,
|
|
239
303
|
"_write_": _unrestricted,
|
|
240
304
|
"_getiter_": _unrestricted,
|
|
241
305
|
"_getitem_": default_guarded_getitem,
|
|
@@ -266,12 +330,72 @@ class Sandbox:
|
|
|
266
330
|
def _is_known_module(self, name: str) -> bool:
|
|
267
331
|
return bool(
|
|
268
332
|
_is_known_module(name)
|
|
269
|
-
or (self.package_name and name.split(".")[0] == self.package_name)
|
|
333
|
+
or (self.package_name and name.split(".")[0] == self.package_name and self.base_path)
|
|
270
334
|
)
|
|
271
335
|
|
|
336
|
+
def _get_module(self, module_name: str) -> Path:
|
|
337
|
+
"""Get the module path for the given module name."""
|
|
338
|
+
module_relative_path = module_name.replace(".", "/")
|
|
339
|
+
module = Path(cast(Path, self.base_path) / f"{module_relative_path}.py")
|
|
340
|
+
|
|
341
|
+
if not module.exists():
|
|
342
|
+
module = Path(cast(Path, self.base_path) / f"{module_relative_path}/__init__.py")
|
|
343
|
+
|
|
344
|
+
return module
|
|
345
|
+
|
|
346
|
+
def _evaluate_module(self, module_name: str) -> None:
|
|
347
|
+
"""Evaluate the given module in the sandbox.
|
|
348
|
+
If the module to import belongs to the same package as the current module, evaluate it inside a sandbox.
|
|
349
|
+
"""
|
|
350
|
+
if not module_name.startswith(self.package_name) or module_name in self._evaluated_modules:
|
|
351
|
+
return # Skip modules outside the package or already evaluated.
|
|
352
|
+
|
|
353
|
+
module = self._get_module(module_name)
|
|
354
|
+
self._evaluate_implicit_imports(module)
|
|
355
|
+
|
|
356
|
+
# Re-check after evaluating implicit imports to avoid duplicate evaluations.
|
|
357
|
+
if module_name not in self._evaluated_modules:
|
|
358
|
+
Sandbox(
|
|
359
|
+
module, namespace=module_name, evaluated_modules=self._evaluated_modules
|
|
360
|
+
).execute()
|
|
361
|
+
self._evaluated_modules[module_name] = True
|
|
362
|
+
|
|
363
|
+
# Reload the module if already imported to ensure the latest version is used.
|
|
364
|
+
if sys.modules.get(module_name):
|
|
365
|
+
importlib.reload(sys.modules[module_name])
|
|
366
|
+
|
|
367
|
+
def _evaluate_implicit_imports(self, module: Path) -> None:
|
|
368
|
+
"""Evaluate implicit imports in the sandbox."""
|
|
369
|
+
# Determine the parent module to check for implicit imports.
|
|
370
|
+
parent = module.parent.parent if module.name == "__init__.py" else module.parent
|
|
371
|
+
base_path = cast(Path, self.base_path)
|
|
372
|
+
|
|
373
|
+
# Skip evaluation if the parent module is outside the base path or already the source code root.
|
|
374
|
+
if not parent.is_relative_to(base_path) or parent == base_path:
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
module_name = parent.relative_to(base_path).as_posix().replace("/", ".")
|
|
378
|
+
init_file = parent / "__init__.py"
|
|
379
|
+
|
|
380
|
+
if module_name not in self._evaluated_modules:
|
|
381
|
+
if init_file.exists():
|
|
382
|
+
# Mark as evaluated to prevent infinite recursion.
|
|
383
|
+
self._evaluated_modules[module_name] = True
|
|
384
|
+
Sandbox(
|
|
385
|
+
init_file, namespace=module_name, evaluated_modules=self._evaluated_modules
|
|
386
|
+
).execute()
|
|
387
|
+
else:
|
|
388
|
+
# Mark as evaluated even if no init file exists to prevent redundant checks.
|
|
389
|
+
self._evaluated_modules[module_name] = True
|
|
390
|
+
|
|
391
|
+
self._evaluate_implicit_imports(parent)
|
|
392
|
+
|
|
272
393
|
def _safe_import(self, name: str, *args: Any, **kwargs: Any) -> Any:
|
|
273
394
|
if not (self._is_known_module(name)):
|
|
274
395
|
raise ImportError(f"{name!r} is not an allowed import.")
|
|
396
|
+
|
|
397
|
+
self._evaluate_module(name)
|
|
398
|
+
|
|
275
399
|
return __import__(name, *args, **kwargs)
|
|
276
400
|
|
|
277
401
|
def execute(self) -> dict:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sdk_version": "0.1.4",
|
|
3
|
+
"plugin_version": "0.0.1",
|
|
4
|
+
"name": "test_implicit_imports_plugin",
|
|
5
|
+
"description": "Edit the description in CANVAS_MANIFEST.json",
|
|
6
|
+
"components": {
|
|
7
|
+
"protocols": [
|
|
8
|
+
{
|
|
9
|
+
"class": "test_implicit_imports_plugin.protocols.my_protocol:Forbidden",
|
|
10
|
+
"description": "A protocol that does xyz...",
|
|
11
|
+
"data_access": {
|
|
12
|
+
"event": "",
|
|
13
|
+
"read": [],
|
|
14
|
+
"write": []
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"class": "test_implicit_imports_plugin.protocols.my_protocol:Allowed",
|
|
19
|
+
"description": "A protocol that does xyz...",
|
|
20
|
+
"data_access": {
|
|
21
|
+
"event": "",
|
|
22
|
+
"read": [],
|
|
23
|
+
"write": []
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"commands": [],
|
|
28
|
+
"content": [],
|
|
29
|
+
"effects": [],
|
|
30
|
+
"views": []
|
|
31
|
+
},
|
|
32
|
+
"secrets": [],
|
|
33
|
+
"tags": {},
|
|
34
|
+
"references": [],
|
|
35
|
+
"license": "",
|
|
36
|
+
"diagram": false,
|
|
37
|
+
"readme": "./README.md"
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
test_forbiden_implicit_imports_plugin
|
|
2
|
+
=====================================
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
|
|
6
|
+
A description of this plugin
|
|
7
|
+
|
|
8
|
+
### Important Note!
|
|
9
|
+
|
|
10
|
+
The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
|
|
11
|
+
gets updated if you add, remove, or rename protocols.
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from canvas_sdk.effects import Effect
|
|
2
|
+
from canvas_sdk.events import EventType
|
|
3
|
+
from canvas_sdk.protocols import BaseProtocol
|
|
4
|
+
from logger import log
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Forbidden(BaseProtocol):
|
|
8
|
+
"""You should put a helpful description of this protocol's behavior here."""
|
|
9
|
+
|
|
10
|
+
# Name the event type you wish to run in response to
|
|
11
|
+
RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
|
|
12
|
+
|
|
13
|
+
def compute(self) -> list[Effect]:
|
|
14
|
+
"""This method gets called when an event of the type RESPONDS_TO is fired."""
|
|
15
|
+
from test_implicit_imports_plugin.utils.base import OtherClass
|
|
16
|
+
|
|
17
|
+
OtherClass()
|
|
18
|
+
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Allowed(BaseProtocol):
|
|
23
|
+
"""You should put a helpful description of this protocol's behavior here."""
|
|
24
|
+
|
|
25
|
+
RESPONDS_TO = EventType.Name(EventType.UNKNOWN)
|
|
26
|
+
|
|
27
|
+
def compute(self) -> list[Effect]:
|
|
28
|
+
"""This method gets called when an event of the type RESPONDS_TO is fired."""
|
|
29
|
+
from test_implicit_imports_plugin.templates import Template
|
|
30
|
+
|
|
31
|
+
log.info(Template().render())
|
|
32
|
+
|
|
33
|
+
return []
|
plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sdk_version": "0.1.4",
|
|
3
|
+
"plugin_version": "0.0.1",
|
|
4
|
+
"name": "test_module_forbidden_imports_plugin",
|
|
5
|
+
"description": "Edit the description in CANVAS_MANIFEST.json",
|
|
6
|
+
"components": {
|
|
7
|
+
"protocols": [
|
|
8
|
+
{
|
|
9
|
+
"class": "test_module_forbidden_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_forbidden_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.
|
plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py
ADDED
|
File without changes
|
plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py
ADDED
|
File without changes
|
plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from test_module_forbidden_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_forbidden_imports_runtime_plugin",
|
|
5
|
+
"description": "Edit the description in CANVAS_MANIFEST.json",
|
|
6
|
+
"components": {
|
|
7
|
+
"protocols": [
|
|
8
|
+
{
|
|
9
|
+
"class": "test_module_forbidden_imports_runtime_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_forbidden_imports_runtime_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.
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
+
def compute(self) -> list[Effect]:
|
|
15
|
+
"""This method gets called when an event of the type RESPONDS_TO is fired."""
|
|
16
|
+
from test_module_forbidden_imports_runtime_plugin.other_module.base import import_me
|
|
17
|
+
|
|
18
|
+
return [Effect(type=EffectType.LOG, payload=import_me())]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sdk_version": "0.1.4",
|
|
3
|
+
"plugin_version": "0.0.1",
|
|
4
|
+
"name": "test_render_template",
|
|
5
|
+
"description": "Edit the description in CANVAS_MANIFEST.json",
|
|
6
|
+
"components": {
|
|
7
|
+
"protocols": [
|
|
8
|
+
{
|
|
9
|
+
"class": "test_render_template.protocols.my_protocol:ValidTemplate",
|
|
10
|
+
"description": "A protocol that does xyz...",
|
|
11
|
+
"data_access": {
|
|
12
|
+
"event": "",
|
|
13
|
+
"read": [],
|
|
14
|
+
"write": []
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"class": "test_render_template.protocols.my_protocol:InvalidTemplate",
|
|
19
|
+
"description": "A protocol that does xyz...",
|
|
20
|
+
"data_access": {
|
|
21
|
+
"event": "",
|
|
22
|
+
"read": [],
|
|
23
|
+
"write": []
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"class": "test_render_template.protocols.my_protocol:ForbiddenTemplate",
|
|
28
|
+
"description": "A protocol that does xyz...",
|
|
29
|
+
"data_access": {
|
|
30
|
+
"event": "",
|
|
31
|
+
"read": [],
|
|
32
|
+
"write": []
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"commands": [],
|
|
37
|
+
"content": [],
|
|
38
|
+
"effects": [],
|
|
39
|
+
"views": []
|
|
40
|
+
},
|
|
41
|
+
"secrets": [],
|
|
42
|
+
"tags": {},
|
|
43
|
+
"references": [],
|
|
44
|
+
"license": "",
|
|
45
|
+
"diagram": false,
|
|
46
|
+
"readme": "./README.md"
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
test_render_template
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
## Description
|
|
5
|
+
|
|
6
|
+
A description of this plugin
|
|
7
|
+
|
|
8
|
+
### Important Note!
|
|
9
|
+
|
|
10
|
+
The CANVAS_MANIFEST.json is used when installing your plugin. Please ensure it
|
|
11
|
+
gets updated if you add, remove, or rename protocols.
|
|
File without changes
|