canvas 0.35.0__tar.gz → 0.35.1__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 (280) hide show
  1. {canvas-0.35.0 → canvas-0.35.1}/PKG-INFO +2 -2
  2. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/plugin_runner.py +57 -89
  3. {canvas-0.35.0 → canvas-0.35.1}/pubsub/pubsub.py +10 -1
  4. {canvas-0.35.0 → canvas-0.35.1}/pyproject.toml +2 -4
  5. {canvas-0.35.0 → canvas-0.35.1}/settings.py +4 -0
  6. {canvas-0.35.0 → canvas-0.35.1}/.gitignore +0 -0
  7. {canvas-0.35.0 → canvas-0.35.1}/README.md +0 -0
  8. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/__init__.py +0 -0
  9. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/__init__.py +0 -0
  10. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/auth/__init__.py +0 -0
  11. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/auth/utils.py +0 -0
  12. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/__init__.py +0 -0
  13. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/emit.py +0 -0
  14. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ALLERGY_INTOLERANCE_CREATED.ndjson +0 -0
  15. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ALLERGY_INTOLERANCE_UPDATED.ndjson +0 -0
  16. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CANCELED.ndjson +0 -0
  17. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CHECKED_IN.ndjson +0 -0
  18. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_CREATED.ndjson +0 -0
  19. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_NO_SHOWED.ndjson +0 -0
  20. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_RESCHEDULED.ndjson +0 -0
  21. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_RESTORED.ndjson +0 -0
  22. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/APPOINTMENT_UPDATED.ndjson +0 -0
  23. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__CONDITION_SELECTED.ndjson +0 -0
  24. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_COMMIT.ndjson +0 -0
  25. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_ORIGINATE.ndjson +0 -0
  26. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__POST_UPDATE.ndjson +0 -0
  27. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_COMMIT.ndjson +0 -0
  28. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  29. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ASSESS_COMMAND__PRE_UPDATE.ndjson +0 -0
  30. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/BILLING_LINE_ITEM_CREATED.ndjson +0 -0
  31. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/BILLING_LINE_ITEM_UPDATED.ndjson +0 -0
  32. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/CONDITION_ASSESSED.ndjson +0 -0
  33. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/CONDITION_CREATED.ndjson +0 -0
  34. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/CONDITION_UPDATED.ndjson +0 -0
  35. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/CRON.ndjson +0 -0
  36. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ENCOUNTER_CREATED.ndjson +0 -0
  37. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/ENCOUNTER_UPDATED.ndjson +0 -0
  38. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_CREATED.ndjson +0 -0
  39. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_STATEMENT_CREATED.ndjson +0 -0
  40. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_STATEMENT_UPDATED.ndjson +0 -0
  41. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/IMMUNIZATION_UPDATED.ndjson +0 -0
  42. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/INTERVIEW_CREATED.ndjson +0 -0
  43. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/INTERVIEW_UPDATED.ndjson +0 -0
  44. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/LAB_ORDER_CREATED.ndjson +0 -0
  45. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/LAB_ORDER_UPDATED.ndjson +0 -0
  46. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_LIST_ITEM_CREATED.ndjson +0 -0
  47. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_LIST_ITEM_UPDATED.ndjson +0 -0
  48. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_COMMIT.ndjson +0 -0
  49. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_ORIGINATE.ndjson +0 -0
  50. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__POST_UPDATE.ndjson +0 -0
  51. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_COMMIT.ndjson +0 -0
  52. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  53. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT_COMMAND__PRE_UPDATE.ndjson +0 -0
  54. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/MEDICATION_STATEMENT__MEDICATION__POST_SEARCH.ndjson +0 -0
  55. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/PATIENT_CREATED.ndjson +0 -0
  56. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/PATIENT_UPDATED.ndjson +0 -0
  57. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/PLAN_COMMAND__POST_ORIGINATE.ndjson +0 -0
  58. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/PLAN_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  59. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_COMMIT.ndjson +0 -0
  60. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_ORIGINATE.ndjson +0 -0
  61. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__POST_UPDATE.ndjson +0 -0
  62. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_COMMIT.ndjson +0 -0
  63. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_ORIGINATE.ndjson +0 -0
  64. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE_COMMAND__PRE_UPDATE.ndjson +0 -0
  65. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/QUESTIONNAIRE__QUESTIONNAIRE__POST_SEARCH.ndjson +0 -0
  66. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/SIMPLE_API_REQUEST.ndjson +0 -0
  67. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/TASK_COMMENT_CREATED.ndjson +0 -0
  68. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/TASK_CREATED.ndjson +0 -0
  69. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/TASK_UPDATED.ndjson +0 -0
  70. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_CREATED.ndjson +0 -0
  71. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_UPDATED.ndjson +0 -0
  72. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/logs/__init__.py +0 -0
  73. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/logs/logs.py +0 -0
  74. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/plugin/__init__.py +0 -0
  75. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/plugin/plugin.py +0 -0
  76. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/run_plugins/__init__.py +0 -0
  77. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/apps/run_plugins/run_plugins.py +0 -0
  78. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/main.py +0 -0
  79. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/cookiecutter.json +0 -0
  80. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json +0 -0
  81. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/README.md +0 -0
  82. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/__init__.py +0 -0
  83. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/my_application.py +0 -0
  84. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/assets/python-logo.png +0 -0
  85. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/default/cookiecutter.json +0 -0
  86. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json +0 -0
  87. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/README.md +0 -0
  88. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/__init__.py +0 -0
  89. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +0 -0
  90. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/__init__.py +0 -0
  91. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/context/__init__.py +0 -0
  92. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/context/context.py +0 -0
  93. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/print/__init__.py +0 -0
  94. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/print/print.py +0 -0
  95. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/urls/__init__.py +0 -0
  96. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/urls/urls.py +0 -0
  97. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/validators/__init__.py +0 -0
  98. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/validators/manifest_schema.py +0 -0
  99. {canvas-0.35.0 → canvas-0.35.1}/canvas_cli/utils/validators/validators.py +0 -0
  100. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/effects_pb2.py +0 -0
  101. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/effects_pb2.pyi +0 -0
  102. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/effects_pb2_grpc.py +0 -0
  103. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/events_pb2.py +0 -0
  104. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/events_pb2.pyi +0 -0
  105. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/events_pb2_grpc.py +0 -0
  106. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/plugins_pb2.py +0 -0
  107. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/plugins_pb2.pyi +0 -0
  108. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/messages/plugins_pb2_grpc.py +0 -0
  109. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/services/plugin_runner_pb2.py +0 -0
  110. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/services/plugin_runner_pb2.pyi +0 -0
  111. {canvas-0.35.0 → canvas-0.35.1}/canvas_generated/services/plugin_runner_pb2_grpc.py +0 -0
  112. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/__init__.py +0 -0
  113. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/base.py +0 -0
  114. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/__init__.py +0 -0
  115. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/base.py +0 -0
  116. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/__init__.py +0 -0
  117. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/adjust_prescription.py +0 -0
  118. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/allergy.py +0 -0
  119. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/assess.py +0 -0
  120. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/change_medication.py +0 -0
  121. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/close_goal.py +0 -0
  122. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/diagnose.py +0 -0
  123. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/exam.py +0 -0
  124. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/family_history.py +0 -0
  125. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/follow_up.py +0 -0
  126. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/goal.py +0 -0
  127. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/history_present_illness.py +0 -0
  128. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/imaging_order.py +0 -0
  129. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/instruct.py +0 -0
  130. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/lab_order.py +0 -0
  131. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/medical_history.py +0 -0
  132. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/medication_statement.py +0 -0
  133. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/past_surgical_history.py +0 -0
  134. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/perform.py +0 -0
  135. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/plan.py +0 -0
  136. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/prescribe.py +0 -0
  137. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/questionnaire/__init__.py +0 -0
  138. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/questionnaire/question.py +0 -0
  139. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/reason_for_visit.py +0 -0
  140. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/refer.py +0 -0
  141. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/refill.py +0 -0
  142. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/remove_allergy.py +0 -0
  143. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/resolve_condition.py +0 -0
  144. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/review_of_systems.py +0 -0
  145. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/stop_medication.py +0 -0
  146. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/structured_assessment.py +0 -0
  147. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/task.py +0 -0
  148. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/update_diagnosis.py +0 -0
  149. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/update_goal.py +0 -0
  150. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/commands/vitals.py +0 -0
  151. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/commands/constants.py +0 -0
  152. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/__init__.py +0 -0
  153. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/banner_alert/__init__.py +0 -0
  154. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/banner_alert/add_banner_alert.py +0 -0
  155. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/banner_alert/remove_banner_alert.py +0 -0
  156. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/base.py +0 -0
  157. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/billing_line_item/__init__.py +0 -0
  158. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/billing_line_item/add_billing_line_item.py +0 -0
  159. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/billing_line_item/remove_billing_line_item.py +0 -0
  160. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/billing_line_item/update_billing_line_item.py +0 -0
  161. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/launch_modal.py +0 -0
  162. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/note/__init__.py +0 -0
  163. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/note/appointment.py +0 -0
  164. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/note/base.py +0 -0
  165. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/note/note.py +0 -0
  166. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient/__init__.py +0 -0
  167. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient/base.py +0 -0
  168. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_chart_summary_configuration.py +0 -0
  169. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_portal/__init__.py +0 -0
  170. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_portal/application_configuration.py +0 -0
  171. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_portal/form_result.py +0 -0
  172. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_portal_menu_configuration.py +0 -0
  173. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/patient_profile_configuration.py +0 -0
  174. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/protocol_card/__init__.py +0 -0
  175. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/protocol_card/protocol_card.py +0 -0
  176. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/questionnaire_result.py +0 -0
  177. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/send_invite.py +0 -0
  178. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/show_button.py +0 -0
  179. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/simple_api.py +0 -0
  180. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/surescripts/__init__.py +0 -0
  181. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/surescripts/surescripts_messages.py +0 -0
  182. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/task/__init__.py +0 -0
  183. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/task/task.py +0 -0
  184. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/update_user.py +0 -0
  185. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/widgets/__init__.py +0 -0
  186. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/effects/widgets/portal_widget.py +0 -0
  187. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/events/__init__.py +0 -0
  188. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/events/base.py +0 -0
  189. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/__init__.py +0 -0
  190. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/action_button.py +0 -0
  191. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/application.py +0 -0
  192. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/base.py +0 -0
  193. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/cron_task.py +0 -0
  194. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/simple_api/__init__.py +0 -0
  195. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/simple_api/api.py +0 -0
  196. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/simple_api/exceptions.py +0 -0
  197. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/simple_api/security.py +0 -0
  198. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/handlers/simple_api/tools.py +0 -0
  199. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/protocols/__init__.py +0 -0
  200. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/protocols/base.py +0 -0
  201. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/protocols/clinical_quality_measure.py +0 -0
  202. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/protocols/timeframe.py +0 -0
  203. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/questionnaires/__init__.py +0 -0
  204. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/questionnaires/utils.py +0 -0
  205. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/templates/__init__.py +0 -0
  206. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/templates/utils.py +0 -0
  207. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/utils/__init__.py +0 -0
  208. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/utils/http.py +0 -0
  209. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/utils/metrics.py +0 -0
  210. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/utils/plugins.py +0 -0
  211. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/__init__.py +0 -0
  212. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/apps.py +0 -0
  213. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/__init__.py +0 -0
  214. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/allergy_intolerance.py +0 -0
  215. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/appointment.py +0 -0
  216. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/assessment.py +0 -0
  217. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/banner_alert.py +0 -0
  218. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/base.py +0 -0
  219. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/billing.py +0 -0
  220. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/care_team.py +0 -0
  221. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/command.py +0 -0
  222. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/common.py +0 -0
  223. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/condition.py +0 -0
  224. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/coverage.py +0 -0
  225. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/detected_issue.py +0 -0
  226. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/device.py +0 -0
  227. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/imaging.py +0 -0
  228. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/lab.py +0 -0
  229. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/medication.py +0 -0
  230. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/message.py +0 -0
  231. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/note.py +0 -0
  232. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/observation.py +0 -0
  233. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/organization.py +0 -0
  234. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/patient.py +0 -0
  235. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/practicelocation.py +0 -0
  236. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/protocol_override.py +0 -0
  237. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/questionnaire.py +0 -0
  238. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/reason_for_visit.py +0 -0
  239. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/staff.py +0 -0
  240. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/task.py +0 -0
  241. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/team.py +0 -0
  242. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/data/user.py +0 -0
  243. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/v1/models.py +0 -0
  244. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/__init__.py +0 -0
  245. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/_utilities.py +0 -0
  246. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/custom.py +0 -0
  247. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/hcc2018.py +0 -0
  248. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/__init__.py +0 -0
  249. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/adverse_event.py +0 -0
  250. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/allergy.py +0 -0
  251. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/assessment.py +0 -0
  252. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/communication.py +0 -0
  253. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/condition.py +0 -0
  254. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/device.py +0 -0
  255. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/diagnostic_study.py +0 -0
  256. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/encounter.py +0 -0
  257. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/immunization.py +0 -0
  258. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/individual_characteristic.py +0 -0
  259. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/intervention.py +0 -0
  260. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/laboratory_test.py +0 -0
  261. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/medication.py +0 -0
  262. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/physical_exam.py +0 -0
  263. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/procedure.py +0 -0
  264. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/v2022/symptom.py +0 -0
  265. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/value_set/value_set.py +0 -0
  266. {canvas-0.35.0 → canvas-0.35.1}/canvas_sdk/views/__init__.py +0 -0
  267. {canvas-0.35.0 → canvas-0.35.1}/logger/__init__.py +0 -0
  268. {canvas-0.35.0 → canvas-0.35.1}/logger/logger.py +0 -0
  269. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/__init__.py +0 -0
  270. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/authentication.py +0 -0
  271. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/aws_headers.py +0 -0
  272. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/exceptions.py +0 -0
  273. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/installation.py +0 -0
  274. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/load_all_plugins.py +0 -0
  275. {canvas-0.35.0 → canvas-0.35.1}/plugin_runner/sandbox.py +0 -0
  276. {canvas-0.35.0 → canvas-0.35.1}/protobufs/canvas_generated/messages/effects.proto +0 -0
  277. {canvas-0.35.0 → canvas-0.35.1}/protobufs/canvas_generated/messages/events.proto +0 -0
  278. {canvas-0.35.0 → canvas-0.35.1}/protobufs/canvas_generated/messages/plugins.proto +0 -0
  279. {canvas-0.35.0 → canvas-0.35.1}/protobufs/canvas_generated/services/plugin_runner.proto +0 -0
  280. {canvas-0.35.0 → canvas-0.35.1}/pubsub/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: canvas
3
- Version: 0.35.0
3
+ Version: 0.35.1
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
@@ -18,7 +18,7 @@ Requires-Dist: ipython<9,>=8.21.0
18
18
  Requires-Dist: jsonschema<5,>=4.21.1
19
19
  Requires-Dist: keyring
20
20
  Requires-Dist: protobuf<5,>=4.25.3
21
- Requires-Dist: psycopg[binary]<4,>=3.2.2
21
+ Requires-Dist: psycopg[binary,pool]<4,>=3.2.2
22
22
  Requires-Dist: pydantic<3,>=2.6.1
23
23
  Requires-Dist: pyjwt==2.10.1
24
24
  Requires-Dist: python-dotenv<2,>=1.0.1
@@ -1,23 +1,28 @@
1
- import asyncio
2
1
  import json
3
2
  import os
4
3
  import pathlib
5
4
  import pickle
6
5
  import pkgutil
7
6
  import sys
7
+ import threading
8
8
  import traceback
9
9
  from collections import defaultdict
10
- from collections.abc import AsyncGenerator
10
+ from collections.abc import Iterable
11
+ from concurrent.futures import ThreadPoolExecutor
11
12
  from http import HTTPStatus
13
+ from time import sleep
12
14
  from typing import Any, TypedDict
13
15
 
14
16
  import grpc
15
- import redis.asyncio as redis
17
+ import redis
16
18
  import sentry_sdk
17
- from asgiref.sync import sync_to_async
18
- from django.db import connections
19
+ from django.core.signals import request_finished, request_started
20
+ from redis.backoff import ExponentialBackoff
21
+ from redis.exceptions import ConnectionError, TimeoutError
22
+ from redis.retry import Retry
19
23
  from sentry_sdk.integrations.logging import ignore_logger
20
24
 
25
+ import settings
21
26
  from canvas_generated.messages.effects_pb2 import EffectType
22
27
  from canvas_generated.messages.plugins_pb2 import ReloadPluginsRequest, ReloadPluginsResponse
23
28
  from canvas_generated.services.plugin_runner_pb2_grpc import (
@@ -29,7 +34,7 @@ from canvas_sdk.effects.simple_api import Response
29
34
  from canvas_sdk.events import Event, EventRequest, EventResponse, EventType
30
35
  from canvas_sdk.protocols import ClinicalQualityMeasure
31
36
  from canvas_sdk.utils import metrics
32
- from canvas_sdk.utils.metrics import measured, statsd_client
37
+ from canvas_sdk.utils.metrics import measured
33
38
  from logger import log
34
39
  from plugin_runner.authentication import token_for_plugin
35
40
  from plugin_runner.installation import install_plugins
@@ -39,7 +44,6 @@ from settings import (
39
44
  CUSTOMER_IDENTIFIER,
40
45
  ENV,
41
46
  IS_PRODUCTION_CUSTOMER,
42
- IS_TESTING,
43
47
  MANIFEST_FILE_NAME,
44
48
  PLUGIN_DIRECTORY,
45
49
  REDIS_ENDPOINT,
@@ -149,33 +153,12 @@ class PluginManifest(TypedDict):
149
153
  readme: str
150
154
 
151
155
 
152
- @sync_to_async
153
- def reconnect_if_needed() -> None:
154
- """
155
- Reconnect to the database if the connection has been closed.
156
-
157
- NOTE: Django database functions are not async-safe and must be called
158
- via e.g. sync_to_async. They will silently fail and block the handler
159
- if called in an async context directly.
160
- """
161
- if IS_TESTING:
162
- return
163
-
164
- connection = connections["default"]
165
-
166
- if not connection.is_usable():
167
- log.debug("Connection was unusable, reconnecting...")
168
- connection.connect()
169
-
170
-
171
156
  class PluginRunner(PluginRunnerServicer):
172
157
  """This process runs provided plugins that register interest in incoming events."""
173
158
 
174
159
  sandbox: Sandbox
175
160
 
176
- async def HandleEvent(
177
- self, request: EventRequest, context: Any
178
- ) -> AsyncGenerator[EventResponse, None]:
161
+ def HandleEvent(self, request: EventRequest, context: Any) -> Iterable[EventResponse]:
179
162
  """This is invoked when an event comes in."""
180
163
  event = Event(request)
181
164
  with metrics.measure(
@@ -190,7 +173,8 @@ class PluginRunner(PluginRunnerServicer):
190
173
  sentry_sdk.set_tag("event-name", event_name)
191
174
 
192
175
  if relevant_plugins:
193
- await reconnect_if_needed()
176
+ # Send the Django request_started signal
177
+ request_started.send(sender=self.__class__)
194
178
 
195
179
  if event_type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
196
180
  plugin_name = event.target.id
@@ -238,7 +222,7 @@ class PluginRunner(PluginRunnerServicer):
238
222
  "event": event_name,
239
223
  },
240
224
  ):
241
- _effects = await sync_to_async(handler.compute)()
225
+ _effects = handler.compute()
242
226
  effects = [
243
227
  Effect(
244
228
  type=effect.type,
@@ -286,24 +270,30 @@ class PluginRunner(PluginRunnerServicer):
286
270
 
287
271
  # Don't log anything if a plugin handler didn't actually run.
288
272
  if relevant_plugins:
273
+ # Send the Django request_finished signal
274
+ request_finished.send(sender=self.__class__)
275
+
289
276
  log.info(f"Responded to Event {event_name}.")
290
277
 
291
278
  yield EventResponse(success=True, effects=effect_list)
292
279
 
293
- async def ReloadPlugins(
280
+ def ReloadPlugins(
294
281
  self, request: ReloadPluginsRequest, context: Any
295
- ) -> AsyncGenerator[ReloadPluginsResponse, None]:
282
+ ) -> Iterable[ReloadPluginsResponse]:
296
283
  """This is invoked when we need to reload plugins."""
297
284
  log.info("Reloading plugins...")
298
285
  try:
299
- await publish_message(message={"action": "reload"})
286
+ publish_message(message={"action": "reload"})
300
287
  except ImportError:
301
288
  yield ReloadPluginsResponse(success=False)
302
289
  else:
303
290
  yield ReloadPluginsResponse(success=True)
304
291
 
305
292
 
306
- async def synchronize_plugins(run_once: bool = False) -> None:
293
+ STOP_SYNCHRONIZER = threading.Event()
294
+
295
+
296
+ def synchronize_plugins(run_once: bool = False) -> None:
307
297
  """
308
298
  Listen for messages on the pubsub channel that will indicate it is
309
299
  necessary to reinstall and reload plugins.
@@ -312,16 +302,10 @@ async def synchronize_plugins(run_once: bool = False) -> None:
312
302
 
313
303
  _, pubsub = get_client()
314
304
 
315
- await pubsub.psubscribe(CHANNEL_NAME)
316
-
317
- while True:
318
- message = await pubsub.get_message(ignore_subscribe_messages=True, timeout=5.0)
319
-
320
- await pubsub.check_health()
305
+ pubsub.psubscribe(CHANNEL_NAME)
321
306
 
322
- if not pubsub.connection.is_connected: # type: ignore
323
- log.info("synchronize_plugins: reconnecting to Redis")
324
- await pubsub.connection.connect() # type: ignore
307
+ while not STOP_SYNCHRONIZER.is_set():
308
+ message = pubsub.get_message(ignore_subscribe_messages=True, timeout=5.0)
325
309
 
326
310
  if message is None:
327
311
  continue
@@ -357,21 +341,21 @@ async def synchronize_plugins(run_once: bool = False) -> None:
357
341
  break
358
342
 
359
343
 
360
- async def synchronize_plugins_and_report_errors() -> None:
344
+ def synchronize_plugins_and_report_errors() -> None:
361
345
  """
362
346
  Run synchronize_plugins() in perpetuity and report any encountered errors.
363
347
  """
364
348
  log.info("synchronize_plugins: starting loop...")
365
349
 
366
- while True:
350
+ while not STOP_SYNCHRONIZER.is_set():
367
351
  try:
368
- await synchronize_plugins()
352
+ synchronize_plugins()
369
353
  except Exception as e:
370
354
  log.error(f"synchronize_plugins: error: {e}")
371
355
  sentry_sdk.capture_exception(e)
372
356
 
373
357
  # don't crush redis if we're retrying in a tight loop
374
- await asyncio.sleep(0.5)
358
+ sleep(0.5)
375
359
 
376
360
 
377
361
  def validate_effects(effects: list[Effect]) -> list[Effect]:
@@ -434,17 +418,22 @@ def find_modules(base_path: pathlib.Path, prefix: str | None = None) -> list[str
434
418
  return modules
435
419
 
436
420
 
437
- async def publish_message(message: dict) -> None:
421
+ def publish_message(message: dict) -> None:
438
422
  """Publish a message to the pubsub channel."""
439
423
  log.info(f'Publishing message to pubsub channel "{CHANNEL_NAME}"')
440
424
  client, _ = get_client()
441
425
 
442
- await client.publish(CHANNEL_NAME, pickle.dumps(message))
426
+ client.publish(CHANNEL_NAME, pickle.dumps(message))
443
427
 
444
428
 
445
429
  def get_client() -> tuple[redis.Redis, redis.client.PubSub]:
446
- """Return an async Redis client and pubsub object."""
447
- client = redis.Redis.from_url(REDIS_ENDPOINT)
430
+ """Return a Redis client and pubsub object."""
431
+ client = redis.from_url(
432
+ REDIS_ENDPOINT,
433
+ retry=Retry(backoff=ExponentialBackoff(), retries=10),
434
+ retry_on_error=[ConnectionError, TimeoutError, ConnectionResetError],
435
+ health_check_interval=1,
436
+ )
448
437
  pubsub = client.pubsub()
449
438
 
450
439
  return client, pubsub
@@ -594,62 +583,41 @@ def load_plugins(specified_plugin_paths: list[str] | None = None) -> None:
594
583
 
595
584
  refresh_event_type_map()
596
585
 
597
- for key in EventType.keys(): # noqa: SIM118
598
- value = len(EVENT_HANDLER_MAP[key]) if key in EVENT_HANDLER_MAP else 0
599
- statsd_client.gauge("plugins.event_handler_count", value, tags={"event": key})
600
-
601
-
602
- _cleanup_coroutines = []
603
586
 
604
-
605
- async def serve(specified_plugin_paths: list[str] | None = None) -> None:
606
- """Run the server."""
587
+ # NOTE: specified_plugin_paths powers the `canvas run-plugins` command
588
+ def main(specified_plugin_paths: list[str] | None = None) -> None:
589
+ """Run the server and the synchronize_plugins loop."""
607
590
  port = "50051"
608
591
 
609
- server = grpc.aio.server()
592
+ executor = ThreadPoolExecutor(max_workers=settings.PLUGIN_RUNNER_MAX_WORKERS)
593
+ server = grpc.server(thread_pool=executor)
610
594
  server.add_insecure_port("127.0.0.1:" + port)
611
595
 
612
596
  add_PluginRunnerServicer_to_server(PluginRunner(), server)
613
597
 
614
598
  log.info(f"Starting server, listening on port {port}")
615
599
 
616
- # Only install plugins if the plugin runner was not started from the CLI
600
+ # Only install plugins and start the synchronizer thread if the plugin runner was not started
601
+ # from the CLI
602
+ synchronizer_thread = threading.Thread(target=synchronize_plugins_and_report_errors)
617
603
  if specified_plugin_paths is None:
618
604
  install_plugins()
605
+ STOP_SYNCHRONIZER.clear()
606
+ synchronizer_thread.start()
619
607
 
620
608
  load_plugins(specified_plugin_paths)
621
609
 
622
- await server.start()
623
-
624
- async def server_graceful_shutdown() -> None:
625
- log.info("Starting graceful shutdown...")
626
- await server.stop(5)
627
-
628
- _cleanup_coroutines.append(server_graceful_shutdown())
629
-
630
- await server.wait_for_termination()
631
-
632
-
633
- # NOTE: specified_plugin_paths powers the `canvas run-plugins` command
634
- def main(specified_plugin_paths: list[str] | None = None) -> None:
635
- """Run the server and the synchronize_plugins loop."""
636
- loop = asyncio.new_event_loop()
637
-
638
- asyncio.set_event_loop(loop)
610
+ server.start()
639
611
 
640
612
  try:
641
- coroutines = [serve(specified_plugin_paths)]
642
-
643
- # Only start the synchronizer if the plugin runner was not started from the CLI
644
- if specified_plugin_paths is None:
645
- coroutines.append(synchronize_plugins_and_report_errors())
646
-
647
- loop.run_until_complete(asyncio.gather(*coroutines))
613
+ server.wait_for_termination()
648
614
  except KeyboardInterrupt:
649
615
  pass
650
616
  finally:
651
- loop.run_until_complete(*_cleanup_coroutines)
652
- loop.close()
617
+ executor.shutdown(wait=True, cancel_futures=True)
618
+ if synchronizer_thread.is_alive():
619
+ STOP_SYNCHRONIZER.set()
620
+ synchronizer_thread.join()
653
621
 
654
622
 
655
623
  if __name__ == "__main__":
@@ -2,6 +2,9 @@ import os
2
2
  from typing import Any
3
3
 
4
4
  import redis
5
+ from redis.backoff import ExponentialBackoff
6
+ from redis.exceptions import ConnectionError, TimeoutError
7
+ from redis.retry import Retry
5
8
 
6
9
  REDIS_ENDPOINT = os.getenv("REDIS_ENDPOINT")
7
10
  CUSTOMER_IDENTIFIER = os.getenv("CUSTOMER_IDENTIFIER")
@@ -24,7 +27,13 @@ class PubSubBase:
24
27
 
25
28
  def _create_client(self) -> redis.Redis | None:
26
29
  if self.redis_endpoint and self.channel:
27
- return redis.Redis.from_url(self.redis_endpoint, decode_responses=True)
30
+ return redis.Redis.from_url(
31
+ self.redis_endpoint,
32
+ decode_responses=True,
33
+ retry=Retry(backoff=ExponentialBackoff(), retries=10),
34
+ retry_on_error=[ConnectionError, TimeoutError, ConnectionResetError],
35
+ health_check_interval=1,
36
+ )
28
37
 
29
38
  return None
30
39
 
@@ -9,7 +9,6 @@ dev = [
9
9
  "mypy",
10
10
  "pre-commit",
11
11
  "pytest",
12
- "pytest-asyncio>=0.24,<0.26",
13
12
  "pytest-mock",
14
13
  "python-semantic-release",
15
14
  "requests-mock",
@@ -34,7 +33,7 @@ dependencies = [
34
33
  "jsonschema>=4.21.1,<5",
35
34
  "keyring",
36
35
  "protobuf>=4.25.3,<5",
37
- "psycopg[binary]>=3.2.2,<4",
36
+ "psycopg[binary,pool]>=3.2.2,<4",
38
37
  "pydantic>=2.6.1,<3",
39
38
  "pyjwt==2.10.1",
40
39
  "python-dotenv>=1.0.1,<2",
@@ -53,7 +52,7 @@ license = "MIT"
53
52
  name = "canvas"
54
53
  readme = "README.md"
55
54
  requires-python = ">=3.11,<3.13"
56
- version = "0.35.0"
55
+ version = "0.35.1"
57
56
 
58
57
  [project.scripts]
59
58
  canvas = "canvas_cli.main:app"
@@ -119,7 +118,6 @@ init_typed = true
119
118
 
120
119
  [tool.pytest.ini_options]
121
120
  addopts = "--durations=5 --durations-min=0.25 --pdbcls=IPython.terminal.debugger:TerminalPdb"
122
- asyncio_default_fixture_loop_scope = "session"
123
121
  filterwarnings = [
124
122
  'ignore:No directory at.*static.*:UserWarning:whitenoise.base',
125
123
  'ignore:SelectableGroups dict interface is deprecated. .*:DeprecationWarning'
@@ -46,12 +46,15 @@ CANVAS_SDK_DB_PASSWORD = os.getenv("CANVAS_SDK_DB_PASSWORD", "app")
46
46
  CANVAS_SDK_DB_HOST = os.getenv("CANVAS_SDK_DB_HOST", "home-app-db")
47
47
  CANVAS_SDK_DB_PORT = os.getenv("CANVAS_SDK_DB_PORT", "5432")
48
48
 
49
+ PLUGIN_RUNNER_MAX_WORKERS = int(os.getenv("PLUGIN_RUNNER_MAX_WORKERS", 5))
50
+
49
51
  if os.getenv("DATABASE_URL"):
50
52
  parsed_url = parse.urlparse(os.getenv("DATABASE_URL"))
51
53
 
52
54
  DATABASES = {
53
55
  "default": {
54
56
  "ENGINE": "django.db.backends.postgresql",
57
+ "OPTIONS": {"pool": {"min_size": 2, "max_size": PLUGIN_RUNNER_MAX_WORKERS}},
55
58
  "NAME": parsed_url.path[1:],
56
59
  "USER": os.getenv("CANVAS_SDK_DATABASE_ROLE"),
57
60
  "PASSWORD": os.getenv("CANVAS_SDK_DATABASE_ROLE_PASSWORD"),
@@ -63,6 +66,7 @@ else:
63
66
  DATABASES = {
64
67
  "default": {
65
68
  "ENGINE": "django.db.backends.postgresql",
69
+ "OPTIONS": {"pool": {"min_size": 2, "max_size": PLUGIN_RUNNER_MAX_WORKERS}},
66
70
  "NAME": CANVAS_SDK_DB_NAME,
67
71
  "USER": CANVAS_SDK_DB_USERNAME,
68
72
  "PASSWORD": CANVAS_SDK_DB_PASSWORD,
File without changes
File without changes
File without changes
File without changes