canvas 0.63.0__py3-none-any.whl → 0.89.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.
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/METADATA +4 -1
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/RECORD +184 -98
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/WHEEL +1 -1
- canvas_cli/apps/emit/event_fixtures/UNKNOWN.ndjson +1 -0
- canvas_cli/apps/logs/logs.py +386 -22
- canvas_cli/main.py +3 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +46 -4
- canvas_cli/utils/context/context.py +13 -13
- canvas_cli/utils/validators/manifest_schema.py +26 -1
- canvas_generated/messages/effects_pb2.py +5 -5
- canvas_generated/messages/effects_pb2.pyi +108 -2
- canvas_generated/messages/events_pb2.py +6 -6
- canvas_generated/messages/events_pb2.pyi +282 -2
- canvas_sdk/clients/__init__.py +1 -0
- canvas_sdk/clients/llms/__init__.py +17 -0
- canvas_sdk/clients/llms/libraries/__init__.py +11 -0
- canvas_sdk/clients/llms/libraries/llm_anthropic.py +87 -0
- canvas_sdk/clients/llms/libraries/llm_api.py +143 -0
- canvas_sdk/clients/llms/libraries/llm_google.py +92 -0
- canvas_sdk/clients/llms/libraries/llm_openai.py +98 -0
- canvas_sdk/clients/llms/structures/__init__.py +9 -0
- canvas_sdk/clients/llms/structures/llm_response.py +33 -0
- canvas_sdk/clients/llms/structures/llm_tokens.py +53 -0
- canvas_sdk/clients/llms/structures/llm_turn.py +47 -0
- canvas_sdk/clients/llms/structures/settings/__init__.py +13 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings.py +27 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_anthropic.py +43 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gemini.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt4.py +40 -0
- canvas_sdk/clients/llms/structures/settings/llm_settings_gpt5.py +48 -0
- canvas_sdk/clients/third_party.py +3 -0
- canvas_sdk/commands/__init__.py +12 -0
- canvas_sdk/commands/base.py +33 -2
- canvas_sdk/commands/commands/adjust_prescription.py +4 -0
- canvas_sdk/commands/commands/custom_command.py +86 -0
- canvas_sdk/commands/commands/family_history.py +17 -1
- canvas_sdk/commands/commands/immunization_statement.py +42 -2
- canvas_sdk/commands/commands/medication_statement.py +16 -1
- canvas_sdk/commands/commands/past_surgical_history.py +16 -1
- canvas_sdk/commands/commands/perform.py +18 -1
- canvas_sdk/commands/commands/prescribe.py +8 -9
- canvas_sdk/commands/commands/refill.py +5 -5
- canvas_sdk/commands/commands/resolve_condition.py +5 -5
- canvas_sdk/commands/commands/review/__init__.py +3 -0
- canvas_sdk/commands/commands/review/base.py +72 -0
- canvas_sdk/commands/commands/review/imaging.py +13 -0
- canvas_sdk/commands/commands/review/lab.py +13 -0
- canvas_sdk/commands/commands/review/referral.py +13 -0
- canvas_sdk/commands/commands/review/uncategorized_document.py +13 -0
- canvas_sdk/commands/validation.py +43 -0
- canvas_sdk/effects/batch_originate.py +22 -0
- canvas_sdk/effects/calendar/__init__.py +13 -3
- canvas_sdk/effects/calendar/{create_calendar.py → calendar.py} +19 -5
- canvas_sdk/effects/calendar/event.py +172 -0
- canvas_sdk/effects/claim_label.py +93 -0
- canvas_sdk/effects/claim_line_item.py +47 -0
- canvas_sdk/effects/claim_queue.py +49 -0
- canvas_sdk/effects/fax/__init__.py +3 -0
- canvas_sdk/effects/fax/base.py +77 -0
- canvas_sdk/effects/fax/note.py +42 -0
- canvas_sdk/effects/metadata.py +15 -1
- canvas_sdk/effects/note/__init__.py +8 -1
- canvas_sdk/effects/note/appointment.py +135 -7
- canvas_sdk/effects/note/base.py +17 -0
- canvas_sdk/effects/note/message.py +22 -14
- canvas_sdk/effects/note/note.py +150 -1
- canvas_sdk/effects/observation/__init__.py +11 -0
- canvas_sdk/effects/observation/base.py +206 -0
- canvas_sdk/effects/patient/__init__.py +2 -0
- canvas_sdk/effects/patient/base.py +8 -0
- canvas_sdk/effects/payment/__init__.py +11 -0
- canvas_sdk/effects/payment/base.py +355 -0
- canvas_sdk/effects/payment/post_claim_payment.py +49 -0
- canvas_sdk/effects/send_contact_verification.py +42 -0
- canvas_sdk/effects/task/__init__.py +2 -1
- canvas_sdk/effects/task/task.py +30 -0
- canvas_sdk/effects/validation/__init__.py +3 -0
- canvas_sdk/effects/validation/base.py +92 -0
- canvas_sdk/events/base.py +15 -0
- canvas_sdk/handlers/application.py +7 -7
- canvas_sdk/handlers/simple_api/api.py +1 -4
- canvas_sdk/handlers/simple_api/websocket.py +1 -4
- canvas_sdk/handlers/utils.py +14 -0
- canvas_sdk/questionnaires/utils.py +1 -0
- canvas_sdk/templates/utils.py +17 -4
- canvas_sdk/test_utils/factories/FACTORY_GUIDE.md +362 -0
- canvas_sdk/test_utils/factories/__init__.py +115 -0
- canvas_sdk/test_utils/factories/calendar.py +24 -0
- canvas_sdk/test_utils/factories/claim.py +81 -0
- canvas_sdk/test_utils/factories/claim_diagnosis_code.py +16 -0
- canvas_sdk/test_utils/factories/coverage.py +17 -0
- canvas_sdk/test_utils/factories/imaging.py +74 -0
- canvas_sdk/test_utils/factories/lab.py +192 -0
- canvas_sdk/test_utils/factories/medication_history.py +75 -0
- canvas_sdk/test_utils/factories/note.py +52 -0
- canvas_sdk/test_utils/factories/organization.py +50 -0
- canvas_sdk/test_utils/factories/practicelocation.py +88 -0
- canvas_sdk/test_utils/factories/referral.py +81 -0
- canvas_sdk/test_utils/factories/staff.py +111 -0
- canvas_sdk/test_utils/factories/task.py +66 -0
- canvas_sdk/test_utils/factories/uncategorized_clinical_document.py +48 -0
- canvas_sdk/utils/metrics.py +4 -1
- canvas_sdk/v1/data/__init__.py +66 -7
- canvas_sdk/v1/data/allergy_intolerance.py +5 -11
- canvas_sdk/v1/data/appointment.py +18 -4
- canvas_sdk/v1/data/assessment.py +2 -12
- canvas_sdk/v1/data/banner_alert.py +2 -4
- canvas_sdk/v1/data/base.py +53 -14
- canvas_sdk/v1/data/billing.py +8 -11
- canvas_sdk/v1/data/calendar.py +64 -0
- canvas_sdk/v1/data/care_team.py +4 -10
- canvas_sdk/v1/data/claim.py +172 -66
- canvas_sdk/v1/data/claim_diagnosis_code.py +19 -0
- canvas_sdk/v1/data/claim_line_item.py +2 -5
- canvas_sdk/v1/data/coding.py +19 -0
- canvas_sdk/v1/data/command.py +2 -4
- canvas_sdk/v1/data/common.py +10 -0
- canvas_sdk/v1/data/compound_medication.py +3 -4
- canvas_sdk/v1/data/condition.py +4 -9
- canvas_sdk/v1/data/coverage.py +66 -26
- canvas_sdk/v1/data/detected_issue.py +20 -20
- canvas_sdk/v1/data/device.py +2 -14
- canvas_sdk/v1/data/discount.py +2 -5
- canvas_sdk/v1/data/encounter.py +44 -0
- canvas_sdk/v1/data/facility.py +1 -0
- canvas_sdk/v1/data/goal.py +2 -14
- canvas_sdk/v1/data/imaging.py +4 -30
- canvas_sdk/v1/data/immunization.py +7 -15
- canvas_sdk/v1/data/lab.py +12 -65
- canvas_sdk/v1/data/line_item_transaction.py +2 -5
- canvas_sdk/v1/data/medication.py +3 -8
- canvas_sdk/v1/data/medication_history.py +142 -0
- canvas_sdk/v1/data/medication_statement.py +41 -0
- canvas_sdk/v1/data/message.py +4 -8
- canvas_sdk/v1/data/note.py +37 -38
- canvas_sdk/v1/data/observation.py +9 -36
- canvas_sdk/v1/data/organization.py +70 -9
- canvas_sdk/v1/data/patient.py +8 -12
- canvas_sdk/v1/data/patient_consent.py +4 -14
- canvas_sdk/v1/data/payment_collection.py +2 -5
- canvas_sdk/v1/data/posting.py +3 -9
- canvas_sdk/v1/data/practicelocation.py +66 -7
- canvas_sdk/v1/data/protocol_override.py +3 -4
- canvas_sdk/v1/data/protocol_result.py +3 -3
- canvas_sdk/v1/data/questionnaire.py +10 -26
- canvas_sdk/v1/data/reason_for_visit.py +2 -6
- canvas_sdk/v1/data/referral.py +41 -17
- canvas_sdk/v1/data/staff.py +34 -26
- canvas_sdk/v1/data/stop_medication_event.py +27 -0
- canvas_sdk/v1/data/task.py +30 -11
- canvas_sdk/v1/data/team.py +2 -4
- canvas_sdk/v1/data/uncategorized_clinical_document.py +84 -0
- canvas_sdk/v1/data/user.py +14 -0
- canvas_sdk/v1/data/utils.py +5 -0
- canvas_sdk/value_set/v2026/__init__.py +1 -0
- canvas_sdk/value_set/v2026/adverse_event.py +157 -0
- canvas_sdk/value_set/v2026/allergy.py +116 -0
- canvas_sdk/value_set/v2026/assessment.py +466 -0
- canvas_sdk/value_set/v2026/communication.py +496 -0
- canvas_sdk/value_set/v2026/condition.py +52934 -0
- canvas_sdk/value_set/v2026/device.py +315 -0
- canvas_sdk/value_set/v2026/diagnostic_study.py +5243 -0
- canvas_sdk/value_set/v2026/encounter.py +2714 -0
- canvas_sdk/value_set/v2026/immunization.py +297 -0
- canvas_sdk/value_set/v2026/individual_characteristic.py +339 -0
- canvas_sdk/value_set/v2026/intervention.py +1703 -0
- canvas_sdk/value_set/v2026/laboratory_test.py +1831 -0
- canvas_sdk/value_set/v2026/medication.py +8218 -0
- canvas_sdk/value_set/v2026/no_qdm_category_assigned.py +26493 -0
- canvas_sdk/value_set/v2026/physical_exam.py +342 -0
- canvas_sdk/value_set/v2026/procedure.py +27869 -0
- canvas_sdk/value_set/v2026/symptom.py +625 -0
- logger/logger.py +30 -31
- logger/logstash.py +282 -0
- logger/pubsub.py +26 -0
- plugin_runner/allowed-module-imports.json +940 -9
- plugin_runner/generate_allowed_imports.py +1 -0
- plugin_runner/installation.py +2 -2
- plugin_runner/plugin_runner.py +21 -24
- plugin_runner/sandbox.py +34 -0
- protobufs/canvas_generated/messages/effects.proto +65 -0
- protobufs/canvas_generated/messages/events.proto +150 -51
- settings.py +27 -11
- canvas_sdk/effects/calendar/create_event.py +0 -43
- {canvas-0.63.0.dist-info → canvas-0.89.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
|
|
4
|
+
from pydantic_core import InitErrorDetails
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.effects.base import EffectType, _BaseEffect
|
|
7
|
+
from canvas_sdk.v1.data import PatientContactPoint
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SendContactVerificationEffect(_BaseEffect):
|
|
11
|
+
"""
|
|
12
|
+
An Effect that will send a verification for a Patient Contact Point.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
class Meta:
|
|
16
|
+
effect_type = EffectType.PATIENT_PORTAL__SEND_CONTACT_VERIFICATION
|
|
17
|
+
|
|
18
|
+
contact_point_id: str | UUID
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def values(self) -> dict[str, Any]:
|
|
22
|
+
"""The contact point id."""
|
|
23
|
+
return {"contact_point_id": self.contact_point_id}
|
|
24
|
+
|
|
25
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
26
|
+
errors = super()._get_error_details(method)
|
|
27
|
+
|
|
28
|
+
contact_point_exists = PatientContactPoint.objects.filter(id=self.contact_point_id).exists()
|
|
29
|
+
|
|
30
|
+
if not contact_point_exists:
|
|
31
|
+
errors.append(
|
|
32
|
+
self._create_error_detail(
|
|
33
|
+
"value",
|
|
34
|
+
"Patient Contact Point does not exist",
|
|
35
|
+
self.contact_point_id,
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return errors
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__exports__ = ("SendContactVerificationEffect",)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from .task import AddTask, AddTaskComment, TaskStatus, UpdateTask
|
|
1
|
+
from .task import AddTask, AddTaskComment, TaskMetadata, TaskStatus, UpdateTask
|
|
2
2
|
|
|
3
3
|
__all__ = __exports__ = (
|
|
4
4
|
"AddTask",
|
|
5
5
|
"AddTaskComment",
|
|
6
6
|
"TaskStatus",
|
|
7
|
+
"TaskMetadata",
|
|
7
8
|
"UpdateTask",
|
|
8
9
|
)
|
canvas_sdk/effects/task/task.py
CHANGED
|
@@ -4,8 +4,11 @@ from typing import Any, Self, cast
|
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from pydantic import model_validator
|
|
7
|
+
from pydantic_core import InitErrorDetails
|
|
7
8
|
|
|
8
9
|
from canvas_sdk.effects.base import EffectType, _BaseEffect
|
|
10
|
+
from canvas_sdk.effects.metadata import BaseMetadata
|
|
11
|
+
from canvas_sdk.v1.data import Task
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class TaskStatus(Enum):
|
|
@@ -140,9 +143,36 @@ class UpdateTask(_BaseEffect):
|
|
|
140
143
|
return value_dict
|
|
141
144
|
|
|
142
145
|
|
|
146
|
+
class TaskMetadata(BaseMetadata):
|
|
147
|
+
"""Task Metadata."""
|
|
148
|
+
|
|
149
|
+
class Meta:
|
|
150
|
+
effect_type = "TASK_METADATA"
|
|
151
|
+
|
|
152
|
+
task_id: str
|
|
153
|
+
|
|
154
|
+
def _get_error_details(self, method: Any) -> list[InitErrorDetails]:
|
|
155
|
+
"""Get the error details for the effect.
|
|
156
|
+
If task_id is not found, return an error detail.
|
|
157
|
+
"""
|
|
158
|
+
errors = super()._get_error_details(method)
|
|
159
|
+
|
|
160
|
+
if not Task.objects.filter(id=self.task_id).exists():
|
|
161
|
+
errors.append(
|
|
162
|
+
self._create_error_detail(
|
|
163
|
+
"task_id",
|
|
164
|
+
f"Task with id: {self.task_id} does not exist.",
|
|
165
|
+
self.task_id,
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return errors
|
|
170
|
+
|
|
171
|
+
|
|
143
172
|
__exports__ = (
|
|
144
173
|
"AddTask",
|
|
145
174
|
"AddTaskComment",
|
|
146
175
|
"TaskStatus",
|
|
176
|
+
"TaskMetadata",
|
|
147
177
|
"UpdateTask",
|
|
148
178
|
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Self
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.effects import EffectType
|
|
7
|
+
from canvas_sdk.effects.base import _BaseEffect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ValidationError:
|
|
12
|
+
"""
|
|
13
|
+
Represents a validation error for an effect or command.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
message: The validation error message to display
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
message: str
|
|
20
|
+
|
|
21
|
+
def __post_init__(self) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Validate and normalize the error message after initialization.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: If message is empty
|
|
27
|
+
"""
|
|
28
|
+
if not self.message or not self.message.strip():
|
|
29
|
+
raise ValueError("Error message cannot be empty")
|
|
30
|
+
|
|
31
|
+
self.message = self.message.strip()
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> dict[str, str]:
|
|
34
|
+
"""Convert the validation error to a dictionary."""
|
|
35
|
+
return {"message": self.message}
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"ValidationError(message={self.message!r})"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _BaseValidationErrorEffect(_BaseEffect):
|
|
42
|
+
"""
|
|
43
|
+
Abstract effect to abort an event.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
class Meta:
|
|
47
|
+
effect_type = EffectType.UNKNOWN_EFFECT
|
|
48
|
+
|
|
49
|
+
errors: list[ValidationError] = Field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
def add_error(self, message: str | ValidationError) -> Self:
|
|
52
|
+
"""
|
|
53
|
+
Add a validation error to the effect.
|
|
54
|
+
|
|
55
|
+
This method allows incremental building of validation errors
|
|
56
|
+
and supports method chaining.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
message: The error message to display, or a ValidationError object
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Self for method chaining
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If message is empty
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
effect = CommandValidationErrorEffect()
|
|
69
|
+
effect.add_error("Narrative is required")
|
|
70
|
+
effect.add_error("Dosage must be positive")
|
|
71
|
+
|
|
72
|
+
# Using ValidationError objects
|
|
73
|
+
error = ValidationError("Already validated error")
|
|
74
|
+
effect.add_error(error)
|
|
75
|
+
|
|
76
|
+
# Method chaining
|
|
77
|
+
effect.add_error("Error 1").add_error("Error 2")
|
|
78
|
+
"""
|
|
79
|
+
if isinstance(message, ValidationError):
|
|
80
|
+
self.errors.append(message)
|
|
81
|
+
else:
|
|
82
|
+
error = ValidationError(message=message)
|
|
83
|
+
self.errors.append(error)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def values(self) -> dict[str, Any]:
|
|
88
|
+
"""Payload to include in the Effect."""
|
|
89
|
+
return {"errors": [error.to_dict() for error in self.errors]}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__exports__ = ("ValidationError",)
|
canvas_sdk/events/base.py
CHANGED
|
@@ -8,6 +8,7 @@ from django.db import models
|
|
|
8
8
|
|
|
9
9
|
from canvas_generated.messages.events_pb2 import Event as EventRequest
|
|
10
10
|
from canvas_generated.messages.events_pb2 import EventType
|
|
11
|
+
from canvas_sdk.v1.data import CanvasUser
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclasses.dataclass
|
|
@@ -23,6 +24,18 @@ class TargetType:
|
|
|
23
24
|
return self.type._default_manager.filter(id=self.id).first() if self.type else None
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
@dataclasses.dataclass
|
|
28
|
+
class Actor:
|
|
29
|
+
"""The actor that triggered the event."""
|
|
30
|
+
|
|
31
|
+
id: str | None
|
|
32
|
+
|
|
33
|
+
@cached_property
|
|
34
|
+
def instance(self) -> CanvasUser | None:
|
|
35
|
+
"""Return the instance of the actor."""
|
|
36
|
+
return CanvasUser._default_manager.filter(dbid=self.id).first() if self.id else None
|
|
37
|
+
|
|
38
|
+
|
|
26
39
|
class Event:
|
|
27
40
|
"""An event that occurs in the Canvas environment."""
|
|
28
41
|
|
|
@@ -46,6 +59,8 @@ class Event:
|
|
|
46
59
|
self.name = EventType.Name(self.type)
|
|
47
60
|
self.context = context
|
|
48
61
|
self.target = TargetType(id=event_request.target, type=target_model)
|
|
62
|
+
self.actor = Actor(id=event_request.actor)
|
|
63
|
+
self.source = event_request.source
|
|
49
64
|
|
|
50
65
|
|
|
51
66
|
__exports__ = ("TargetType", "Event")
|
|
@@ -3,6 +3,7 @@ from abc import ABC, abstractmethod
|
|
|
3
3
|
from canvas_sdk.effects import Effect
|
|
4
4
|
from canvas_sdk.events import EventType
|
|
5
5
|
from canvas_sdk.handlers import BaseHandler
|
|
6
|
+
from canvas_sdk.handlers.utils import normalize_effects
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class Application(BaseHandler, ABC):
|
|
@@ -15,23 +16,22 @@ class Application(BaseHandler, ABC):
|
|
|
15
16
|
|
|
16
17
|
def compute(self) -> list[Effect]:
|
|
17
18
|
"""Handle the application events."""
|
|
19
|
+
if self.event.target.id != self.identifier:
|
|
20
|
+
return []
|
|
18
21
|
match self.event.type:
|
|
19
22
|
case EventType.APPLICATION__ON_OPEN:
|
|
20
|
-
return
|
|
23
|
+
return normalize_effects(self.on_open())
|
|
21
24
|
case EventType.APPLICATION__ON_CONTEXT_CHANGE:
|
|
22
|
-
|
|
23
|
-
effect = self.on_context_change()
|
|
24
|
-
return [effect] if effect is not None else []
|
|
25
|
-
return []
|
|
25
|
+
return normalize_effects(self.on_context_change())
|
|
26
26
|
case _:
|
|
27
27
|
return []
|
|
28
28
|
|
|
29
29
|
@abstractmethod
|
|
30
|
-
def on_open(self) -> Effect:
|
|
30
|
+
def on_open(self) -> Effect | list[Effect]:
|
|
31
31
|
"""Handle the application open event."""
|
|
32
32
|
...
|
|
33
33
|
|
|
34
|
-
def on_context_change(self) -> Effect | None:
|
|
34
|
+
def on_context_change(self) -> Effect | list[Effect] | None:
|
|
35
35
|
"""Handle the application context change event."""
|
|
36
36
|
return None
|
|
37
37
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import json
|
|
3
3
|
import re
|
|
4
|
-
import traceback
|
|
5
4
|
from abc import ABC
|
|
6
5
|
from base64 import b64decode
|
|
7
6
|
from collections.abc import Callable
|
|
@@ -323,9 +322,7 @@ class SimpleAPIBase(BaseHandler, ABC):
|
|
|
323
322
|
else:
|
|
324
323
|
raise AssertionError(f"Cannot handle event type {EventType.Name(self.event.type)}")
|
|
325
324
|
except Exception as exception:
|
|
326
|
-
|
|
327
|
-
for error_line in error_line_with_newlines.split("\n"):
|
|
328
|
-
log.error(error_line)
|
|
325
|
+
log.exception(f"Error handling '{EventType.Name(self.event.type)}' event")
|
|
329
326
|
|
|
330
327
|
sentry_sdk.capture_exception(exception)
|
|
331
328
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import traceback
|
|
2
1
|
from abc import ABC
|
|
3
2
|
from functools import cached_property
|
|
4
3
|
from typing import ClassVar
|
|
@@ -52,9 +51,7 @@ class WebSocketAPI(BaseHandler, ABC):
|
|
|
52
51
|
else:
|
|
53
52
|
raise AssertionError(f"Cannot handle event type {EventType.Name(self.event.type)}")
|
|
54
53
|
except Exception as exception:
|
|
55
|
-
|
|
56
|
-
for error_line in error_line_with_newlines.split("\n"):
|
|
57
|
-
log.error(error_line)
|
|
54
|
+
log.exception(f"Error handling '{EventType.Name(self.event.type)}' event")
|
|
58
55
|
|
|
59
56
|
sentry_sdk.capture_exception(exception)
|
|
60
57
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from canvas_sdk.effects import Effect
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def normalize_effects(effects: Effect | list[Effect] | None) -> list[Effect]:
|
|
5
|
+
"""Normalize effects to a list of Effect instances."""
|
|
6
|
+
if effects is None:
|
|
7
|
+
return []
|
|
8
|
+
if isinstance(effects, Effect):
|
|
9
|
+
return [effects]
|
|
10
|
+
if isinstance(effects, list):
|
|
11
|
+
return [e for e in effects if isinstance(e, Effect)]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__exports__ = ()
|
canvas_sdk/templates/utils.py
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
from functools import cache, lru_cache
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
|
-
from django.template import
|
|
5
|
+
from django.template.backends.django import get_installed_libraries
|
|
6
|
+
from django.template.engine import Engine
|
|
5
7
|
|
|
6
8
|
from canvas_sdk.utils.plugins import plugin_context
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
@cache
|
|
12
|
+
def _installed_template_libraries() -> dict[str, str]:
|
|
13
|
+
"""Cache Django's template tag libraries lookup."""
|
|
14
|
+
return get_installed_libraries()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@lru_cache(maxsize=5)
|
|
18
|
+
def _engine_for_plugin(plugin_dir: str) -> Engine:
|
|
19
|
+
"""Create a Django template engine for the given plugin directory."""
|
|
20
|
+
return Engine(dirs=[plugin_dir], libraries=_installed_template_libraries())
|
|
21
|
+
|
|
22
|
+
|
|
9
23
|
@plugin_context
|
|
10
24
|
def render_to_string(
|
|
11
25
|
template_name: str,
|
|
@@ -36,9 +50,8 @@ def render_to_string(
|
|
|
36
50
|
elif not template_path.exists():
|
|
37
51
|
raise FileNotFoundError(f"Template {template_name} not found.")
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return template.render(Context(context))
|
|
53
|
+
engine = _engine_for_plugin(plugin_dir)
|
|
54
|
+
return engine.render_to_string(str(template_path), context=context)
|
|
42
55
|
|
|
43
56
|
|
|
44
57
|
__exports__ = ("render_to_string",)
|