From 195074cd3ac1b5d01ed02de27f004d6f184f88e9 Mon Sep 17 00:00:00 2001 From: Christopher Sande Date: Tue, 4 Feb 2025 12:25:01 -0600 Subject: [PATCH] Added an API response effect; updated API handler to handle response effects --- canvas_generated/messages/effects_pb2.py | 4 +- canvas_generated/messages/effects_pb2.pyi | 2 + canvas_sdk/effects/simple_api.py | 27 ++++++++ canvas_sdk/handlers/simple_api/__init__.py | 4 +- canvas_sdk/handlers/simple_api/api.py | 64 ++++++++++++++++--- .../canvas_generated/messages/effects.proto | 2 + 6 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 canvas_sdk/effects/simple_api.py diff --git a/canvas_generated/messages/effects_pb2.py b/canvas_generated/messages/effects_pb2.py index aafc12fb..a0bb8944 100644 --- a/canvas_generated/messages/effects_pb2.py +++ b/canvas_generated/messages/effects_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'canvas_generated/messages/effects.proto\x12\x06\x63\x61nvas\"c\n\x06\x45\x66\x66\x65\x63t\x12 \n\x04type\x18\x01 \x01(\x0e\x32\x12.canvas.EffectType\x12\x0f\n\x07payload\x18\x02 \x01(\t\x12\x13\n\x0bplugin_name\x18\x03 \x01(\t\x12\x11\n\tclassname\x18\x04 \x01(\t*\x84-\n\nEffectType\x12\x12\n\x0eUNKNOWN_EFFECT\x10\x00\x12\x07\n\x03LOG\x10\x01\x12\x14\n\x10\x41\x44\x44_PLAN_COMMAND\x10\x02\x12\x1f\n\x1b\x41UTOCOMPLETE_SEARCH_RESULTS\x10\x03\x12\x14\n\x10\x41\x44\x44_BANNER_ALERT\x10\x04\x12\x17\n\x13REMOVE_BANNER_ALERT\x10\x05\x12\x1c\n\x18ORIGINATE_ASSESS_COMMAND\x10\x06\x12\x17\n\x13\x45\x44IT_ASSESS_COMMAND\x10\x07\x12\x19\n\x15\x44\x45LETE_ASSESS_COMMAND\x10\x08\x12\x19\n\x15\x43OMMIT_ASSESS_COMMAND\x10\t\x12!\n\x1d\x45NTER_IN_ERROR_ASSESS_COMMAND\x10\n\x12\x1e\n\x1aORIGINATE_DIAGNOSE_COMMAND\x10\x0b\x12\x19\n\x15\x45\x44IT_DIAGNOSE_COMMAND\x10\x0c\x12\x1b\n\x17\x44\x45LETE_DIAGNOSE_COMMAND\x10\r\x12\x1b\n\x17\x43OMMIT_DIAGNOSE_COMMAND\x10\x0e\x12#\n\x1f\x45NTER_IN_ERROR_DIAGNOSE_COMMAND\x10\x0f\x12\x1a\n\x16ORIGINATE_GOAL_COMMAND\x10\x10\x12\x15\n\x11\x45\x44IT_GOAL_COMMAND\x10\x11\x12\x17\n\x13\x44\x45LETE_GOAL_COMMAND\x10\x12\x12\x17\n\x13\x43OMMIT_GOAL_COMMAND\x10\x13\x12\x1f\n\x1b\x45NTER_IN_ERROR_GOAL_COMMAND\x10\x14\x12\x19\n\x15ORIGINATE_HPI_COMMAND\x10\x15\x12\x14\n\x10\x45\x44IT_HPI_COMMAND\x10\x16\x12\x16\n\x12\x44\x45LETE_HPI_COMMAND\x10\x17\x12\x16\n\x12\x43OMMIT_HPI_COMMAND\x10\x18\x12\x1e\n\x1a\x45NTER_IN_ERROR_HPI_COMMAND\x10\x19\x12*\n&ORIGINATE_MEDICATION_STATEMENT_COMMAND\x10\x1a\x12%\n!EDIT_MEDICATION_STATEMENT_COMMAND\x10\x1b\x12\'\n#DELETE_MEDICATION_STATEMENT_COMMAND\x10\x1c\x12\'\n#COMMIT_MEDICATION_STATEMENT_COMMAND\x10\x1d\x12/\n+ENTER_IN_ERROR_MEDICATION_STATEMENT_COMMAND\x10\x1e\x12\x1a\n\x16ORIGINATE_PLAN_COMMAND\x10\x1f\x12\x15\n\x11\x45\x44IT_PLAN_COMMAND\x10 \x12\x17\n\x13\x44\x45LETE_PLAN_COMMAND\x10!\x12\x17\n\x13\x43OMMIT_PLAN_COMMAND\x10\"\x12\x1f\n\x1b\x45NTER_IN_ERROR_PLAN_COMMAND\x10#\x12\x1f\n\x1bORIGINATE_PRESCRIBE_COMMAND\x10$\x12\x1a\n\x16\x45\x44IT_PRESCRIBE_COMMAND\x10%\x12\x1c\n\x18\x44\x45LETE_PRESCRIBE_COMMAND\x10&\x12\x1c\n\x18\x43OMMIT_PRESCRIBE_COMMAND\x10\'\x12$\n ENTER_IN_ERROR_PRESCRIBE_COMMAND\x10(\x12#\n\x1fORIGINATE_QUESTIONNAIRE_COMMAND\x10)\x12\x1e\n\x1a\x45\x44IT_QUESTIONNAIRE_COMMAND\x10*\x12 \n\x1c\x44\x45LETE_QUESTIONNAIRE_COMMAND\x10+\x12 \n\x1c\x43OMMIT_QUESTIONNAIRE_COMMAND\x10,\x12(\n$ENTER_IN_ERROR_QUESTIONNAIRE_COMMAND\x10-\x12&\n\"ORIGINATE_REASON_FOR_VISIT_COMMAND\x10.\x12!\n\x1d\x45\x44IT_REASON_FOR_VISIT_COMMAND\x10/\x12#\n\x1f\x44\x45LETE_REASON_FOR_VISIT_COMMAND\x10\x30\x12#\n\x1f\x43OMMIT_REASON_FOR_VISIT_COMMAND\x10\x31\x12+\n\'ENTER_IN_ERROR_REASON_FOR_VISIT_COMMAND\x10\x32\x12%\n!ORIGINATE_STOP_MEDICATION_COMMAND\x10\x33\x12 \n\x1c\x45\x44IT_STOP_MEDICATION_COMMAND\x10\x34\x12\"\n\x1e\x44\x45LETE_STOP_MEDICATION_COMMAND\x10\x35\x12\"\n\x1e\x43OMMIT_STOP_MEDICATION_COMMAND\x10\x36\x12*\n&ENTER_IN_ERROR_STOP_MEDICATION_COMMAND\x10\x37\x12!\n\x1dORIGINATE_UPDATE_GOAL_COMMAND\x10\x38\x12\x1c\n\x18\x45\x44IT_UPDATE_GOAL_COMMAND\x10\x39\x12\x1e\n\x1a\x44\x45LETE_UPDATE_GOAL_COMMAND\x10:\x12\x1e\n\x1a\x43OMMIT_UPDATE_GOAL_COMMAND\x10;\x12&\n\"ENTER_IN_ERROR_UPDATE_GOAL_COMMAND\x10<\x12\x1d\n\x19ORIGINATE_PERFORM_COMMAND\x10=\x12\x18\n\x14\x45\x44IT_PERFORM_COMMAND\x10>\x12\x1a\n\x16\x44\x45LETE_PERFORM_COMMAND\x10?\x12\x1a\n\x16\x43OMMIT_PERFORM_COMMAND\x10@\x12\"\n\x1e\x45NTER_IN_ERROR_PERFORM_COMMAND\x10\x41\x12\x1e\n\x1aORIGINATE_INSTRUCT_COMMAND\x10\x42\x12\x19\n\x15\x45\x44IT_INSTRUCT_COMMAND\x10\x43\x12\x1b\n\x17\x44\x45LETE_INSTRUCT_COMMAND\x10\x44\x12\x1b\n\x17\x43OMMIT_INSTRUCT_COMMAND\x10\x45\x12#\n\x1f\x45NTER_IN_ERROR_INSTRUCT_COMMAND\x10\x46\x12\x1f\n\x1bORIGINATE_LAB_ORDER_COMMAND\x10G\x12\x1a\n\x16\x45\x44IT_LAB_ORDER_COMMAND\x10H\x12\x1c\n\x18\x44\x45LETE_LAB_ORDER_COMMAND\x10I\x12\x1c\n\x18\x43OMMIT_LAB_ORDER_COMMAND\x10J\x12$\n ENTER_IN_ERROR_LAB_ORDER_COMMAND\x10K\x12$\n ORIGINATE_FAMILY_HISTORY_COMMAND\x10L\x12\x1f\n\x1b\x45\x44IT_FAMILY_HISTORY_COMMAND\x10M\x12!\n\x1d\x44\x45LETE_FAMILY_HISTORY_COMMAND\x10N\x12!\n\x1d\x43OMMIT_FAMILY_HISTORY_COMMAND\x10O\x12)\n%ENTER_IN_ERROR_FAMILY_HISTORY_COMMAND\x10P\x12\x1d\n\x19ORIGINATE_ALLERGY_COMMAND\x10Q\x12\x18\n\x14\x45\x44IT_ALLERGY_COMMAND\x10R\x12\x1a\n\x16\x44\x45LETE_ALLERGY_COMMAND\x10S\x12\x1a\n\x16\x43OMMIT_ALLERGY_COMMAND\x10T\x12\"\n\x1e\x45NTER_IN_ERROR_ALLERGY_COMMAND\x10U\x12$\n ORIGINATE_REMOVE_ALLERGY_COMMAND\x10V\x12\x1f\n\x1b\x45\x44IT_REMOVE_ALLERGY_COMMAND\x10W\x12!\n\x1d\x44\x45LETE_REMOVE_ALLERGY_COMMAND\x10X\x12!\n\x1d\x43OMMIT_REMOVE_ALLERGY_COMMAND\x10Y\x12)\n%ENTER_IN_ERROR_REMOVE_ALLERGY_COMMAND\x10Z\x12&\n\"ORIGINATE_SURGICAL_HISTORY_COMMAND\x10[\x12!\n\x1d\x45\x44IT_SURGICAL_HISTORY_COMMAND\x10\\\x12#\n\x1f\x44\x45LETE_SURGICAL_HISTORY_COMMAND\x10]\x12#\n\x1f\x43OMMIT_SURGICAL_HISTORY_COMMAND\x10^\x12+\n\'ENTER_IN_ERROR_SURGICAL_HISTORY_COMMAND\x10_\x12\x0f\n\x0b\x43REATE_TASK\x10\x64\x12\x0f\n\x0bUPDATE_TASK\x10\x65\x12\x17\n\x13\x43REATE_TASK_COMMENT\x10\x66\x12%\n!ORIGINATE_MEDICAL_HISTORY_COMMAND\x10g\x12 \n\x1c\x45\x44IT_MEDICAL_HISTORY_COMMAND\x10h\x12\"\n\x1e\x44\x45LETE_MEDICAL_HISTORY_COMMAND\x10i\x12\"\n\x1e\x43OMMIT_MEDICAL_HISTORY_COMMAND\x10j\x12*\n&ENTER_IN_ERROR_MEDICAL_HISTORY_COMMAND\x10k\x12\x1f\n\x1b\x41\x44\x44_OR_UPDATE_PROTOCOL_CARD\x10n\x12\x1b\n\x16ORIGINATE_TASK_COMMAND\x10\x85\x01\x12\x16\n\x11\x45\x44IT_TASK_COMMAND\x10\x86\x01\x12\x18\n\x13\x44\x45LETE_TASK_COMMAND\x10\x87\x01\x12\x18\n\x13\x43OMMIT_TASK_COMMAND\x10\x88\x01\x12 \n\x1b\x45NTER_IN_ERROR_TASK_COMMAND\x10\x89\x01\x12\x1c\n\x18ORIGINATE_REFILL_COMMAND\x10q\x12\x17\n\x13\x45\x44IT_REFILL_COMMAND\x10r\x12\x19\n\x15\x44\x45LETE_REFILL_COMMAND\x10s\x12\x19\n\x15\x43OMMIT_REFILL_COMMAND\x10t\x12!\n\x1d\x45NTER_IN_ERROR_REFILL_COMMAND\x10u\x12\x1c\n\x18ORIGINATE_VITALS_COMMAND\x10v\x12\x17\n\x13\x45\x44IT_VITALS_COMMAND\x10w\x12\x19\n\x15\x44\x45LETE_VITALS_COMMAND\x10x\x12\x19\n\x15\x43OMMIT_VITALS_COMMAND\x10y\x12!\n\x1d\x45NTER_IN_ERROR_VITALS_COMMAND\x10z\x12&\n\"ORIGINATE_UPDATE_DIAGNOSIS_COMMAND\x10{\x12!\n\x1d\x45\x44IT_UPDATE_DIAGNOSIS_COMMAND\x10|\x12#\n\x1f\x44\x45LETE_UPDATE_DIAGNOSIS_COMMAND\x10}\x12#\n\x1f\x43OMMIT_UPDATE_DIAGNOSIS_COMMAND\x10~\x12+\n\'ENTER_IN_ERROR_UPDATE_DIAGNOSIS_COMMAND\x10\x7f\x12!\n\x1cORIGINATE_CLOSE_GOAL_COMMAND\x10\x80\x01\x12\x1c\n\x17\x45\x44IT_CLOSE_GOAL_COMMAND\x10\x81\x01\x12\x1e\n\x19\x44\x45LETE_CLOSE_GOAL_COMMAND\x10\x82\x01\x12\x1e\n\x19\x43OMMIT_CLOSE_GOAL_COMMAND\x10\x83\x01\x12&\n!ENTER_IN_ERROR_CLOSE_GOAL_COMMAND\x10\x84\x01\x12 \n\x1b\x43REATE_QUESTIONNAIRE_RESULT\x10\x8a\x01\x12-\n(ANNOTATE_PATIENT_CHART_CONDITION_RESULTS\x10\xc8\x01\x12%\n ANNOTATE_CLAIM_CONDITION_RESULTS\x10\xac\x02\x12(\n#SHOW_PATIENT_CHART_SUMMARY_SECTIONS\x10\x90\x03\x12\"\n\x1dSHOW_PATIENT_PROFILE_SECTIONS\x10\xf4\x03\x12\x37\n2PATIENT_PROFILE__ADD_PHARMACY__POST_SEARCH_RESULTS\x10\xf5\x03\x12)\n$SEND_SURESCRIPTS_ELIGIBILITY_REQUEST\x10\xd8\x04\x12\x30\n+SEND_SURESCRIPTS_MEDICATION_HISTORY_REQUEST\x10\xd9\x04\x12&\n!SEND_SURESCRIPTS_BENEFITS_REQUEST\x10\xda\x04\x12\x1b\n\x16ORIGINATE_EXAM_COMMAND\x10\xbc\x05\x12\x16\n\x11\x45\x44IT_EXAM_COMMAND\x10\xbd\x05\x12\x18\n\x13\x44\x45LETE_EXAM_COMMAND\x10\xbe\x05\x12\x18\n\x13\x43OMMIT_EXAM_COMMAND\x10\xbf\x05\x12 \n\x1b\x45NTER_IN_ERROR_EXAM_COMMAND\x10\xc0\x05\x12\x1a\n\x15ORIGINATE_ROS_COMMAND\x10\xa0\x06\x12\x15\n\x10\x45\x44IT_ROS_COMMAND\x10\xa1\x06\x12\x17\n\x12\x44\x45LETE_ROS_COMMAND\x10\xa2\x06\x12\x17\n\x12\x43OMMIT_ROS_COMMAND\x10\xa3\x06\x12\x1f\n\x1a\x45NTER_IN_ERROR_ROS_COMMAND\x10\xa4\x06\x12,\n\'ORIGINATE_STRUCTURED_ASSESSMENT_COMMAND\x10\x84\x07\x12\'\n\"EDIT_STRUCTURED_ASSESSMENT_COMMAND\x10\x85\x07\x12)\n$DELETE_STRUCTURED_ASSESSMENT_COMMAND\x10\x86\x07\x12)\n$COMMIT_STRUCTURED_ASSESSMENT_COMMAND\x10\x87\x07\x12\x31\n,ENTER_IN_ERROR_STRUCTURED_ASSESSMENT_COMMAND\x10\x88\x07\x12\x17\n\x12SHOW_ACTION_BUTTON\x10\xe8\x07\x12(\n#PATIENT_PORTAL__INTAKE_FORM_RESULTS\x10\xd0\x0f\x12.\n)PATIENT_PORTAL__APPOINTMENT_IS_CANCELABLE\x10\xd1\x0f\x12\x31\n,PATIENT_PORTAL__APPOINTMENT_IS_RESCHEDULABLE\x10\xd2\x0f\x12=\n8PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH_RESULTS\x10\xd5\x0f\x12M\nHPATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__PRE_SEARCH_RESULTS\x10\xd6\x0f\x12N\nIPATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH_RESULTS\x10\xd7\x0f\x12\x45\n@PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__PRE_SEARCH_RESULTS\x10\xd8\x0f\x12\x46\nAPATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS\x10\xd9\x0f\x12\x45\n@PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH_RESULTS\x10\xda\x0f\x12\x46\nAPATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS\x10\xdb\x0f\x12\x11\n\x0cLAUNCH_MODAL\x10\xb8\x17\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'canvas_generated/messages/effects.proto\x12\x06\x63\x61nvas\"c\n\x06\x45\x66\x66\x65\x63t\x12 \n\x04type\x18\x01 \x01(\x0e\x32\x12.canvas.EffectType\x12\x0f\n\x07payload\x18\x02 \x01(\t\x12\x13\n\x0bplugin_name\x18\x03 \x01(\t\x12\x11\n\tclassname\x18\x04 \x01(\t*\x9e-\n\nEffectType\x12\x12\n\x0eUNKNOWN_EFFECT\x10\x00\x12\x07\n\x03LOG\x10\x01\x12\x14\n\x10\x41\x44\x44_PLAN_COMMAND\x10\x02\x12\x1f\n\x1b\x41UTOCOMPLETE_SEARCH_RESULTS\x10\x03\x12\x14\n\x10\x41\x44\x44_BANNER_ALERT\x10\x04\x12\x17\n\x13REMOVE_BANNER_ALERT\x10\x05\x12\x1c\n\x18ORIGINATE_ASSESS_COMMAND\x10\x06\x12\x17\n\x13\x45\x44IT_ASSESS_COMMAND\x10\x07\x12\x19\n\x15\x44\x45LETE_ASSESS_COMMAND\x10\x08\x12\x19\n\x15\x43OMMIT_ASSESS_COMMAND\x10\t\x12!\n\x1d\x45NTER_IN_ERROR_ASSESS_COMMAND\x10\n\x12\x1e\n\x1aORIGINATE_DIAGNOSE_COMMAND\x10\x0b\x12\x19\n\x15\x45\x44IT_DIAGNOSE_COMMAND\x10\x0c\x12\x1b\n\x17\x44\x45LETE_DIAGNOSE_COMMAND\x10\r\x12\x1b\n\x17\x43OMMIT_DIAGNOSE_COMMAND\x10\x0e\x12#\n\x1f\x45NTER_IN_ERROR_DIAGNOSE_COMMAND\x10\x0f\x12\x1a\n\x16ORIGINATE_GOAL_COMMAND\x10\x10\x12\x15\n\x11\x45\x44IT_GOAL_COMMAND\x10\x11\x12\x17\n\x13\x44\x45LETE_GOAL_COMMAND\x10\x12\x12\x17\n\x13\x43OMMIT_GOAL_COMMAND\x10\x13\x12\x1f\n\x1b\x45NTER_IN_ERROR_GOAL_COMMAND\x10\x14\x12\x19\n\x15ORIGINATE_HPI_COMMAND\x10\x15\x12\x14\n\x10\x45\x44IT_HPI_COMMAND\x10\x16\x12\x16\n\x12\x44\x45LETE_HPI_COMMAND\x10\x17\x12\x16\n\x12\x43OMMIT_HPI_COMMAND\x10\x18\x12\x1e\n\x1a\x45NTER_IN_ERROR_HPI_COMMAND\x10\x19\x12*\n&ORIGINATE_MEDICATION_STATEMENT_COMMAND\x10\x1a\x12%\n!EDIT_MEDICATION_STATEMENT_COMMAND\x10\x1b\x12\'\n#DELETE_MEDICATION_STATEMENT_COMMAND\x10\x1c\x12\'\n#COMMIT_MEDICATION_STATEMENT_COMMAND\x10\x1d\x12/\n+ENTER_IN_ERROR_MEDICATION_STATEMENT_COMMAND\x10\x1e\x12\x1a\n\x16ORIGINATE_PLAN_COMMAND\x10\x1f\x12\x15\n\x11\x45\x44IT_PLAN_COMMAND\x10 \x12\x17\n\x13\x44\x45LETE_PLAN_COMMAND\x10!\x12\x17\n\x13\x43OMMIT_PLAN_COMMAND\x10\"\x12\x1f\n\x1b\x45NTER_IN_ERROR_PLAN_COMMAND\x10#\x12\x1f\n\x1bORIGINATE_PRESCRIBE_COMMAND\x10$\x12\x1a\n\x16\x45\x44IT_PRESCRIBE_COMMAND\x10%\x12\x1c\n\x18\x44\x45LETE_PRESCRIBE_COMMAND\x10&\x12\x1c\n\x18\x43OMMIT_PRESCRIBE_COMMAND\x10\'\x12$\n ENTER_IN_ERROR_PRESCRIBE_COMMAND\x10(\x12#\n\x1fORIGINATE_QUESTIONNAIRE_COMMAND\x10)\x12\x1e\n\x1a\x45\x44IT_QUESTIONNAIRE_COMMAND\x10*\x12 \n\x1c\x44\x45LETE_QUESTIONNAIRE_COMMAND\x10+\x12 \n\x1c\x43OMMIT_QUESTIONNAIRE_COMMAND\x10,\x12(\n$ENTER_IN_ERROR_QUESTIONNAIRE_COMMAND\x10-\x12&\n\"ORIGINATE_REASON_FOR_VISIT_COMMAND\x10.\x12!\n\x1d\x45\x44IT_REASON_FOR_VISIT_COMMAND\x10/\x12#\n\x1f\x44\x45LETE_REASON_FOR_VISIT_COMMAND\x10\x30\x12#\n\x1f\x43OMMIT_REASON_FOR_VISIT_COMMAND\x10\x31\x12+\n\'ENTER_IN_ERROR_REASON_FOR_VISIT_COMMAND\x10\x32\x12%\n!ORIGINATE_STOP_MEDICATION_COMMAND\x10\x33\x12 \n\x1c\x45\x44IT_STOP_MEDICATION_COMMAND\x10\x34\x12\"\n\x1e\x44\x45LETE_STOP_MEDICATION_COMMAND\x10\x35\x12\"\n\x1e\x43OMMIT_STOP_MEDICATION_COMMAND\x10\x36\x12*\n&ENTER_IN_ERROR_STOP_MEDICATION_COMMAND\x10\x37\x12!\n\x1dORIGINATE_UPDATE_GOAL_COMMAND\x10\x38\x12\x1c\n\x18\x45\x44IT_UPDATE_GOAL_COMMAND\x10\x39\x12\x1e\n\x1a\x44\x45LETE_UPDATE_GOAL_COMMAND\x10:\x12\x1e\n\x1a\x43OMMIT_UPDATE_GOAL_COMMAND\x10;\x12&\n\"ENTER_IN_ERROR_UPDATE_GOAL_COMMAND\x10<\x12\x1d\n\x19ORIGINATE_PERFORM_COMMAND\x10=\x12\x18\n\x14\x45\x44IT_PERFORM_COMMAND\x10>\x12\x1a\n\x16\x44\x45LETE_PERFORM_COMMAND\x10?\x12\x1a\n\x16\x43OMMIT_PERFORM_COMMAND\x10@\x12\"\n\x1e\x45NTER_IN_ERROR_PERFORM_COMMAND\x10\x41\x12\x1e\n\x1aORIGINATE_INSTRUCT_COMMAND\x10\x42\x12\x19\n\x15\x45\x44IT_INSTRUCT_COMMAND\x10\x43\x12\x1b\n\x17\x44\x45LETE_INSTRUCT_COMMAND\x10\x44\x12\x1b\n\x17\x43OMMIT_INSTRUCT_COMMAND\x10\x45\x12#\n\x1f\x45NTER_IN_ERROR_INSTRUCT_COMMAND\x10\x46\x12\x1f\n\x1bORIGINATE_LAB_ORDER_COMMAND\x10G\x12\x1a\n\x16\x45\x44IT_LAB_ORDER_COMMAND\x10H\x12\x1c\n\x18\x44\x45LETE_LAB_ORDER_COMMAND\x10I\x12\x1c\n\x18\x43OMMIT_LAB_ORDER_COMMAND\x10J\x12$\n ENTER_IN_ERROR_LAB_ORDER_COMMAND\x10K\x12$\n ORIGINATE_FAMILY_HISTORY_COMMAND\x10L\x12\x1f\n\x1b\x45\x44IT_FAMILY_HISTORY_COMMAND\x10M\x12!\n\x1d\x44\x45LETE_FAMILY_HISTORY_COMMAND\x10N\x12!\n\x1d\x43OMMIT_FAMILY_HISTORY_COMMAND\x10O\x12)\n%ENTER_IN_ERROR_FAMILY_HISTORY_COMMAND\x10P\x12\x1d\n\x19ORIGINATE_ALLERGY_COMMAND\x10Q\x12\x18\n\x14\x45\x44IT_ALLERGY_COMMAND\x10R\x12\x1a\n\x16\x44\x45LETE_ALLERGY_COMMAND\x10S\x12\x1a\n\x16\x43OMMIT_ALLERGY_COMMAND\x10T\x12\"\n\x1e\x45NTER_IN_ERROR_ALLERGY_COMMAND\x10U\x12$\n ORIGINATE_REMOVE_ALLERGY_COMMAND\x10V\x12\x1f\n\x1b\x45\x44IT_REMOVE_ALLERGY_COMMAND\x10W\x12!\n\x1d\x44\x45LETE_REMOVE_ALLERGY_COMMAND\x10X\x12!\n\x1d\x43OMMIT_REMOVE_ALLERGY_COMMAND\x10Y\x12)\n%ENTER_IN_ERROR_REMOVE_ALLERGY_COMMAND\x10Z\x12&\n\"ORIGINATE_SURGICAL_HISTORY_COMMAND\x10[\x12!\n\x1d\x45\x44IT_SURGICAL_HISTORY_COMMAND\x10\\\x12#\n\x1f\x44\x45LETE_SURGICAL_HISTORY_COMMAND\x10]\x12#\n\x1f\x43OMMIT_SURGICAL_HISTORY_COMMAND\x10^\x12+\n\'ENTER_IN_ERROR_SURGICAL_HISTORY_COMMAND\x10_\x12\x0f\n\x0b\x43REATE_TASK\x10\x64\x12\x0f\n\x0bUPDATE_TASK\x10\x65\x12\x17\n\x13\x43REATE_TASK_COMMENT\x10\x66\x12%\n!ORIGINATE_MEDICAL_HISTORY_COMMAND\x10g\x12 \n\x1c\x45\x44IT_MEDICAL_HISTORY_COMMAND\x10h\x12\"\n\x1e\x44\x45LETE_MEDICAL_HISTORY_COMMAND\x10i\x12\"\n\x1e\x43OMMIT_MEDICAL_HISTORY_COMMAND\x10j\x12*\n&ENTER_IN_ERROR_MEDICAL_HISTORY_COMMAND\x10k\x12\x1f\n\x1b\x41\x44\x44_OR_UPDATE_PROTOCOL_CARD\x10n\x12\x1b\n\x16ORIGINATE_TASK_COMMAND\x10\x85\x01\x12\x16\n\x11\x45\x44IT_TASK_COMMAND\x10\x86\x01\x12\x18\n\x13\x44\x45LETE_TASK_COMMAND\x10\x87\x01\x12\x18\n\x13\x43OMMIT_TASK_COMMAND\x10\x88\x01\x12 \n\x1b\x45NTER_IN_ERROR_TASK_COMMAND\x10\x89\x01\x12\x1c\n\x18ORIGINATE_REFILL_COMMAND\x10q\x12\x17\n\x13\x45\x44IT_REFILL_COMMAND\x10r\x12\x19\n\x15\x44\x45LETE_REFILL_COMMAND\x10s\x12\x19\n\x15\x43OMMIT_REFILL_COMMAND\x10t\x12!\n\x1d\x45NTER_IN_ERROR_REFILL_COMMAND\x10u\x12\x1c\n\x18ORIGINATE_VITALS_COMMAND\x10v\x12\x17\n\x13\x45\x44IT_VITALS_COMMAND\x10w\x12\x19\n\x15\x44\x45LETE_VITALS_COMMAND\x10x\x12\x19\n\x15\x43OMMIT_VITALS_COMMAND\x10y\x12!\n\x1d\x45NTER_IN_ERROR_VITALS_COMMAND\x10z\x12&\n\"ORIGINATE_UPDATE_DIAGNOSIS_COMMAND\x10{\x12!\n\x1d\x45\x44IT_UPDATE_DIAGNOSIS_COMMAND\x10|\x12#\n\x1f\x44\x45LETE_UPDATE_DIAGNOSIS_COMMAND\x10}\x12#\n\x1f\x43OMMIT_UPDATE_DIAGNOSIS_COMMAND\x10~\x12+\n\'ENTER_IN_ERROR_UPDATE_DIAGNOSIS_COMMAND\x10\x7f\x12!\n\x1cORIGINATE_CLOSE_GOAL_COMMAND\x10\x80\x01\x12\x1c\n\x17\x45\x44IT_CLOSE_GOAL_COMMAND\x10\x81\x01\x12\x1e\n\x19\x44\x45LETE_CLOSE_GOAL_COMMAND\x10\x82\x01\x12\x1e\n\x19\x43OMMIT_CLOSE_GOAL_COMMAND\x10\x83\x01\x12&\n!ENTER_IN_ERROR_CLOSE_GOAL_COMMAND\x10\x84\x01\x12 \n\x1b\x43REATE_QUESTIONNAIRE_RESULT\x10\x8a\x01\x12-\n(ANNOTATE_PATIENT_CHART_CONDITION_RESULTS\x10\xc8\x01\x12%\n ANNOTATE_CLAIM_CONDITION_RESULTS\x10\xac\x02\x12(\n#SHOW_PATIENT_CHART_SUMMARY_SECTIONS\x10\x90\x03\x12\"\n\x1dSHOW_PATIENT_PROFILE_SECTIONS\x10\xf4\x03\x12\x37\n2PATIENT_PROFILE__ADD_PHARMACY__POST_SEARCH_RESULTS\x10\xf5\x03\x12)\n$SEND_SURESCRIPTS_ELIGIBILITY_REQUEST\x10\xd8\x04\x12\x30\n+SEND_SURESCRIPTS_MEDICATION_HISTORY_REQUEST\x10\xd9\x04\x12&\n!SEND_SURESCRIPTS_BENEFITS_REQUEST\x10\xda\x04\x12\x1b\n\x16ORIGINATE_EXAM_COMMAND\x10\xbc\x05\x12\x16\n\x11\x45\x44IT_EXAM_COMMAND\x10\xbd\x05\x12\x18\n\x13\x44\x45LETE_EXAM_COMMAND\x10\xbe\x05\x12\x18\n\x13\x43OMMIT_EXAM_COMMAND\x10\xbf\x05\x12 \n\x1b\x45NTER_IN_ERROR_EXAM_COMMAND\x10\xc0\x05\x12\x1a\n\x15ORIGINATE_ROS_COMMAND\x10\xa0\x06\x12\x15\n\x10\x45\x44IT_ROS_COMMAND\x10\xa1\x06\x12\x17\n\x12\x44\x45LETE_ROS_COMMAND\x10\xa2\x06\x12\x17\n\x12\x43OMMIT_ROS_COMMAND\x10\xa3\x06\x12\x1f\n\x1a\x45NTER_IN_ERROR_ROS_COMMAND\x10\xa4\x06\x12,\n\'ORIGINATE_STRUCTURED_ASSESSMENT_COMMAND\x10\x84\x07\x12\'\n\"EDIT_STRUCTURED_ASSESSMENT_COMMAND\x10\x85\x07\x12)\n$DELETE_STRUCTURED_ASSESSMENT_COMMAND\x10\x86\x07\x12)\n$COMMIT_STRUCTURED_ASSESSMENT_COMMAND\x10\x87\x07\x12\x31\n,ENTER_IN_ERROR_STRUCTURED_ASSESSMENT_COMMAND\x10\x88\x07\x12\x17\n\x12SHOW_ACTION_BUTTON\x10\xe8\x07\x12(\n#PATIENT_PORTAL__INTAKE_FORM_RESULTS\x10\xd0\x0f\x12.\n)PATIENT_PORTAL__APPOINTMENT_IS_CANCELABLE\x10\xd1\x0f\x12\x31\n,PATIENT_PORTAL__APPOINTMENT_IS_RESCHEDULABLE\x10\xd2\x0f\x12=\n8PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH_RESULTS\x10\xd5\x0f\x12M\nHPATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__PRE_SEARCH_RESULTS\x10\xd6\x0f\x12N\nIPATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH_RESULTS\x10\xd7\x0f\x12\x45\n@PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__PRE_SEARCH_RESULTS\x10\xd8\x0f\x12\x46\nAPATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS\x10\xd9\x0f\x12\x45\n@PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH_RESULTS\x10\xda\x0f\x12\x46\nAPATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS\x10\xdb\x0f\x12\x11\n\x0cLAUNCH_MODAL\x10\xb8\x17\x12\x18\n\x13SIMPLE_API_RESPONSE\x10\xa0\x1f\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -22,7 +22,7 @@ if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _globals['_EFFECTTYPE']._serialized_start=153 - _globals['_EFFECTTYPE']._serialized_end=5917 + _globals['_EFFECTTYPE']._serialized_end=5943 _globals['_EFFECT']._serialized_start=51 _globals['_EFFECT']._serialized_end=150 # @@protoc_insertion_point(module_scope) diff --git a/canvas_generated/messages/effects_pb2.pyi b/canvas_generated/messages/effects_pb2.pyi index 8e993dad..30812922 100644 --- a/canvas_generated/messages/effects_pb2.pyi +++ b/canvas_generated/messages/effects_pb2.pyi @@ -173,6 +173,7 @@ class EffectType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH_RESULTS: _ClassVar[EffectType] PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS: _ClassVar[EffectType] LAUNCH_MODAL: _ClassVar[EffectType] + SIMPLE_API_RESPONSE: _ClassVar[EffectType] UNKNOWN_EFFECT: EffectType LOG: EffectType ADD_PLAN_COMMAND: EffectType @@ -339,6 +340,7 @@ PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS: EffectType PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH_RESULTS: EffectType PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS: EffectType LAUNCH_MODAL: EffectType +SIMPLE_API_RESPONSE: EffectType class Effect(_message.Message): __slots__ = ("type", "payload", "plugin_name", "classname") diff --git a/canvas_sdk/effects/simple_api.py b/canvas_sdk/effects/simple_api.py new file mode 100644 index 00000000..01b19c3d --- /dev/null +++ b/canvas_sdk/effects/simple_api.py @@ -0,0 +1,27 @@ +import json +from collections.abc import Mapping +from typing import Any + +from canvas_generated.messages.effects_pb2 import EffectType +from canvas_sdk.effects import Effect + + +class SimpleAPIResponse: + """SimpleAPI response class.""" + + def __init__( + self, content: Mapping[str, Any], status_code: int, headers: Mapping[str, Any] | None = None + ) -> None: + self._content = content + self._status_code = status_code + self._headers = {**(headers or {}), "Content-Type": "application/json"} + + def apply(self) -> Effect: + """Convert the response into an effect.""" + payload = { + "body": self._content, + "status_code": self._status_code, + "headers": self._headers or {}, + } + + return Effect(type=EffectType.SIMPLE_API_RESPONSE, payload=json.dumps(payload)) diff --git a/canvas_sdk/handlers/simple_api/__init__.py b/canvas_sdk/handlers/simple_api/__init__.py index 2ffa38b5..ea968946 100644 --- a/canvas_sdk/handlers/simple_api/__init__.py +++ b/canvas_sdk/handlers/simple_api/__init__.py @@ -1,3 +1,3 @@ -from .api import Request, SimpleAPI +from .api import SimpleAPI, SimpleAPIRequest -__all__ = ["Request", "SimpleAPI"] +__all__ = ["SimpleAPI", "SimpleAPIRequest"] diff --git a/canvas_sdk/handlers/simple_api/api.py b/canvas_sdk/handlers/simple_api/api.py index e457cd1f..a3e1bdbb 100644 --- a/canvas_sdk/handlers/simple_api/api.py +++ b/canvas_sdk/handlers/simple_api/api.py @@ -1,19 +1,37 @@ -from collections.abc import Callable +from collections.abc import Callable, Iterable from inspect import ismethod from typing import Any from urllib.parse import parse_qs -from canvas_sdk.effects import Effect +from canvas_sdk.effects import Effect, EffectType +from canvas_sdk.effects.simple_api import SimpleAPIResponse from canvas_sdk.events import Event, EventType from canvas_sdk.handlers.base import BaseHandler # TODO: Auth requirements on the home-app side? # TODO: Do we need to handle routing based on path regex (i.e. path parameters)? # TODO: Do we need to handle repeated header names? Django concatenates; other platforms handle them as lists -# TODO: Do we need to handle non-JSON request and response bodies? Maybe not; we would have to Base64 encode complex data to send to gRPC anyway; a user could do the same in a JSON body. +# TODO: Do we need to handle non-JSON request and response bodies? Maybe not; we would have to Base64 encode complex data to send to gRPC anyway; a user could do the same in a JSON body (like in FHIR). +# TODO: 404 Not Found # TODO: Error handling for duplicate routes; test install/reload -# TODO: Response effect; disallow returning returning multiple response effects -class Request: +# TODO: Prevent subclasses from overloading any base class methods. Example: +# class Base: +# def method(self): +# print("This method cannot be overridden.") +# +# def __init_subclass__(cls, **kwargs): +# super().__init_subclass__(**kwargs) +# if 'method' in cls.__dict__: +# raise TypeError(f"{cls.__name__} is not allowed to override 'method'") +# +# class Derived(Base): +# def method(self): # This will raise TypeError at runtime +# print("Attempting to override.") +# +# d = Derived() # TypeError: Derived is not allowed to override 'method' + + +class SimpleAPIRequest: """Request class for incoming requests to the API.""" def __init__(self, event: Event) -> None: @@ -21,11 +39,12 @@ def __init__(self, event: Event) -> None: self.method = event.context["method"] self.path = event.context["path"] self.query_string = event.context["query_string"] - self.query_params = parse_qs(self.query_string) self.body = event.context["body"] + self.query_params = parse_qs(self.query_string) + -RouteHandler = Callable[[Request], list[Effect]] +RouteHandler = Callable[[SimpleAPIRequest], SimpleAPIResponse | list[SimpleAPIResponse | Effect]] def get(path: str) -> Callable[[RouteHandler], RouteHandler]: @@ -89,10 +108,37 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def compute(self) -> list[Effect]: """Route the incoming request to the handler based on the HTTP method and path.""" - request = Request(self.event) + request = SimpleAPIRequest(self.event) + # Get the handler method handler = self._routes.get((request.method, request.path)) if not handler: return [] - return handler(request) + # Handle the request + effects = handler(request) + if not isinstance(effects, Iterable): + effects = [effects] + + # Transform any API responses into effects if they aren't already effects + response_count = 0 + for index, effect in enumerate(effects): + if isinstance(effect, SimpleAPIResponse): + effects[index] = effect.apply() + if effects[index].type == EffectType.SIMPLE_API_RESPONSE: + response_count += 1 + + # TODO: What should the behavior be for non-response effects if this error condition occurs? + # If there is more than one response, remove the responses and return an error response + # instead. Allow non-response effects to pass through unaffected. + if response_count > 1: + effects = [ + effect for effect in effects if effect.type != EffectType.SIMPLE_API_RESPONSE + ] + effects.append( + SimpleAPIResponse( + content={"error": "Multiple responses provided"}, status_code=500 + ).apply() + ) + + return effects diff --git a/protobufs/canvas_generated/messages/effects.proto b/protobufs/canvas_generated/messages/effects.proto index 22c6a1ee..e278f53b 100644 --- a/protobufs/canvas_generated/messages/effects.proto +++ b/protobufs/canvas_generated/messages/effects.proto @@ -212,6 +212,8 @@ enum EffectType { PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS = 2011; LAUNCH_MODAL = 3000; + + SIMPLE_API_RESPONSE = 4000; } message Effect {