canvas 0.26.0__tar.gz → 0.28.0__tar.gz

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 (363) hide show
  1. {canvas-0.26.0 → canvas-0.28.0}/PKG-INFO +1 -1
  2. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json +6 -4
  3. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/validators/manifest_schema.py +3 -0
  4. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/perform.py +1 -1
  5. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/show_button.py +2 -1
  6. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/action_button.py +6 -1
  7. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/simple_api/api.py +193 -28
  8. canvas-0.28.0/canvas_sdk/handlers/simple_api/tools.py +115 -0
  9. canvas-0.28.0/canvas_sdk/tests/handlers/test_simple_api.py +986 -0
  10. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/__init__.py +2 -0
  11. canvas-0.28.0/canvas_sdk/v1/data/banner_alert.py +27 -0
  12. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/lab.py +2 -2
  13. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/staff.py +3 -4
  14. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/sandbox.py +1 -0
  15. {canvas-0.26.0 → canvas-0.28.0}/pyproject.toml +1 -1
  16. canvas-0.26.0/canvas_sdk/tests/handlers/test_simple_api.py +0 -828
  17. {canvas-0.26.0 → canvas-0.28.0}/.gitignore +0 -0
  18. {canvas-0.26.0 → canvas-0.28.0}/README.md +0 -0
  19. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/__init__.py +0 -0
  20. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/__init__.py +0 -0
  21. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/auth/__init__.py +0 -0
  22. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/auth/tests.py +0 -0
  23. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/auth/utils.py +0 -0
  24. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/__init__.py +0 -0
  25. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/emit.py +0 -0
  26. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ALLERGY_INTOLERANCE_CREATED.ndjson +0 -0
  27. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ALLERGY_INTOLERANCE_UPDATED.ndjson +0 -0
  28. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CANCELED.ndjson +0 -0
  29. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CHECKED_IN.ndjson +0 -0
  30. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CREATED.ndjson +0 -0
  31. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_NO_SHOWED.ndjson +0 -0
  32. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_RESCHEDULED.ndjson +0 -0
  33. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_RESTORED.ndjson +0 -0
  34. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_UPDATED.ndjson +0 -0
  35. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__CONDITION_SELECTED.ndjson +0 -0
  36. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_COMMIT.ndjson +0 -0
  37. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_ORIGINATE.ndjson +0 -0
  38. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_UPDATE.ndjson +0 -0
  39. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_COMMIT.ndjson +0 -0
  40. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  41. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_UPDATE.ndjson +0 -0
  42. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/BILLING_LINE_ITEM_CREATED.ndjson +0 -0
  43. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/BILLING_LINE_ITEM_UPDATED.ndjson +0 -0
  44. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/CONDITION_ASSESSED.ndjson +0 -0
  45. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/CONDITION_CREATED.ndjson +0 -0
  46. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/CONDITION_UPDATED.ndjson +0 -0
  47. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/CRON.ndjson +0 -0
  48. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ENCOUNTER_CREATED.ndjson +0 -0
  49. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/ENCOUNTER_UPDATED.ndjson +0 -0
  50. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_CREATED.ndjson +0 -0
  51. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_STATEMENT_CREATED.ndjson +0 -0
  52. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_STATEMENT_UPDATED.ndjson +0 -0
  53. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_UPDATED.ndjson +0 -0
  54. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/INTERVIEW_CREATED.ndjson +0 -0
  55. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/INTERVIEW_UPDATED.ndjson +0 -0
  56. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/LAB_ORDER_CREATED.ndjson +0 -0
  57. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/LAB_ORDER_UPDATED.ndjson +0 -0
  58. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_LIST_ITEM_CREATED.ndjson +0 -0
  59. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_LIST_ITEM_UPDATED.ndjson +0 -0
  60. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_COMMIT.ndjson +0 -0
  61. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_ORIGINATE.ndjson +0 -0
  62. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_UPDATE.ndjson +0 -0
  63. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_COMMIT.ndjson +0 -0
  64. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  65. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_UPDATE.ndjson +0 -0
  66. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT__MEDICATION__POST_SEARCH.ndjson +0 -0
  67. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/PATIENT_CREATED.ndjson +0 -0
  68. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/PATIENT_UPDATED.ndjson +0 -0
  69. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/PLAN_COMMAND__POST_ORIGINATE.ndjson +0 -0
  70. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/PLAN_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  71. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_COMMIT.ndjson +0 -0
  72. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_ORIGINATE.ndjson +0 -0
  73. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_UPDATE.ndjson +0 -0
  74. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_COMMIT.ndjson +0 -0
  75. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  76. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_UPDATE.ndjson +0 -0
  77. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE__QUESTIONNAIRE__POST_SEARCH.ndjson +0 -0
  78. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/TASK_COMMENT_CREATED.ndjson +0 -0
  79. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/TASK_CREATED.ndjson +0 -0
  80. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/TASK_UPDATED.ndjson +0 -0
  81. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_CREATED.ndjson +0 -0
  82. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_UPDATED.ndjson +0 -0
  83. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/logs/__init__.py +0 -0
  84. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/logs/logs.py +0 -0
  85. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/plugin/__init__.py +0 -0
  86. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/plugin/plugin.py +0 -0
  87. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/plugin/tests.py +0 -0
  88. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/run_plugins/__init__.py +0 -0
  89. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/apps/run_plugins/run_plugins.py +0 -0
  90. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/conftest.py +0 -0
  91. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/main.py +0 -0
  92. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/cookiecutter.json +0 -0
  93. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/README.md +0 -0
  94. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/__init__.py +0 -0
  95. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/my_application.py +0 -0
  96. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/assets/python-logo.png +0 -0
  97. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/default/cookiecutter.json +0 -0
  98. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json +0 -0
  99. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/README.md +0 -0
  100. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/__init__.py +0 -0
  101. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +0 -0
  102. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/tests.py +0 -0
  103. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/__init__.py +0 -0
  104. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/context/__init__.py +0 -0
  105. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/context/context.py +0 -0
  106. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/context/tests.py +0 -0
  107. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/print/__init__.py +0 -0
  108. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/print/print.py +0 -0
  109. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/print/tests.py +0 -0
  110. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/urls/__init__.py +0 -0
  111. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/urls/tests.py +0 -0
  112. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/urls/urls.py +0 -0
  113. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/validators/__init__.py +0 -0
  114. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/validators/tests.py +0 -0
  115. {canvas-0.26.0 → canvas-0.28.0}/canvas_cli/utils/validators/validators.py +0 -0
  116. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/effects_pb2.py +0 -0
  117. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/effects_pb2.pyi +0 -0
  118. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/effects_pb2_grpc.py +0 -0
  119. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/events_pb2.py +0 -0
  120. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/events_pb2.pyi +0 -0
  121. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/events_pb2_grpc.py +0 -0
  122. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/plugins_pb2.py +0 -0
  123. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/plugins_pb2.pyi +0 -0
  124. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/messages/plugins_pb2_grpc.py +0 -0
  125. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/services/plugin_runner_pb2.py +0 -0
  126. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/services/plugin_runner_pb2.pyi +0 -0
  127. {canvas-0.26.0 → canvas-0.28.0}/canvas_generated/services/plugin_runner_pb2_grpc.py +0 -0
  128. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/__init__.py +0 -0
  129. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/base.py +0 -0
  130. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/__init__.py +0 -0
  131. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/base.py +0 -0
  132. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/__init__.py +0 -0
  133. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/adjust_prescription.py +0 -0
  134. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/allergy.py +0 -0
  135. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/assess.py +0 -0
  136. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/close_goal.py +0 -0
  137. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/diagnose.py +0 -0
  138. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/exam.py +0 -0
  139. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/family_history.py +0 -0
  140. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/follow_up.py +0 -0
  141. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/goal.py +0 -0
  142. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/history_present_illness.py +0 -0
  143. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/imaging_order.py +0 -0
  144. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/instruct.py +0 -0
  145. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/lab_order.py +0 -0
  146. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/medical_history.py +0 -0
  147. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/medication_statement.py +0 -0
  148. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/past_surgical_history.py +0 -0
  149. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/plan.py +0 -0
  150. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/prescribe.py +0 -0
  151. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/questionnaire/__init__.py +0 -0
  152. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/questionnaire/question.py +0 -0
  153. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/reason_for_visit.py +0 -0
  154. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/refer.py +0 -0
  155. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/refill.py +0 -0
  156. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/remove_allergy.py +0 -0
  157. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/resolve_condition.py +0 -0
  158. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/review_of_systems.py +0 -0
  159. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/stop_medication.py +0 -0
  160. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/structured_assessment.py +0 -0
  161. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/task.py +0 -0
  162. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/update_diagnosis.py +0 -0
  163. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/update_goal.py +0 -0
  164. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/commands/vitals.py +0 -0
  165. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/constants.py +0 -0
  166. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/protocol/__init__.py +0 -0
  167. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/protocol/tests.py +0 -0
  168. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/schema/__init__.py +0 -0
  169. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/schema/tests.py +0 -0
  170. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/test_base_command.py +0 -0
  171. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/test_utils.py +0 -0
  172. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/unit/__init__.py +0 -0
  173. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/commands/tests/unit/tests.py +0 -0
  174. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/__init__.py +0 -0
  175. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/banner_alert/__init__.py +0 -0
  176. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/banner_alert/add_banner_alert.py +0 -0
  177. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/banner_alert/remove_banner_alert.py +0 -0
  178. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/banner_alert/tests.py +0 -0
  179. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/base.py +0 -0
  180. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/billing_line_item/__init__.py +0 -0
  181. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/billing_line_item/add_billing_line_item.py +0 -0
  182. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/billing_line_item/remove_billing_line_item.py +0 -0
  183. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/billing_line_item/update_billing_line_item.py +0 -0
  184. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/launch_modal.py +0 -0
  185. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/patient_chart_summary_configuration.py +0 -0
  186. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/patient_portal/__init__.py +0 -0
  187. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/patient_portal/form_result.py +0 -0
  188. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/patient_portal_menu_configuration.py +0 -0
  189. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/patient_profile_configuration.py +0 -0
  190. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/protocol_card/__init__.py +0 -0
  191. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/protocol_card/protocol_card.py +0 -0
  192. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/protocol_card/tests.py +0 -0
  193. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/questionnaire_result.py +0 -0
  194. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/simple_api.py +0 -0
  195. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/surescripts/__init__.py +0 -0
  196. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/surescripts/surescripts_messages.py +0 -0
  197. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/task/__init__.py +0 -0
  198. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/task/task.py +0 -0
  199. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/widgets/__init__.py +0 -0
  200. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/effects/widgets/portal_widget.py +0 -0
  201. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/events/__init__.py +0 -0
  202. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/events/base.py +0 -0
  203. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/__init__.py +0 -0
  204. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/application.py +0 -0
  205. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/base.py +0 -0
  206. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/cron_task.py +0 -0
  207. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/simple_api/__init__.py +0 -0
  208. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/simple_api/exceptions.py +0 -0
  209. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/handlers/simple_api/security.py +0 -0
  210. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/protocols/__init__.py +0 -0
  211. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/protocols/base.py +0 -0
  212. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/protocols/clinical_quality_measure.py +0 -0
  213. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/protocols/timeframe.py +0 -0
  214. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/questionnaires/__init__.py +0 -0
  215. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/questionnaires/tests/__init__.py +0 -0
  216. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/questionnaires/tests/test_utils.py +0 -0
  217. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/questionnaires/utils.py +0 -0
  218. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/templates/__init__.py +0 -0
  219. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/templates/tests/__init__.py +0 -0
  220. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/templates/tests/test_utils.py +0 -0
  221. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/templates/utils.py +0 -0
  222. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/tests/__init__.py +0 -0
  223. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/tests/handlers/__init__.py +0 -0
  224. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/utils/__init__.py +0 -0
  225. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/utils/http.py +0 -0
  226. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/utils/plugins.py +0 -0
  227. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/utils/stats.py +0 -0
  228. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/utils/tests.py +0 -0
  229. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/__init__.py +0 -0
  230. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/apps.py +0 -0
  231. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/allergy_intolerance.py +0 -0
  232. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/appointment.py +0 -0
  233. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/assessment.py +0 -0
  234. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/base.py +0 -0
  235. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/billing.py +0 -0
  236. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/care_team.py +0 -0
  237. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/command.py +0 -0
  238. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/common.py +0 -0
  239. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/condition.py +0 -0
  240. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/coverage.py +0 -0
  241. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/detected_issue.py +0 -0
  242. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/device.py +0 -0
  243. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/imaging.py +0 -0
  244. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/medication.py +0 -0
  245. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/note.py +0 -0
  246. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/observation.py +0 -0
  247. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/organization.py +0 -0
  248. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/patient.py +0 -0
  249. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/practicelocation.py +0 -0
  250. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/protocol_override.py +0 -0
  251. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/questionnaire.py +0 -0
  252. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/reason_for_visit.py +0 -0
  253. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/task.py +0 -0
  254. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/team.py +0 -0
  255. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/data/user.py +0 -0
  256. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/v1/models.py +0 -0
  257. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/__init__.py +0 -0
  258. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/custom.py +0 -0
  259. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/hcc2018.py +0 -0
  260. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/tests/test_value_sets.py +0 -0
  261. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/__init__.py +0 -0
  262. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/adverse_event.py +0 -0
  263. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/allergy.py +0 -0
  264. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/assessment.py +0 -0
  265. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/communication.py +0 -0
  266. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/condition.py +0 -0
  267. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/device.py +0 -0
  268. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/diagnostic_study.py +0 -0
  269. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/encounter.py +0 -0
  270. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/immunization.py +0 -0
  271. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/individual_characteristic.py +0 -0
  272. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/intervention.py +0 -0
  273. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/laboratory_test.py +0 -0
  274. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/medication.py +0 -0
  275. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/physical_exam.py +0 -0
  276. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/procedure.py +0 -0
  277. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/v2022/symptom.py +0 -0
  278. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/value_set/value_set.py +0 -0
  279. {canvas-0.26.0 → canvas-0.28.0}/canvas_sdk/views/__init__.py +0 -0
  280. {canvas-0.26.0 → canvas-0.28.0}/logger/__init__.py +0 -0
  281. {canvas-0.26.0 → canvas-0.28.0}/logger/logger.py +0 -0
  282. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/__init__.py +0 -0
  283. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/authentication.py +0 -0
  284. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/aws_headers.py +0 -0
  285. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/exceptions.py +0 -0
  286. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/installation.py +0 -0
  287. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/plugin_runner.py +0 -0
  288. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/__init__.py +0 -0
  289. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json +0 -0
  290. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/example_plugin/README.md +0 -0
  291. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/example_plugin/__init__.py +0 -0
  292. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/example_plugin/protocols/__init__.py +0 -0
  293. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/example_plugin/protocols/my_protocol.py +0 -0
  294. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +0 -0
  295. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +0 -0
  296. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
  297. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +0 -0
  298. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +0 -0
  299. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +0 -0
  300. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +0 -0
  301. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +0 -0
  302. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_load_questionnaire/CANVAS_MANIFEST.json +0 -0
  303. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_load_questionnaire/README.md +0 -0
  304. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/__init__.py +0 -0
  305. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/my_protocol.py +0 -0
  306. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_load_questionnaire/questionnaires/example_questionnaire.yml +0 -0
  307. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +0 -0
  308. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +0 -0
  309. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
  310. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +0 -0
  311. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
  312. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +0 -0
  313. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +0 -0
  314. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +0 -0
  315. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
  316. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +0 -0
  317. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
  318. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +0 -0
  319. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/CANVAS_MANIFEST.json +0 -0
  320. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/README.md +0 -0
  321. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/__init__.py +0 -0
  322. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/base.py +0 -0
  323. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/__init__.py +0 -0
  324. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/my_protocol.py +0 -0
  325. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/CANVAS_MANIFEST.json +0 -0
  326. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/README.md +0 -0
  327. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/__init__.py +0 -0
  328. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/base.py +0 -0
  329. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/__init__.py +0 -0
  330. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/my_protocol.py +0 -0
  331. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/CANVAS_MANIFEST.json +0 -0
  332. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/README.md +0 -0
  333. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/__init__.py +0 -0
  334. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/base.py +0 -0
  335. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/__init__.py +0 -0
  336. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/my_protocol.py +0 -0
  337. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/CANVAS_MANIFEST.json +0 -0
  338. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/README.md +0 -0
  339. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/__init__.py +0 -0
  340. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/base.py +0 -0
  341. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py +0 -0
  342. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py +0 -0
  343. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_render_template/CANVAS_MANIFEST.json +0 -0
  344. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_render_template/README.md +0 -0
  345. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py +0 -0
  346. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py +0 -0
  347. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html +0 -0
  348. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_simple_api/CANVAS_MANIFEST.json +0 -0
  349. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_simple_api/README.md +0 -0
  350. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_simple_api/__init__.py +0 -0
  351. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/__init__.py +0 -0
  352. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/my_protocol.py +0 -0
  353. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/test_application.py +0 -0
  354. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/test_plugin_installer.py +0 -0
  355. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/test_plugin_runner.py +0 -0
  356. {canvas-0.26.0 → canvas-0.28.0}/plugin_runner/tests/test_sandbox.py +0 -0
  357. {canvas-0.26.0 → canvas-0.28.0}/protobufs/canvas_generated/messages/effects.proto +0 -0
  358. {canvas-0.26.0 → canvas-0.28.0}/protobufs/canvas_generated/messages/events.proto +0 -0
  359. {canvas-0.26.0 → canvas-0.28.0}/protobufs/canvas_generated/messages/plugins.proto +0 -0
  360. {canvas-0.26.0 → canvas-0.28.0}/protobufs/canvas_generated/services/plugin_runner.proto +0 -0
  361. {canvas-0.26.0 → canvas-0.28.0}/pubsub/__init__.py +0 -0
  362. {canvas-0.26.0 → canvas-0.28.0}/pubsub/pubsub.py +0 -0
  363. {canvas-0.26.0 → canvas-0.28.0}/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: canvas
3
- Version: 0.26.0
3
+ Version: 0.28.0
4
4
  Summary: SDK to customize event-driven actions in your Canvas instance
5
5
  Author-email: Canvas Team <engineering@canvasmedical.com>
6
6
  License-Expression: MIT
@@ -3,10 +3,12 @@
3
3
  "plugin_version": "0.0.1",
4
4
  "name": "{{ cookiecutter.__project_slug }}",
5
5
  "description": "Edit the description in CANVAS_MANIFEST.json",
6
- "origins": {
7
- "urls": ["https://www.canvasmedical.com/extensions"],
8
- "scripts": []
9
- },
6
+ "url_permissions": [
7
+ {
8
+ "url": "https://www.canvasmedical.com/extensions",
9
+ "permissions": []
10
+ }
11
+ ],
10
12
  "components": {
11
13
  "applications": [
12
14
  {
@@ -57,6 +57,7 @@ manifest_schema = {
57
57
  "readme",
58
58
  ],
59
59
  "additionalProperties": False,
60
+ "allOf": [{"not": {"required": ["url_permissions", "origins"]}}],
60
61
  "$defs": {
61
62
  "origins": {
62
63
  "type": "object",
@@ -69,6 +70,7 @@ manifest_schema = {
69
70
  "type": "array",
70
71
  "items": {
71
72
  "type": "object",
73
+ "additionalProperties": False,
72
74
  "properties": {
73
75
  "url": {"type": "string"},
74
76
  "permissions": {
@@ -83,6 +85,7 @@ manifest_schema = {
83
85
  },
84
86
  },
85
87
  },
88
+ "required": ["url", "permissions"],
86
89
  },
87
90
  },
88
91
  "component": {
@@ -8,5 +8,5 @@ class PerformCommand(BaseCommand):
8
8
  key = "perform"
9
9
  commit_required_fields = ("cpt_code",)
10
10
 
11
- cpt_code: str
11
+ cpt_code: str | None = None
12
12
  notes: str | None = None
@@ -16,11 +16,12 @@ class ShowButtonEffect(_BaseEffect):
16
16
 
17
17
  key: str = Field(min_length=1)
18
18
  title: str = Field(min_length=1)
19
+ priority: int = Field(default=0)
19
20
 
20
21
  @property
21
22
  def values(self) -> dict[str, Any]:
22
23
  """The ShowButtonEffect's values."""
23
- return {"key": self.key, "title": self.title}
24
+ return {"key": self.key, "title": self.title, "priority": self.priority}
24
25
 
25
26
  @property
26
27
  def effect_payload(self) -> dict[str, Any]:
@@ -48,6 +48,7 @@ class ActionButton(BaseHandler):
48
48
  BUTTON_TITLE: str = ""
49
49
  BUTTON_KEY: str = ""
50
50
  BUTTON_LOCATION: ButtonLocation
51
+ PRIORITY: int = 0
51
52
 
52
53
  @abstractmethod
53
54
  def handle(self) -> list[Effect]:
@@ -68,7 +69,11 @@ class ActionButton(BaseHandler):
68
69
  if show_button_event_match:
69
70
  location = show_button_event_match.group(1)
70
71
  if self.ButtonLocation[location] == self.BUTTON_LOCATION and self.visible():
71
- return [ShowButtonEffect(key=self.BUTTON_KEY, title=self.BUTTON_TITLE).apply()]
72
+ return [
73
+ ShowButtonEffect(
74
+ key=self.BUTTON_KEY, title=self.BUTTON_TITLE, priority=self.PRIORITY
75
+ ).apply()
76
+ ]
72
77
  elif self.context["key"] == self.BUTTON_KEY:
73
78
  return self.handle()
74
79
 
@@ -5,10 +5,8 @@ from base64 import b64decode
5
5
  from collections.abc import Callable
6
6
  from functools import cached_property
7
7
  from http import HTTPStatus
8
- from typing import Any, TypeVar
9
- from urllib.parse import parse_qs
10
-
11
- from requests.structures import CaseInsensitiveDict
8
+ from typing import Any, ClassVar, Protocol, TypeVar, cast
9
+ from urllib.parse import parse_qsl
12
10
 
13
11
  from canvas_sdk.effects import Effect, EffectType
14
12
  from canvas_sdk.effects.simple_api import JSONResponse, Response
@@ -19,9 +17,9 @@ from plugin_runner.exceptions import PluginError
19
17
 
20
18
  from .exceptions import AuthenticationError, InvalidCredentialsError
21
19
  from .security import Credentials
20
+ from .tools import CaseInsensitiveMultiDict, MultiDict, separate_headers
22
21
 
23
22
  # TODO: Routing by path regex?
24
- # TODO: Support multipart/form-data by adding helpers to the request class
25
23
  # TODO: Log requests in a format similar to other API frameworks (probably need effect metadata)
26
24
  # TODO: Support Effect metadata that is separate from payload
27
25
  # TODO: Encode event payloads with MessagePack instead of JSON
@@ -30,6 +28,137 @@ from .security import Credentials
30
28
  JSON = dict[str, "JSON"] | list["JSON"] | int | float | str | bool | None
31
29
 
32
30
 
31
+ class FormPart(Protocol):
32
+ """
33
+ Protocol for representing a form part in the body of a multipart/form-data request.
34
+
35
+ A form part can represent a simple string value, or a file with a content type.
36
+ """
37
+
38
+ @staticmethod
39
+ def is_file() -> bool:
40
+ """Return True or False depending on whether the form part represents a file."""
41
+ ...
42
+
43
+
44
+ class StringFormPart(FormPart):
45
+ """Class for representing a form part that is a simple string value."""
46
+
47
+ def __init__(self, name: str, value: str) -> None:
48
+ self.name = name
49
+ self.value = value
50
+
51
+ @staticmethod
52
+ def is_file() -> bool:
53
+ """Return True or False depending on whether the form part represents a file."""
54
+ return False
55
+
56
+ def __eq__(self, other: object) -> bool:
57
+ if isinstance(other, FileFormPart):
58
+ return False
59
+
60
+ if not isinstance(other, StringFormPart):
61
+ return NotImplemented
62
+
63
+ return self.name == other.name and self.value == other.value
64
+
65
+
66
+ class FileFormPart(FormPart):
67
+ """Class for representing a form part that is a file."""
68
+
69
+ def __init__(
70
+ self, name: str, filename: str, content: bytes, content_type: str | None = None
71
+ ) -> None:
72
+ self.name = name
73
+ self.filename = filename
74
+ self.content = content
75
+ self.content_type = content_type
76
+
77
+ @staticmethod
78
+ def is_file() -> bool:
79
+ """Return True or False depending on whether the form part represents a file."""
80
+ return True
81
+
82
+ def __eq__(self, other: object) -> bool:
83
+ if isinstance(other, StringFormPart):
84
+ return False
85
+
86
+ if not isinstance(other, FileFormPart):
87
+ return NotImplemented
88
+
89
+ return all(
90
+ (
91
+ self.name == other.name,
92
+ self.filename == other.filename,
93
+ self.content == other.content,
94
+ self.content_type == other.content_type,
95
+ )
96
+ )
97
+
98
+
99
+ def parse_multipart_form(form: bytes, boundary: str) -> MultiDict[str, FormPart]:
100
+ """Parse a multipart form and return a dict of string to list of form parts."""
101
+ form_data: list[tuple[str, FormPart]] = []
102
+
103
+ # Split the body by the boundary value and iterate over the parts. The first and last
104
+ # parts can be skipped because there are delimiters on at the start and end of the body
105
+ parts = form.split(f"--{boundary}".encode())
106
+ for part in parts[1:-1]:
107
+ # Each part may be either a simple string value or a file. Simple string values
108
+ # will just have a name and a value, whereas files will have a name, content type,
109
+ # filename, and value.
110
+ name = None
111
+ content_type = None
112
+ filename = None
113
+
114
+ # Split the part into the headers and the value (i.e. the content)
115
+ value: str | bytes
116
+ headers, value = part.split(b"\r\n\r\n", maxsplit=1)
117
+
118
+ # Iterate over the headers and extract the name, filename, and content type
119
+ for header in headers.decode().split("\r\n"):
120
+ # There are only two possible headers: Content-Disposition and Content-Type
121
+ if header.lower().startswith("content-disposition: form-data;"):
122
+ # Iterate over the content disposition parameters to get the form name and
123
+ # filename
124
+ for parameter in header.split(";")[1:]:
125
+ parameter_name, parameter_value = parameter.strip().split("=")
126
+
127
+ # Strip the quotes from the value
128
+ parameter_value = parameter_value[1:-1]
129
+
130
+ if parameter_name == "name":
131
+ name = parameter_value
132
+ elif parameter_name == "filename":
133
+ filename = parameter_value
134
+ elif header.lower().startswith("content-type"):
135
+ # Files will have a content type, so grab it
136
+ content_type = header.split(":")[1].strip()
137
+
138
+ if not name or not value:
139
+ raise RuntimeError("Invalid multipart/form-data request body")
140
+
141
+ # Strip off the trailing newline characters from the value
142
+ value = value[:-2]
143
+
144
+ # Now we have all the data, so append it to the list of form data
145
+ if filename:
146
+ # Because a filename was provided, we know it's a file and not a simple string value
147
+ form_data.append(
148
+ (
149
+ name,
150
+ FileFormPart(
151
+ name=name, filename=filename, content=value, content_type=content_type
152
+ ),
153
+ )
154
+ )
155
+ else:
156
+ # Decode the string before adding it
157
+ form_data.append((name, StringFormPart(name, value.decode())))
158
+
159
+ return MultiDict(form_data)
160
+
161
+
33
162
  class Request:
34
163
  """Request class for incoming requests to the API."""
35
164
 
@@ -38,10 +167,21 @@ class Request:
38
167
  self.path = event.context["path"]
39
168
  self.query_string = event.context["query_string"]
40
169
  self._body = event.context["body"]
41
- self.headers: CaseInsensitiveDict = CaseInsensitiveDict(event.context["headers"])
42
-
43
- self.query_params = parse_qs(self.query_string)
44
- self.content_type = self.headers.get("Content-Type")
170
+ self.headers = CaseInsensitiveMultiDict(separate_headers(event.context["headers"]))
171
+
172
+ self.query_params = MultiDict(parse_qsl(self.query_string))
173
+
174
+ # Parse the content type and any included content type parameters
175
+ content_type = self.headers.get("Content-Type")
176
+ self._content_type_parameters = {}
177
+ if content_type:
178
+ content_type, *parameters = content_type.split(";")
179
+ self.content_type = content_type.strip()
180
+ for parameter in parameters:
181
+ name, value = parameter.strip().split("=")
182
+ self._content_type_parameters[name] = value
183
+ else:
184
+ self.content_type = None
45
185
 
46
186
  @cached_property
47
187
  def body(self) -> bytes:
@@ -49,13 +189,31 @@ class Request:
49
189
  return b64decode(self._body)
50
190
 
51
191
  def json(self) -> JSON:
52
- """Return the response JSON."""
192
+ """Return the response body as a JSON dict."""
53
193
  return json.loads(self.body)
54
194
 
55
195
  def text(self) -> str:
56
196
  """Return the response body as plain text."""
57
197
  return self.body.decode()
58
198
 
199
+ def form_data(self) -> MultiDict[str, FormPart]:
200
+ """Return the response body as a dict of string to list of FormPart objects."""
201
+ form_data: MultiDict[str, FormPart]
202
+
203
+ if self.content_type == "application/x-www-form-urlencoded":
204
+ # For request bodies that are URL-encoded, just parse them and return them as simple
205
+ # form parts
206
+ form_data = MultiDict(
207
+ (name, StringFormPart(name, value)) for name, value in parse_qsl(self.body.decode())
208
+ )
209
+ elif self.content_type == "multipart/form-data":
210
+ # Parse request bodies that are multipart forms
211
+ form_data = parse_multipart_form(self.body, self._content_type_parameters["boundary"])
212
+ else:
213
+ raise RuntimeError(f"Cannot parse content type {self.content_type} as form data")
214
+
215
+ return form_data
216
+
59
217
 
60
218
  SimpleAPIType = TypeVar("SimpleAPIType", bound="SimpleAPIBase")
61
219
 
@@ -108,20 +266,23 @@ class SimpleAPIBase(BaseHandler, ABC):
108
266
  EventType.Name(EventType.SIMPLE_API_REQUEST),
109
267
  ]
110
268
 
111
- def __init__(self, *args: Any, **kwargs: Any) -> None:
112
- super().__init__(*args, **kwargs)
269
+ _ROUTES: ClassVar[dict[tuple[str, str], RouteHandler]]
270
+
271
+ def __init_subclass__(cls, **kwargs: Any) -> None:
272
+ super().__init_subclass__(**kwargs)
113
273
 
114
274
  # Build the registry of routes so that requests can be routed to the correct handler. This
115
- # is done by iterating over the methods on the class instance and looking for methods that
116
- # have been marked by the handler decorators (get, post, etc.).
117
- self._routes: dict[tuple[str, str], Callable] = {}
118
- for attr in self.__class__.__dict__.values():
275
+ # is done by iterating over the methods on the class and looking for methods that have been
276
+ # marked by the handler decorators (get, post, etc.).
277
+ cls._ROUTES = {}
278
+ for attr in cls.__dict__.values():
119
279
  if callable(attr) and (route := getattr(attr, "route", None)):
120
280
  method, relative_path = route
121
- path = f"{self._path_prefix()}{relative_path}"
122
- self._routes[(method, path)] = attr
281
+ path = f"{cls._path_prefix()}{relative_path}"
282
+ cls._ROUTES[(method, path)] = attr
123
283
 
124
- def _path_prefix(self) -> str:
284
+ @classmethod
285
+ def _path_prefix(cls) -> str:
125
286
  return ""
126
287
 
127
288
  @cached_property
@@ -174,7 +335,7 @@ class SimpleAPIBase(BaseHandler, ABC):
174
335
  def _handle_request(self) -> list[Effect]:
175
336
  """Route the incoming request to the handler method based on the HTTP method and path."""
176
337
  # Get the handler method
177
- handler = self._routes[(self.request.method, self.request.path)]
338
+ handler = self._ROUTES[(self.request.method, self.request.path)]
178
339
 
179
340
  # Handle the request
180
341
  effects = handler(self)
@@ -191,7 +352,7 @@ class SimpleAPIBase(BaseHandler, ABC):
191
352
  if isinstance(effect, Response):
192
353
  effects[index] = effect.apply()
193
354
 
194
- if effects[index].type == EffectType.SIMPLE_API_RESPONSE:
355
+ if cast(Effect, effects[index]).type == EffectType.SIMPLE_API_RESPONSE:
195
356
  # If a response has already been found, return an error response immediately
196
357
  if response_found:
197
358
  log.error(f"Multiple responses provided by {SimpleAPI.__name__} handler")
@@ -210,13 +371,13 @@ class SimpleAPIBase(BaseHandler, ABC):
210
371
  # If the handler returned an error response, return only that response effect
211
372
  # and omit any other included effects
212
373
  if 400 <= status_code <= 599:
213
- return [effects[index]]
374
+ return [cast(Effect, effects[index])]
214
375
 
215
- return effects
376
+ return cast(list[Effect], effects)
216
377
 
217
378
  def accept_event(self) -> bool:
218
379
  """Ignore the event if the handler does not implement the route."""
219
- return (self.request.method, self.request.path) in self._routes
380
+ return (self.request.method, self.request.path) in self._ROUTES
220
381
 
221
382
  def authenticate(self, credentials: Credentials) -> bool:
222
383
  """Method the user should override to authenticate requests."""
@@ -257,8 +418,12 @@ class SimpleAPI(SimpleAPIBase, ABC):
257
418
  f"class attributes: {', '.join(f'{cls.__name__}.{name}' for name in names)}"
258
419
  )
259
420
 
260
- def _path_prefix(self) -> str:
261
- return getattr(self, "PREFIX", None) or ""
421
+ @classmethod
422
+ def _path_prefix(cls) -> str:
423
+ # getattr needs a default else it will raise an exception. We also need to ensure that the
424
+ # final value is a string if the user specifies "None" as the prefix because this value gets
425
+ # prepended to the URL path
426
+ return getattr(cls, "PREFIX", "") or ""
262
427
 
263
428
 
264
429
  class SimpleAPIRoute(SimpleAPIBase, ABC):
@@ -271,8 +436,6 @@ class SimpleAPIRoute(SimpleAPIBase, ABC):
271
436
  f"Setting a PREFIX value on a {SimpleAPIRoute.__name__} is not allowed"
272
437
  )
273
438
 
274
- super().__init_subclass__(**kwargs)
275
-
276
439
  for attr_name, attr_value in cls.__dict__.items():
277
440
  decorator: Callable | None
278
441
  match attr_name:
@@ -307,6 +470,8 @@ class SimpleAPIRoute(SimpleAPIBase, ABC):
307
470
 
308
471
  decorator(path)(attr_value)
309
472
 
473
+ super().__init_subclass__(**kwargs)
474
+
310
475
  def get(self) -> list[Response | Effect]:
311
476
  """Stub method for GET handler."""
312
477
  return []
@@ -0,0 +1,115 @@
1
+ from collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, ValuesView
2
+ from typing import Any, TypeVar, overload
3
+
4
+ KeyType = TypeVar("KeyType")
5
+ ValueType = TypeVar("ValueType", covariant=True)
6
+
7
+
8
+ class MultiDict(Mapping[KeyType, ValueType]):
9
+ """Immutable key-value data structure that can store multiple values per key."""
10
+
11
+ def __init__(self, items: Iterable[tuple[KeyType, ValueType]] | None = None) -> None:
12
+ self._dict: dict[KeyType, ValueType] = {}
13
+ self._items = []
14
+ for key, value in items or ():
15
+ if key not in self._dict:
16
+ self._dict[key] = value
17
+ self._items.append((key, value))
18
+
19
+ def __len__(self) -> int:
20
+ return len(self._dict)
21
+
22
+ def __iter__(self) -> Iterator[KeyType]:
23
+ return iter(self.keys())
24
+
25
+ def __getitem__(self, key: KeyType, /) -> ValueType:
26
+ return self._dict[key]
27
+
28
+ def __contains__(self, x: object, /) -> bool:
29
+ return x in self._dict
30
+
31
+ @overload
32
+ def get(self, __key: KeyType, /) -> Any: ...
33
+
34
+ @overload
35
+ def get(self, __key: KeyType, /, __default: Any = None) -> Any: ...
36
+
37
+ def get(self, __key: KeyType, __default: Any | None = None) -> Any:
38
+ """Get a value for a key if present, and if not, return the default value."""
39
+ return self._dict.get(__key, __default)
40
+
41
+ def get_list(self, __key: KeyType) -> list[ValueType]:
42
+ """Get the values for a key if present, and if not, return an empty list."""
43
+ return [value for key, value in self._items if key == __key]
44
+
45
+ def items(self) -> ItemsView[KeyType, ValueType]:
46
+ """Return an items view of the dict."""
47
+ return self._dict.items()
48
+
49
+ def multi_items(self) -> Iterable[tuple[KeyType, ValueType]]:
50
+ """Return an iterable of tuples of the keys and values in the dict."""
51
+ yield from self._items
52
+
53
+ def keys(self) -> KeysView[KeyType]:
54
+ """Return a keys view of the dict."""
55
+ return self._dict.keys()
56
+
57
+ def __reversed__(self) -> Iterator[KeyType]:
58
+ return iter(reversed(list(self.keys())))
59
+
60
+ def values(self) -> ValuesView[ValueType]:
61
+ """Return a values view of the dict."""
62
+ return self._dict.values()
63
+
64
+ def __eq__(self, other: object) -> bool:
65
+ if not isinstance(other, MultiDict):
66
+ return NotImplemented
67
+
68
+ return other._items == self._items
69
+
70
+
71
+ class CaseInsensitiveMultiDict(MultiDict[str, ValueType]):
72
+ """
73
+ Case-insensitive immutable key-value data structure that can store multiple values per key.
74
+
75
+ Keys in the dict are interpreted in a case-insensitive manner.
76
+ """
77
+
78
+ def __init__(self, items: Iterable[tuple[str, ValueType]] | None = None) -> None:
79
+ super().__init__(((key.lower(), value) for key, value in items or ()))
80
+
81
+ def __getitem__(self, key: str, /) -> ValueType:
82
+ return super().__getitem__(key.lower())
83
+
84
+ def __contains__(self, x: object, /) -> bool:
85
+ if not isinstance(x, str):
86
+ return False
87
+
88
+ return super().__contains__(x.lower())
89
+
90
+ def get(self, __key: KeyType, __default: Any | None = None) -> Any:
91
+ """Get a value for a key if present, and if not, return the default value."""
92
+ if not isinstance(__key, str):
93
+ return __default
94
+
95
+ return super().get(__key.lower(), __default)
96
+
97
+ def get_list(self, __key: KeyType) -> list[ValueType]:
98
+ """Get the values for a key if present, and if not, return an empty list."""
99
+ if not isinstance(__key, str):
100
+ return []
101
+
102
+ return super().get_list(__key.lower())
103
+
104
+
105
+ def separate_headers(headers: Mapping[str, str]) -> list[tuple[str, str]]:
106
+ """
107
+ Break apart header values containing comma-separated lists into discrete key-value pairs.
108
+ """
109
+ headers_list = []
110
+
111
+ for key, values in headers.items():
112
+ for value in values.split(","):
113
+ headers_list.append((key, value.strip()))
114
+
115
+ return headers_list