canvas 0.3.1__py3-none-any.whl → 0.4.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.

Files changed (36) hide show
  1. {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/METADATA +2 -1
  2. {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/RECORD +36 -34
  3. canvas_cli/apps/emit/emit.py +1 -1
  4. canvas_cli/apps/logs/logs.py +6 -6
  5. canvas_cli/apps/plugin/plugin.py +11 -7
  6. canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py +1 -1
  7. canvas_cli/tests.py +12 -5
  8. canvas_cli/utils/context/context.py +2 -2
  9. canvas_cli/utils/context/tests.py +5 -4
  10. canvas_cli/utils/print/print.py +1 -1
  11. canvas_cli/utils/print/tests.py +2 -3
  12. canvas_generated/messages/events_pb2.py +2 -2
  13. canvas_generated/messages/events_pb2.pyi +4 -0
  14. canvas_sdk/base.py +2 -1
  15. canvas_sdk/commands/base.py +25 -25
  16. canvas_sdk/commands/tests/protocol/tests.py +5 -3
  17. canvas_sdk/commands/tests/test_utils.py +8 -44
  18. canvas_sdk/commands/tests/unit/tests.py +3 -3
  19. canvas_sdk/data/client.py +1 -1
  20. canvas_sdk/effects/banner_alert/tests.py +12 -4
  21. canvas_sdk/effects/protocol_card/protocol_card.py +1 -1
  22. canvas_sdk/effects/protocol_card/tests.py +2 -2
  23. canvas_sdk/protocols/clinical_quality_measure.py +1 -0
  24. canvas_sdk/utils/http.py +2 -2
  25. canvas_sdk/v1/data/common.py +46 -0
  26. canvas_sdk/v1/data/imaging.py +102 -0
  27. canvas_sdk/v1/data/lab.py +182 -10
  28. canvas_sdk/v1/data/patient.py +4 -1
  29. canvas_sdk/v1/data/questionnaire.py +4 -2
  30. canvas_sdk/value_set/tests/test_value_sets.py +9 -6
  31. canvas_sdk/value_set/v2022/intervention.py +0 -24
  32. canvas_sdk/value_set/value_set.py +24 -21
  33. plugin_runner/plugin_runner.py +59 -8
  34. plugin_runner/sandbox.py +1 -1
  35. {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/WHEEL +0 -0
  36. {canvas-0.3.1.dist-info → canvas-0.4.0.dist-info}/entry_points.txt +0 -0
canvas_sdk/v1/data/lab.py CHANGED
@@ -1,10 +1,21 @@
1
1
  from django.db import models
2
2
 
3
3
  from canvas_sdk.v1.data.base import CommittableModelManager
4
+ from canvas_sdk.v1.data.condition import Condition
4
5
  from canvas_sdk.v1.data.patient import Patient
6
+
7
+ # from canvas_sdk.v1.data.staff import Staff
5
8
  from canvas_sdk.v1.data.user import CanvasUser
6
9
 
7
10
 
11
+ class TransmissionType(models.TextChoices):
12
+ """Choices for transmission types."""
13
+
14
+ FAX = "F", "fax"
15
+ HL7 = "H", "hl7"
16
+ MANUAL = "M", "manual"
17
+
18
+
8
19
  class LabReport(models.Model):
9
20
  """A class representing a lab report."""
10
21
 
@@ -23,19 +34,23 @@ class LabReport(models.Model):
23
34
  junked = models.BooleanField()
24
35
  requires_signature = models.BooleanField()
25
36
  assigned_date = models.DateTimeField()
26
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_reports")
27
- transmission_type = models.CharField()
37
+ patient = models.ForeignKey(
38
+ Patient, on_delete=models.DO_NOTHING, related_name="lab_reports", null=True
39
+ )
40
+ transmission_type = models.CharField(choices=TransmissionType)
28
41
  for_test_only = models.BooleanField()
29
42
  external_id = models.CharField()
30
43
  version = models.IntegerField()
31
44
  requisition_number = models.CharField()
32
- review = models.ForeignKey("LabReview", related_name="reports", on_delete=models.DO_NOTHING)
45
+ review = models.ForeignKey(
46
+ "LabReview", related_name="reports", on_delete=models.DO_NOTHING, null=True
47
+ )
33
48
  original_date = models.DateTimeField()
34
49
  date_performed = models.DateTimeField()
35
50
  custom_document_name = models.CharField()
36
51
  originator = 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)
52
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
53
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
39
54
  deleted = models.BooleanField()
40
55
 
41
56
 
@@ -55,12 +70,12 @@ class LabReview(models.Model):
55
70
  modified = models.DateTimeField()
56
71
  originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
57
72
  deleted = models.BooleanField()
58
- committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
59
- entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
73
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
74
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
60
75
  internal_comment = models.TextField()
61
76
  message_to_patient = models.CharField()
62
77
  status = models.CharField()
63
- patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING)
78
+ patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_reviews")
64
79
  patient_communication_method = models.CharField()
65
80
 
66
81
 
@@ -76,7 +91,9 @@ class LabValue(models.Model):
76
91
  dbid = models.BigIntegerField(primary_key=True)
77
92
  created = models.DateTimeField()
78
93
  modified = models.DateTimeField()
79
- report = models.ForeignKey("LabReport", related_name="values", on_delete=models.DO_NOTHING)
94
+ report = models.ForeignKey(
95
+ "LabReport", related_name="values", on_delete=models.DO_NOTHING, null=True
96
+ )
80
97
  value = models.TextField()
81
98
  units = models.CharField()
82
99
  abnormal_flag = models.CharField()
@@ -98,7 +115,162 @@ class LabValueCoding(models.Model):
98
115
  dbid = models.BigIntegerField(primary_key=True)
99
116
  created = models.DateTimeField()
100
117
  modified = models.DateTimeField()
101
- value = models.ForeignKey(LabValue, on_delete=models.DO_NOTHING, related_name="codings")
118
+ value = models.ForeignKey(
119
+ LabValue, on_delete=models.DO_NOTHING, related_name="codings", null=True
120
+ )
102
121
  code = models.CharField()
103
122
  name = models.CharField()
104
123
  system = models.CharField()
124
+
125
+
126
+ class LabOrder(models.Model):
127
+ """A class representing a lab order."""
128
+
129
+ class SpecimenCollectionType(models.TextChoices):
130
+ """Choices for specimen collection types."""
131
+
132
+ ON_LOCATION = "L", "on location"
133
+ PATIENT_SERVICE_CENTER = "P", "patient service center"
134
+ OTHER = "O", "other"
135
+
136
+ class CourtesyCopyType(models.TextChoices):
137
+ """Choices for courtesy copy types."""
138
+
139
+ ACCOUNT = "A", "account"
140
+ FAX = "F", "fax"
141
+ PATIENT = "P", "patient"
142
+
143
+ class ManualProcessingStatus(models.TextChoices):
144
+ """Choices for manual processing statuses."""
145
+
146
+ MANUAL_PROCESSING_STATUS_NEEDS_REVIEW = "NEEDS_REVIEW", "Needs Review"
147
+ MANUAL_PROCESSING_STATUS_IN_PROGRESS = "IN_PROGRESS", "In Progress"
148
+ MANUAL_PROCESSING_STATUS_PROCESSED = "PROCESSED", "Processed"
149
+ MANUAL_PROCESSING_STATUS_FLAGGED = "FLAGGED", "Flagged"
150
+
151
+ class Meta:
152
+ managed = False
153
+ app_label = "canvas_sdk"
154
+ db_table = "canvas_sdk_data_api_laborder_001"
155
+
156
+ id = models.UUIDField()
157
+ dbid = models.BigIntegerField(primary_key=True)
158
+ created = models.DateTimeField()
159
+ modified = models.DateTimeField()
160
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
161
+ deleted = models.BooleanField()
162
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
163
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING, null=True)
164
+ patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="lab_orders")
165
+ ontology_lab_partner = models.CharField()
166
+ # TODO - uncomment when the Note model is finished
167
+ # note = models.ForeignKey("Note", on_delete=models.DO_NOTHING)
168
+ comment = models.CharField()
169
+ requisition_number = models.CharField()
170
+ is_patient_bill = models.BooleanField(null=True)
171
+ date_ordered = models.DateTimeField()
172
+ fasting_status = models.BooleanField(null=True)
173
+ specimen_collection_type = models.CharField(choices=SpecimenCollectionType, null=True)
174
+ transmission_type = models.CharField(choices=TransmissionType, null=True)
175
+ courtesy_copy_type = models.CharField(choices=CourtesyCopyType, null=True)
176
+ courtesy_copy_number = models.CharField()
177
+ courtesy_copy_text = models.CharField()
178
+ # TODO - uncomment when Staff model is added
179
+ # ordering_provider = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, null=True)
180
+ parent_order = models.ForeignKey("LabOrder", on_delete=models.DO_NOTHING, null=True)
181
+ healthgorilla_id = models.CharField()
182
+ manual_processing_status = models.CharField(choices=ManualProcessingStatus)
183
+ manual_processing_comment = models.CharField()
184
+ labcorp_abn_url = models.URLField()
185
+
186
+
187
+ class LabOrderReason(models.Model):
188
+ """A class representing a lab order reason."""
189
+
190
+ class LabReasonMode(models.TextChoices):
191
+ """Choices for lab order reasons."""
192
+
193
+ MONITOR = "MO", "monitor"
194
+ INVESTIGATE = "IN", "investigate"
195
+ SCREEN_FOR = "SF", "screen for"
196
+ UNKNOWN = "UNK", "unknown"
197
+
198
+ class Meta:
199
+ managed = False
200
+ app_label = "canvas_sdk"
201
+ db_table = "canvas_sdk_data_api_laborderreason_001"
202
+
203
+ dbid = models.BigIntegerField(primary_key=True)
204
+ created = models.DateTimeField()
205
+ modified = models.DateTimeField()
206
+ originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
207
+ deleted = models.BooleanField()
208
+ committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
209
+ entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
210
+ order = models.ForeignKey(LabOrder, on_delete=models.DO_NOTHING, related_name="reasons")
211
+ mode = models.CharField(max_length=30, choices=LabReasonMode)
212
+
213
+
214
+ class LabOrderReasonCondition(models.Model):
215
+ """A class representing a lab order reason's condition."""
216
+
217
+ class Meta:
218
+ managed = False
219
+ app_label = "canvas_sdk"
220
+ db_table = "canvas_sdk_data_api_laborderreasoncondition_001"
221
+
222
+ dbid = models.BigIntegerField(primary_key=True)
223
+ created = models.DateTimeField()
224
+ modified = models.DateTimeField()
225
+ reason = models.ForeignKey(
226
+ LabOrderReason, on_delete=models.DO_NOTHING, related_name="reason_conditions", null=True
227
+ )
228
+ condition = models.ForeignKey(
229
+ Condition,
230
+ on_delete=models.DO_NOTHING,
231
+ related_name="lab_order_reason_conditions",
232
+ null=True,
233
+ )
234
+
235
+
236
+ class LabTest(models.Model):
237
+ """A class representing a lab test."""
238
+
239
+ class LabTestOrderStatus(models.TextChoices):
240
+ """Choicees for lab test order statuses."""
241
+
242
+ NEW = "NE", "new"
243
+ STAGED_FOR_REQUISITION = "SR", "staged for requisition"
244
+ SENDING = "SE", "sending"
245
+ SENDING_FAILED = "SF", "sending failed"
246
+ PROCESSING = "PR", "processing"
247
+ PROCESSING_FAILED = "PF", "processing failed"
248
+ RECEIVED = "RE", "received"
249
+ REVIEWED = "RV", "reviewed"
250
+ INACTIVE = "IN", "inactive"
251
+
252
+ class Meta:
253
+ managed = False
254
+ app_label = "canvas_sdk"
255
+ db_table = "canvas_sdk_data_api_labtest_001"
256
+
257
+ id = models.UUIDField()
258
+ dbid = models.BigIntegerField(primary_key=True)
259
+ ontology_test_name = models.CharField(max_length=512, blank=True, default="")
260
+ ontology_test_code = models.CharField(max_length=512, blank=True, default="")
261
+ status = models.CharField(max_length=30, choices=LabTestOrderStatus)
262
+ report = models.ForeignKey(
263
+ LabReport, on_delete=models.DO_NOTHING, null=True, related_name="tests"
264
+ )
265
+ aoe_code = models.CharField(max_length=10, default="")
266
+ procedure_class = models.CharField(max_length=10, default="")
267
+ specimen_type = models.CharField()
268
+ specimen_source_code = models.CharField()
269
+ specimen_source_description = models.CharField()
270
+ specimen_source_coding_system = models.CharField()
271
+ order = models.ForeignKey(
272
+ LabOrder, on_delete=models.DO_NOTHING, related_name="tests", null=True
273
+ )
274
+
275
+ def __str__(self) -> str:
276
+ return f"{self.ontology_test_name}: f{self.ontology_test_code}"
@@ -49,4 +49,7 @@ class Patient(models.Model):
49
49
  @classmethod
50
50
  def find(cls, id: str) -> Self:
51
51
  """Find a patient by id."""
52
- return cls.objects.get(id=id)
52
+ return cls._default_manager.get(id=id)
53
+
54
+ def __str__(self) -> str:
55
+ return f"{self.first_name} {self.last_name}"
@@ -109,7 +109,7 @@ class Questionnaire(models.Model):
109
109
  code_system = models.CharField()
110
110
  code = models.CharField()
111
111
  search_tags = models.CharField()
112
- questions = models.ManyToManyField(Question, through="QuestionnaireQuestionMap")
112
+ questions = models.ManyToManyField(Question, through="canvas_sdk.QuestionnaireQuestionMap") # type: ignore[misc, var-annotated]
113
113
  use_in_shx = models.BooleanField()
114
114
  carry_forward = models.TextField()
115
115
 
@@ -152,7 +152,9 @@ class Interview(models.Model):
152
152
  patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="interviews")
153
153
  note_id = models.BigIntegerField()
154
154
  appointment_id = models.BigIntegerField()
155
- questionnaires = models.ManyToManyField(Questionnaire, through="InterviewQuestionnaireMap")
155
+ questionnaires = models.ManyToManyField( # type: ignore[var-annotated]
156
+ Questionnaire, through="canvas_sdk.InterviewQuestionnaireMap" # type: ignore[misc]
157
+ )
156
158
  progress_status = models.CharField()
157
159
  created = models.DateTimeField()
158
160
  modified = models.DateTimeField()
@@ -1,11 +1,10 @@
1
- import pytest
2
-
3
1
  from canvas_sdk.value_set.v2022.condition import (
4
2
  DisordersOfTheImmuneSystem,
5
3
  EncephalopathyDueToChildhoodVaccination,
6
4
  Rhabdomyolysis,
7
5
  StableAndUnstableAngina,
8
6
  )
7
+ from canvas_sdk.value_set.value_set import CombinedValueSet
9
8
 
10
9
 
11
10
  def test_value_set_class_values_property() -> None:
@@ -15,7 +14,9 @@ def test_value_set_class_values_property() -> None:
15
14
 
16
15
 
17
16
  def test_value_set_class_pipe_operator_with_two_value_sets() -> None:
18
- combined_value_set = DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
17
+ combined_value_set: CombinedValueSet = (
18
+ DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
19
+ )
19
20
 
20
21
  both_classes_icd_10_codes = DisordersOfTheImmuneSystem.ICD10CM.union(
21
22
  EncephalopathyDueToChildhoodVaccination.ICD10CM
@@ -29,7 +30,7 @@ def test_value_set_class_pipe_operator_with_two_value_sets() -> None:
29
30
 
30
31
 
31
32
  def test_value_set_class_pipe_operator_with_three_value_sets() -> None:
32
- combined_value_set = (
33
+ combined_value_set: CombinedValueSet = (
33
34
  DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination | Rhabdomyolysis
34
35
  )
35
36
 
@@ -45,8 +46,10 @@ def test_value_set_class_pipe_operator_with_three_value_sets() -> None:
45
46
 
46
47
 
47
48
  def test_value_set_class_pipe_operator_with_two_combined_value_sets() -> None:
48
- combined_value_set_1 = DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
49
- combined_value_set_2 = Rhabdomyolysis | StableAndUnstableAngina
49
+ combined_value_set_1: CombinedValueSet = (
50
+ DisordersOfTheImmuneSystem | EncephalopathyDueToChildhoodVaccination
51
+ )
52
+ combined_value_set_2: CombinedValueSet = Rhabdomyolysis | StableAndUnstableAngina
50
53
 
51
54
  combined_value_set = combined_value_set_1 | combined_value_set_2
52
55
 
@@ -1,30 +1,6 @@
1
1
  from ..value_set import ValueSet
2
2
 
3
3
 
4
- class HospiceCareAmbulatory(ValueSet):
5
- """
6
- **Clinical Focus:** The purpose of this value set is to represent concepts of interventions to identify patients receiving hospice care outside of a hospital or long term care facility.
7
-
8
- **Data Element Scope:** This value set may use a model element related to Procedure or Intervention.
9
-
10
- **Inclusion Criteria:** Includes concepts that represent a procedure or intervention for hospice care.
11
-
12
- **Exclusion Criteria:** Excludes concepts that represent palliative care or comfort measures.
13
-
14
- ** Used in:** CMS90v11, CMS134v10, CMS165v10, CMS146v10, CMS124v10, CMS139v10, CMS154v10, CMS56v10, CMS74v11, CMS75v10, CMS137v10, CMS136v11, CMS128v10, CMS122v10, CMS153v10, CMS66v10, CMS130v10, CMS155v10, CMS127v10, CMS117v10, CMS131v10, CMS156v10, CMS125v10
15
- """
16
-
17
- VALUE_SET_NAME = "Hospice care ambulatory"
18
- OID = "2.16.840.1.113762.1.4.1108.15"
19
- DEFINITION_VERSION = "20170504"
20
- EXPANSION_VERSION = "eCQM Update 2021-05-06"
21
-
22
- SNOMEDCT = {
23
- "385763009", # Hospice care (regime/therapy)
24
- "385765002", # Hospice care management (procedure)
25
- }
26
-
27
-
28
4
  class PalliativeCareIntervention(ValueSet):
29
5
  """
30
6
  **Clinical Focus:** The purpose of this value set is to represent concepts for palliative care interventions.
@@ -1,5 +1,7 @@
1
1
  from collections import defaultdict
2
- from typing import Dict, Union, cast
2
+ from typing import Union, cast
3
+
4
+ from django.utils.functional import classproperty
3
5
 
4
6
 
5
7
  class CodeConstants:
@@ -33,7 +35,7 @@ class CodeConstants:
33
35
  URL_NDC = "http://hl7.org/fhir/sid/ndc"
34
36
 
35
37
 
36
- class CodeConstantsURLMapping:
38
+ class CodeConstantsURLMappingMixin:
37
39
  """A class that maps code systems to their URLs."""
38
40
 
39
41
  CODE_SYSTEM_MAPPING = {
@@ -53,21 +55,21 @@ class CodeConstantsURLMapping:
53
55
  }
54
56
 
55
57
 
56
- class CombinedValueSet(CodeConstantsURLMapping):
58
+ class CombinedValueSet(CodeConstantsURLMappingMixin):
57
59
  """A class representing a combination of two value sets."""
58
60
 
59
61
  def __init__(
60
62
  self,
61
- value_set_1: Union["ValueSet", "CombinedValueSet"],
62
- value_set_2: Union["ValueSet", "CombinedValueSet"],
63
+ value_set_1: Union[type["ValueSet"], "CombinedValueSet"],
64
+ value_set_2: Union[type["ValueSet"], "CombinedValueSet"],
63
65
  ) -> None:
64
66
  self.value_set_1 = value_set_1
65
67
  self.value_set_2 = value_set_2
66
68
 
67
69
  @property
68
- def values(self) -> Dict[str, set]:
70
+ def values(self) -> dict[str, set]:
69
71
  """A property that returns the combined values from both value sets."""
70
- values: Dict[str, set] = defaultdict(set)
72
+ values: dict[str, set] = defaultdict(set)
71
73
 
72
74
  for vs in [self.value_set_1, self.value_set_2]:
73
75
  sub_values = vs.values
@@ -77,7 +79,7 @@ class CombinedValueSet(CodeConstantsURLMapping):
77
79
 
78
80
  return values
79
81
 
80
- def __or__(self, value_set: Union["ValueSet", "CombinedValueSet"]) -> "CombinedValueSet":
82
+ def __or__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet":
81
83
  """Implements the `|` (or) operator to combine value sets."""
82
84
  return CombinedValueSet(self, value_set)
83
85
 
@@ -85,22 +87,23 @@ class CombinedValueSet(CodeConstantsURLMapping):
85
87
  class ValueSystems(type):
86
88
  """A metaclass for defining a ValueSet."""
87
89
 
88
- @property
89
- def values(cls) -> dict[str, set]:
90
- """A property that returns a dictionary of code systems and their associated values."""
91
- return {
92
- system: getattr(cls, system)
93
- for system in cast(ValueSet, cls).CODE_SYSTEM_MAPPING.keys()
94
- if hasattr(cls, system)
95
- }
90
+ def __or__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet": # type: ignore[override]
91
+ """Implements the `|` (or) operator."""
92
+ return CombinedValueSet(cast(type["ValueSet"], self), value_set)
96
93
 
97
- def __or__(self, value_set: Union["ValueSet", "CombinedValueSet"]) -> CombinedValueSet: # type: ignore[override]
94
+ def __ror__(self, value_set: Union[type["ValueSet"], "CombinedValueSet"]) -> "CombinedValueSet": # type: ignore[override]
98
95
  """Implements the `|` (or) operator."""
99
- return CombinedValueSet(cast(ValueSet, self), value_set)
96
+ return self.__or__(value_set)
100
97
 
101
98
 
102
- class ValueSet(CodeConstantsURLMapping, metaclass=ValueSystems):
99
+ class ValueSet(CodeConstantsURLMappingMixin, metaclass=ValueSystems):
103
100
  """The Base class for a ValueSet."""
104
101
 
105
- values: dict[str, set]
106
- pass
102
+ @classproperty
103
+ def values(cls) -> dict[str, set]:
104
+ """A property that returns a dictionary of code systems and their associated values."""
105
+ return {
106
+ system: getattr(cls, system)
107
+ for system in cls.CODE_SYSTEM_MAPPING.keys()
108
+ if hasattr(cls, system)
109
+ }
@@ -9,7 +9,7 @@ import time
9
9
  import traceback
10
10
  from collections import defaultdict
11
11
  from types import FrameType
12
- from typing import Any, Optional
12
+ from typing import Any, AsyncGenerator, Optional, TypedDict, cast
13
13
 
14
14
  import grpc
15
15
  import statsd
@@ -54,6 +54,49 @@ LOADED_PLUGINS: dict = {}
54
54
  EVENT_PROTOCOL_MAP: dict = {}
55
55
 
56
56
 
57
+ class DataAccess(TypedDict):
58
+ """DataAccess."""
59
+
60
+ event: str
61
+ read: list[str]
62
+ write: list[str]
63
+
64
+
65
+ Protocol = TypedDict(
66
+ "Protocol",
67
+ {
68
+ "class": str,
69
+ "data_access": DataAccess,
70
+ },
71
+ )
72
+
73
+
74
+ class Components(TypedDict):
75
+ """Components."""
76
+
77
+ protocols: list[Protocol]
78
+ commands: list[dict]
79
+ content: list[dict]
80
+ effects: list[dict]
81
+ views: list[dict]
82
+
83
+
84
+ class PluginManifest(TypedDict):
85
+ """PluginManifest."""
86
+
87
+ sdk_version: str
88
+ plugin_version: str
89
+ name: str
90
+ description: str
91
+ components: Components
92
+ secrets: list[dict]
93
+ tags: dict[str, str]
94
+ references: list[str]
95
+ license: str
96
+ diagram: bool
97
+ readme: str
98
+
99
+
57
100
  class PluginRunner(PluginRunnerServicer):
58
101
  """This process runs provided plugins that register interest in incoming events."""
59
102
 
@@ -63,12 +106,20 @@ class PluginRunner(PluginRunnerServicer):
63
106
 
64
107
  sandbox: Sandbox
65
108
 
66
- async def HandleEvent(self, request: Event, context: Any) -> EventResponse:
109
+ async def HandleEvent(
110
+ self, request: Event, context: Any
111
+ ) -> AsyncGenerator[EventResponse, None]:
67
112
  """This is invoked when an event comes in."""
68
113
  event_start_time = time.time()
69
- event_name = EventType.Name(request.type)
114
+ event_type = request.type
115
+ event_name = EventType.Name(event_type)
70
116
  relevant_plugins = EVENT_PROTOCOL_MAP.get(event_name, [])
71
117
 
118
+ if event_type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
119
+ plugin_name = request.target
120
+ # filter only for the plugin(s) that were created/updated
121
+ relevant_plugins = [p for p in relevant_plugins if p.startswith(f"{plugin_name}:")]
122
+
72
123
  effect_list = []
73
124
 
74
125
  for plugin_name in relevant_plugins:
@@ -129,7 +180,7 @@ class PluginRunner(PluginRunnerServicer):
129
180
 
130
181
  async def ReloadPlugins(
131
182
  self, request: ReloadPluginsRequest, context: Any
132
- ) -> ReloadPluginsResponse:
183
+ ) -> AsyncGenerator[ReloadPluginsResponse, None]:
133
184
  """This is invoked when we need to reload plugins."""
134
185
  try:
135
186
  load_plugins()
@@ -166,13 +217,13 @@ def load_or_reload_plugin(path: pathlib.Path) -> None:
166
217
  log.info(f"Loading {path}")
167
218
 
168
219
  manifest_file = path / MANIFEST_FILE_NAME
169
- manifest_json = manifest_file.read_text()
220
+ manifest_json_str = manifest_file.read_text()
170
221
 
171
222
  # the name is the folder name underneath the plugins directory
172
223
  name = path.name
173
224
 
174
225
  try:
175
- manifest_json = json.loads(manifest_json)
226
+ manifest_json: PluginManifest = json.loads(manifest_json_str)
176
227
  except Exception as e:
177
228
  log.error(f'Unable to load plugin "{name}": {e}')
178
229
  return
@@ -264,8 +315,8 @@ def load_plugins(specified_plugin_paths: list[str] | None = None) -> None:
264
315
  for plugin_path in plugin_paths:
265
316
  # when we import plugins we'll use the module name directly so we need to add the plugin
266
317
  # directory to the path
267
- path_to_append = f"./{plugin_path.parent}"
268
- sys.path.append(path_to_append)
318
+ path_to_append = pathlib.Path(".") / plugin_path.parent
319
+ sys.path.append(path_to_append.as_posix())
269
320
  else:
270
321
  candidates = os.listdir(PLUGIN_DIRECTORY)
271
322
 
plugin_runner/sandbox.py CHANGED
@@ -113,7 +113,7 @@ class Sandbox:
113
113
  => 'from _a import x' is ok, because '_a' is not added to the scope.
114
114
  """
115
115
  for name in node.names:
116
- if "*" in name.name and not _is_known_module(node.module):
116
+ if "*" in name.name and node.module and not _is_known_module(node.module):
117
117
  self.error(node, '"*" imports are not allowed.')
118
118
  self.check_name(node, name.name)
119
119
  if name.asname:
File without changes