canvas 0.53.2__py3-none-any.whl → 0.55.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.53.2.dist-info → canvas-0.55.0.dist-info}/METADATA +1 -1
- {canvas-0.53.2.dist-info → canvas-0.55.0.dist-info}/RECORD +29 -22
- canvas_cli/apps/plugin/plugin.py +40 -8
- canvas_cli/utils/validators/manifest_schema.py +2 -0
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +16 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +16 -0
- canvas_sdk/caching/client.py +3 -1
- canvas_sdk/caching/plugins.py +7 -4
- canvas_sdk/effects/group.py +20 -0
- canvas_sdk/effects/panel_configuration.py +90 -0
- canvas_sdk/effects/patient_chart_group.py +30 -0
- canvas_sdk/effects/payment_processor.py +137 -0
- canvas_sdk/handlers/payment_processors/__init__.py +1 -0
- canvas_sdk/handlers/payment_processors/base.py +93 -0
- canvas_sdk/handlers/payment_processors/card.py +200 -0
- canvas_sdk/questionnaires/utils.py +2 -2
- canvas_sdk/templates/utils.py +5 -3
- canvas_sdk/utils/plugins.py +33 -20
- canvas_sdk/v1/data/patient.py +7 -0
- plugin_runner/allowed-module-imports.json +23 -0
- plugin_runner/installation.py +4 -1
- plugin_runner/plugin_runner.py +23 -0
- plugin_runner/sandbox.py +1 -0
- protobufs/canvas_generated/messages/effects.proto +11 -0
- protobufs/canvas_generated/messages/events.proto +10 -0
- {canvas-0.53.2.dist-info → canvas-0.55.0.dist-info}/WHEEL +0 -0
- {canvas-0.53.2.dist-info → canvas-0.55.0.dist-info}/entry_points.txt +0 -0
canvas_sdk/templates/utils.py
CHANGED
|
@@ -3,12 +3,14 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from django.template import Context, Template
|
|
5
5
|
|
|
6
|
-
from canvas_sdk.utils.plugins import
|
|
6
|
+
from canvas_sdk.utils.plugins import plugin_context
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@
|
|
9
|
+
@plugin_context
|
|
10
10
|
def render_to_string(
|
|
11
|
-
template_name: str,
|
|
11
|
+
template_name: str,
|
|
12
|
+
context: dict[str, Any] | None = None,
|
|
13
|
+
**kwargs: Any,
|
|
12
14
|
) -> str | None:
|
|
13
15
|
"""Load a template and render it with the given context.
|
|
14
16
|
|
canvas_sdk/utils/plugins.py
CHANGED
|
@@ -8,19 +8,39 @@ from canvas_sdk.utils.metrics import measured
|
|
|
8
8
|
from settings import PLUGIN_DIRECTORY
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def find_plugin_ancestor(frame: FrameType | None, max_depth: int = 10) -> FrameType | None:
|
|
12
|
+
"""
|
|
13
|
+
Recurse backwards to find any plugin ancestor of this frame.
|
|
14
|
+
"""
|
|
15
|
+
parent_frame = frame.f_back if frame else None
|
|
16
|
+
|
|
17
|
+
if not parent_frame:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
if max_depth == 0:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
if "__is_plugin__" in parent_frame.f_globals:
|
|
24
|
+
return parent_frame
|
|
25
|
+
|
|
26
|
+
return find_plugin_ancestor(frame=parent_frame, max_depth=max_depth - 1)
|
|
27
|
+
|
|
28
|
+
|
|
11
29
|
@measured
|
|
12
|
-
def
|
|
30
|
+
def plugin_context(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
13
31
|
"""Decorator to restrict a function's execution to plugins only."""
|
|
14
32
|
|
|
15
33
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
16
|
-
|
|
17
|
-
caller = current_frame.f_back if current_frame else None
|
|
34
|
+
plugin_frame = find_plugin_ancestor(inspect.currentframe())
|
|
18
35
|
|
|
19
|
-
if not
|
|
20
|
-
|
|
36
|
+
if not plugin_frame:
|
|
37
|
+
raise RuntimeError(
|
|
38
|
+
"Method that expected plugin context was called from outside a plugin."
|
|
39
|
+
)
|
|
21
40
|
|
|
22
|
-
plugin_name =
|
|
41
|
+
plugin_name = plugin_frame.f_globals["__name__"].split(".")[0]
|
|
23
42
|
plugin_dir = Path(PLUGIN_DIRECTORY) / plugin_name
|
|
43
|
+
|
|
24
44
|
kwargs["plugin_name"] = plugin_name
|
|
25
45
|
kwargs["plugin_dir"] = plugin_dir.resolve()
|
|
26
46
|
|
|
@@ -30,24 +50,17 @@ def plugin_only(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
30
50
|
|
|
31
51
|
|
|
32
52
|
@measured
|
|
33
|
-
def is_plugin_caller(
|
|
53
|
+
def is_plugin_caller() -> tuple[bool, str | None]:
|
|
34
54
|
"""Check if a function is called from a plugin."""
|
|
35
|
-
|
|
36
|
-
caller = current_frame.f_back if current_frame else None
|
|
37
|
-
|
|
38
|
-
if not caller:
|
|
39
|
-
return False, None
|
|
55
|
+
plugin_frame = find_plugin_ancestor(inspect.currentframe())
|
|
40
56
|
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
else:
|
|
45
|
-
return False, None
|
|
57
|
+
if plugin_frame:
|
|
58
|
+
module = plugin_frame.f_globals.get("__name__")
|
|
59
|
+
qualname = plugin_frame.f_code.co_qualname
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
qualname = caller.f_code.co_qualname
|
|
61
|
+
return True, f"{module}.{qualname}"
|
|
49
62
|
|
|
50
|
-
return
|
|
63
|
+
return False, None
|
|
51
64
|
|
|
52
65
|
|
|
53
66
|
__exports__ = ()
|
canvas_sdk/v1/data/patient.py
CHANGED
|
@@ -99,6 +99,13 @@ class Patient(Model):
|
|
|
99
99
|
def __str__(self) -> str:
|
|
100
100
|
return f"{self.first_name} {self.last_name}"
|
|
101
101
|
|
|
102
|
+
@property
|
|
103
|
+
def full_name(self) -> str:
|
|
104
|
+
"""Returns the patient's full name."""
|
|
105
|
+
return " ".join(
|
|
106
|
+
n for n in (self.first_name, self.middle_name, self.last_name, self.suffix) if n
|
|
107
|
+
)
|
|
108
|
+
|
|
102
109
|
def age_at(self, time: arrow.Arrow) -> float:
|
|
103
110
|
"""Given a datetime, returns what the patient's age would be at that datetime."""
|
|
104
111
|
age = float(0)
|
|
@@ -212,6 +212,9 @@
|
|
|
212
212
|
"canvas_sdk.effects.compound_medications.compound_medication": [
|
|
213
213
|
"CompoundMedication"
|
|
214
214
|
],
|
|
215
|
+
"canvas_sdk.effects.group": [
|
|
216
|
+
"Group"
|
|
217
|
+
],
|
|
215
218
|
"canvas_sdk.effects.launch_modal": [
|
|
216
219
|
"LaunchModalEffect"
|
|
217
220
|
],
|
|
@@ -236,6 +239,9 @@
|
|
|
236
239
|
"canvas_sdk.effects.note.note": [
|
|
237
240
|
"Note"
|
|
238
241
|
],
|
|
242
|
+
"canvas_sdk.effects.panel_configuration": [
|
|
243
|
+
"PanelConfiguration"
|
|
244
|
+
],
|
|
239
245
|
"canvas_sdk.effects.patient": [
|
|
240
246
|
"CreatePatientExternalIdentifier",
|
|
241
247
|
"Patient",
|
|
@@ -250,6 +256,9 @@
|
|
|
250
256
|
"canvas_sdk.effects.patient.create_patient_external_identifier": [
|
|
251
257
|
"CreatePatientExternalIdentifier"
|
|
252
258
|
],
|
|
259
|
+
"canvas_sdk.effects.patient_chart_group": [
|
|
260
|
+
"PatientChartGroup"
|
|
261
|
+
],
|
|
253
262
|
"canvas_sdk.effects.patient_chart_summary_configuration": [
|
|
254
263
|
"PatientChartSummaryConfiguration"
|
|
255
264
|
],
|
|
@@ -279,6 +288,14 @@
|
|
|
279
288
|
"canvas_sdk.effects.patient_profile_configuration": [
|
|
280
289
|
"PatientProfileConfiguration"
|
|
281
290
|
],
|
|
291
|
+
"canvas_sdk.effects.payment_processor": [
|
|
292
|
+
"AddPaymentMethodResponse",
|
|
293
|
+
"CardTransaction",
|
|
294
|
+
"PaymentMethod",
|
|
295
|
+
"PaymentProcessorForm",
|
|
296
|
+
"PaymentProcessorMetadata",
|
|
297
|
+
"RemovePaymentMethodResponse"
|
|
298
|
+
],
|
|
282
299
|
"canvas_sdk.effects.protocol_card": [
|
|
283
300
|
"ProtocolCard",
|
|
284
301
|
"Recommendation"
|
|
@@ -364,6 +381,12 @@
|
|
|
364
381
|
"canvas_sdk.handlers.cron_task": [
|
|
365
382
|
"CronTask"
|
|
366
383
|
],
|
|
384
|
+
"canvas_sdk.handlers.payment_processors.base": [
|
|
385
|
+
"PaymentProcessor"
|
|
386
|
+
],
|
|
387
|
+
"canvas_sdk.handlers.payment_processors.card": [
|
|
388
|
+
"CardPaymentProcessor"
|
|
389
|
+
],
|
|
367
390
|
"canvas_sdk.handlers.simple_api": [
|
|
368
391
|
"APIKeyAuthMixin",
|
|
369
392
|
"APIKeyCredentials",
|
plugin_runner/installation.py
CHANGED
|
@@ -147,6 +147,8 @@ def download_plugin(plugin_package: str) -> Generator[Path, None, None]:
|
|
|
147
147
|
|
|
148
148
|
with open(download_path, "wb") as download_file:
|
|
149
149
|
response = requests.request(method=method, url=f"https://{host}{path}", headers=headers)
|
|
150
|
+
response.raise_for_status()
|
|
151
|
+
|
|
150
152
|
download_file.write(response.content)
|
|
151
153
|
|
|
152
154
|
yield download_path
|
|
@@ -168,7 +170,8 @@ def install_plugin(plugin_name: str, attributes: PluginAttributes) -> None:
|
|
|
168
170
|
|
|
169
171
|
install_plugin_secrets(plugin_name=plugin_name, secrets=attributes["secrets"])
|
|
170
172
|
except Exception as e:
|
|
171
|
-
log.error(f'Failed to install plugin "{plugin_name}", version {attributes["version"]}')
|
|
173
|
+
log.error(f'Failed to install plugin "{plugin_name}", version {attributes["version"]}: {e}')
|
|
174
|
+
|
|
172
175
|
sentry_sdk.capture_exception(e)
|
|
173
176
|
|
|
174
177
|
raise PluginInstallationError() from e
|
plugin_runner/plugin_runner.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
import pathlib
|
|
@@ -213,6 +214,28 @@ class PluginRunner(PluginRunnerServicer):
|
|
|
213
214
|
# respond to SimpleAPI request events are not relevant
|
|
214
215
|
plugin_name = event.context["plugin_name"]
|
|
215
216
|
relevant_plugins = [p for p in relevant_plugins if p.startswith(f"{plugin_name}:")]
|
|
217
|
+
elif event_type in {
|
|
218
|
+
EventType.REVENUE__PAYMENT_PROCESSOR__CHARGE,
|
|
219
|
+
EventType.REVENUE__PAYMENT_PROCESSOR__SELECTED,
|
|
220
|
+
EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST,
|
|
221
|
+
EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD,
|
|
222
|
+
EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE,
|
|
223
|
+
}:
|
|
224
|
+
# The target plugin's name will be part of the payment processor identifier, so other plugins that
|
|
225
|
+
# respond to payment processor charge events are not relevant
|
|
226
|
+
try:
|
|
227
|
+
plugin_name = (
|
|
228
|
+
base64.b64decode(event.context["identifier"]).decode("utf-8").split(".")[0]
|
|
229
|
+
)
|
|
230
|
+
relevant_plugins = [
|
|
231
|
+
p for p in relevant_plugins if p.startswith(f"{plugin_name}:")
|
|
232
|
+
]
|
|
233
|
+
except Exception as ex:
|
|
234
|
+
log.error(
|
|
235
|
+
f"Failed to decode identifier for event {event_name} with context {event.context}"
|
|
236
|
+
)
|
|
237
|
+
sentry_sdk.capture_exception(ex)
|
|
238
|
+
relevant_plugins = []
|
|
216
239
|
|
|
217
240
|
effect_list = []
|
|
218
241
|
|
plugin_runner/sandbox.py
CHANGED
|
@@ -303,6 +303,17 @@ enum EffectType {
|
|
|
303
303
|
|
|
304
304
|
CREATE_COMPOUND_MEDICATION = 6020;
|
|
305
305
|
UPDATE_COMPOUND_MEDICATION = 6021;
|
|
306
|
+
|
|
307
|
+
PATIENT_CHART__GROUP_ITEMS = 6030;
|
|
308
|
+
|
|
309
|
+
SHOW_PANEL_SECTIONS = 6100;
|
|
310
|
+
|
|
311
|
+
REVENUE__PAYMENT_PROCESSOR__METADATA = 7001;
|
|
312
|
+
REVENUE__PAYMENT_PROCESSOR__FORM = 7002;
|
|
313
|
+
REVENUE__PAYMENT_PROCESSOR__CREDIT_CARD_TRANSACTION = 7003;
|
|
314
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD = 7004;
|
|
315
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__ADD_RESPONSE = 7005;
|
|
316
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__REMOVE_RESPONSE = 7006;
|
|
306
317
|
}
|
|
307
318
|
|
|
308
319
|
message Effect {
|
|
@@ -1108,6 +1108,7 @@ enum EventType {
|
|
|
1108
1108
|
PATIENT_PROFILE__SECTION_CONFIGURATION = 100002;
|
|
1109
1109
|
PATIENT_PROFILE__ADD_PHARMACY__POST_SEARCH = 100003;
|
|
1110
1110
|
|
|
1111
|
+
PATIENT_CHART__MEDICATIONS = 100004;
|
|
1111
1112
|
CLAIM__CONDITIONS = 101000;
|
|
1112
1113
|
|
|
1113
1114
|
PLUGIN_CREATED = 102000;
|
|
@@ -1160,6 +1161,15 @@ enum EventType {
|
|
|
1160
1161
|
DOCUMENT_REFERENCE_CREATED = 150000;
|
|
1161
1162
|
DOCUMENT_REFERENCE_UPDATED = 150001;
|
|
1162
1163
|
DOCUMENT_REFERENCE_DELETED = 150002;
|
|
1164
|
+
|
|
1165
|
+
PANEL_SECTIONS_CONFIGURATION = 15100;
|
|
1166
|
+
|
|
1167
|
+
REVENUE__PAYMENT_PROCESSOR__LIST = 160001;
|
|
1168
|
+
REVENUE__PAYMENT_PROCESSOR__CHARGE = 160003;
|
|
1169
|
+
REVENUE__PAYMENT_PROCESSOR__SELECTED = 160002;
|
|
1170
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST = 160004;
|
|
1171
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD = 160005;
|
|
1172
|
+
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE = 160006;
|
|
1163
1173
|
}
|
|
1164
1174
|
|
|
1165
1175
|
message Event {
|
|
File without changes
|
|
File without changes
|