canvas 0.33.0__py3-none-any.whl → 0.33.1__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 (254) hide show
  1. {canvas-0.33.0.dist-info → canvas-0.33.1.dist-info}/METADATA +2 -1
  2. canvas-0.33.1.dist-info/RECORD +272 -0
  3. canvas_sdk/__init__.py +3 -0
  4. canvas_sdk/commands/__init__.py +1 -1
  5. canvas_sdk/commands/base.py +3 -0
  6. canvas_sdk/commands/commands/__init__.py +1 -0
  7. canvas_sdk/commands/commands/adjust_prescription.py +3 -0
  8. canvas_sdk/commands/commands/allergy.py +7 -0
  9. canvas_sdk/commands/commands/assess.py +2 -0
  10. canvas_sdk/commands/commands/close_goal.py +3 -0
  11. canvas_sdk/commands/commands/diagnose.py +3 -0
  12. canvas_sdk/commands/commands/exam.py +3 -0
  13. canvas_sdk/commands/commands/family_history.py +3 -0
  14. canvas_sdk/commands/commands/follow_up.py +3 -0
  15. canvas_sdk/commands/commands/goal.py +3 -0
  16. canvas_sdk/commands/commands/history_present_illness.py +3 -0
  17. canvas_sdk/commands/commands/imaging_order.py +3 -0
  18. canvas_sdk/commands/commands/instruct.py +3 -0
  19. canvas_sdk/commands/commands/lab_order.py +3 -0
  20. canvas_sdk/commands/commands/medical_history.py +3 -0
  21. canvas_sdk/commands/commands/medication_statement.py +2 -0
  22. canvas_sdk/commands/commands/past_surgical_history.py +3 -0
  23. canvas_sdk/commands/commands/perform.py +3 -0
  24. canvas_sdk/commands/commands/plan.py +3 -0
  25. canvas_sdk/commands/commands/prescribe.py +8 -0
  26. canvas_sdk/commands/commands/questionnaire/__init__.py +3 -13
  27. canvas_sdk/commands/commands/questionnaire/question.py +10 -0
  28. canvas_sdk/commands/commands/reason_for_visit.py +3 -0
  29. canvas_sdk/commands/commands/refer.py +3 -0
  30. canvas_sdk/commands/commands/refill.py +3 -0
  31. canvas_sdk/commands/commands/remove_allergy.py +3 -0
  32. canvas_sdk/commands/commands/resolve_condition.py +3 -0
  33. canvas_sdk/commands/commands/review_of_systems.py +3 -0
  34. canvas_sdk/commands/commands/stop_medication.py +3 -0
  35. canvas_sdk/commands/commands/structured_assessment.py +3 -0
  36. canvas_sdk/commands/commands/task.py +7 -0
  37. canvas_sdk/commands/commands/update_diagnosis.py +3 -0
  38. canvas_sdk/commands/commands/update_goal.py +3 -0
  39. canvas_sdk/commands/commands/vitals.py +3 -0
  40. canvas_sdk/commands/constants.py +8 -0
  41. canvas_sdk/effects/__init__.py +1 -1
  42. canvas_sdk/effects/banner_alert/__init__.py +1 -1
  43. canvas_sdk/effects/banner_alert/add_banner_alert.py +3 -0
  44. canvas_sdk/effects/banner_alert/remove_banner_alert.py +3 -0
  45. canvas_sdk/effects/base.py +7 -0
  46. canvas_sdk/effects/billing_line_item/__init__.py +5 -1
  47. canvas_sdk/effects/billing_line_item/add_billing_line_item.py +3 -0
  48. canvas_sdk/effects/billing_line_item/remove_billing_line_item.py +3 -0
  49. canvas_sdk/effects/billing_line_item/update_billing_line_item.py +3 -0
  50. canvas_sdk/effects/launch_modal.py +3 -0
  51. canvas_sdk/effects/patient_chart_summary_configuration.py +3 -0
  52. canvas_sdk/effects/patient_portal/__init__.py +1 -0
  53. canvas_sdk/effects/patient_portal/application_configuration.py +3 -0
  54. canvas_sdk/effects/patient_portal/form_result.py +3 -0
  55. canvas_sdk/effects/patient_portal_menu_configuration.py +3 -0
  56. canvas_sdk/effects/patient_profile_configuration.py +5 -1
  57. canvas_sdk/effects/protocol_card/__init__.py +1 -1
  58. canvas_sdk/effects/protocol_card/protocol_card.py +6 -0
  59. canvas_sdk/effects/questionnaire_result.py +3 -0
  60. canvas_sdk/effects/send_invite.py +3 -0
  61. canvas_sdk/effects/show_button.py +3 -0
  62. canvas_sdk/effects/simple_api.py +9 -0
  63. canvas_sdk/effects/surescripts/__init__.py +2 -2
  64. canvas_sdk/effects/surescripts/surescripts_messages.py +7 -0
  65. canvas_sdk/effects/task/__init__.py +6 -1
  66. canvas_sdk/effects/task/task.py +8 -0
  67. canvas_sdk/effects/update_user.py +3 -0
  68. canvas_sdk/effects/widgets/__init__.py +1 -1
  69. canvas_sdk/effects/widgets/portal_widget.py +3 -0
  70. canvas_sdk/events/__init__.py +6 -1
  71. canvas_sdk/events/base.py +3 -0
  72. canvas_sdk/handlers/__init__.py +1 -1
  73. canvas_sdk/handlers/action_button.py +6 -0
  74. canvas_sdk/handlers/application.py +3 -0
  75. canvas_sdk/handlers/base.py +3 -0
  76. canvas_sdk/handlers/cron_task.py +3 -0
  77. canvas_sdk/handlers/simple_api/__init__.py +3 -2
  78. canvas_sdk/handlers/simple_api/api.py +26 -1
  79. canvas_sdk/handlers/simple_api/exceptions.py +10 -0
  80. canvas_sdk/handlers/simple_api/security.py +21 -5
  81. canvas_sdk/handlers/simple_api/tools.py +9 -0
  82. canvas_sdk/protocols/__init__.py +1 -1
  83. canvas_sdk/protocols/base.py +3 -0
  84. canvas_sdk/protocols/clinical_quality_measure.py +6 -1
  85. canvas_sdk/protocols/timeframe.py +3 -0
  86. canvas_sdk/questionnaires/__init__.py +1 -1
  87. canvas_sdk/questionnaires/utils.py +7 -0
  88. canvas_sdk/templates/__init__.py +1 -1
  89. canvas_sdk/templates/utils.py +3 -0
  90. canvas_sdk/utils/__init__.py +1 -1
  91. canvas_sdk/utils/http.py +69 -35
  92. canvas_sdk/utils/plugins.py +4 -0
  93. canvas_sdk/utils/stats.py +11 -0
  94. canvas_sdk/v1/__init__.py +1 -0
  95. canvas_sdk/v1/apps.py +3 -0
  96. canvas_sdk/v1/data/__init__.py +2 -2
  97. canvas_sdk/v1/data/allergy_intolerance.py +3 -0
  98. canvas_sdk/v1/data/appointment.py +7 -0
  99. canvas_sdk/v1/data/assessment.py +3 -0
  100. canvas_sdk/v1/data/banner_alert.py +3 -0
  101. canvas_sdk/v1/data/base.py +3 -0
  102. canvas_sdk/v1/data/billing.py +7 -0
  103. canvas_sdk/v1/data/care_team.py +7 -0
  104. canvas_sdk/v1/data/command.py +3 -0
  105. canvas_sdk/v1/data/common.py +18 -0
  106. canvas_sdk/v1/data/condition.py +7 -0
  107. canvas_sdk/v1/data/coverage.py +14 -0
  108. canvas_sdk/v1/data/detected_issue.py +3 -0
  109. canvas_sdk/v1/data/device.py +3 -0
  110. canvas_sdk/v1/data/imaging.py +7 -0
  111. canvas_sdk/v1/data/lab.py +16 -0
  112. canvas_sdk/v1/data/medication.py +3 -0
  113. canvas_sdk/v1/data/note.py +9 -0
  114. canvas_sdk/v1/data/observation.py +9 -0
  115. canvas_sdk/v1/data/organization.py +3 -0
  116. canvas_sdk/v1/data/patient.py +18 -2
  117. canvas_sdk/v1/data/practicelocation.py +7 -0
  118. canvas_sdk/v1/data/protocol_override.py +7 -0
  119. canvas_sdk/v1/data/questionnaire.py +16 -3
  120. canvas_sdk/v1/data/reason_for_visit.py +3 -0
  121. canvas_sdk/v1/data/staff.py +3 -0
  122. canvas_sdk/v1/data/task.py +12 -0
  123. canvas_sdk/v1/data/team.py +8 -1
  124. canvas_sdk/v1/data/user.py +3 -0
  125. canvas_sdk/v1/models.py +2 -0
  126. canvas_sdk/value_set/__init__.py +1 -0
  127. canvas_sdk/value_set/_utilities.py +16 -0
  128. canvas_sdk/value_set/custom.py +4 -0
  129. canvas_sdk/value_set/hcc2018.py +3 -0
  130. canvas_sdk/value_set/v2022/__init__.py +1 -0
  131. canvas_sdk/value_set/v2022/adverse_event.py +3 -0
  132. canvas_sdk/value_set/v2022/allergy.py +5 -0
  133. canvas_sdk/value_set/v2022/assessment.py +5 -0
  134. canvas_sdk/value_set/v2022/communication.py +5 -0
  135. canvas_sdk/value_set/v2022/condition.py +5 -0
  136. canvas_sdk/value_set/v2022/device.py +5 -0
  137. canvas_sdk/value_set/v2022/diagnostic_study.py +5 -0
  138. canvas_sdk/value_set/v2022/encounter.py +5 -0
  139. canvas_sdk/value_set/v2022/immunization.py +5 -0
  140. canvas_sdk/value_set/v2022/individual_characteristic.py +5 -0
  141. canvas_sdk/value_set/v2022/intervention.py +5 -0
  142. canvas_sdk/value_set/v2022/laboratory_test.py +5 -0
  143. canvas_sdk/value_set/v2022/medication.py +5 -0
  144. canvas_sdk/value_set/v2022/physical_exam.py +5 -0
  145. canvas_sdk/value_set/v2022/procedure.py +5 -0
  146. canvas_sdk/value_set/v2022/symptom.py +3 -0
  147. canvas_sdk/value_set/value_set.py +9 -0
  148. canvas_sdk/views/__init__.py +1 -0
  149. logger/__init__.py +2 -0
  150. logger/logger.py +3 -0
  151. plugin_runner/aws_headers.py +1 -1
  152. plugin_runner/load_all_plugins.py +202 -0
  153. plugin_runner/plugin_runner.py +21 -24
  154. plugin_runner/sandbox.py +497 -115
  155. settings.py +5 -2
  156. canvas-0.33.0.dist-info/RECORD +0 -366
  157. canvas_cli/apps/auth/tests.py +0 -155
  158. canvas_cli/apps/plugin/tests.py +0 -85
  159. canvas_cli/conftest.py +0 -28
  160. canvas_cli/tests.py +0 -217
  161. canvas_cli/utils/context/tests.py +0 -131
  162. canvas_cli/utils/print/tests.py +0 -69
  163. canvas_cli/utils/urls/tests.py +0 -12
  164. canvas_cli/utils/validators/tests.py +0 -37
  165. canvas_sdk/commands/tests/protocol/__init__.py +0 -0
  166. canvas_sdk/commands/tests/protocol/tests.py +0 -83
  167. canvas_sdk/commands/tests/schema/__init__.py +0 -0
  168. canvas_sdk/commands/tests/schema/tests.py +0 -108
  169. canvas_sdk/commands/tests/test_base_command.py +0 -81
  170. canvas_sdk/commands/tests/test_utils.py +0 -375
  171. canvas_sdk/commands/tests/unit/__init__.py +0 -0
  172. canvas_sdk/commands/tests/unit/tests.py +0 -278
  173. canvas_sdk/effects/banner_alert/tests.py +0 -288
  174. canvas_sdk/effects/protocol_card/tests.py +0 -191
  175. canvas_sdk/questionnaires/tests/__init__.py +0 -0
  176. canvas_sdk/questionnaires/tests/test_utils.py +0 -74
  177. canvas_sdk/templates/tests/__init__.py +0 -0
  178. canvas_sdk/templates/tests/test_utils.py +0 -43
  179. canvas_sdk/tests/__init__.py +0 -0
  180. canvas_sdk/tests/handlers/__init__.py +0 -0
  181. canvas_sdk/tests/handlers/test_simple_api.py +0 -1167
  182. canvas_sdk/utils/tests.py +0 -72
  183. canvas_sdk/value_set/tests/test_value_sets.py +0 -72
  184. plugin_runner/tests/__init__.py +0 -0
  185. plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json +0 -29
  186. plugin_runner/tests/fixtures/plugins/example_plugin/README.md +0 -12
  187. plugin_runner/tests/fixtures/plugins/example_plugin/__init__.py +0 -0
  188. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/__init__.py +0 -0
  189. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/my_protocol.py +0 -18
  190. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +0 -38
  191. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +0 -11
  192. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
  193. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +0 -33
  194. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +0 -3
  195. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +0 -6
  196. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +0 -5
  197. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +0 -4
  198. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/CANVAS_MANIFEST.json +0 -52
  199. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/README.md +0 -11
  200. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/__init__.py +0 -0
  201. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/my_protocol.py +0 -39
  202. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/questionnaires/example_questionnaire.yml +0 -61
  203. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +0 -29
  204. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +0 -12
  205. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
  206. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +0 -10
  207. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
  208. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +0 -18
  209. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +0 -29
  210. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +0 -12
  211. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
  212. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +0 -10
  213. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
  214. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +0 -18
  215. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/CANVAS_MANIFEST.json +0 -29
  216. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/README.md +0 -12
  217. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/__init__.py +0 -0
  218. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/base.py +0 -3
  219. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/__init__.py +0 -0
  220. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/my_protocol.py +0 -18
  221. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/CANVAS_MANIFEST.json +0 -29
  222. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/README.md +0 -12
  223. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/__init__.py +0 -0
  224. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/base.py +0 -6
  225. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/__init__.py +0 -0
  226. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/my_protocol.py +0 -18
  227. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/CANVAS_MANIFEST.json +0 -29
  228. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/README.md +0 -12
  229. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/__init__.py +0 -0
  230. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/base.py +0 -8
  231. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/__init__.py +0 -0
  232. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/my_protocol.py +0 -18
  233. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/CANVAS_MANIFEST.json +0 -29
  234. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/README.md +0 -12
  235. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/__init__.py +0 -0
  236. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/base.py +0 -3
  237. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py +0 -0
  238. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py +0 -18
  239. plugin_runner/tests/fixtures/plugins/test_render_template/CANVAS_MANIFEST.json +0 -47
  240. plugin_runner/tests/fixtures/plugins/test_render_template/README.md +0 -11
  241. plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py +0 -0
  242. plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py +0 -43
  243. plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html +0 -10
  244. plugin_runner/tests/fixtures/plugins/test_simple_api/CANVAS_MANIFEST.json +0 -47
  245. plugin_runner/tests/fixtures/plugins/test_simple_api/README.md +0 -11
  246. plugin_runner/tests/fixtures/plugins/test_simple_api/__init__.py +0 -0
  247. plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/__init__.py +0 -0
  248. plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/my_protocol.py +0 -43
  249. plugin_runner/tests/test_application.py +0 -65
  250. plugin_runner/tests/test_plugin_installer.py +0 -127
  251. plugin_runner/tests/test_plugin_runner.py +0 -388
  252. plugin_runner/tests/test_sandbox.py +0 -137
  253. {canvas-0.33.0.dist-info → canvas-0.33.1.dist-info}/WHEEL +0 -0
  254. {canvas-0.33.0.dist-info → canvas-0.33.1.dist-info}/entry_points.txt +0 -0
@@ -1,85 +0,0 @@
1
- import shutil
2
- from collections.abc import Generator
3
- from datetime import datetime
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- import pytest
8
- import typer
9
- from typer.testing import CliRunner
10
-
11
- from canvas_cli.main import app
12
-
13
- from .plugin import validate_package
14
-
15
- runner = CliRunner()
16
-
17
-
18
- def test_validate_package_unexistant_path() -> None:
19
- """Tests the validate_package callback with an invalid folder."""
20
- with pytest.raises(typer.BadParameter):
21
- validate_package(Path("/a_random_url_that_will_not_exist_or_so_I_hope"))
22
-
23
-
24
- def test_validate_package_wrong_file_type(tmp_path: Path) -> None:
25
- """Tests the validate_package callback with an invalid file type."""
26
- invalid_file = tmp_path / "tmp_file.zip"
27
- invalid_file.write_text("definitely not a python package")
28
-
29
- with pytest.raises(typer.BadParameter):
30
- validate_package(invalid_file)
31
-
32
-
33
- def test_validate_package_valid_file(tmp_path: Path) -> None:
34
- """Tests the validate_package callback with a valid file type."""
35
- package_path = tmp_path / "test-package.whl"
36
- package_path.write_text("something")
37
- result = validate_package(package_path)
38
- assert result == package_path
39
-
40
-
41
- @pytest.fixture(scope="session")
42
- def init_plugin_name() -> str:
43
- """The plugin name to be used for the canvas cli init test."""
44
- return f"testing_init-{datetime.now().timestamp()}".replace(".", "")
45
-
46
-
47
- @pytest.fixture(autouse=True, scope="session")
48
- def clean_up_plugin(init_plugin_name: str) -> Generator[Any, Any, Any]:
49
- """Cleans up the plugin directory after the test."""
50
- yield
51
- if Path(f"./{init_plugin_name}").exists():
52
- shutil.rmtree(Path(f"./{init_plugin_name}"))
53
-
54
-
55
- def test_canvas_init(init_plugin_name: str) -> None:
56
- """Tests that the CLI successfully creates a plugin with init."""
57
- result = runner.invoke(app, "init", input=init_plugin_name)
58
- assert result.exit_code == 0
59
-
60
- # plugin directory exists
61
- plugin = Path(f"./{init_plugin_name}")
62
- assert plugin.exists()
63
- assert plugin.is_dir()
64
-
65
- # manifest file exists
66
- manifest = Path(f"./{init_plugin_name}/CANVAS_MANIFEST.json")
67
- assert manifest.exists()
68
- assert manifest.is_file()
69
- manifest_result = runner.invoke(app, f"validate-manifest {init_plugin_name}")
70
- assert manifest_result.exit_code == 0
71
-
72
- # readme file exists
73
- readme = Path(f"./{init_plugin_name}/README.md")
74
- assert readme.exists()
75
- assert readme.is_file()
76
-
77
- # protocols dir exists
78
- protocols = Path(f"./{init_plugin_name}/protocols")
79
- assert protocols.exists()
80
- assert protocols.is_dir()
81
-
82
- # protocol file exists in protocols dir
83
- protocol = Path(f"./{init_plugin_name}/protocols/my_protocol.py")
84
- assert protocol.exists()
85
- assert protocol.is_file()
canvas_cli/conftest.py DELETED
@@ -1,28 +0,0 @@
1
- from pathlib import Path
2
-
3
- import pytest
4
-
5
- import canvas_cli.main
6
- from canvas_cli.utils.context import context
7
-
8
-
9
- @pytest.fixture(autouse=True)
10
- def monkeypatch_app_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
11
- """Monkeypatch `get_app_dir` in order to return a temp dir when testing, so we don't overwrite our config file."""
12
-
13
- def app_dir() -> str:
14
- return str(tmp_path)
15
-
16
- monkeypatch.setattr(canvas_cli.main, "get_app_dir", app_dir)
17
-
18
-
19
- @pytest.fixture(autouse=True)
20
- def reset_context_variables() -> None:
21
- """Reset the context properties to their default value.
22
-
23
- This is needed because we cannot build a `reset` method in the CLIContext class,
24
- because `load_from_file` loads properties dynamically.
25
- Also since this is a CLI, it's not expected to keep the global context in memory for more than a run,
26
- which definitely happens with tests run
27
- """
28
- context._default_host = None
canvas_cli/tests.py DELETED
@@ -1,217 +0,0 @@
1
- import os
2
- import shutil
3
- from collections.abc import Callable, Generator
4
- from datetime import datetime
5
- from pathlib import Path
6
- from typing import Any, cast
7
- from unittest.mock import MagicMock, patch
8
- from urllib.parse import urlparse
9
-
10
- import pytest
11
- from django.core.exceptions import ImproperlyConfigured
12
- from typer.testing import CliRunner
13
-
14
- import settings
15
- from canvas_cli.apps.auth.utils import CONFIG_PATH
16
-
17
- from .main import app
18
-
19
- runner = CliRunner()
20
-
21
-
22
- @pytest.fixture(scope="session")
23
- def plugin_name() -> str:
24
- """The plugin name to be used for the canvas cli test."""
25
- return f"cli-{datetime.now().timestamp()}".replace(".", "")
26
-
27
-
28
- @pytest.fixture(scope="session")
29
- def create_or_update_config_auth_file_for_testing(plugin_name: str) -> Generator[None, None, None]:
30
- """Creates the necessary config file for auth before performing cli tests."""
31
- if not settings.INTEGRATION_TEST_URL:
32
- raise ImproperlyConfigured("INTEGRATION_TEST_URL is not set")
33
-
34
- host = cast(str, urlparse(settings.INTEGRATION_TEST_URL).hostname).replace(
35
- ".canvasmedical.com", ""
36
- )
37
- client_id = settings.INTEGRATION_TEST_CLIENT_ID
38
- client_secret = settings.INTEGRATION_TEST_CLIENT_SECRET
39
-
40
- path = CONFIG_PATH
41
- if not path.exists():
42
- if not path.parent.exists():
43
- path.parent.mkdir()
44
- path.touch()
45
-
46
- temp_path = path.parent / "temp_credentials.ini"
47
-
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)
65
-
66
- yield
67
-
68
- with open(temp_path) as temp:
69
- original_content = temp.read()
70
- with open(path, "w") as f:
71
- f.write(original_content)
72
- os.remove(temp_path)
73
-
74
-
75
- @pytest.fixture(autouse=True, scope="session")
76
- def write_plugin(plugin_name: str) -> Generator[Any, Any, Any]:
77
- """Writes a plugin to the file system."""
78
- runner.invoke(app, "init", input=plugin_name)
79
-
80
- protocol_code = """
81
- from canvas_sdk.events import EventType
82
- from canvas_sdk.protocols import BaseProtocol
83
- from logger import log
84
-
85
- class Protocol(BaseProtocol):
86
- RESPONDS_TO = EventType.Name(EventType.ASSESS_COMMAND__CONDITION_SELECTED)
87
- NARRATIVE_STRING = "I was inserted from my plugin's protocol."
88
-
89
- def compute(self):
90
- log.info(self.NARRATIVE_STRING)
91
- return []
92
- """
93
-
94
- with open(f"./{plugin_name}/protocols/my_protocol.py", "w") as protocol:
95
- protocol.write(protocol_code)
96
-
97
- yield
98
-
99
- if Path(f"./{plugin_name}").exists():
100
- shutil.rmtree(Path(f"./{plugin_name}"))
101
-
102
-
103
- def list_empty_plugins(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
104
- """Step 1 - list all plugins."""
105
- return ("list", 0, [], [f"{plugin_name}"])
106
-
107
-
108
- def install_new_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
109
- """Step 2 - install a new plugin."""
110
- return (
111
- f"install {plugin_name}",
112
- 0,
113
- [
114
- f"Plugin {plugin_name} has a valid CANVAS_MANIFEST.json file",
115
- "Installing plugin:",
116
- "Posting",
117
- f"Plugin {plugin_name} successfully installed!",
118
- ],
119
- [],
120
- )
121
-
122
-
123
- def list_newly_installed_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
124
- """Step 3 - list all plugins, including newly installed one."""
125
- return ("list", 0, [f"{plugin_name}@0.0.1 enabled"], [])
126
-
127
-
128
- def disable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
129
- """Step 4 - disable plugin."""
130
- return (
131
- f"disable {plugin_name}",
132
- 0,
133
- [f"Disabling {plugin_name} using ", f"Plugin {plugin_name} successfully disabled!"],
134
- [],
135
- )
136
-
137
-
138
- def list_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
139
- """Step 5 - list disabled plugin."""
140
- return ("list", 0, [f"{plugin_name}@0.0.1 disabled"], [])
141
-
142
-
143
- def enable_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
144
- """Step 6 - enable the disabled plugin."""
145
- return (
146
- f"enable {plugin_name}",
147
- 0,
148
- [f"Enabling {plugin_name} using ", f"Plugin {plugin_name} successfully enabled!"],
149
- [],
150
- )
151
-
152
-
153
- def uninstall_enabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
154
- """Step 7 - try to uninstall the enabled plugin."""
155
- return (
156
- f"uninstall {plugin_name}",
157
- 1,
158
- [
159
- f"Uninstalling {plugin_name} using",
160
- 'Status code 403: {"detail":"Cannot delete an enabled plugin."}',
161
- ],
162
- [],
163
- )
164
-
165
-
166
- def uninstall_disabled_plugin(plugin_name: str) -> tuple[str, int, list[str], list[str]]:
167
- """Step 8 - disable and then uninstall the plugin."""
168
- runner.invoke(app, f"disable {plugin_name}")
169
-
170
- return (
171
- f"uninstall {plugin_name}",
172
- 0,
173
- [
174
- f"Uninstalling {plugin_name} using",
175
- f"Plugin {plugin_name} successfully uninstalled!",
176
- ],
177
- [],
178
- )
179
-
180
-
181
- @pytest.mark.integtest
182
- @patch("keyring.get_password")
183
- @patch("keyring.set_password")
184
- @pytest.mark.parametrize(
185
- "step",
186
- [
187
- (list_empty_plugins),
188
- (install_new_plugin),
189
- (list_newly_installed_plugin),
190
- (disable_plugin),
191
- (list_disabled_plugin),
192
- (enable_plugin),
193
- (uninstall_enabled_plugin),
194
- (uninstall_disabled_plugin),
195
- (list_empty_plugins),
196
- ],
197
- )
198
- def test_canvas_list_install_disable_enable_uninstall(
199
- mock_get_password: MagicMock,
200
- mock_set_password: MagicMock,
201
- plugin_name: str,
202
- create_or_update_config_auth_file_for_testing: None,
203
- step: Callable,
204
- ) -> None:
205
- """Tests that the Canvas CLI can list, install, disable, enable, and uninstall a plugin."""
206
- mock_get_password.return_value = None
207
- mock_set_password.return_value = None
208
-
209
- (command, expected_exit_code, expected_outputs, expected_no_outputs) = step(plugin_name)
210
-
211
- result = runner.invoke(app, command)
212
-
213
- assert result.exit_code == expected_exit_code
214
- for expected_output in expected_outputs:
215
- assert expected_output in result.stdout
216
- for expected_no_output in expected_no_outputs:
217
- assert expected_no_output not in result.stdout
@@ -1,131 +0,0 @@
1
- import json
2
- import os
3
- from collections.abc import Generator
4
- from pathlib import Path
5
-
6
- import pytest
7
- import typer
8
-
9
- from canvas_cli.utils.context import CLIContext
10
-
11
-
12
- class CLIContextTestHelper(CLIContext):
13
- """CLIContext subclass that defines some properties we can test."""
14
-
15
- _persistent_mock_property: str | None = None
16
- _transient_mock_property: bool = False
17
-
18
- @property
19
- def persistent_mock_property(self) -> str | None:
20
- """Mock persistent property."""
21
- return self._persistent_mock_property
22
-
23
- @persistent_mock_property.setter
24
- @CLIContext.persistent
25
- def persistent_mock_property(self, new_persistent_mock_property: str | None) -> None:
26
- self._persistent_mock_property = new_persistent_mock_property
27
-
28
- @property
29
- def transient_mock_property(self) -> bool:
30
- """Mock transient property."""
31
- return self._transient_mock_property
32
-
33
- @transient_mock_property.setter
34
- def transient_mock_property(self, new_transient_mock_property: bool) -> None:
35
- self._transient_mock_property = new_transient_mock_property
36
-
37
-
38
- @pytest.fixture
39
- def config_file(tmp_path: Path) -> Generator[Path, None, None]:
40
- """Fixture that yields an empty config file and cleans up after itself."""
41
- config_file = tmp_path / "mock_config.json"
42
- yield config_file
43
- os.remove(config_file)
44
-
45
-
46
- @pytest.fixture
47
- def valid_mock_config_file(config_file: Path) -> Path:
48
- """Fixture that yields a valid mock config file."""
49
- mock_config = {
50
- "persistent_mock_property": "mock-value",
51
- }
52
- with open(config_file, "w") as f:
53
- json.dump(mock_config, f)
54
-
55
- return config_file
56
-
57
-
58
- @pytest.fixture
59
- def invalid_json_mock_config_file(config_file: Path) -> Path:
60
- """Fixture that yields an invalid config file."""
61
- config_file.write_text("Absolutely invalid JSON")
62
-
63
- return config_file
64
-
65
-
66
- @pytest.fixture
67
- def invalid_properties_mock_config_file(config_file: Path) -> Path:
68
- """Fixture that yields a valid mock config file."""
69
- mock_config = {
70
- "unknown-property": "mock-value",
71
- }
72
- with open(config_file, "w") as f:
73
- json.dump(mock_config, f)
74
-
75
- return config_file
76
-
77
-
78
- def test_valid_load_from_file(valid_mock_config_file: Path) -> None:
79
- """Test loading a valid config file."""
80
- context = CLIContextTestHelper()
81
- context.load_from_file(valid_mock_config_file)
82
-
83
- assert context.persistent_mock_property == "mock-value"
84
-
85
-
86
- def test_invalid_load_from_file(invalid_json_mock_config_file: Path) -> None:
87
- """Test loading an invalid config file aborts the execution."""
88
- context = CLIContextTestHelper()
89
-
90
- with pytest.raises(typer.Abort):
91
- context.load_from_file(invalid_json_mock_config_file)
92
-
93
-
94
- def test_load_invalid_property(invalid_properties_mock_config_file: Path) -> None:
95
- """Since we dynamically load the properties, this test ensures that unknown properties don't throw exceptions."""
96
- context = CLIContextTestHelper()
97
-
98
- context.load_from_file(invalid_properties_mock_config_file)
99
-
100
-
101
- def test_config_persistence(valid_mock_config_file: Path) -> None:
102
- """Test marking a property with @persistent stores the value in the config file."""
103
- context = CLIContextTestHelper()
104
- context.load_from_file(valid_mock_config_file)
105
-
106
- assert context.persistent_mock_property == "mock-value"
107
-
108
- context.persistent_mock_property = "new-value"
109
-
110
- # This won't ever happen but since the values are in memory, we need to create a new context instance,
111
- # as if mimicking a new program launch
112
- context_b = CLIContextTestHelper()
113
- context_b.load_from_file(valid_mock_config_file)
114
-
115
- assert context_b.persistent_mock_property == "new-value"
116
-
117
-
118
- def test_config_transience(valid_mock_config_file: Path) -> None:
119
- """Test the properties transient default."""
120
- context = CLIContextTestHelper()
121
- context.load_from_file(valid_mock_config_file)
122
-
123
- assert context.transient_mock_property is False
124
- context.transient_mock_property = True
125
-
126
- # This won't ever happen but since the values are in memory, we need to create a new context instance,
127
- # as if mimicking a new program launch
128
- context_b = CLIContextTestHelper()
129
- context_b.load_from_file(valid_mock_config_file)
130
-
131
- assert context_b.transient_mock_property is False
@@ -1,69 +0,0 @@
1
- import json
2
- from typing import Any
3
- from unittest.mock import Mock
4
-
5
- import pytest
6
- from requests import Response
7
-
8
- from canvas_cli.utils.print import print
9
-
10
-
11
- @pytest.mark.parametrize(
12
- "message", ["a simple message", ["an array", "of messages"], {"one": "test"}]
13
- )
14
- def test_print_json_outputs_valid_json(message: Any, capfd: pytest.CaptureFixture[str]) -> None:
15
- """Test the output of print is always valid json."""
16
- print.json(message)
17
- output, _ = capfd.readouterr()
18
- try:
19
- json.loads(output)
20
- except ValueError as exc:
21
- assert AssertionError(f"{output} is not valid json: {exc}")
22
-
23
-
24
- def test_print_json_outputs_kwargs(capfd: pytest.CaptureFixture[str]) -> None:
25
- """Test the output of print contains all given kwargs."""
26
- print.json("A message", status_code=200, a_string="a_value", an_array=[1, 2], a_dict={"one": 2})
27
- output, _ = capfd.readouterr()
28
- try:
29
- json_dict = json.loads(output)
30
-
31
- assert json_dict.get("a_string") == "a_value"
32
- assert json_dict.get("an_array") == [1, 2]
33
- assert json_dict.get("a_dict") == {"one": 2}
34
-
35
- except ValueError as exc:
36
- assert AssertionError(f"{output} is not valid json: {exc}")
37
-
38
-
39
- def test_print_overrides_default(capfd: pytest.CaptureFixture[str]) -> None:
40
- """Test using `print` defaults to Rich."""
41
- message = "Testing print"
42
- print(message)
43
- output, _ = capfd.readouterr()
44
- assert message + "\n" == output
45
-
46
-
47
- def test_print_response_non_json_text(capfd: pytest.CaptureFixture[str]) -> None:
48
- """Test print.response with a non-json response."""
49
- response = Mock(spec=Response)
50
- response.status_code = 200
51
- response.text = "testing text"
52
- response.json.side_effect = json.JSONDecodeError("", "", 0)
53
- print.response(response)
54
- output, _ = capfd.readouterr()
55
- assert json.loads(output) == json.loads(
56
- '{"status_code": 200, "success": true, "message": "testing text"}'
57
- )
58
-
59
-
60
- def test_print_response_json_text(capfd: pytest.CaptureFixture[str]) -> None:
61
- """Test print.response with a json response."""
62
- response = Mock(spec=Response)
63
- response.status_code = 201
64
- response.json.return_value = {"something": True}
65
- print.response(response)
66
- output, _ = capfd.readouterr()
67
- assert json.loads(output) == json.loads(
68
- '{"status_code": 201, "success": true, "message": {"something": true} }'
69
- )
@@ -1,12 +0,0 @@
1
- import pytest
2
-
3
- from canvas_cli.utils.urls import CoreEndpoint
4
-
5
-
6
- @pytest.mark.parametrize("path", ["a-path", "/a-path", "/a-path/", "a-path/"])
7
- def test_endpoint_builder(path: str) -> None:
8
- """Test that the endpoint is always generated with a trailing `/`."""
9
- assert (
10
- CoreEndpoint.LOG.build("https://test-host.com", path)
11
- == "https://test-host.com/core/api/v1/logging/a-path/"
12
- )
@@ -1,37 +0,0 @@
1
- import pytest
2
-
3
- from canvas_cli.utils.validators import validate_manifest_file
4
-
5
-
6
- @pytest.fixture
7
- def protocol_manifest_example() -> dict:
8
- """Return a valid protocol manifest example."""
9
- return {
10
- "sdk_version": "0.3.1",
11
- "plugin_version": "1.0.1",
12
- "name": "Prompt to prescribe when assessing condition",
13
- "description": "To assist in ....",
14
- "components": {
15
- "protocols": [
16
- {
17
- "class": "prompt_to_prescribe.protocols.prompt_when_assessing.PromptWhenAssessing",
18
- "description": "probably the same as the plugin's description",
19
- "data_access": {
20
- "event": "",
21
- "read": ["conditions"],
22
- "write": ["commands"],
23
- },
24
- }
25
- ]
26
- },
27
- "tags": {"patient_sourcing_and_intake": ["symptom_triage"]},
28
- "references": [],
29
- "license": "",
30
- "diagram": False,
31
- "readme": "README.MD",
32
- }
33
-
34
-
35
- def test_manifest_file_schema(protocol_manifest_example: dict) -> None:
36
- """Test that no exception raised when a valid manifest file is validated."""
37
- validate_manifest_file(protocol_manifest_example)
File without changes
@@ -1,83 +0,0 @@
1
- from collections.abc import Generator
2
- from datetime import datetime
3
- from typing import cast
4
-
5
- import pytest
6
-
7
- import settings
8
- from canvas_sdk.commands.tests.test_utils import (
9
- COMMANDS,
10
- MaskedValue,
11
- clean_up_files_and_plugins,
12
- create_new_note,
13
- get_original_note_body_commands,
14
- get_token,
15
- install_plugin,
16
- trigger_plugin_event,
17
- wait_for_log,
18
- write_protocol_code,
19
- )
20
-
21
-
22
- @pytest.fixture(scope="session")
23
- def token() -> MaskedValue:
24
- """Get a valid token."""
25
- return get_token()
26
-
27
-
28
- @pytest.fixture(scope="session")
29
- def new_note(token: MaskedValue) -> dict:
30
- """Create a new note."""
31
- return create_new_note(token)
32
-
33
-
34
- @pytest.fixture(scope="session")
35
- def plugin_name() -> str:
36
- """The plugin name to be used."""
37
- return f"commands{datetime.now().timestamp()}".replace(".", "")
38
-
39
-
40
- @pytest.fixture(scope="session")
41
- def write_and_install_protocol_and_clean_up(
42
- plugin_name: str, token: MaskedValue, new_note: dict
43
- ) -> Generator[None, None, None]:
44
- """Write the protocol code, install the plugin, and clean up after the test."""
45
- write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS)
46
- message_received_event, thread, ws = wait_for_log(
47
- cast(str, settings.INTEGRATION_TEST_URL),
48
- token.value,
49
- f"Loading plugin '{plugin_name}",
50
- )
51
- install_plugin(plugin_name, token)
52
- message_received_event.wait(timeout=15.0)
53
-
54
- # unfortunately sometimes the log websocket just doesn't return any
55
- # messages, so asserting on the state of the timeout here causes failures
56
- # even though the delay itself will cause the waiting test to pass (because
57
- # the plugin has been loaded).
58
- # timeout_not_hit = message_received_event.wait(timeout=15.0)
59
- # if not timeout_not_hit:
60
- # ws.close()
61
- # assert timeout_not_hit, f"plugin loading message timeout hit: Loading plugin '{plugin_name}"
62
-
63
- yield
64
-
65
- ws.close()
66
- thread.join()
67
- clean_up_files_and_plugins(plugin_name, token)
68
-
69
-
70
- @pytest.mark.integtest
71
- def test_protocol_that_inserts_every_command(
72
- write_and_install_protocol_and_clean_up: None, token: MaskedValue, new_note: dict
73
- ) -> None:
74
- """Test that the protocol inserts every command."""
75
- trigger_plugin_event(token)
76
-
77
- commands_in_body = get_original_note_body_commands(new_note["id"], token)
78
-
79
- # TODO: Temporary workaround to ignore the updateGoal command until the integration test instance is fixed.
80
- command_keys = [c.Meta.key for c in COMMANDS]
81
- assert len(command_keys) == len(commands_in_body)
82
- for i, command_key in enumerate(command_keys):
83
- assert commands_in_body[i] == command_key
File without changes