canvas 0.13.2__py3-none-any.whl → 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of canvas might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: canvas
3
- Version: 0.13.2
3
+ Version: 0.14.0
4
4
  Summary: SDK to customize event-driven actions in your Canvas instance
5
5
  License: MIT
6
6
  Author: Canvas Team
@@ -10,8 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
- Requires-Dist: boto3 (>=1.35.88,<2.0.0)
14
- Requires-Dist: boto3-stubs[s3] (>=1.35.88,<2.0.0)
15
13
  Requires-Dist: cookiecutter
16
14
  Requires-Dist: cron-converter (>=1.2.1,<2.0.0)
17
15
  Requires-Dist: deprecation (>=2.1.0,<3.0.0)
@@ -97,8 +97,8 @@ canvas_cli/utils/validators/__init__.py,sha256=rBvSR2O1hWkNAnUBdcr-zUkmqT796_A61
97
97
  canvas_cli/utils/validators/manifest_schema.py,sha256=6PQmK2AdMWxrziBtv6jcpyoNk3SQEZsJCt3PrI0ZYm4,3961
98
98
  canvas_cli/utils/validators/tests.py,sha256=KhaOdbxGUK2QwL2KnycAJJkqclYQSXF7CKg-scSf0DU,1259
99
99
  canvas_cli/utils/validators/validators.py,sha256=lrUBQ0sF7seTe_pNGsNgASdr2BGp6g-Qui58V4H9qfQ,1598
100
- canvas_generated/messages/effects_pb2.py,sha256=hvORKOxKesEFMnvrcu-QAzWDDaMXEpnkOGifVulkh9k,9029
101
- canvas_generated/messages/effects_pb2.pyi,sha256=AULAWggal2prDVKBg82axRO2uzQBzj0Qp9cvHn8FUOg,15588
100
+ canvas_generated/messages/effects_pb2.py,sha256=_fAq_TZ_82aC1qNdilFTZ53glzUJhqZ8ze19qnOJJqU,9082
101
+ canvas_generated/messages/effects_pb2.pyi,sha256=UraC7VQ8GeymsfiMl5q8e0csHOKq2xv47vagCPTSe9o,15683
102
102
  canvas_generated/messages/effects_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
103
103
  canvas_generated/messages/events_pb2.py,sha256=5xnMRMlqBTkxM3h8EVUW3EiNZ1LNoBGvMWFlwdf2epI,39868
104
104
  canvas_generated/messages/events_pb2.pyi,sha256=HVqS44OgMYGL47svBJE9iQbLXaOm-EoMvBYvdP6Ha3c,71175
@@ -162,6 +162,7 @@ canvas_sdk/effects/patient_profile_configuration.py,sha256=ZXBx_PnJP-KKRIwuDI4tS
162
162
  canvas_sdk/effects/protocol_card/__init__.py,sha256=AwXAARybFhVIfYwqiRThG6Ne3ARnKgvuvuGUknWpwTo,134
163
163
  canvas_sdk/effects/protocol_card/protocol_card.py,sha256=aL8lXRBm1ByU-X0xzgBepxKnUTLvmr-OC7G-C-EBjy8,2596
164
164
  canvas_sdk/effects/protocol_card/tests.py,sha256=ZiDZTjn-Z6UpEfR1S-6XDcJXlPlTPpLbg2PgvkQD8Uk,6815
165
+ canvas_sdk/effects/questionnaire_result.py,sha256=dXJE6la5aP5xLCnT9iMCOS6PiKu0Zz733Ku2lqa4CnM,816
165
166
  canvas_sdk/effects/show_button.py,sha256=JnW9nM8S_GUXIOufs-uef3pg0HPDxSbF0l51Wh1Xxgw,715
166
167
  canvas_sdk/effects/surescripts/surescripts_messages.py,sha256=tMG5ry2mZA5pwO0uGJfYZnJuVpj5MVhS2lYT5IRsb1Y,2582
167
168
  canvas_sdk/effects/task/task.py,sha256=olG7gJN3CC1JKrGU4SdVvRq8qLduPuiNz80C_dTaY58,2837
@@ -172,7 +173,7 @@ canvas_sdk/handlers/action_button.py,sha256=8q7zo96k_4SV4JUHc_GoqbBksXW2dk_EE33H
172
173
  canvas_sdk/handlers/application.py,sha256=8HwgJeHThCpif-vLAfgQU0En3Y07AEyONPIWkLUoqyM,910
173
174
  canvas_sdk/handlers/base.py,sha256=E5UCnckZJM2N7G-NEWh5Wj2BmSARM6dNxS26Ly8AKug,1277
174
175
  canvas_sdk/handlers/cron_task.py,sha256=Q4_D3bDKE5hyEpCf0JnGSgZlnLh_g9RSP3oEfIuLJ9Y,945
175
- canvas_sdk/models/__init__.py,sha256=Toz3-9Im7ZsT5_i8ToS3U6LLFm3qCerkb95EwPc-alA,321
176
+ canvas_sdk/models/__init__.py,sha256=o2axmFmIcYihBwPCeErDyjiEKCdN1rVuJWr7P81AXnM,376
176
177
  canvas_sdk/protocols/__init__.py,sha256=3u9zet5D4DX4V953tLCoN1xhaOhAUCwGwscMv-7IIxo,186
177
178
  canvas_sdk/protocols/base.py,sha256=sbm0uOk3PPfPemqBmHh2hawE5utC6no46EmvyMN8Y7Q,179
178
179
  canvas_sdk/protocols/clinical_quality_measure.py,sha256=39ddtSIBCFB4DF7Kpuh2uDwSxH-mV7sjrsVrQY7tEuo,4835
@@ -185,26 +186,26 @@ canvas_sdk/utils/stats.py,sha256=sJhIW_IssUVefQN6rrUAt1P0KvVIUIYcnpZlMHLibNA,732
185
186
  canvas_sdk/utils/tests.py,sha256=0Buh_7PvDU1D081_rSJoYSJwIHMOBbL0gtGS3bSKe7s,2285
186
187
  canvas_sdk/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
188
  canvas_sdk/v1/data/__init__.py,sha256=Ea0apSYEz2UEhlZT9bmLH0rqmh75-2C0AsxbqDIfYnU,567
188
- canvas_sdk/v1/data/allergy_intolerance.py,sha256=WmBDis-XuMPdyuUhLuv36KFyNUvP7Zg8Y8lacp1nGRY,2039
189
+ canvas_sdk/v1/data/allergy_intolerance.py,sha256=BJD8eDVpdwxPTM2ebdZeCLG8-lafS_kxFHhPJsnZY7g,2099
189
190
  canvas_sdk/v1/data/base.py,sha256=ZMZEpwkeg3ewl3QsbPAF7-z1MqqWu5hCR-IibWQCiHI,6189
190
- canvas_sdk/v1/data/billing.py,sha256=w5vK7aWqOGEkgjrzJWTiDsywUdxFdK7naEImZkvKxHk,2040
191
- canvas_sdk/v1/data/command.py,sha256=z9LHw95Vq8efZE5y9r-Ei-Yzt8qj-o-BE3x9BBb4yPQ,945
191
+ canvas_sdk/v1/data/billing.py,sha256=sAC5IMV526pdEZ9FwEmN7dz30F0kOHqH7Rh_JBwReuQ,2076
192
+ canvas_sdk/v1/data/command.py,sha256=Tk4oA-QBhq8p38xUClrv6Wp0uaIbpFW1iHDZV356_Y0,1636
192
193
  canvas_sdk/v1/data/common.py,sha256=OvF4Oxo3BqE1Cv2lfyA1MXp_q_VHwoihyiJ0ETfa5lY,3459
193
- canvas_sdk/v1/data/condition.py,sha256=qUiwTf4Le5GIhWRgvXZFsXTS4nH4trNBc2mtJsO12FQ,1858
194
- canvas_sdk/v1/data/detected_issue.py,sha256=c1rKZuPV_tTp_1yDJJa88gIsixpPV_F-a3eooje8BW8,1710
195
- canvas_sdk/v1/data/device.py,sha256=uxeQRT8ljzBmDwsARFDB9MZKAVAKOA-pu4nAa6fcHeU,1655
196
- canvas_sdk/v1/data/imaging.py,sha256=z6CtVcBYv8EL7fxzT0pWPUuh1mt5XFPlRDNNbOjvLng,4303
197
- canvas_sdk/v1/data/lab.py,sha256=9_yZAX_JQRWRkdjegivpAd3NAlZ_pV-t6eehPKXhJKU,10909
198
- canvas_sdk/v1/data/medication.py,sha256=PGOOTnhrPSSExSuQJyIdsDLSg09riMb2usvpRmcUo7s,1998
199
- canvas_sdk/v1/data/note.py,sha256=Konugbme7rYTSlP5XICi24-VJkaDc0cUGDF28JRT8Bw,6562
200
- canvas_sdk/v1/data/observation.py,sha256=BftaK7tOZaTlfUKoTH_A-X23J4DUFRVWpvZhkBJ1DJg,3710
194
+ canvas_sdk/v1/data/condition.py,sha256=g_kBv5zV3ynUg8fZbBqIrVTUBbl70r7pED-YxmnZ540,1930
195
+ canvas_sdk/v1/data/detected_issue.py,sha256=IvxHrBa2YK_ylHNi2HGSK0ji2yk92C_6EDDnsMh6CnY,1790
196
+ canvas_sdk/v1/data/device.py,sha256=PkRPKhMOA848O2gzgb1_LL0-YxUp-hPMQ_B0JKAQIvw,1713
197
+ canvas_sdk/v1/data/imaging.py,sha256=IOavBqdKDQAa6Tzcbl9GUVrkMOCMoCqIt5Tr9AV-qLc,4430
198
+ canvas_sdk/v1/data/lab.py,sha256=yYSCgqrMuAD-ebG-1oAiFaImyPnn3RE0ynqXJbrjTy8,11061
199
+ canvas_sdk/v1/data/medication.py,sha256=c33tsmUnVsehmdIDr1VNP_2Q56UNZoaoja3oV35Y898,2070
200
+ canvas_sdk/v1/data/note.py,sha256=tT0tHC2KtDP6QHFKtfJLYF45zb0P1GJAk90OAPPuoWE,6642
201
+ canvas_sdk/v1/data/observation.py,sha256=iY6FaQahN1sa0yIRGqLHJGDKhtJu_rLiv0C8yJr4-Ak,3812
201
202
  canvas_sdk/v1/data/organization.py,sha256=SsRcBZfExmr87Wq2aU54AoTZzXJjfvHoV4XVF7oqvRA,995
202
203
  canvas_sdk/v1/data/patient.py,sha256=iUSTAsEB_cVEUP12mkvJ7J5DkYhUZWmHTDw4JPdB6pc,3090
203
- canvas_sdk/v1/data/practicelocation.py,sha256=R57iEQoFO0TRaXJIiOkNci4TzH2tWex47AzAVC-dOiI,4242
204
- canvas_sdk/v1/data/protocol_override.py,sha256=Dra2qf5-VX7qsK5mWy6U5XU7Y9v4NGkobRU4BAeAxkU,1835
205
- canvas_sdk/v1/data/questionnaire.py,sha256=8RIX_YtxCZd2NK9hK41ehywHA5vHks1wqigZf5LHPvI,6908
206
- canvas_sdk/v1/data/staff.py,sha256=J-U5NeKH-P41ph6pWiTd5v-QYQHV0GmvxxGVVUUf1nc,2786
207
- canvas_sdk/v1/data/task.py,sha256=9N66XL4B51rla4Q-Tn7STIGvR5Qr6-DNqfMYnT6t8yE,3585
204
+ canvas_sdk/v1/data/practicelocation.py,sha256=nM2T4rzkutIZWQCTULvMNGpvTuWOvOrvISNWjOPe2Io,4264
205
+ canvas_sdk/v1/data/protocol_override.py,sha256=FQBMaKTKCCnr4ZYgsorAYKSWh_YVuFAW_UqHmHifUTY,1851
206
+ canvas_sdk/v1/data/questionnaire.py,sha256=xTqZUfTjz5eyRdP2xh_bJ8YPc5pWxevsto_U0uf1v0I,7068
207
+ canvas_sdk/v1/data/staff.py,sha256=GFTdRB_FlpssB44kPDNJ8-S-cK14mm9banDZcv-wyuk,2819
208
+ canvas_sdk/v1/data/task.py,sha256=hQHJstXntQjCK3Fgg0C7ktqvaqmTmvUCy2PeyfkStGU,3668
208
209
  canvas_sdk/v1/data/user.py,sha256=DmA8fNNGwiNOmuR-RYxQw1NpxN5yYhgwtRtRBRJ_22g,309
209
210
  canvas_sdk/value_set/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
210
211
  canvas_sdk/value_set/custom.py,sha256=0xzGP86ioCGcsx8wwf46SXfe9IefLSGVq7Z4yrHbEUU,19709
@@ -232,10 +233,11 @@ logger/__init__.py,sha256=9o2iRCjzFEhfULgXvrrECRFK-4IslWJTqKKjTCEUbq8,61
232
233
  logger/logger.py,sha256=axf7UffBJtETjwDCtmi1IaaJKsvcFj8zaLfouGsq68A,1847
233
234
  plugin_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
234
235
  plugin_runner/authentication.py,sha256=SDPso2AogtLAV_H0LuMDp99IMZuF3oTq-Q_AXAvJ8uc,1116
236
+ plugin_runner/aws_headers.py,sha256=DenX_nAMVhXMJZw88PLZbqJsi5_XriNtr3jE-eJqHY4,2773
235
237
  plugin_runner/exceptions.py,sha256=YnRZiQVzbU3HrVlmEXLje_np99009YnhTRVHHyBCtqc,433
236
- plugin_runner/plugin_installer.py,sha256=Ah3A1j6vO8wi4HPRYVlxF1w-GqbuTvLmDCQvLQK3Oy8,7020
237
- plugin_runner/plugin_runner.py,sha256=at5BTolHnVWQci-GTUbvJsI-hsjeR9lDCnSz9OhIJ34,15296
238
- plugin_runner/plugin_synchronizer.py,sha256=EwBpKopmArjXxsE9hWpvHRSaVjCq4mg8kXxK3hQMeCk,2684
238
+ plugin_runner/plugin_installer.py,sha256=QNc222WQHJk9d8YE8Npi3mlkCApbYVH4M9VZX5-AYxw,7676
239
+ plugin_runner/plugin_runner.py,sha256=EZ3snfYZ5kirlELY0LH6stcz6NuaJQ_WJE8aKi59EVU,15269
240
+ plugin_runner/plugin_synchronizer.py,sha256=GcY2oKwJc8beDoAJwo-0MkY4rxNzoF-Bk1bF9MOtO6U,2851
239
241
  plugin_runner/sandbox.py,sha256=JUbphdu1iY0rPU6DdMEjFA32BBsZXHUz1KguCYpmrug,9384
240
242
  plugin_runner/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
241
243
  plugin_runner/tests/data/plugins/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -269,13 +271,13 @@ plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/bas
269
271
  plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
270
272
  plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py,sha256=V_6DPXsJaD12zGyKqGjYY2JFtKEk4E0eIAgBWax_MGc,629
271
273
  plugin_runner/tests/test_application.py,sha256=ZirW14UGorfkQ_VSbnoKtmTvx6lHFS2EqndYNczIQfo,2391
272
- plugin_runner/tests/test_plugin_installer.py,sha256=BETY7stpf_DZdSBLSSBc_NeAWcLU0m_cptG8C8_rk1Q,3937
273
- plugin_runner/tests/test_plugin_runner.py,sha256=pj9C3-GNsTyUYNIkOiihmxAO67FQE1GU5BoM727SnxA,7539
274
- plugin_runner/tests/test_sandbox.py,sha256=6Jw2Akcl1KQpXb01FxDvI_ZToJzOcpABwFkXK3fl8BU,3434
274
+ plugin_runner/tests/test_plugin_installer.py,sha256=JNzLTAl7K8vFYe_WIt-_-VxuxoD_EdLnkceJWVfFvYA,3999
275
+ plugin_runner/tests/test_plugin_runner.py,sha256=htO1Pu-73AusrBP1EPeaeVPXcDYyHSEb-aHpSZFTz04,7029
276
+ plugin_runner/tests/test_sandbox.py,sha256=GX_nKgpU2R6s3V__dogkqEhLOXcqsQlic2kjwSwl_30,3835
275
277
  pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
276
278
  pubsub/pubsub.py,sha256=pyTW0JU8mtaqiAV6g6xjZwel1CVy2EonPMU-_vkmhUM,1044
277
- settings.py,sha256=UG0jV2Sht7ftEJjqEyLKn9fld0jAtblqO7oJdfjNYvA,2144
278
- canvas-0.13.2.dist-info/METADATA,sha256=JikPMsbC7-iYOeMXZv-pSWyq06V0H3D1Des1-IFnfro,4793
279
- canvas-0.13.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
280
- canvas-0.13.2.dist-info/entry_points.txt,sha256=VSmSo1IZ3aEfL7enmLmlWSraS_IIkoXNVeyXzgRxFiY,46
281
- canvas-0.13.2.dist-info/RECORD,,
279
+ settings.py,sha256=S_nlo7S2UxJUXrKRCv2hRXXysKrjJ9pxjT2Amb-JSao,2392
280
+ canvas-0.14.0.dist-info/METADATA,sha256=SQTZ2hB4GgCqEI8hSV_QsRNJXpnsjzmlpkzjerlkaaM,4703
281
+ canvas-0.14.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
282
+ canvas-0.14.0.dist-info/entry_points.txt,sha256=VSmSo1IZ3aEfL7enmLmlWSraS_IIkoXNVeyXzgRxFiY,46
283
+ canvas-0.14.0.dist-info/RECORD,,
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
14
14
 
15
15
 
16
16
 
17
- 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*\x83(\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(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\x11\n\x0cLAUNCH_MODAL\x10\xb8\x17\x62\x06proto3')
17
+ 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*\xa5(\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\x11\n\x0cLAUNCH_MODAL\x10\xb8\x17\x62\x06proto3')
18
18
 
19
19
  _globals = globals()
20
20
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,7 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'canvas_generated.messages.e
22
22
  if _descriptor._USE_C_DESCRIPTORS == False:
23
23
  DESCRIPTOR._options = None
24
24
  _globals['_EFFECTTYPE']._serialized_start=153
25
- _globals['_EFFECTTYPE']._serialized_end=5276
25
+ _globals['_EFFECTTYPE']._serialized_end=5310
26
26
  _globals['_EFFECT']._serialized_start=51
27
27
  _globals['_EFFECT']._serialized_end=150
28
28
  # @@protoc_insertion_point(module_scope)
@@ -137,6 +137,7 @@ class EffectType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
137
137
  DELETE_CLOSE_GOAL_COMMAND: _ClassVar[EffectType]
138
138
  COMMIT_CLOSE_GOAL_COMMAND: _ClassVar[EffectType]
139
139
  ENTER_IN_ERROR_CLOSE_GOAL_COMMAND: _ClassVar[EffectType]
140
+ CREATE_QUESTIONNAIRE_RESULT: _ClassVar[EffectType]
140
141
  ANNOTATE_PATIENT_CHART_CONDITION_RESULTS: _ClassVar[EffectType]
141
142
  ANNOTATE_CLAIM_CONDITION_RESULTS: _ClassVar[EffectType]
142
143
  SHOW_PATIENT_CHART_SUMMARY_SECTIONS: _ClassVar[EffectType]
@@ -293,6 +294,7 @@ EDIT_CLOSE_GOAL_COMMAND: EffectType
293
294
  DELETE_CLOSE_GOAL_COMMAND: EffectType
294
295
  COMMIT_CLOSE_GOAL_COMMAND: EffectType
295
296
  ENTER_IN_ERROR_CLOSE_GOAL_COMMAND: EffectType
297
+ CREATE_QUESTIONNAIRE_RESULT: EffectType
296
298
  ANNOTATE_PATIENT_CHART_CONDITION_RESULTS: EffectType
297
299
  ANNOTATE_CLAIM_CONDITION_RESULTS: EffectType
298
300
  SHOW_PATIENT_CHART_SUMMARY_SECTIONS: EffectType
@@ -0,0 +1,29 @@
1
+ from typing import Any
2
+
3
+ from canvas_sdk.effects.base import EffectType, _BaseEffect
4
+
5
+
6
+ class CreateQuestionnaireResult(_BaseEffect):
7
+ """CreateQuestionnaireResult effect."""
8
+
9
+ class Meta:
10
+ effect_type = EffectType.CREATE_QUESTIONNAIRE_RESULT
11
+
12
+ interview_id: str | None = None
13
+ score: float | None = None
14
+ abnormal: bool | None = None
15
+ narrative: str | None = None
16
+ code_system: str | None = None
17
+ code: str | None = None
18
+
19
+ @property
20
+ def values(self) -> dict[str, Any]:
21
+ """Make the payload."""
22
+ return {
23
+ "interview_id": self.interview_id,
24
+ "score": self.score,
25
+ "abnormal": self.abnormal or False,
26
+ "narrative": self.narrative or "",
27
+ "code_system": self.code_system,
28
+ "code": self.code,
29
+ }
@@ -5,3 +5,4 @@ from canvas_sdk.v1.data.condition import Condition
5
5
  from canvas_sdk.v1.data.note import Note
6
6
  from canvas_sdk.v1.data.patient import Patient
7
7
  from canvas_sdk.v1.data.user import CanvasUser
8
+ from canvas_sdk.v1.data.questionnaire import Interview
@@ -20,12 +20,13 @@ class AllergyIntolerance(models.Model):
20
20
  created = models.DateTimeField()
21
21
  modified = models.DateTimeField()
22
22
  deleted = models.BooleanField()
23
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
24
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
23
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
24
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
25
25
  patient = models.ForeignKey(
26
26
  Patient,
27
27
  on_delete=models.DO_NOTHING,
28
28
  related_name="allergy_intolerances",
29
+ null=True,
29
30
  )
30
31
  note_id = models.BigIntegerField()
31
32
  allergy_intolerance_type = models.CharField()
@@ -58,4 +59,5 @@ class AllergyIntoleranceCoding(models.Model):
58
59
  AllergyIntolerance,
59
60
  on_delete=models.DO_NOTHING,
60
61
  related_name="codings",
62
+ null=True,
61
63
  )
@@ -46,9 +46,11 @@ class BillingLineItem(models.Model):
46
46
  dbid = models.BigIntegerField(primary_key=True)
47
47
  created = models.DateTimeField()
48
48
  modified = models.DateTimeField()
49
- note = models.ForeignKey(Note, on_delete=models.DO_NOTHING, related_name="billing_line_items")
49
+ note = models.ForeignKey(
50
+ Note, on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
51
+ )
50
52
  patient = models.ForeignKey(
51
- Patient, on_delete=models.DO_NOTHING, related_name="billing_line_items"
53
+ Patient, on_delete=models.DO_NOTHING, related_name="billing_line_items", null=True
52
54
  )
53
55
  cpt = models.CharField()
54
56
  charge = models.DecimalField()
@@ -1,3 +1,4 @@
1
+ from django.apps import apps
1
2
  from django.db import models
2
3
 
3
4
  from canvas_sdk.v1.data.patient import Patient
@@ -16,12 +17,24 @@ class Command(models.Model):
16
17
  dbid = models.BigIntegerField(primary_key=True)
17
18
  created = models.DateTimeField()
18
19
  modified = models.DateTimeField()
19
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
20
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
21
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
20
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
21
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
22
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
22
23
  state = models.CharField()
23
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING)
24
+ patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, null=True)
24
25
  note_id = models.BigIntegerField()
25
26
  schema_key = models.TextField()
26
27
  data = models.JSONField()
27
28
  origination_source = models.CharField()
29
+ anchor_object_type = models.CharField()
30
+ anchor_object_dbid = models.BigIntegerField()
31
+
32
+ @property
33
+ def anchor_object(self) -> models.Model | None:
34
+ """
35
+ Use the anchor_object_type and anchor_object_dbid to get the anchor object for the command.
36
+ """
37
+ # TODO: Is the anchor object type enough here, or do we need a mapping? The home-app model
38
+ # names might not exactly match the plugins model names.
39
+ anchor_model = apps.get_model(app_label="canvas_sdk", model_name=self.anchor_object_type)
40
+ return anchor_model.objects.get(dbid=self.anchor_object_dbid)
@@ -35,9 +35,11 @@ class Condition(models.Model):
35
35
  id = models.UUIDField()
36
36
  dbid = models.BigIntegerField(primary_key=True)
37
37
  deleted = models.BooleanField()
38
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
39
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
40
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="conditions")
38
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
39
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
40
+ patient = models.ForeignKey(
41
+ Patient, on_delete=models.DO_NOTHING, related_name="conditions", null=True
42
+ )
41
43
  onset_date = models.DateField()
42
44
  resolution_date = models.DateField()
43
45
  clinical_status = models.CharField(choices=ClinicalStatus.choices)
@@ -57,4 +59,6 @@ class ConditionCoding(models.Model):
57
59
  code = models.CharField()
58
60
  display = models.CharField()
59
61
  user_selected = models.BooleanField()
60
- condition = models.ForeignKey(Condition, on_delete=models.DO_NOTHING, related_name="codings")
62
+ condition = models.ForeignKey(
63
+ Condition, on_delete=models.DO_NOTHING, related_name="codings", null=True
64
+ )
@@ -18,11 +18,11 @@ class DetectedIssue(models.Model):
18
18
  modified = models.DateTimeField()
19
19
  identified = models.DateTimeField()
20
20
  deleted = models.BooleanField()
21
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
22
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
23
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
21
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
22
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
23
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
24
24
  patient = models.ForeignKey(
25
- Patient, on_delete=models.DO_NOTHING, related_name="detected_issues"
25
+ Patient, on_delete=models.DO_NOTHING, related_name="detected_issues", null=True
26
26
  )
27
27
  code = models.CharField()
28
28
  status = models.CharField()
@@ -48,5 +48,8 @@ class DetectedIssueEvidence(models.Model):
48
48
  display = models.CharField()
49
49
  user_selected = models.BooleanField()
50
50
  detected_issue = models.ForeignKey(
51
- DetectedIssue, on_delete=models.DO_NOTHING, related_name="evidence"
51
+ DetectedIssue,
52
+ on_delete=models.DO_NOTHING,
53
+ related_name="evidence",
54
+ null=True,
52
55
  )
@@ -16,10 +16,12 @@ class Device(models.Model):
16
16
  dbid = models.BigIntegerField(primary_key=True)
17
17
  created = models.DateTimeField()
18
18
  modified = models.DateTimeField()
19
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
20
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
21
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
22
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="devices")
19
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
20
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
21
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
22
+ patient = models.ForeignKey(
23
+ Patient, on_delete=models.DO_NOTHING, related_name="devices", null=True
24
+ )
23
25
  note_id = models.BigIntegerField()
24
26
  deleted = models.BooleanField()
25
27
  labeled_contains_NRL = models.BooleanField()
@@ -22,23 +22,25 @@ class ImagingOrder(models.Model):
22
22
  dbid = models.BigIntegerField(primary_key=True)
23
23
  created = models.DateTimeField()
24
24
  modified = models.DateTimeField()
25
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
25
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
26
26
  deleted = models.BooleanField()
27
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
27
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
28
28
  entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
29
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="imaging_orders")
29
+ patient = models.ForeignKey(
30
+ Patient, on_delete=models.DO_NOTHING, related_name="imaging_orders", null=True
31
+ )
30
32
  # TODO - uncomment when Note model is complete
31
- # note = models.ForeigneKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_orders")
33
+ # note = models.ForeigneKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_orders", null=True)
32
34
  imaging = models.CharField()
33
35
  # TODO - uncomment when ServiceProvider model is complete
34
- # imaging_center = models.ForeignKey(ServiceProvider, related_name="imaging_orders", null=True, on_delete=models.DO_NOTHING)
36
+ # imaging_center = models.ForeignKey(ServiceProvider, on_delete=models.DO_NOTHING, related_name="imaging_orders", null=True)
35
37
  note_to_radiologist = models.CharField()
36
38
  internal_comment = models.CharField()
37
39
  status = models.CharField(choices=OrderStatus)
38
40
  date_time_ordered = models.DateTimeField()
39
41
  priority = models.CharField()
40
42
  # TODO - uncomment when Staff model is complete
41
- # ordering_provider = models.ForeignKey(Staff, on_delete=models.CASCADE, related_name="imaging_orders", null=True)
43
+ # ordering_provider = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, related_name="imaging_orders", null=True)
42
44
  delegated = models.BooleanField(default=False)
43
45
 
44
46
 
@@ -54,19 +56,19 @@ class ImagingReview(models.Model):
54
56
  dbid = models.BigIntegerField(primary_key=True)
55
57
  created = models.DateTimeField()
56
58
  modified = models.DateTimeField()
57
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
59
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
58
60
  deleted = models.BooleanField()
59
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
60
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
61
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
62
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
61
63
  patient_communication_method = models.CharField(choices=ReviewPatientCommunicationMethod)
62
64
  # TODO - uncomment when Note model is complete
63
- # note = models.ForeignKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_reviews")
65
+ # note = models.ForeignKey(Note, on_delete=models.DO_NOTHING, related_name="imaging_reviews", null=True)
64
66
  internal_comment = models.CharField()
65
67
  message_to_patient = models.CharField()
66
68
  is_released_to_patient = models.BooleanField()
67
69
  status = models.CharField(choices=ReviewStatus)
68
70
  patient = models.ForeignKey(
69
- Patient, on_delete=models.DO_NOTHING, related_name="imaging_reviews"
71
+ Patient, on_delete=models.DO_NOTHING, related_name="imaging_reviews", null=True
70
72
  )
71
73
 
72
74
 
@@ -92,11 +94,11 @@ class ImagingReport(models.Model):
92
94
  requires_signature = models.BooleanField()
93
95
  assigned_date = models.DateTimeField()
94
96
  patient = models.ForeignKey(
95
- Patient, on_delete=models.DO_NOTHING, related_name="imaging_results"
97
+ Patient, on_delete=models.DO_NOTHING, related_name="imaging_results", null=True
96
98
  )
97
99
  order = models.ForeignKey(ImagingOrder, on_delete=models.DO_NOTHING, null=True)
98
100
  source = models.CharField(choices=ImagingReportSource)
99
101
  name = models.CharField()
100
102
  result_date = models.DateField()
101
103
  original_date = models.DateField()
102
- review = models.ForeignKey(ImagingReview, null=True, on_delete=models.DO_NOTHING)
104
+ review = models.ForeignKey(ImagingReview, on_delete=models.DO_NOTHING, null=True)
canvas_sdk/v1/data/lab.py CHANGED
@@ -47,12 +47,12 @@ class LabReport(models.Model):
47
47
  version = models.IntegerField()
48
48
  requisition_number = models.CharField()
49
49
  review = models.ForeignKey(
50
- "LabReview", related_name="reports", on_delete=models.DO_NOTHING, null=True
50
+ "LabReview", on_delete=models.DO_NOTHING, related_name="reports", null=True
51
51
  )
52
52
  original_date = models.DateTimeField()
53
53
  date_performed = models.DateTimeField()
54
54
  custom_document_name = models.CharField()
55
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
55
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
56
56
  committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
57
57
  entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
58
58
  deleted = models.BooleanField()
@@ -72,14 +72,16 @@ class LabReview(models.Model):
72
72
  dbid = models.BigIntegerField(primary_key=True)
73
73
  created = models.DateTimeField()
74
74
  modified = models.DateTimeField()
75
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
75
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
76
76
  deleted = models.BooleanField()
77
77
  committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
78
78
  entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
79
79
  internal_comment = models.TextField()
80
80
  message_to_patient = models.CharField()
81
81
  status = models.CharField()
82
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_reviews")
82
+ patient = models.ForeignKey(
83
+ Patient, on_delete=models.DO_NOTHING, related_name="lab_reviews", null=True
84
+ )
83
85
  patient_communication_method = models.CharField()
84
86
 
85
87
 
@@ -113,7 +115,7 @@ class LabValue(models.Model):
113
115
  created = models.DateTimeField()
114
116
  modified = models.DateTimeField()
115
117
  report = models.ForeignKey(
116
- "LabReport", related_name="values", on_delete=models.DO_NOTHING, null=True
118
+ "LabReport", on_delete=models.DO_NOTHING, related_name="values", null=True
117
119
  )
118
120
  value = models.TextField()
119
121
  units = models.CharField()
@@ -178,14 +180,16 @@ class LabOrder(models.Model):
178
180
  dbid = models.BigIntegerField(primary_key=True)
179
181
  created = models.DateTimeField()
180
182
  modified = models.DateTimeField()
181
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
183
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
182
184
  deleted = models.BooleanField()
183
185
  committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
184
186
  entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
185
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_orders")
187
+ patient = models.ForeignKey(
188
+ Patient, on_delete=models.DO_NOTHING, related_name="lab_orders", null=True
189
+ )
186
190
  ontology_lab_partner = models.CharField()
187
191
  # TODO - uncomment when the Note model is finished
188
- # note = models.ForeignKey("Note", on_delete=models.DO_NOTHING)
192
+ # note = models.ForeignKey("Note", on_delete=models.DO_NOTHING, null=True)
189
193
  comment = models.CharField()
190
194
  requisition_number = models.CharField()
191
195
  is_patient_bill = models.BooleanField(null=True)
@@ -224,11 +228,13 @@ class LabOrderReason(models.Model):
224
228
  dbid = models.BigIntegerField(primary_key=True)
225
229
  created = models.DateTimeField()
226
230
  modified = models.DateTimeField()
227
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
231
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
228
232
  deleted = models.BooleanField()
229
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
230
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
231
- order = models.ForeignKey(LabOrder, on_delete=models.DO_NOTHING, related_name="reasons")
233
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
234
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
235
+ order = models.ForeignKey(
236
+ LabOrder, on_delete=models.DO_NOTHING, related_name="reasons", null=True
237
+ )
232
238
  mode = models.CharField(max_length=30, choices=LabReasonMode)
233
239
 
234
240
 
@@ -281,7 +287,7 @@ class LabTest(models.Model):
281
287
  ontology_test_code = models.CharField(max_length=512, blank=True, default="")
282
288
  status = models.CharField(max_length=30, choices=LabTestOrderStatus)
283
289
  report = models.ForeignKey(
284
- LabReport, on_delete=models.DO_NOTHING, null=True, related_name="tests"
290
+ LabReport, on_delete=models.DO_NOTHING, related_name="tests", null=True
285
291
  )
286
292
  aoe_code = models.CharField(max_length=10, default="")
287
293
  procedure_class = models.CharField(max_length=10, default="")
@@ -31,10 +31,12 @@ class Medication(models.Model):
31
31
 
32
32
  id = models.UUIDField()
33
33
  dbid = models.BigIntegerField(primary_key=True)
34
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="medications")
34
+ patient = models.ForeignKey(
35
+ Patient, on_delete=models.DO_NOTHING, related_name="medications", null=True
36
+ )
35
37
  deleted = models.BooleanField()
36
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
37
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
38
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
39
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
38
40
  status = models.CharField(choices=Status.choices)
39
41
  start_date = models.DateTimeField()
40
42
  end_date = models.DateTimeField()
@@ -59,4 +61,6 @@ class MedicationCoding(models.Model):
59
61
  code = models.CharField()
60
62
  display = models.CharField()
61
63
  user_selected = models.BooleanField()
62
- medication = models.ForeignKey(Medication, on_delete=models.DO_NOTHING, related_name="codings")
64
+ medication = models.ForeignKey(
65
+ Medication, on_delete=models.DO_NOTHING, related_name="codings", null=True
66
+ )
@@ -140,21 +140,23 @@ class Note(models.Model):
140
140
  dbid = models.BigIntegerField(db_column="dbid", primary_key=True)
141
141
  created = models.DateTimeField()
142
142
  modified = models.DateTimeField()
143
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING)
144
- provider = models.ForeignKey("Staff", on_delete=models.DO_NOTHING, related_name="notes")
143
+ patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, null=True)
144
+ provider = models.ForeignKey(
145
+ "Staff", on_delete=models.DO_NOTHING, related_name="notes", null=True
146
+ )
145
147
  note_type = models.CharField(choices=NoteTypes.choices, null=True)
146
148
  note_type_version = models.ForeignKey(
147
- "NoteType", on_delete=models.DO_NOTHING, related_name="notes"
149
+ "NoteType", on_delete=models.DO_NOTHING, related_name="notes", null=True
148
150
  )
149
151
  title = models.TextField()
150
152
  body = models.JSONField()
151
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
153
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
152
154
  last_modified_by_staff = models.ForeignKey("Staff", on_delete=models.DO_NOTHING, null=True)
153
155
  checksum = models.CharField()
154
156
  billing_note = models.TextField()
155
157
  # TODO -implement InpatientStay model
156
- # inpatient_stay = models.ForeignKey("InpatientStay", on_delete=models.DO_NOTHING)
158
+ # inpatient_stay = models.ForeignKey("InpatientStay", on_delete=models.DO_NOTHING, null=True)
157
159
  related_data = models.JSONField()
158
- location = models.ForeignKey("PracticeLocation", on_delete=models.DO_NOTHING)
160
+ location = models.ForeignKey("PracticeLocation", on_delete=models.DO_NOTHING, null=True)
159
161
  datetime_of_service = models.DateTimeField()
160
162
  place_of_service = models.CharField()
@@ -25,13 +25,15 @@ class Observation(models.Model):
25
25
  dbid = models.BigIntegerField(primary_key=True)
26
26
  created = models.DateTimeField()
27
27
  modified = models.DateTimeField()
28
- originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
29
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
30
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
28
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
29
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
30
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
31
31
  deleted = models.BooleanField()
32
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="observations")
32
+ patient = models.ForeignKey(
33
+ Patient, on_delete=models.DO_NOTHING, related_name="observations", null=True
34
+ )
33
35
  is_member_of = models.ForeignKey(
34
- "self", on_delete=models.DO_NOTHING, null=True, related_name="members"
36
+ "self", on_delete=models.DO_NOTHING, related_name="members", null=True
35
37
  )
36
38
  category = models.CharField()
37
39
  units = models.TextField()
@@ -56,7 +58,7 @@ class ObservationCoding(models.Model):
56
58
  display = models.CharField()
57
59
  user_selected = models.BooleanField()
58
60
  observation = models.ForeignKey(
59
- Observation, on_delete=models.DO_NOTHING, related_name="codings"
61
+ Observation, on_delete=models.DO_NOTHING, related_name="codings", null=True
60
62
  )
61
63
 
62
64
 
@@ -72,7 +74,7 @@ class ObservationComponent(models.Model):
72
74
  created = models.DateTimeField()
73
75
  modified = models.DateTimeField()
74
76
  observation = models.ForeignKey(
75
- Observation, on_delete=models.DO_NOTHING, related_name="components"
77
+ Observation, on_delete=models.DO_NOTHING, related_name="components", null=True
76
78
  )
77
79
  value_quantity = models.TextField()
78
80
  value_quantity_unit = models.TextField()
@@ -94,7 +96,7 @@ class ObservationComponentCoding(models.Model):
94
96
  display = models.CharField()
95
97
  user_selected = models.BooleanField()
96
98
  observation_component = models.ForeignKey(
97
- ObservationComponent, on_delete=models.DO_NOTHING, related_name="codings"
99
+ ObservationComponent, on_delete=models.DO_NOTHING, related_name="codings", null=True
98
100
  )
99
101
 
100
102
 
@@ -113,5 +115,5 @@ class ObservationValueCoding(models.Model):
113
115
  display = models.CharField()
114
116
  user_selected = models.BooleanField()
115
117
  observation = models.ForeignKey(
116
- Observation, on_delete=models.DO_NOTHING, related_name="value_codings"
118
+ Observation, on_delete=models.DO_NOTHING, related_name="value_codings", null=True
117
119
  )
@@ -65,7 +65,7 @@ class PracticeLocation(models.Model):
65
65
  created = models.DateTimeField()
66
66
  modified = models.DateTimeField()
67
67
  organization = models.ForeignKey(
68
- "Organization", on_delete=models.DO_NOTHING, related_name="practice_locations"
68
+ "Organization", on_delete=models.DO_NOTHING, related_name="practice_locations", null=True
69
69
  )
70
70
  place_of_service_code = models.CharField(choices=PracticeLocationPOS.choices)
71
71
  full_name = models.CharField()
@@ -96,7 +96,7 @@ class PracticeLocationSetting(models.Model):
96
96
 
97
97
  dbid = models.BigIntegerField(primary_key=True)
98
98
  practice_location = models.ForeignKey(
99
- PracticeLocation, on_delete=models.DO_NOTHING, related_name="settings"
99
+ PracticeLocation, on_delete=models.DO_NOTHING, related_name="settings", null=True
100
100
  )
101
101
  name = models.CharField()
102
102
  value = models.JSONField()
@@ -35,12 +35,10 @@ class ProtocolOverride(models.Model):
35
35
  created = models.DateTimeField()
36
36
  modified = models.DateTimeField()
37
37
  deleted = models.BooleanField()
38
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
39
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
38
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
39
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
40
40
  patient = models.ForeignKey(
41
- Patient,
42
- on_delete=models.DO_NOTHING,
43
- related_name="protocol_overrides",
41
+ Patient, on_delete=models.DO_NOTHING, related_name="protocol_overrides", null=True
44
42
  )
45
43
  protocol_key = models.CharField()
46
44
  is_adjustment = models.BooleanField()
@@ -47,7 +47,7 @@ class ResponseOption(models.Model):
47
47
  code_description = models.CharField()
48
48
  value = models.CharField()
49
49
  response_option_set = models.ForeignKey(
50
- ResponseOptionSet, on_delete=models.DO_NOTHING, related_name="options"
50
+ ResponseOptionSet, on_delete=models.DO_NOTHING, related_name="options", null=True
51
51
  )
52
52
  ordering = models.IntegerField()
53
53
 
@@ -67,7 +67,7 @@ class Question(models.Model):
67
67
  status = models.CharField()
68
68
  name = models.CharField()
69
69
  response_option_set = models.ForeignKey(
70
- ResponseOptionSet, on_delete=models.DO_NOTHING, related_name="questions"
70
+ ResponseOptionSet, on_delete=models.DO_NOTHING, related_name="questions", null=True
71
71
  )
72
72
  acknowledge_only = models.BooleanField()
73
73
  show_prologue = models.BooleanField()
@@ -126,8 +126,8 @@ class QuestionnaireQuestionMap(models.Model):
126
126
  created = models.DateTimeField()
127
127
  modified = models.DateTimeField()
128
128
  status = models.CharField()
129
- questionnaire = models.ForeignKey(Questionnaire, on_delete=models.DO_NOTHING)
130
- question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
129
+ questionnaire = models.ForeignKey(Questionnaire, on_delete=models.DO_NOTHING, null=True)
130
+ question = models.ForeignKey(Question, on_delete=models.DO_NOTHING, null=True)
131
131
 
132
132
 
133
133
  class Interview(models.Model):
@@ -143,13 +143,15 @@ class Interview(models.Model):
143
143
  id = models.UUIDField()
144
144
  dbid = models.BigIntegerField(primary_key=True)
145
145
  deleted = models.BooleanField()
146
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
147
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
146
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
147
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
148
148
  status = models.CharField()
149
149
  name = models.CharField()
150
150
  language_id = models.BigIntegerField()
151
151
  use_case_in_charting = models.CharField()
152
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="interviews")
152
+ patient = models.ForeignKey(
153
+ Patient, on_delete=models.DO_NOTHING, related_name="interviews", null=True
154
+ )
153
155
  note_id = models.BigIntegerField()
154
156
  appointment_id = models.BigIntegerField()
155
157
  questionnaires = models.ManyToManyField( # type: ignore[var-annotated]
@@ -173,8 +175,8 @@ class InterviewQuestionnaireMap(models.Model):
173
175
  created = models.DateTimeField()
174
176
  modified = models.DateTimeField()
175
177
  status = models.CharField()
176
- interview = models.ForeignKey(Interview, on_delete=models.DO_NOTHING)
177
- questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE)
178
+ interview = models.ForeignKey(Interview, on_delete=models.DO_NOTHING, null=True)
179
+ questionnaire = models.ForeignKey(Questionnaire, on_delete=models.DO_NOTHING, null=True)
178
180
 
179
181
 
180
182
  class InterviewQuestionResponse(models.Model):
@@ -190,16 +192,16 @@ class InterviewQuestionResponse(models.Model):
190
192
  modified = models.DateTimeField()
191
193
  status = models.CharField()
192
194
  interview = models.ForeignKey(
193
- Interview, on_delete=models.DO_NOTHING, related_name="interview_responses"
195
+ Interview, on_delete=models.DO_NOTHING, related_name="interview_responses", null=True
194
196
  )
195
197
  questionnaire = models.ForeignKey(
196
- Questionnaire, on_delete=models.DO_NOTHING, related_name="interview_responses"
198
+ Questionnaire, on_delete=models.DO_NOTHING, related_name="interview_responses", null=True
197
199
  )
198
200
  question = models.ForeignKey(
199
- Question, on_delete=models.DO_NOTHING, related_name="interview_responses"
201
+ Question, on_delete=models.DO_NOTHING, related_name="interview_responses", null=True
200
202
  )
201
203
  response_option = models.ForeignKey(
202
- ResponseOption, on_delete=models.DO_NOTHING, related_name="interview_responses"
204
+ ResponseOption, on_delete=models.DO_NOTHING, related_name="interview_responses", null=True
203
205
  )
204
206
  response_option_value = models.TextField()
205
207
  questionnaire_state = models.TextField()
@@ -54,12 +54,12 @@ class Staff(models.Model):
54
54
  tax_id_type = models.CharField(choices=TaxIDType.choices)
55
55
  spi_number = models.CharField()
56
56
  # TODO - uncomment when Language is developed
57
- # language = models.ForeignKey(Language, on_delete=models.DO_NOTHING, related_name="staff_speakers")
58
- # language_secondary = models.ForeignKey(Language, on_delete=models.DO_NOTHING, related_name="staff_secondary_speakers")
57
+ # language = models.ForeignKey(Language, on_delete=models.DO_NOTHING, related_name="staff_speakers", null=True)
58
+ # language_secondary = models.ForeignKey(Language, on_delete=models.DO_NOTHING, related_name="staff_secondary_speakers", null=True)
59
59
  personal_meeting_room_link = models.URLField(null=True)
60
60
  state = models.JSONField()
61
- user = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
61
+ user = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
62
62
  schedule_column_ordering = models.IntegerField()
63
63
  default_supervising_provider = models.ForeignKey(
64
- "Staff", on_delete=models.DO_NOTHING, null=True, related_name="supervising_team"
64
+ "Staff", on_delete=models.DO_NOTHING, related_name="supervising_team", null=True
65
65
  )
@@ -46,14 +46,16 @@ class Task(models.Model):
46
46
  dbid = models.BigIntegerField(primary_key=True)
47
47
  created = models.DateTimeField()
48
48
  modified = models.DateTimeField()
49
- creator = models.ForeignKey(Staff, related_name="creator_tasks", on_delete=models.DO_NOTHING)
49
+ creator = models.ForeignKey(
50
+ Staff, on_delete=models.DO_NOTHING, related_name="creator_tasks", null=True
51
+ )
50
52
  assignee = models.ForeignKey(
51
- Staff, related_name="assignee_tasks", null=True, on_delete=models.DO_NOTHING
53
+ Staff, on_delete=models.DO_NOTHING, related_name="assignee_tasks", null=True
52
54
  )
53
55
  # TODO - uncomment when Team model is created
54
- # team = models.ForeignKey(Team, related_name="tasks", null=True, on_delete=models.DO_NOTHING)
56
+ # team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, related_name="tasks", null=True)
55
57
  patient = models.ForeignKey(
56
- Patient, blank=True, null=True, on_delete=models.DO_NOTHING, related_name="tasks"
58
+ Patient, on_delete=models.DO_NOTHING, blank=True, related_name="tasks", null=True
57
59
  )
58
60
  task_type = models.CharField(choices=TaskType.choices)
59
61
  tag = models.CharField()
@@ -75,8 +77,10 @@ class TaskComment(models.Model):
75
77
  dbid = models.BigIntegerField(primary_key=True)
76
78
  created = models.DateTimeField()
77
79
  modified = models.DateTimeField()
78
- creator = models.ForeignKey(Staff, related_name="comments", on_delete=models.DO_NOTHING)
79
- task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, related_name="comments")
80
+ creator = models.ForeignKey(
81
+ Staff, on_delete=models.DO_NOTHING, related_name="comments", null=True
82
+ )
83
+ task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, related_name="comments", null=True)
80
84
  body = models.TextField()
81
85
 
82
86
 
@@ -108,5 +112,5 @@ class TaskTaskLabel(models.Model):
108
112
  db_table = "canvas_sdk_data_api_tasktasklabel_001"
109
113
 
110
114
  dbid = models.BigIntegerField(primary_key=True)
111
- task_label = models.ForeignKey(TaskLabel, on_delete=models.DO_NOTHING)
112
- task = models.ForeignKey(Task, on_delete=models.DO_NOTHING)
115
+ task_label = models.ForeignKey(TaskLabel, on_delete=models.DO_NOTHING, null=True)
116
+ task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, null=True)
@@ -0,0 +1,77 @@
1
+ # Shamelessly cribbed from https://charemza.name/blog/posts/aws/python/you-might-not-need-boto-3/
2
+
3
+ import datetime
4
+ import hashlib
5
+ import hmac
6
+ import urllib.parse
7
+
8
+
9
+ def aws_sig_v4_headers(
10
+ access_key_id: str,
11
+ secret_access_key: str,
12
+ pre_auth_headers: dict[str, str],
13
+ service: str,
14
+ region: str,
15
+ host: str,
16
+ method: str,
17
+ path: str,
18
+ query: dict[str, str],
19
+ payload: bytes,
20
+ ) -> dict[str, str]:
21
+ """Constructs signed headers for use in requests made to AWS."""
22
+ algorithm = "AWS4-HMAC-SHA256"
23
+ now = datetime.datetime.utcnow()
24
+ amzdate = now.strftime("%Y%m%dT%H%M%SZ")
25
+ datestamp = now.strftime("%Y%m%d")
26
+ payload_hash = hashlib.sha256(payload).hexdigest()
27
+ credential_scope = f"{datestamp}/{region}/{service}/aws4_request"
28
+
29
+ pre_auth_headers_lower = {
30
+ header_key.lower(): " ".join(header_value.split())
31
+ for header_key, header_value in pre_auth_headers.items()
32
+ }
33
+ required_headers = {
34
+ "host": host,
35
+ "x-amz-content-sha256": payload_hash,
36
+ "x-amz-date": amzdate,
37
+ }
38
+ headers = {**pre_auth_headers_lower, **required_headers}
39
+ header_keys = sorted(headers.keys())
40
+ signed_headers = ";".join(header_keys)
41
+
42
+ def signature() -> str:
43
+ def canonical_request() -> str:
44
+ canonical_uri = urllib.parse.quote(path, safe="/~")
45
+ quoted_query = sorted(
46
+ (urllib.parse.quote(key, safe="~"), urllib.parse.quote(value, safe="~"))
47
+ for key, value in query.items()
48
+ )
49
+ canonical_querystring = "&".join(f"{key}={value}" for key, value in quoted_query)
50
+ canonical_headers = "".join(f"{key}:{headers[key]}\n" for key in header_keys)
51
+
52
+ return (
53
+ f"{method}\n{canonical_uri}\n{canonical_querystring}\n"
54
+ + f"{canonical_headers}\n{signed_headers}\n{payload_hash}"
55
+ )
56
+
57
+ def sign(key: bytes, msg: str) -> bytes:
58
+ return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
59
+
60
+ string_to_sign = (
61
+ f"{algorithm}\n{amzdate}\n{credential_scope}\n"
62
+ + hashlib.sha256(canonical_request().encode("utf-8")).hexdigest()
63
+ )
64
+
65
+ date_key = sign(("AWS4" + secret_access_key).encode("utf-8"), datestamp)
66
+ region_key = sign(date_key, region)
67
+ service_key = sign(region_key, service)
68
+ request_key = sign(service_key, "aws4_request")
69
+ return sign(request_key, string_to_sign).hex()
70
+
71
+ return {
72
+ **pre_auth_headers,
73
+ "x-amz-date": amzdate,
74
+ "x-amz-content-sha256": payload_hash,
75
+ "Authorization": f"{algorithm} Credential={access_key_id}/{credential_scope}, "
76
+ f"SignedHeaders={signed_headers}, Signature=" + signature(),
77
+ }
@@ -9,12 +9,13 @@ from pathlib import Path
9
9
  from typing import Any, TypedDict
10
10
  from urllib import parse
11
11
 
12
- import boto3
13
12
  import psycopg
13
+ import requests
14
14
  from psycopg import Connection
15
15
  from psycopg.rows import dict_row
16
16
 
17
17
  import settings
18
+ from plugin_runner.aws_headers import aws_sig_v4_headers
18
19
  from plugin_runner.exceptions import InvalidPluginFormat, PluginInstallationError
19
20
 
20
21
  # Plugin "packages" include this prefix in the database record for the plugin and the S3 bucket key.
@@ -98,17 +99,34 @@ def _extract_rows_to_dict(rows: list) -> dict[str, PluginAttributes]:
98
99
  @contextmanager
99
100
  def download_plugin(plugin_package: str) -> Generator:
100
101
  """Download the plugin package from the S3 bucket."""
101
- s3 = boto3.client("s3")
102
+ method = "GET"
103
+ host = f"s3-{settings.AWS_REGION}.amazonaws.com"
104
+ bucket = settings.MEDIA_S3_BUCKET_NAME
105
+ customer_identifier = settings.CUSTOMER_IDENTIFIER
106
+ path = f"/{bucket}/{customer_identifier}/{plugin_package}"
107
+ payload = b"This is required for the AWS headers because it is part of the signature"
108
+ pre_auth_headers: dict[str, str] = {}
109
+ query: dict[str, str] = {}
110
+ headers = aws_sig_v4_headers(
111
+ settings.AWS_ACCESS_KEY_ID,
112
+ settings.AWS_SECRET_ACCESS_KEY,
113
+ pre_auth_headers,
114
+ "s3",
115
+ settings.AWS_REGION,
116
+ host,
117
+ method,
118
+ path,
119
+ query,
120
+ payload,
121
+ )
122
+
102
123
  with tempfile.TemporaryDirectory() as temp_dir:
103
124
  prefix_dir = Path(temp_dir) / UPLOAD_TO_PREFIX
104
125
  prefix_dir.mkdir() # create an intermediate directory reflecting the prefix
105
126
  download_path = Path(temp_dir) / plugin_package
106
127
  with open(download_path, "wb") as download_file:
107
- s3.download_fileobj(
108
- "canvas-client-media",
109
- f"{settings.CUSTOMER_IDENTIFIER}/{plugin_package}",
110
- download_file,
111
- )
128
+ response = requests.request(method=method, url=f"https://{host}{path}", headers=headers)
129
+ download_file.write(response.content)
112
130
  yield download_path
113
131
 
114
132
 
@@ -193,7 +193,6 @@ class PluginRunner(PluginRunnerServicer):
193
193
  ) -> AsyncGenerator[ReloadPluginsResponse, None]:
194
194
  """This is invoked when we need to reload plugins."""
195
195
  try:
196
- load_plugins()
197
196
  publish_message({"action": "restart"})
198
197
  except ImportError:
199
198
  yield ReloadPluginsResponse(success=False)
@@ -48,6 +48,8 @@ def main() -> None:
48
48
  try:
49
49
  print("plugin-synchronizer: installing plugins after web container start")
50
50
  install_plugins()
51
+ print("plugin-synchronizer: sending SIGHUP to plugin-runner")
52
+ check_output(["circusctl", "signal", "plugin-runner", "1"], cwd="/app", stderr=STDOUT)
51
53
  except CalledProcessError as e:
52
54
  print("plugin-synchronizer: `install_plugins` failed:", e)
53
55
 
@@ -67,7 +69,7 @@ def main() -> None:
67
69
  data = pickle.loads(message.get("data", pickle.dumps({})))
68
70
 
69
71
  if "action" not in data or "client_id" not in data:
70
- return
72
+ continue
71
73
 
72
74
  if data["action"] == "restart":
73
75
  # Run the plugin installer process
@@ -2,11 +2,10 @@ import json
2
2
  import tarfile
3
3
  import tempfile
4
4
  from pathlib import Path
5
- from unittest.mock import MagicMock
5
+ from unittest.mock import MagicMock, patch
6
6
 
7
7
  from pytest_mock import MockerFixture
8
8
 
9
- import settings
10
9
  from plugin_runner.plugin_installer import (
11
10
  PluginAttributes,
12
11
  _extract_rows_to_dict,
@@ -106,13 +105,12 @@ def test_plugin_installation_from_tarball(mocker: MockerFixture) -> None:
106
105
 
107
106
  def test_download(mocker: MockerFixture) -> None:
108
107
  """Test that the plugin package can be written to disk, mocking out S3."""
109
- mock_s3_client = MagicMock()
110
- mocker.patch("boto3.client", return_value=mock_s3_client)
111
-
112
- plugin_package = "plugins/plugin1.tar.gz"
113
- with download_plugin(plugin_package) as plugin_path:
114
- assert plugin_path.exists()
115
-
116
- mock_s3_client.download_fileobj.assert_called_once_with(
117
- "canvas-client-media", f"{settings.CUSTOMER_IDENTIFIER}/{plugin_package}", mocker.ANY
118
- )
108
+ mock_response = MagicMock()
109
+ mock_response.status_code = 200
110
+ mock_response.content = b"some content in a file"
111
+ with patch("requests.request", return_value=mock_response) as mock_request:
112
+ plugin_package = "plugins/plugin1.tar.gz"
113
+ with download_plugin(plugin_package) as plugin_path:
114
+ assert plugin_path.exists()
115
+ assert plugin_path.read_bytes() == b"some content in a file"
116
+ mock_request.assert_called_once()
@@ -191,17 +191,3 @@ async def test_reload_plugins_event_handler_successfully_loads_plugins(
191
191
  assert len(result) == 1
192
192
  assert result[0].success is True
193
193
  assert "example_plugin:example_plugin.protocols.my_protocol:Protocol" in LOADED_PLUGINS
194
-
195
-
196
- @pytest.mark.asyncio
197
- async def test_reload_plugins_import_error(plugin_runner: PluginRunner) -> None:
198
- """Test ReloadPlugins response when an ImportError occurs."""
199
- request = ReloadPluginsRequest()
200
-
201
- with patch("plugin_runner.plugin_runner.load_plugins", side_effect=ImportError):
202
- responses = []
203
- async for response in plugin_runner.ReloadPlugins(request, None):
204
- responses.append(response)
205
-
206
- assert len(responses) == 1
207
- assert responses[0].success is False
@@ -14,6 +14,11 @@ import os
14
14
  result = os.listdir('.')
15
15
  """
16
16
 
17
+ CODE_WITH_PLUGIN_RUNNER_SETTING_IMPORT = """
18
+ import settings
19
+ result = settings.AWS_SECRET_ACCESS_KEY
20
+ """
21
+
17
22
  CODE_WITH_ALLOWED_IMPORT = """
18
23
  import json
19
24
  result = json.dumps({"key": "value"})
@@ -39,7 +44,14 @@ def test_valid_code_execution() -> None:
39
44
  def test_disallowed_import() -> None:
40
45
  """Test that restricted imports are not allowed."""
41
46
  sandbox = Sandbox(CODE_WITH_RESTRICTED_IMPORT)
42
- with pytest.raises(ImportError, match="os' is not an allowed import."):
47
+ with pytest.raises(ImportError, match="'os' is not an allowed import."):
48
+ sandbox.execute()
49
+
50
+
51
+ def test_plugin_runner_settings_import() -> None:
52
+ """Test that imports of plugin runner settings are not allowed."""
53
+ sandbox = Sandbox(CODE_WITH_PLUGIN_RUNNER_SETTING_IMPORT)
54
+ with pytest.raises(ImportError, match="'settings' is not an allowed import."):
43
55
  sandbox.execute()
44
56
 
45
57
 
settings.py CHANGED
@@ -49,6 +49,10 @@ else:
49
49
 
50
50
  DATABASES = {"default": database_dict}
51
51
 
52
+ AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID", "")
53
+ AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY", "")
54
+ AWS_REGION = os.getenv("AWS_REGION", "us-west-2")
55
+ MEDIA_S3_BUCKET_NAME = os.getenv("MEDIA_S3_BUCKET_NAME", "canvas-client-media")
52
56
 
53
57
  PLUGIN_RUNNER_SIGNING_KEY = os.getenv("PLUGIN_RUNNER_SIGNING_KEY", "")
54
58