canvas 0.16.0__py3-none-any.whl → 0.17.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.
- {canvas-0.16.0.dist-info → canvas-0.17.0.dist-info}/METADATA +25 -31
- {canvas-0.16.0.dist-info → canvas-0.17.0.dist-info}/RECORD +33 -30
- {canvas-0.16.0.dist-info → canvas-0.17.0.dist-info}/WHEEL +1 -1
- canvas-0.17.0.dist-info/entry_points.txt +2 -0
- canvas_cli/apps/plugin/plugin.py +1 -1
- canvas_sdk/commands/tests/protocol/tests.py +12 -2
- canvas_sdk/commands/tests/test_utils.py +4 -9
- canvas_sdk/effects/banner_alert/tests.py +5 -2
- canvas_sdk/utils/http.py +1 -1
- plugin_runner/plugin_installer.py +21 -13
- plugin_runner/plugin_runner.py +67 -14
- plugin_runner/tests/test_plugin_runner.py +37 -5
- protobufs/canvas_generated/messages/effects.proto +225 -0
- protobufs/canvas_generated/messages/events.proto +1049 -0
- protobufs/canvas_generated/messages/plugins.proto +9 -0
- protobufs/canvas_generated/services/plugin_runner.proto +12 -0
- settings.py +4 -1
- canvas-0.16.0.dist-info/entry_points.txt +0 -3
- plugin_runner/plugin_synchronizer.py +0 -92
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: canvas
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: SDK to customize event-driven actions in your Canvas instance
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Requires-Python: >=3.11,<3.13
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
5
|
+
Author-email: Canvas Team <engineering@canvasmedical.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: <3.13,>=3.11
|
|
13
8
|
Requires-Dist: cookiecutter
|
|
14
|
-
Requires-Dist: cron-converter
|
|
15
|
-
Requires-Dist: deprecation
|
|
16
|
-
Requires-Dist: django
|
|
17
|
-
Requires-Dist: django-
|
|
18
|
-
Requires-Dist: django
|
|
19
|
-
Requires-Dist: env-tools
|
|
20
|
-
Requires-Dist: grpcio
|
|
21
|
-
Requires-Dist: ipython
|
|
22
|
-
Requires-Dist: jsonschema
|
|
9
|
+
Requires-Dist: cron-converter<2,>=1.2.1
|
|
10
|
+
Requires-Dist: deprecation<3,>=2.1.0
|
|
11
|
+
Requires-Dist: django-stubs[compatible-mypy]<6,>=5.1.1
|
|
12
|
+
Requires-Dist: django-timezone-utils<0.16,>=0.15.0
|
|
13
|
+
Requires-Dist: django<6,>=5.1.1
|
|
14
|
+
Requires-Dist: env-tools<3,>=2.4.0
|
|
15
|
+
Requires-Dist: grpcio<2,>=1.60.1
|
|
16
|
+
Requires-Dist: ipython<9,>=8.21.0
|
|
17
|
+
Requires-Dist: jsonschema<5,>=4.21.1
|
|
23
18
|
Requires-Dist: keyring
|
|
24
|
-
Requires-Dist: protobuf
|
|
25
|
-
Requires-Dist: psycopg[binary]
|
|
26
|
-
Requires-Dist: pydantic
|
|
27
|
-
Requires-Dist: pyjwt
|
|
28
|
-
Requires-Dist: python-dotenv
|
|
29
|
-
Requires-Dist: rapidfuzz
|
|
30
|
-
Requires-Dist: redis
|
|
19
|
+
Requires-Dist: protobuf<5,>=4.25.3
|
|
20
|
+
Requires-Dist: psycopg[binary]<4,>=3.2.2
|
|
21
|
+
Requires-Dist: pydantic<3,>=2.6.1
|
|
22
|
+
Requires-Dist: pyjwt==2.10.1
|
|
23
|
+
Requires-Dist: python-dotenv<2,>=1.0.1
|
|
24
|
+
Requires-Dist: rapidfuzz<4,>=3.10.1
|
|
25
|
+
Requires-Dist: redis<6,>=5.0.4
|
|
31
26
|
Requires-Dist: requests
|
|
32
|
-
Requires-Dist: restrictedpython
|
|
33
|
-
Requires-Dist: statsd
|
|
27
|
+
Requires-Dist: restrictedpython>=8.0
|
|
28
|
+
Requires-Dist: statsd<5,>=4.0.1
|
|
34
29
|
Requires-Dist: typer
|
|
35
|
-
Requires-Dist: typing-extensions
|
|
36
|
-
Requires-Dist: websocket-client
|
|
30
|
+
Requires-Dist: typing-extensions<4.13,>=4.8
|
|
31
|
+
Requires-Dist: websocket-client<2,>=1.7.0
|
|
37
32
|
Description-Content-Type: text/markdown
|
|
38
33
|
|
|
39
34
|
### Getting Started
|
|
@@ -220,4 +215,3 @@ $ canvas logs [OPTIONS]
|
|
|
220
215
|
|
|
221
216
|
- `--host TEXT`: Canvas instance to connect to
|
|
222
217
|
- `--help`: Show this message and exit.
|
|
223
|
-
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
settings.py,sha256=wtQMRHsbynzEjZb5WmUMV3q27oWc6prLS9ztnEAsqMg,2829
|
|
1
2
|
canvas_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
canvas_cli/conftest.py,sha256=0mEjjU7_w9nq-HCvscxFJClw-EeyQYujNLLRPssksZQ,931
|
|
4
|
+
canvas_cli/main.py,sha256=L6JQkt1yxy30cA3-M9v7JD8WMW4i0M5GPr9kZetAito,2728
|
|
5
|
+
canvas_cli/tests.py,sha256=kY3eeDL-EIlNCeeTponVeqz9Zb5S12imUUCSzEXuYFw,6527
|
|
2
6
|
canvas_cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
7
|
canvas_cli/apps/auth/__init__.py,sha256=gIwJ2qWvRlLqbiRkudrGqTKV-orlb8OTkG487qoRda4,105
|
|
4
8
|
canvas_cli/apps/auth/tests.py,sha256=_q8YvSN1zVgWh_RyWCo4Dkme67tr0ioQ10AkfkpvTbc,5030
|
|
@@ -65,12 +69,10 @@ canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_UPDATED.ndjson,sha256=Er0aUWIYkqb
|
|
|
65
69
|
canvas_cli/apps/logs/__init__.py,sha256=ehY9SRb6nBw81xZF50yyBlUZJtNR2VeVSNI5sFuWJ7o,64
|
|
66
70
|
canvas_cli/apps/logs/logs.py,sha256=BFpZ-2OF2Rs1EMLePo5UjqC9fKQeqm8qZobNTFNCL_M,1972
|
|
67
71
|
canvas_cli/apps/plugin/__init__.py,sha256=G_nLsu6cdko5OjatnbqUyEboGcNlGGLwpZmCBxMKdfo,236
|
|
68
|
-
canvas_cli/apps/plugin/plugin.py,sha256
|
|
72
|
+
canvas_cli/apps/plugin/plugin.py,sha256=-LdjxtO3t3CIasoh7A1-_7bcFN04TkjmfhbRGHNeZH0,15566
|
|
69
73
|
canvas_cli/apps/plugin/tests.py,sha256=SsYeYY25ly9TMn-nkJEZjLaPCyFbT4vs1sN_FnQbJ5U,2746
|
|
70
74
|
canvas_cli/apps/run_plugins/__init__.py,sha256=iAMgX_6D3CdjQodGx_azwhSjouaxquOm8Z8QVXnlTFE,117
|
|
71
75
|
canvas_cli/apps/run_plugins/run_plugins.py,sha256=qsf6-UhFAZpIL-1C50fzSoIwXMsZISxg2fxzM46UHTA,384
|
|
72
|
-
canvas_cli/conftest.py,sha256=0mEjjU7_w9nq-HCvscxFJClw-EeyQYujNLLRPssksZQ,931
|
|
73
|
-
canvas_cli/main.py,sha256=L6JQkt1yxy30cA3-M9v7JD8WMW4i0M5GPr9kZetAito,2728
|
|
74
76
|
canvas_cli/templates/plugins/application/cookiecutter.json,sha256=cI4Wpj68TkKeBP3P16PrjKacTHzsTIpl_rDdzyUpwz4,129
|
|
75
77
|
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json,sha256=rIB41kNzzRV9QyX1ssHNi4n4wXyuXeBy2gVG83hxsOA,908
|
|
76
78
|
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
@@ -82,7 +84,6 @@ canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/CANVAS_MA
|
|
|
82
84
|
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
83
85
|
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
86
|
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py,sha256=fKLLcOIwvSWenW8-7tr8VqnF4Iox_5wU9V-Qw9UySsA,2381
|
|
85
|
-
canvas_cli/tests.py,sha256=kY3eeDL-EIlNCeeTponVeqz9Zb5S12imUUCSzEXuYFw,6527
|
|
86
87
|
canvas_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
88
|
canvas_cli/utils/context/__init__.py,sha256=HhYvI-hydP0mV18nJiU7uo5gk0yN7EYNgouxieoGDOE,102
|
|
88
89
|
canvas_cli/utils/context/context.py,sha256=wk4TxlslF52uD9nXcEZ1eY8L1rcEHk7k-6YBVwaWnVY,5191
|
|
@@ -113,6 +114,7 @@ canvas_sdk/__init__.py,sha256=d7V1Qsp4hSqp8opmvqp-0J33uibArUjwENMfzSDAdZg,102
|
|
|
113
114
|
canvas_sdk/base.py,sha256=aaWilIx70lsAE76x7QaJolyIvcFnXDGPw1UMv9t0FDw,2035
|
|
114
115
|
canvas_sdk/commands/__init__.py,sha256=cNQy1Y_Ji3cPush2hbaoXC3pbIFmobv_Vm-UG0w0hlc,2696
|
|
115
116
|
canvas_sdk/commands/base.py,sha256=ScfOo4QXTInhTf6OGLT5dEu81yhZAcgVT8qxwIKWbow,5155
|
|
117
|
+
canvas_sdk/commands/constants.py,sha256=RqnokmQns-v0QErE1tCxFqFnDxsbRhigRMeTPqLTvz0,360
|
|
116
118
|
canvas_sdk/commands/commands/allergy.py,sha256=_S0tY43sErRlTeUGCC6Thb8gMSD_JiOmRSFdLaDlr48,1296
|
|
117
119
|
canvas_sdk/commands/commands/assess.py,sha256=IjsvkhzKinRaykMRzbnk6G4JAkzDhyqGG6ZvjDhNAuE,1723
|
|
118
120
|
canvas_sdk/commands/commands/close_goal.py,sha256=RTmi8fGPEEag8YSrlsPn7sy9Y-WymVuU8IWpXRKgNjg,792
|
|
@@ -140,30 +142,29 @@ canvas_sdk/commands/commands/task.py,sha256=p-WskHqj_q8TqllarEw73AIW6n2J7uzyIso9
|
|
|
140
142
|
canvas_sdk/commands/commands/update_diagnosis.py,sha256=fqPcKBaiRemo8Qa4H4g6ZvKM2YczVlj9FxpBGjs8J9Q,824
|
|
141
143
|
canvas_sdk/commands/commands/update_goal.py,sha256=ddvxlDnoTm5k-G5aJ-pW2fMIC-s5K8jYia9tISXsxAk,1580
|
|
142
144
|
canvas_sdk/commands/commands/vitals.py,sha256=hOuDXbNwqJbsBAvvkpjrXWqssrdQPxEfCox1NlI0h4A,3127
|
|
143
|
-
canvas_sdk/commands/
|
|
145
|
+
canvas_sdk/commands/tests/test_utils.py,sha256=qkzcTDjyvrpXyPkm3f6KBVy-4lqJ1cOq2QSc-bPVPsU,12004
|
|
144
146
|
canvas_sdk/commands/tests/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
145
|
-
canvas_sdk/commands/tests/protocol/tests.py,sha256=
|
|
147
|
+
canvas_sdk/commands/tests/protocol/tests.py,sha256=e0fiJUKsfi0aYSlaTdrMHybZntC7oBdaHks9PSrMuhU,2168
|
|
146
148
|
canvas_sdk/commands/tests/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
147
149
|
canvas_sdk/commands/tests/schema/tests.py,sha256=rSu146RouMrPLv0G-E6QtgQhG2vkUCCk1M3Baa6fXBg,3701
|
|
148
|
-
canvas_sdk/commands/tests/test_utils.py,sha256=1xW-ennv0rF91k3uCg1qUnsZpRRwQoeISM_mhc4SYwU,12169
|
|
149
150
|
canvas_sdk/commands/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
151
|
canvas_sdk/commands/tests/unit/tests.py,sha256=FsjNpUxkS4F89g341uuc5GkMCTp_JACTx32wxcwYcWg,9948
|
|
151
152
|
canvas_sdk/effects/__init__.py,sha256=rcMI7IaVe8kDaYRngvlH5JPOL7FBSGIoLZB4UCN18d8,151
|
|
152
|
-
canvas_sdk/effects/banner_alert/__init__.py,sha256=PcMmOjLHP_MOZiP8157JkTdcO4mZTn-kFcRylOB9AK0,209
|
|
153
|
-
canvas_sdk/effects/banner_alert/add_banner_alert.py,sha256=W4Fv9IHGCWvVtFiH3s68JBRpUb8b3xsncGt2WfgMFc0,1681
|
|
154
|
-
canvas_sdk/effects/banner_alert/remove_banner_alert.py,sha256=PK_LzXBQ-rOH3fX182ISyutu4W69xyGgMX-1G4g4XhY,613
|
|
155
|
-
canvas_sdk/effects/banner_alert/tests.py,sha256=7YJfUanytqU_kaXsY2y6bEEwKhpXItOpmvXOncGsquc,9665
|
|
156
153
|
canvas_sdk/effects/base.py,sha256=znIaDNkCYbzJxf4Q80hLRYUG5H73c8IMq1UCzeBcXjw,714
|
|
157
154
|
canvas_sdk/effects/launch_modal.py,sha256=2yVY7r6hY5YYiBKae0rpS4Q0UgJPJtGlLuZGj68hm8s,1058
|
|
158
155
|
canvas_sdk/effects/patient_chart_summary_configuration.py,sha256=_Gx7UIp4NLaSYYLLcBSOjtOD4Skqot_kt_x8zI3Ccwo,1184
|
|
156
|
+
canvas_sdk/effects/patient_profile_configuration.py,sha256=ZXBx_PnJP-KKRIwuDI4tS1evuC3_dPoiMz5KfD-QGKw,1304
|
|
157
|
+
canvas_sdk/effects/questionnaire_result.py,sha256=dXJE6la5aP5xLCnT9iMCOS6PiKu0Zz733Ku2lqa4CnM,816
|
|
158
|
+
canvas_sdk/effects/show_button.py,sha256=JnW9nM8S_GUXIOufs-uef3pg0HPDxSbF0l51Wh1Xxgw,715
|
|
159
|
+
canvas_sdk/effects/banner_alert/__init__.py,sha256=PcMmOjLHP_MOZiP8157JkTdcO4mZTn-kFcRylOB9AK0,209
|
|
160
|
+
canvas_sdk/effects/banner_alert/add_banner_alert.py,sha256=W4Fv9IHGCWvVtFiH3s68JBRpUb8b3xsncGt2WfgMFc0,1681
|
|
161
|
+
canvas_sdk/effects/banner_alert/remove_banner_alert.py,sha256=PK_LzXBQ-rOH3fX182ISyutu4W69xyGgMX-1G4g4XhY,613
|
|
162
|
+
canvas_sdk/effects/banner_alert/tests.py,sha256=DoCQ9ZfpcUBne-Le-zCCOTjzF_WHyuiay-FCex4aHJ0,9665
|
|
159
163
|
canvas_sdk/effects/patient_portal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
160
164
|
canvas_sdk/effects/patient_portal/intake_form_results.py,sha256=5GESYemwh7PKkmeagC8n5db6XLbtiBNV7S4b-vsUW5I,713
|
|
161
|
-
canvas_sdk/effects/patient_profile_configuration.py,sha256=ZXBx_PnJP-KKRIwuDI4tS1evuC3_dPoiMz5KfD-QGKw,1304
|
|
162
165
|
canvas_sdk/effects/protocol_card/__init__.py,sha256=AwXAARybFhVIfYwqiRThG6Ne3ARnKgvuvuGUknWpwTo,134
|
|
163
166
|
canvas_sdk/effects/protocol_card/protocol_card.py,sha256=aL8lXRBm1ByU-X0xzgBepxKnUTLvmr-OC7G-C-EBjy8,2596
|
|
164
167
|
canvas_sdk/effects/protocol_card/tests.py,sha256=ZiDZTjn-Z6UpEfR1S-6XDcJXlPlTPpLbg2PgvkQD8Uk,6815
|
|
165
|
-
canvas_sdk/effects/questionnaire_result.py,sha256=dXJE6la5aP5xLCnT9iMCOS6PiKu0Zz733Ku2lqa4CnM,816
|
|
166
|
-
canvas_sdk/effects/show_button.py,sha256=JnW9nM8S_GUXIOufs-uef3pg0HPDxSbF0l51Wh1Xxgw,715
|
|
167
168
|
canvas_sdk/effects/surescripts/surescripts_messages.py,sha256=tMG5ry2mZA5pwO0uGJfYZnJuVpj5MVhS2lYT5IRsb1Y,2582
|
|
168
169
|
canvas_sdk/effects/task/task.py,sha256=olG7gJN3CC1JKrGU4SdVvRq8qLduPuiNz80C_dTaY58,2837
|
|
169
170
|
canvas_sdk/events/__init__.py,sha256=6Yr0Ot1yWzu_ce2ycOJJ9jdRxo0QOsSkPjNCTrjds8c,237
|
|
@@ -178,17 +179,18 @@ canvas_sdk/protocols/base.py,sha256=sbm0uOk3PPfPemqBmHh2hawE5utC6no46EmvyMN8Y7Q,
|
|
|
178
179
|
canvas_sdk/protocols/clinical_quality_measure.py,sha256=8cU93ah9YsPecpZR1-csAbg69oFn9a8LtjHjYMMHedw,4844
|
|
179
180
|
canvas_sdk/protocols/timeframe.py,sha256=SlTDhTy0TqPXKS9JZFeTVApQJDf8C-NIRLqFJltB17g,1148
|
|
180
181
|
canvas_sdk/templates/__init__.py,sha256=crz8FE6yoCgwTrqosLDHM7cOiVdhWgWz0l0J--1sgmM,69
|
|
182
|
+
canvas_sdk/templates/utils.py,sha256=pjU9ZOAb4MHb8ui4l3f1yWRczy62JCF51rn6fWAjMKY,1645
|
|
181
183
|
canvas_sdk/templates/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
182
184
|
canvas_sdk/templates/tests/test_utils.py,sha256=VRahmmVwXKcp1NMLoA3BZL4cFFXzFnD-i5IUpcEeXTg,1832
|
|
183
|
-
canvas_sdk/templates/utils.py,sha256=pjU9ZOAb4MHb8ui4l3f1yWRczy62JCF51rn6fWAjMKY,1645
|
|
184
185
|
canvas_sdk/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
185
186
|
canvas_sdk/utils/__init__.py,sha256=sFuhqcWvXb2-33FOuXZgWuUeC5jZL2MDoqjGoQZGwd8,60
|
|
186
187
|
canvas_sdk/utils/db.py,sha256=0AO5bhu-k9OsAHpXe1RHzyDZnBGHUEzrv8-vYtTIoeA,592
|
|
187
|
-
canvas_sdk/utils/http.py,sha256=
|
|
188
|
+
canvas_sdk/utils/http.py,sha256=x_BoQvA4_ioIjy3cjBXTZ1hWA_OXBNTySMsG6rM1t8s,2275
|
|
188
189
|
canvas_sdk/utils/stats.py,sha256=sJhIW_IssUVefQN6rrUAt1P0KvVIUIYcnpZlMHLibNA,732
|
|
189
190
|
canvas_sdk/utils/tests.py,sha256=0Buh_7PvDU1D081_rSJoYSJwIHMOBbL0gtGS3bSKe7s,2285
|
|
190
191
|
canvas_sdk/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
191
192
|
canvas_sdk/v1/apps.py,sha256=z5HVdICPLYrbWM8ZXK89Xu7RWaXuKU4AFRrPAZGz0C4,151
|
|
193
|
+
canvas_sdk/v1/models.py,sha256=q9Sofiu9JDH5g04H8NHYIqAtBYxH4KjnwOldoKsY9Lk,206
|
|
192
194
|
canvas_sdk/v1/data/__init__.py,sha256=1xQqNKh9rSwyLqT8pE8PYNOx3DTxcS9qXuU79OqjEDU,2903
|
|
193
195
|
canvas_sdk/v1/data/allergy_intolerance.py,sha256=cm29fCOFiHHHVyff9kreWFUPZM--Nydw3a0FSbWh5-w,2303
|
|
194
196
|
canvas_sdk/v1/data/appointment.py,sha256=rK4Vl2bE_aE73vGx8Elj-tJdDdIbQNTbl0uLtWme89Y,1900
|
|
@@ -215,10 +217,10 @@ canvas_sdk/v1/data/questionnaire.py,sha256=1Vo5QUsOLYbZiQ64QFpgY7Mj6wY8vsV8f9uPi
|
|
|
215
217
|
canvas_sdk/v1/data/staff.py,sha256=QyFxvFAvNwgTm4gf-uPH6A8ibFE2TBhEckZpAM7gNpw,2762
|
|
216
218
|
canvas_sdk/v1/data/task.py,sha256=5KNn88APPNOHEk4s1ZJRBBav8-AEQTUH039Vio_ZtAk,3471
|
|
217
219
|
canvas_sdk/v1/data/user.py,sha256=XwhYTBuPHWdDc9afaZKB2AA-nHtlT3p7TOvUQo0m20Q,276
|
|
218
|
-
canvas_sdk/v1/models.py,sha256=q9Sofiu9JDH5g04H8NHYIqAtBYxH4KjnwOldoKsY9Lk,206
|
|
219
220
|
canvas_sdk/value_set/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
220
221
|
canvas_sdk/value_set/custom.py,sha256=0xzGP86ioCGcsx8wwf46SXfe9IefLSGVq7Z4yrHbEUU,19709
|
|
221
222
|
canvas_sdk/value_set/hcc2018.py,sha256=fNFfNzsOMJ-ALTcZaqfTU8CspZSPnZwBPDobS5DBgoY,2198461
|
|
223
|
+
canvas_sdk/value_set/value_set.py,sha256=KPrnlrbzzbspqmdFtPnt_raWscXiKiyTOMcPfVavt2w,3929
|
|
222
224
|
canvas_sdk/value_set/tests/test_value_sets.py,sha256=ZoDdR6npR9L8h7qGdQj3B7lRghHI3PBV3klRDYV7wak,3116
|
|
223
225
|
canvas_sdk/value_set/v2022/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
224
226
|
canvas_sdk/value_set/v2022/adverse_event.py,sha256=8zsNFBsLDRY0mrPomzogD-W15xyo2CFFkiEcT2ruGCg,1032
|
|
@@ -237,7 +239,6 @@ canvas_sdk/value_set/v2022/medication.py,sha256=L1yNzrwD2nUox8F6x8RSKhmFsn9VcFbF
|
|
|
237
239
|
canvas_sdk/value_set/v2022/physical_exam.py,sha256=C5PfLR4YB6pB_0wIy6Wfocyso5a9wuwGoKj84k_jR-U,7645
|
|
238
240
|
canvas_sdk/value_set/v2022/procedure.py,sha256=CxbGfQc_YkQa1JpwZq90uSLaSSD4k-gPWmVNz3ard9E,367807
|
|
239
241
|
canvas_sdk/value_set/v2022/symptom.py,sha256=4-ouetYI8soAAZy9Gq8AXBiqTfJjdgfZgWZPhBE-Ing,9384
|
|
240
|
-
canvas_sdk/value_set/value_set.py,sha256=KPrnlrbzzbspqmdFtPnt_raWscXiKiyTOMcPfVavt2w,3929
|
|
241
242
|
canvas_sdk/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
242
243
|
logger/__init__.py,sha256=9o2iRCjzFEhfULgXvrrECRFK-4IslWJTqKKjTCEUbq8,61
|
|
243
244
|
logger/logger.py,sha256=axf7UffBJtETjwDCtmi1IaaJKsvcFj8zaLfouGsq68A,1847
|
|
@@ -245,11 +246,14 @@ plugin_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
245
246
|
plugin_runner/authentication.py,sha256=SDPso2AogtLAV_H0LuMDp99IMZuF3oTq-Q_AXAvJ8uc,1116
|
|
246
247
|
plugin_runner/aws_headers.py,sha256=DenX_nAMVhXMJZw88PLZbqJsi5_XriNtr3jE-eJqHY4,2773
|
|
247
248
|
plugin_runner/exceptions.py,sha256=YnRZiQVzbU3HrVlmEXLje_np99009YnhTRVHHyBCtqc,433
|
|
248
|
-
plugin_runner/plugin_installer.py,sha256=
|
|
249
|
-
plugin_runner/plugin_runner.py,sha256=
|
|
250
|
-
plugin_runner/plugin_synchronizer.py,sha256=GcY2oKwJc8beDoAJwo-0MkY4rxNzoF-Bk1bF9MOtO6U,2851
|
|
249
|
+
plugin_runner/plugin_installer.py,sha256=0gHMpI4CDkWljbU7HLhbPjl2E_oHxDZMlmPtTqCyS9o,7748
|
|
250
|
+
plugin_runner/plugin_runner.py,sha256=h_FHStJrvRiG9DSf8uXHKFb4aby2wBzLAe10Ov4xnas,16437
|
|
251
251
|
plugin_runner/sandbox.py,sha256=SdTfPWzs5mcMmQ5J9ESpTk5c3e1nQG8F4N0pKPuK48M,14499
|
|
252
252
|
plugin_runner/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
253
|
+
plugin_runner/tests/test_application.py,sha256=e1R2YagMRD96gZALx-Zra-e-sR3SiP7cIpI6pheZnUc,2427
|
|
254
|
+
plugin_runner/tests/test_plugin_installer.py,sha256=i3ELG5cRfaG7ETLwk8aJhahgJuPfKjIrXL250t2wpzI,4434
|
|
255
|
+
plugin_runner/tests/test_plugin_runner.py,sha256=D2NyTGogu1FpiFo0MKZXcJRWFh4Ix6uznYnthdXXzk4,11632
|
|
256
|
+
plugin_runner/tests/test_sandbox.py,sha256=fDIkRBzoVWClvIMOI37FvJ1TRZ5mAztt88YZ0OPqsA8,4231
|
|
253
257
|
plugin_runner/tests/data/plugins/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
254
258
|
plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json,sha256=J9T_E5vqUX4HITHbFsd6JQpw3YvMS4wR_lhI5JL2KMk,749
|
|
255
259
|
plugin_runner/tests/fixtures/plugins/example_plugin/README.md,sha256=t9pKwFf8iQPASqdXwfkA5JXkAr8KcSDX6AeW3CMiKVY,246
|
|
@@ -305,14 +309,13 @@ plugin_runner/tests/fixtures/plugins/test_render_template/README.md,sha256=EEt-t
|
|
|
305
309
|
plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
306
310
|
plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py,sha256=uQLaxFzb4mIbmcxbIoDyz2USuWP_lW0SMI_JDaoGEPg,1521
|
|
307
311
|
plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html,sha256=IWI5mZBGOPlI7AH4dT7ebxa1Q9kgSpRzC45qv3CaH7Q,132
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
+
protobufs/canvas_generated/messages/effects.proto,sha256=72L5I4pnPnsNWzvZjSVzpYHlcwdyrFk1N7AGwcL0Zlc,6534
|
|
313
|
+
protobufs/canvas_generated/messages/events.proto,sha256=ymaaxJwXG71nbbVmEQwqAyBtZFFljUTY8sjQ-ju7GdA,45727
|
|
314
|
+
protobufs/canvas_generated/messages/plugins.proto,sha256=oNainUPWFYQjgCX7bJEPI9_VnHC5VZduzOqgR4Q7dNM,109
|
|
315
|
+
protobufs/canvas_generated/services/plugin_runner.proto,sha256=doadBKn5k4xAtOgR-q_pEvW4yzxpUaHNOowMG6CL5GY,304
|
|
312
316
|
pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
313
317
|
pubsub/pubsub.py,sha256=pyTW0JU8mtaqiAV6g6xjZwel1CVy2EonPMU-_vkmhUM,1044
|
|
314
|
-
|
|
315
|
-
canvas-0.
|
|
316
|
-
canvas-0.
|
|
317
|
-
canvas-0.
|
|
318
|
-
canvas-0.16.0.dist-info/RECORD,,
|
|
318
|
+
canvas-0.17.0.dist-info/METADATA,sha256=UCQcCBA9TGVqFz4CR5ypshNjiPVNVPscthbRtDgJuEs,4375
|
|
319
|
+
canvas-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
320
|
+
canvas-0.17.0.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
|
|
321
|
+
canvas-0.17.0.dist-info/RECORD,,
|
canvas_cli/apps/plugin/plugin.py
CHANGED
|
@@ -40,7 +40,7 @@ def validate_package(package: Path) -> Path:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def _build_package(package: Path) -> Path:
|
|
43
|
-
"""
|
|
43
|
+
"""Compresses `package` and returns the built archive, ignoring symlinks, hidden folders, and hidden files."""
|
|
44
44
|
package = package.resolve()
|
|
45
45
|
|
|
46
46
|
if not package.exists() or not package.is_dir():
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from collections.abc import Generator
|
|
2
2
|
from datetime import datetime
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
|
|
7
|
+
import settings
|
|
6
8
|
from canvas_sdk.commands.tests.test_utils import (
|
|
7
9
|
COMMANDS,
|
|
8
10
|
MaskedValue,
|
|
@@ -12,6 +14,7 @@ from canvas_sdk.commands.tests.test_utils import (
|
|
|
12
14
|
get_token,
|
|
13
15
|
install_plugin,
|
|
14
16
|
trigger_plugin_event,
|
|
17
|
+
wait_for_log,
|
|
15
18
|
write_protocol_code,
|
|
16
19
|
)
|
|
17
20
|
|
|
@@ -40,10 +43,18 @@ def write_and_install_protocol_and_clean_up(
|
|
|
40
43
|
) -> Generator[None, None, None]:
|
|
41
44
|
"""Write the protocol code, install the plugin, and clean up after the test."""
|
|
42
45
|
write_protocol_code(new_note["externallyExposableId"], plugin_name, COMMANDS)
|
|
46
|
+
message_received_event, thread, ws = wait_for_log(
|
|
47
|
+
cast(str, settings.INTEGRATION_TEST_URL),
|
|
48
|
+
token.value,
|
|
49
|
+
f"Loading plugin '{plugin_name}",
|
|
50
|
+
)
|
|
43
51
|
install_plugin(plugin_name, token)
|
|
52
|
+
message_received_event.wait(timeout=5.0)
|
|
44
53
|
|
|
45
54
|
yield
|
|
46
55
|
|
|
56
|
+
ws.close()
|
|
57
|
+
thread.join()
|
|
47
58
|
clean_up_files_and_plugins(plugin_name, token)
|
|
48
59
|
|
|
49
60
|
|
|
@@ -57,8 +68,7 @@ def test_protocol_that_inserts_every_command(
|
|
|
57
68
|
commands_in_body = get_original_note_body_commands(new_note["id"], token)
|
|
58
69
|
|
|
59
70
|
# TODO: Temporary workaround to ignore the updateGoal command until the integration test instance is fixed.
|
|
60
|
-
command_keys = [c.Meta.key for c in COMMANDS
|
|
61
|
-
|
|
71
|
+
command_keys = [c.Meta.key for c in COMMANDS]
|
|
62
72
|
assert len(command_keys) == len(commands_in_body)
|
|
63
73
|
for i, command_key in enumerate(command_keys):
|
|
64
74
|
assert commands_in_body[i] == command_key
|
|
@@ -190,11 +190,6 @@ class Protocol(BaseProtocol):
|
|
|
190
190
|
def install_plugin(plugin_name: str, token: MaskedValue) -> None:
|
|
191
191
|
"""Install a plugin."""
|
|
192
192
|
with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
|
|
193
|
-
message_received_event = wait_for_log(
|
|
194
|
-
cast(str, settings.INTEGRATION_TEST_URL),
|
|
195
|
-
token.value,
|
|
196
|
-
f"Loading plugin '{plugin_name}",
|
|
197
|
-
)
|
|
198
193
|
response = requests.post(
|
|
199
194
|
plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
|
|
200
195
|
data={"is_enabled": True},
|
|
@@ -203,8 +198,6 @@ def install_plugin(plugin_name: str, token: MaskedValue) -> None:
|
|
|
203
198
|
)
|
|
204
199
|
response.raise_for_status()
|
|
205
200
|
|
|
206
|
-
message_received_event.wait(timeout=5.0)
|
|
207
|
-
|
|
208
201
|
|
|
209
202
|
def trigger_plugin_event(token: MaskedValue) -> None:
|
|
210
203
|
"""Trigger a plugin event."""
|
|
@@ -328,7 +321,9 @@ def get_token() -> MaskedValue:
|
|
|
328
321
|
return MaskedValue(response.json()["access_token"])
|
|
329
322
|
|
|
330
323
|
|
|
331
|
-
def wait_for_log(
|
|
324
|
+
def wait_for_log(
|
|
325
|
+
host: str, token: str, message: str
|
|
326
|
+
) -> tuple[threading.Event, threading.Thread, websocket.WebSocketApp]:
|
|
332
327
|
"""Wait for a specific log message."""
|
|
333
328
|
hostname = cast(str, urlparse(host).hostname)
|
|
334
329
|
instance = hostname.removesuffix(".canvasmedical.com")
|
|
@@ -362,4 +357,4 @@ def wait_for_log(host: str, token: str, message: str) -> threading.Event:
|
|
|
362
357
|
|
|
363
358
|
connected_event.wait(timeout=5.0)
|
|
364
359
|
|
|
365
|
-
return message_received_event
|
|
360
|
+
return message_received_event, thread, ws
|
|
@@ -92,10 +92,10 @@ class Protocol(BaseProtocol):
|
|
|
92
92
|
protocol.write(protocol_code)
|
|
93
93
|
|
|
94
94
|
with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
|
|
95
|
-
message_received_event = wait_for_log(
|
|
95
|
+
message_received_event, thread, ws = wait_for_log(
|
|
96
96
|
settings.INTEGRATION_TEST_URL,
|
|
97
97
|
token.value,
|
|
98
|
-
f"Loading plugin '{plugin_name}
|
|
98
|
+
f"Loading plugin '{plugin_name}",
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
# install the plugin
|
|
@@ -111,6 +111,9 @@ class Protocol(BaseProtocol):
|
|
|
111
111
|
|
|
112
112
|
yield
|
|
113
113
|
|
|
114
|
+
ws.close()
|
|
115
|
+
thread.join()
|
|
116
|
+
|
|
114
117
|
# clean up
|
|
115
118
|
if Path(f"./custom-plugins/{plugin_name}").exists():
|
|
116
119
|
shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
|
canvas_sdk/utils/http.py
CHANGED
|
@@ -26,7 +26,7 @@ class Http:
|
|
|
26
26
|
result = fn(self, *args, **kwargs)
|
|
27
27
|
end_time = time.time()
|
|
28
28
|
timing = int((end_time - start_time) * 1000)
|
|
29
|
-
self.statsd_client.timing(f"http_{fn.__name__}", timing)
|
|
29
|
+
self.statsd_client.timing(f"plugins.http_{fn.__name__}", timing)
|
|
30
30
|
return result
|
|
31
31
|
|
|
32
32
|
return cast(F, wrapper)
|
|
@@ -14,9 +14,17 @@ import requests
|
|
|
14
14
|
from psycopg import Connection
|
|
15
15
|
from psycopg.rows import dict_row
|
|
16
16
|
|
|
17
|
-
import settings
|
|
18
17
|
from plugin_runner.aws_headers import aws_sig_v4_headers
|
|
19
18
|
from plugin_runner.exceptions import InvalidPluginFormat, PluginInstallationError
|
|
19
|
+
from settings import (
|
|
20
|
+
AWS_ACCESS_KEY_ID,
|
|
21
|
+
AWS_REGION,
|
|
22
|
+
AWS_SECRET_ACCESS_KEY,
|
|
23
|
+
CUSTOMER_IDENTIFIER,
|
|
24
|
+
MEDIA_S3_BUCKET_NAME,
|
|
25
|
+
PLUGIN_DIRECTORY,
|
|
26
|
+
SECRETS_FILE_NAME,
|
|
27
|
+
)
|
|
20
28
|
|
|
21
29
|
# Plugin "packages" include this prefix in the database record for the plugin and the S3 bucket key.
|
|
22
30
|
UPLOAD_TO_PREFIX = "plugins"
|
|
@@ -100,19 +108,19 @@ def _extract_rows_to_dict(rows: list) -> dict[str, PluginAttributes]:
|
|
|
100
108
|
def download_plugin(plugin_package: str) -> Generator[Path, None, None]:
|
|
101
109
|
"""Download the plugin package from the S3 bucket."""
|
|
102
110
|
method = "GET"
|
|
103
|
-
host = f"s3-{
|
|
104
|
-
bucket =
|
|
105
|
-
customer_identifier =
|
|
111
|
+
host = f"s3-{AWS_REGION}.amazonaws.com"
|
|
112
|
+
bucket = MEDIA_S3_BUCKET_NAME
|
|
113
|
+
customer_identifier = CUSTOMER_IDENTIFIER
|
|
106
114
|
path = f"/{bucket}/{customer_identifier}/{plugin_package}"
|
|
107
115
|
payload = b"This is required for the AWS headers because it is part of the signature"
|
|
108
116
|
pre_auth_headers: dict[str, str] = {}
|
|
109
117
|
query: dict[str, str] = {}
|
|
110
118
|
headers = aws_sig_v4_headers(
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
AWS_ACCESS_KEY_ID,
|
|
120
|
+
AWS_SECRET_ACCESS_KEY,
|
|
113
121
|
pre_auth_headers,
|
|
114
122
|
"s3",
|
|
115
|
-
|
|
123
|
+
AWS_REGION,
|
|
116
124
|
host,
|
|
117
125
|
method,
|
|
118
126
|
path,
|
|
@@ -135,7 +143,7 @@ def install_plugin(plugin_name: str, attributes: PluginAttributes) -> None:
|
|
|
135
143
|
try:
|
|
136
144
|
print(f"Installing plugin '{plugin_name}'")
|
|
137
145
|
|
|
138
|
-
plugin_installation_path = Path(
|
|
146
|
+
plugin_installation_path = Path(PLUGIN_DIRECTORY) / plugin_name
|
|
139
147
|
|
|
140
148
|
# if plugin exists, first uninstall it
|
|
141
149
|
if plugin_installation_path.exists():
|
|
@@ -175,7 +183,7 @@ def install_plugin_secrets(plugin_name: str, secrets: dict[str, str]) -> None:
|
|
|
175
183
|
"""Write the plugin's secrets to disk in the package's directory."""
|
|
176
184
|
print(f"Writing plugin secrets for '{plugin_name}'")
|
|
177
185
|
|
|
178
|
-
secrets_path = Path(
|
|
186
|
+
secrets_path = Path(PLUGIN_DIRECTORY) / plugin_name / SECRETS_FILE_NAME
|
|
179
187
|
|
|
180
188
|
# Did the plugin ship a secrets.json? TOO BAD, IT'S GONE NOW.
|
|
181
189
|
if Path(secrets_path).exists():
|
|
@@ -199,7 +207,7 @@ def disable_plugin(plugin_name: str) -> None:
|
|
|
199
207
|
|
|
200
208
|
def uninstall_plugin(plugin_name: str) -> None:
|
|
201
209
|
"""Remove the plugin from the filesystem."""
|
|
202
|
-
plugin_path = Path(
|
|
210
|
+
plugin_path = Path(PLUGIN_DIRECTORY) / plugin_name
|
|
203
211
|
|
|
204
212
|
if plugin_path.exists():
|
|
205
213
|
shutil.rmtree(plugin_path)
|
|
@@ -207,10 +215,10 @@ def uninstall_plugin(plugin_name: str) -> None:
|
|
|
207
215
|
|
|
208
216
|
def install_plugins() -> None:
|
|
209
217
|
"""Install all enabled plugins."""
|
|
210
|
-
if Path(
|
|
211
|
-
shutil.rmtree(
|
|
218
|
+
if Path(PLUGIN_DIRECTORY).exists():
|
|
219
|
+
shutil.rmtree(PLUGIN_DIRECTORY)
|
|
212
220
|
|
|
213
|
-
os.mkdir(
|
|
221
|
+
os.mkdir(PLUGIN_DIRECTORY)
|
|
214
222
|
|
|
215
223
|
for plugin_name, attributes in enabled_plugins().items():
|
|
216
224
|
try:
|
plugin_runner/plugin_runner.py
CHANGED
|
@@ -2,17 +2,17 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
4
|
import pathlib
|
|
5
|
+
import pickle
|
|
5
6
|
import pkgutil
|
|
6
|
-
import signal
|
|
7
7
|
import sys
|
|
8
8
|
import time
|
|
9
9
|
import traceback
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from collections.abc import AsyncGenerator
|
|
12
|
-
from types import FrameType
|
|
13
12
|
from typing import Any, TypedDict
|
|
14
13
|
|
|
15
14
|
import grpc
|
|
15
|
+
import redis.asyncio as redis
|
|
16
16
|
import statsd
|
|
17
17
|
|
|
18
18
|
from canvas_generated.messages.effects_pb2 import EffectType
|
|
@@ -30,9 +30,15 @@ from canvas_sdk.protocols import ClinicalQualityMeasure
|
|
|
30
30
|
from canvas_sdk.utils.stats import get_duration_ms, tags_to_line_protocol
|
|
31
31
|
from logger import log
|
|
32
32
|
from plugin_runner.authentication import token_for_plugin
|
|
33
|
-
from plugin_runner.
|
|
33
|
+
from plugin_runner.plugin_installer import install_plugins
|
|
34
34
|
from plugin_runner.sandbox import Sandbox
|
|
35
|
-
from settings import
|
|
35
|
+
from settings import (
|
|
36
|
+
CHANNEL_NAME,
|
|
37
|
+
MANIFEST_FILE_NAME,
|
|
38
|
+
PLUGIN_DIRECTORY,
|
|
39
|
+
REDIS_ENDPOINT,
|
|
40
|
+
SECRETS_FILE_NAME,
|
|
41
|
+
)
|
|
36
42
|
|
|
37
43
|
# when we import plugins we'll use the module name directly so we need to add the plugin
|
|
38
44
|
# directory to the path
|
|
@@ -192,14 +198,50 @@ class PluginRunner(PluginRunnerServicer):
|
|
|
192
198
|
self, request: ReloadPluginsRequest, context: Any
|
|
193
199
|
) -> AsyncGenerator[ReloadPluginsResponse, None]:
|
|
194
200
|
"""This is invoked when we need to reload plugins."""
|
|
201
|
+
log.info("Reloading plugins...")
|
|
195
202
|
try:
|
|
196
|
-
publish_message({"action": "
|
|
203
|
+
await publish_message(message={"action": "reload"})
|
|
197
204
|
except ImportError:
|
|
198
205
|
yield ReloadPluginsResponse(success=False)
|
|
199
206
|
else:
|
|
200
207
|
yield ReloadPluginsResponse(success=True)
|
|
201
208
|
|
|
202
209
|
|
|
210
|
+
async def synchronize_plugins(max_iterations: None | int = None) -> None:
|
|
211
|
+
"""Listen for messages on the pubsub channel that will indicate it is necessary to reinstall and reload plugins."""
|
|
212
|
+
client, pubsub = get_client()
|
|
213
|
+
await pubsub.psubscribe(CHANNEL_NAME)
|
|
214
|
+
log.info("Listening for messages on pubsub channel")
|
|
215
|
+
iterations: int = 0
|
|
216
|
+
while (
|
|
217
|
+
max_iterations is None or iterations < max_iterations
|
|
218
|
+
): # max_iterations == -1 means infinite iterations
|
|
219
|
+
iterations += 1
|
|
220
|
+
message = await pubsub.get_message(ignore_subscribe_messages=True, timeout=None)
|
|
221
|
+
if message is not None:
|
|
222
|
+
log.info("Received message from pubsub channel")
|
|
223
|
+
|
|
224
|
+
message_type = message.get("type", "")
|
|
225
|
+
|
|
226
|
+
if message_type != "pmessage":
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
data = pickle.loads(message.get("data", pickle.dumps({})))
|
|
230
|
+
|
|
231
|
+
if "action" not in data:
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
if data["action"] == "reload":
|
|
235
|
+
try:
|
|
236
|
+
log.info(
|
|
237
|
+
"plugin-synchronizer: installing and reloading plugins after receiving command"
|
|
238
|
+
)
|
|
239
|
+
install_plugins()
|
|
240
|
+
load_plugins()
|
|
241
|
+
except Exception as e:
|
|
242
|
+
print("plugin-synchronizer: `install_plugins` failed:", e)
|
|
243
|
+
|
|
244
|
+
|
|
203
245
|
def validate_effects(effects: list[Effect]) -> list[Effect]:
|
|
204
246
|
"""Validates the effects based on predefined rules.
|
|
205
247
|
Keeps only the first AUTOCOMPLETE_SEARCH_RESULTS effect and preserve all non-search-related effects.
|
|
@@ -237,12 +279,6 @@ def apply_effects_to_context(effects: list[Effect], event: Event) -> Event:
|
|
|
237
279
|
return event
|
|
238
280
|
|
|
239
281
|
|
|
240
|
-
def handle_hup_cb(_signum: int, _frame: FrameType | None) -> None:
|
|
241
|
-
"""handle_hup_cb."""
|
|
242
|
-
log.info("Received SIGHUP, reloading plugins...")
|
|
243
|
-
load_plugins()
|
|
244
|
-
|
|
245
|
-
|
|
246
282
|
def find_modules(base_path: pathlib.Path, prefix: str | None = None) -> list[str]:
|
|
247
283
|
"""Find all modules in the specified package path."""
|
|
248
284
|
modules: list[str] = []
|
|
@@ -273,6 +309,22 @@ def sandbox_from_module(base_path: pathlib.Path, module_name: str) -> Any:
|
|
|
273
309
|
return sandbox.execute()
|
|
274
310
|
|
|
275
311
|
|
|
312
|
+
async def publish_message(message: dict) -> None:
|
|
313
|
+
"""Publish a message to the pubsub channel."""
|
|
314
|
+
log.info("Publishing message to pubsub channel")
|
|
315
|
+
client, _ = get_client()
|
|
316
|
+
|
|
317
|
+
await client.publish(CHANNEL_NAME, pickle.dumps(message))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def get_client() -> tuple[redis.Redis, redis.client.PubSub]:
|
|
321
|
+
"""Return an async Redis client and pubsub object."""
|
|
322
|
+
client = redis.Redis.from_url(REDIS_ENDPOINT)
|
|
323
|
+
pubsub = client.pubsub()
|
|
324
|
+
|
|
325
|
+
return client, pubsub
|
|
326
|
+
|
|
327
|
+
|
|
276
328
|
def load_or_reload_plugin(path: pathlib.Path) -> None:
|
|
277
329
|
"""Given a path, load or reload a plugin."""
|
|
278
330
|
log.info(f"Loading {path}")
|
|
@@ -415,6 +467,7 @@ async def serve(specified_plugin_paths: list[str] | None = None) -> None:
|
|
|
415
467
|
|
|
416
468
|
log.info(f"Starting server, listening on port {port}")
|
|
417
469
|
|
|
470
|
+
install_plugins()
|
|
418
471
|
load_plugins(specified_plugin_paths)
|
|
419
472
|
|
|
420
473
|
await server.start()
|
|
@@ -434,10 +487,10 @@ def run_server(specified_plugin_paths: list[str] | None = None) -> None:
|
|
|
434
487
|
|
|
435
488
|
asyncio.set_event_loop(loop)
|
|
436
489
|
|
|
437
|
-
signal.signal(signal.SIGHUP, handle_hup_cb)
|
|
438
|
-
|
|
439
490
|
try:
|
|
440
|
-
loop.run_until_complete(
|
|
491
|
+
loop.run_until_complete(
|
|
492
|
+
asyncio.gather(serve(specified_plugin_paths), synchronize_plugins())
|
|
493
|
+
)
|
|
441
494
|
except KeyboardInterrupt:
|
|
442
495
|
pass
|
|
443
496
|
finally:
|