canvas 0.33.0__py3-none-any.whl → 0.34.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.33.0.dist-info → canvas-0.34.0.dist-info}/METADATA +2 -1
- canvas-0.34.0.dist-info/RECORD +272 -0
- canvas_sdk/__init__.py +3 -0
- canvas_sdk/commands/__init__.py +1 -1
- canvas_sdk/commands/base.py +3 -0
- canvas_sdk/commands/commands/__init__.py +1 -0
- canvas_sdk/commands/commands/adjust_prescription.py +3 -0
- canvas_sdk/commands/commands/allergy.py +7 -0
- canvas_sdk/commands/commands/assess.py +2 -0
- canvas_sdk/commands/commands/close_goal.py +3 -0
- canvas_sdk/commands/commands/diagnose.py +3 -0
- canvas_sdk/commands/commands/exam.py +3 -0
- canvas_sdk/commands/commands/family_history.py +3 -0
- canvas_sdk/commands/commands/follow_up.py +3 -0
- canvas_sdk/commands/commands/goal.py +3 -0
- canvas_sdk/commands/commands/history_present_illness.py +3 -0
- canvas_sdk/commands/commands/imaging_order.py +3 -0
- canvas_sdk/commands/commands/instruct.py +3 -0
- canvas_sdk/commands/commands/lab_order.py +3 -0
- canvas_sdk/commands/commands/medical_history.py +3 -0
- canvas_sdk/commands/commands/medication_statement.py +2 -0
- canvas_sdk/commands/commands/past_surgical_history.py +3 -0
- canvas_sdk/commands/commands/perform.py +3 -0
- canvas_sdk/commands/commands/plan.py +3 -0
- canvas_sdk/commands/commands/prescribe.py +8 -0
- canvas_sdk/commands/commands/questionnaire/__init__.py +3 -13
- canvas_sdk/commands/commands/questionnaire/question.py +10 -0
- canvas_sdk/commands/commands/reason_for_visit.py +3 -0
- canvas_sdk/commands/commands/refer.py +3 -0
- canvas_sdk/commands/commands/refill.py +3 -0
- canvas_sdk/commands/commands/remove_allergy.py +3 -0
- canvas_sdk/commands/commands/resolve_condition.py +3 -0
- canvas_sdk/commands/commands/review_of_systems.py +3 -0
- canvas_sdk/commands/commands/stop_medication.py +3 -0
- canvas_sdk/commands/commands/structured_assessment.py +3 -0
- canvas_sdk/commands/commands/task.py +7 -0
- canvas_sdk/commands/commands/update_diagnosis.py +3 -0
- canvas_sdk/commands/commands/update_goal.py +3 -0
- canvas_sdk/commands/commands/vitals.py +3 -0
- canvas_sdk/commands/constants.py +8 -0
- canvas_sdk/effects/__init__.py +1 -1
- canvas_sdk/effects/banner_alert/__init__.py +1 -1
- canvas_sdk/effects/banner_alert/add_banner_alert.py +3 -0
- canvas_sdk/effects/banner_alert/remove_banner_alert.py +3 -0
- canvas_sdk/effects/base.py +7 -0
- canvas_sdk/effects/billing_line_item/__init__.py +5 -1
- canvas_sdk/effects/billing_line_item/add_billing_line_item.py +3 -0
- canvas_sdk/effects/billing_line_item/remove_billing_line_item.py +3 -0
- canvas_sdk/effects/billing_line_item/update_billing_line_item.py +3 -0
- canvas_sdk/effects/launch_modal.py +3 -0
- canvas_sdk/effects/patient_chart_summary_configuration.py +3 -0
- canvas_sdk/effects/patient_portal/__init__.py +1 -0
- canvas_sdk/effects/patient_portal/application_configuration.py +3 -0
- canvas_sdk/effects/patient_portal/form_result.py +3 -0
- canvas_sdk/effects/patient_portal_menu_configuration.py +3 -0
- canvas_sdk/effects/patient_profile_configuration.py +5 -1
- canvas_sdk/effects/protocol_card/__init__.py +1 -1
- canvas_sdk/effects/protocol_card/protocol_card.py +6 -0
- canvas_sdk/effects/questionnaire_result.py +3 -0
- canvas_sdk/effects/send_invite.py +3 -0
- canvas_sdk/effects/show_button.py +3 -0
- canvas_sdk/effects/simple_api.py +9 -0
- canvas_sdk/effects/surescripts/__init__.py +2 -2
- canvas_sdk/effects/surescripts/surescripts_messages.py +7 -0
- canvas_sdk/effects/task/__init__.py +6 -1
- canvas_sdk/effects/task/task.py +8 -0
- canvas_sdk/effects/update_user.py +3 -0
- canvas_sdk/effects/widgets/__init__.py +1 -1
- canvas_sdk/effects/widgets/portal_widget.py +3 -0
- canvas_sdk/events/__init__.py +6 -1
- canvas_sdk/events/base.py +3 -0
- canvas_sdk/handlers/__init__.py +1 -1
- canvas_sdk/handlers/action_button.py +6 -0
- canvas_sdk/handlers/application.py +3 -0
- canvas_sdk/handlers/base.py +3 -0
- canvas_sdk/handlers/cron_task.py +3 -0
- canvas_sdk/handlers/simple_api/__init__.py +3 -2
- canvas_sdk/handlers/simple_api/api.py +26 -1
- canvas_sdk/handlers/simple_api/exceptions.py +10 -0
- canvas_sdk/handlers/simple_api/security.py +21 -5
- canvas_sdk/handlers/simple_api/tools.py +9 -0
- canvas_sdk/protocols/__init__.py +1 -1
- canvas_sdk/protocols/base.py +3 -0
- canvas_sdk/protocols/clinical_quality_measure.py +6 -1
- canvas_sdk/protocols/timeframe.py +3 -0
- canvas_sdk/questionnaires/__init__.py +1 -1
- canvas_sdk/questionnaires/utils.py +7 -0
- canvas_sdk/templates/__init__.py +1 -1
- canvas_sdk/templates/utils.py +3 -0
- canvas_sdk/utils/__init__.py +1 -1
- canvas_sdk/utils/http.py +94 -35
- canvas_sdk/utils/plugins.py +4 -0
- canvas_sdk/utils/stats.py +11 -0
- canvas_sdk/v1/__init__.py +1 -0
- canvas_sdk/v1/apps.py +3 -0
- canvas_sdk/v1/data/__init__.py +2 -2
- canvas_sdk/v1/data/allergy_intolerance.py +3 -0
- canvas_sdk/v1/data/appointment.py +7 -0
- canvas_sdk/v1/data/assessment.py +3 -0
- canvas_sdk/v1/data/banner_alert.py +3 -0
- canvas_sdk/v1/data/base.py +3 -0
- canvas_sdk/v1/data/billing.py +7 -0
- canvas_sdk/v1/data/care_team.py +7 -0
- canvas_sdk/v1/data/command.py +3 -0
- canvas_sdk/v1/data/common.py +18 -0
- canvas_sdk/v1/data/condition.py +7 -0
- canvas_sdk/v1/data/coverage.py +14 -0
- canvas_sdk/v1/data/detected_issue.py +3 -0
- canvas_sdk/v1/data/device.py +3 -0
- canvas_sdk/v1/data/imaging.py +7 -0
- canvas_sdk/v1/data/lab.py +16 -0
- canvas_sdk/v1/data/medication.py +3 -0
- canvas_sdk/v1/data/note.py +9 -0
- canvas_sdk/v1/data/observation.py +9 -0
- canvas_sdk/v1/data/organization.py +3 -0
- canvas_sdk/v1/data/patient.py +18 -2
- canvas_sdk/v1/data/practicelocation.py +7 -0
- canvas_sdk/v1/data/protocol_override.py +7 -0
- canvas_sdk/v1/data/questionnaire.py +16 -3
- canvas_sdk/v1/data/reason_for_visit.py +3 -0
- canvas_sdk/v1/data/staff.py +3 -0
- canvas_sdk/v1/data/task.py +12 -0
- canvas_sdk/v1/data/team.py +8 -1
- canvas_sdk/v1/data/user.py +3 -0
- canvas_sdk/v1/models.py +2 -0
- canvas_sdk/value_set/__init__.py +1 -0
- canvas_sdk/value_set/_utilities.py +16 -0
- canvas_sdk/value_set/custom.py +4 -0
- canvas_sdk/value_set/hcc2018.py +3 -0
- canvas_sdk/value_set/v2022/__init__.py +1 -0
- canvas_sdk/value_set/v2022/adverse_event.py +3 -0
- canvas_sdk/value_set/v2022/allergy.py +5 -0
- canvas_sdk/value_set/v2022/assessment.py +5 -0
- canvas_sdk/value_set/v2022/communication.py +5 -0
- canvas_sdk/value_set/v2022/condition.py +5 -0
- canvas_sdk/value_set/v2022/device.py +5 -0
- canvas_sdk/value_set/v2022/diagnostic_study.py +5 -0
- canvas_sdk/value_set/v2022/encounter.py +5 -0
- canvas_sdk/value_set/v2022/immunization.py +5 -0
- canvas_sdk/value_set/v2022/individual_characteristic.py +5 -0
- canvas_sdk/value_set/v2022/intervention.py +5 -0
- canvas_sdk/value_set/v2022/laboratory_test.py +5 -0
- canvas_sdk/value_set/v2022/medication.py +5 -0
- canvas_sdk/value_set/v2022/physical_exam.py +5 -0
- canvas_sdk/value_set/v2022/procedure.py +5 -0
- canvas_sdk/value_set/v2022/symptom.py +3 -0
- canvas_sdk/value_set/value_set.py +9 -0
- canvas_sdk/views/__init__.py +1 -0
- logger/__init__.py +2 -0
- logger/logger.py +3 -0
- plugin_runner/aws_headers.py +1 -1
- plugin_runner/load_all_plugins.py +202 -0
- plugin_runner/plugin_runner.py +21 -24
- plugin_runner/sandbox.py +497 -115
- settings.py +5 -2
- canvas-0.33.0.dist-info/RECORD +0 -366
- canvas_cli/apps/auth/tests.py +0 -155
- canvas_cli/apps/plugin/tests.py +0 -85
- canvas_cli/conftest.py +0 -28
- canvas_cli/tests.py +0 -217
- canvas_cli/utils/context/tests.py +0 -131
- canvas_cli/utils/print/tests.py +0 -69
- canvas_cli/utils/urls/tests.py +0 -12
- canvas_cli/utils/validators/tests.py +0 -37
- canvas_sdk/commands/tests/protocol/__init__.py +0 -0
- canvas_sdk/commands/tests/protocol/tests.py +0 -83
- canvas_sdk/commands/tests/schema/__init__.py +0 -0
- canvas_sdk/commands/tests/schema/tests.py +0 -108
- canvas_sdk/commands/tests/test_base_command.py +0 -81
- canvas_sdk/commands/tests/test_utils.py +0 -375
- canvas_sdk/commands/tests/unit/__init__.py +0 -0
- canvas_sdk/commands/tests/unit/tests.py +0 -278
- canvas_sdk/effects/banner_alert/tests.py +0 -288
- canvas_sdk/effects/protocol_card/tests.py +0 -191
- canvas_sdk/questionnaires/tests/__init__.py +0 -0
- canvas_sdk/questionnaires/tests/test_utils.py +0 -74
- canvas_sdk/templates/tests/__init__.py +0 -0
- canvas_sdk/templates/tests/test_utils.py +0 -43
- canvas_sdk/tests/__init__.py +0 -0
- canvas_sdk/tests/handlers/__init__.py +0 -0
- canvas_sdk/tests/handlers/test_simple_api.py +0 -1167
- canvas_sdk/utils/tests.py +0 -72
- canvas_sdk/value_set/tests/test_value_sets.py +0 -72
- plugin_runner/tests/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/example_plugin/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/example_plugin/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/example_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/example_plugin/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +0 -38
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +0 -11
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +0 -33
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +0 -3
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +0 -6
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +0 -5
- plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +0 -4
- plugin_runner/tests/fixtures/plugins/test_load_questionnaire/CANVAS_MANIFEST.json +0 -52
- plugin_runner/tests/fixtures/plugins/test_load_questionnaire/README.md +0 -11
- plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/my_protocol.py +0 -39
- plugin_runner/tests/fixtures/plugins/test_load_questionnaire/questionnaires/example_questionnaire.yml +0 -61
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +0 -10
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +0 -10
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/base.py +0 -3
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/base.py +0 -6
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/base.py +0 -8
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/CANVAS_MANIFEST.json +0 -29
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/README.md +0 -12
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/base.py +0 -3
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py +0 -18
- plugin_runner/tests/fixtures/plugins/test_render_template/CANVAS_MANIFEST.json +0 -47
- plugin_runner/tests/fixtures/plugins/test_render_template/README.md +0 -11
- plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py +0 -43
- plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html +0 -10
- plugin_runner/tests/fixtures/plugins/test_simple_api/CANVAS_MANIFEST.json +0 -47
- plugin_runner/tests/fixtures/plugins/test_simple_api/README.md +0 -11
- plugin_runner/tests/fixtures/plugins/test_simple_api/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/__init__.py +0 -0
- plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/my_protocol.py +0 -43
- plugin_runner/tests/test_application.py +0 -65
- plugin_runner/tests/test_plugin_installer.py +0 -127
- plugin_runner/tests/test_plugin_runner.py +0 -388
- plugin_runner/tests/test_sandbox.py +0 -137
- {canvas-0.33.0.dist-info → canvas-0.34.0.dist-info}/WHEEL +0 -0
- {canvas-0.33.0.dist-info → canvas-0.34.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import decimal
|
|
2
|
-
from datetime import date, datetime
|
|
3
|
-
from typing import get_origin
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
import requests
|
|
7
|
-
|
|
8
|
-
import settings
|
|
9
|
-
from canvas_sdk.commands.base import _BaseCommand
|
|
10
|
-
from canvas_sdk.commands.constants import ClinicalQuantity, Coding
|
|
11
|
-
from canvas_sdk.commands.tests.test_utils import (
|
|
12
|
-
COMMANDS,
|
|
13
|
-
MaskedValue,
|
|
14
|
-
create_new_note,
|
|
15
|
-
get_token,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@pytest.fixture(scope="session")
|
|
20
|
-
def token() -> MaskedValue:
|
|
21
|
-
"""Get a valid token."""
|
|
22
|
-
return get_token()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@pytest.fixture(scope="session")
|
|
26
|
-
def new_note(token: MaskedValue) -> dict:
|
|
27
|
-
"""Create a new note."""
|
|
28
|
-
return create_new_note(token)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.fixture
|
|
32
|
-
def command_type_map() -> dict[str, type]:
|
|
33
|
-
"""Map of command field types to their corresponding Python types."""
|
|
34
|
-
return {
|
|
35
|
-
"AutocompleteField": str,
|
|
36
|
-
"MultiLineTextField": str,
|
|
37
|
-
"TextField": str,
|
|
38
|
-
"ChoiceField": str,
|
|
39
|
-
"DateField": datetime,
|
|
40
|
-
"ApproximateDateField": date,
|
|
41
|
-
"IntegerField": int,
|
|
42
|
-
"DecimalField": decimal.Decimal,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@pytest.mark.integtest
|
|
47
|
-
@pytest.mark.parametrize(
|
|
48
|
-
"Command",
|
|
49
|
-
COMMANDS,
|
|
50
|
-
)
|
|
51
|
-
def test_command_schema_matches_command_api(
|
|
52
|
-
token: MaskedValue,
|
|
53
|
-
command_type_map: dict[str, str],
|
|
54
|
-
new_note: dict,
|
|
55
|
-
Command: _BaseCommand,
|
|
56
|
-
) -> None:
|
|
57
|
-
"""Test that the command schema matches the command API."""
|
|
58
|
-
# first create the command in the new note
|
|
59
|
-
data = {"noteKey": new_note["externallyExposableId"], "schemaKey": Command.Meta.key}
|
|
60
|
-
headers = {"Authorization": f"Bearer {token.value}"}
|
|
61
|
-
url = f"{settings.INTEGRATION_TEST_URL}/core/api/v1/commands/"
|
|
62
|
-
command_resp = requests.post(url, headers=headers, data=data).json()
|
|
63
|
-
assert "uuid" in command_resp
|
|
64
|
-
command_uuid = command_resp["uuid"]
|
|
65
|
-
|
|
66
|
-
# next, request the fields of the newly created command
|
|
67
|
-
url = f"{settings.INTEGRATION_TEST_URL}/core/api/v1/commands/{command_uuid}/fields/"
|
|
68
|
-
command_fields_resp = requests.get(url, headers=headers).json()
|
|
69
|
-
assert command_fields_resp["schema"] == Command.Meta.key
|
|
70
|
-
|
|
71
|
-
command_fields = command_fields_resp["fields"]
|
|
72
|
-
if Command.Meta.key == "questionnaire":
|
|
73
|
-
# questionnaire's fields vary per questionnaire, so just check the first two fields which never vary
|
|
74
|
-
command_fields = command_fields[:2]
|
|
75
|
-
expected_fields = Command.command_schema()
|
|
76
|
-
assert len(command_fields) == len(expected_fields)
|
|
77
|
-
|
|
78
|
-
for actual_field in command_fields:
|
|
79
|
-
name = actual_field["name"]
|
|
80
|
-
assert name in expected_fields
|
|
81
|
-
expected_field = expected_fields[name]
|
|
82
|
-
|
|
83
|
-
assert expected_field["required"] == actual_field["required"]
|
|
84
|
-
|
|
85
|
-
expected_type = expected_field["type"]
|
|
86
|
-
if expected_type is Coding:
|
|
87
|
-
expected_type = expected_type.__annotations__["code"]
|
|
88
|
-
|
|
89
|
-
if expected_type is ClinicalQuantity:
|
|
90
|
-
expected_type = expected_type.__annotations__["representative_ndc"]
|
|
91
|
-
|
|
92
|
-
actual_type = command_type_map.get(actual_field["type"])
|
|
93
|
-
if actual_field["type"] == "AutocompleteField" and name[-1] == "s":
|
|
94
|
-
# this condition initially created for Prescribe.indications,
|
|
95
|
-
# but could apply to other AutocompleteField fields that are lists
|
|
96
|
-
# making the assumption here that if the field ends in 's' (like indications), it is a list
|
|
97
|
-
assert get_origin(expected_type) is list
|
|
98
|
-
|
|
99
|
-
else:
|
|
100
|
-
assert expected_type == actual_type
|
|
101
|
-
|
|
102
|
-
if (choices := actual_field["choices"]) is None:
|
|
103
|
-
assert expected_field["choices"] is None
|
|
104
|
-
continue
|
|
105
|
-
|
|
106
|
-
assert len(expected_field["choices"]) == len(choices)
|
|
107
|
-
for choice in choices:
|
|
108
|
-
assert choice["value"] in expected_field["choices"]
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import uuid
|
|
3
|
-
from enum import Enum
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from canvas_sdk.commands.base import _BaseCommand
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class DummyEnum(Enum):
|
|
11
|
-
"""A dummy enum class for testing purposes."""
|
|
12
|
-
|
|
13
|
-
LOW = "low"
|
|
14
|
-
HIGH = "high"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class DummyCommand(_BaseCommand):
|
|
18
|
-
"""A dummy command class for testing purposes."""
|
|
19
|
-
|
|
20
|
-
class Meta:
|
|
21
|
-
key = "dummyCommand"
|
|
22
|
-
|
|
23
|
-
# Fields
|
|
24
|
-
int_field: int = 0
|
|
25
|
-
str_field: str = ""
|
|
26
|
-
enum_field: DummyEnum | None = None
|
|
27
|
-
date_field: datetime.date | None = None
|
|
28
|
-
uuid_field: uuid.UUID | None = None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.fixture
|
|
32
|
-
def dummy_command_instance() -> DummyCommand:
|
|
33
|
-
"""Fixture to return a mock instance of DummyCommand for testing."""
|
|
34
|
-
cmd = DummyCommand(int_field=10, str_field="hello")
|
|
35
|
-
# Set additional fields after instantiation.
|
|
36
|
-
cmd.enum_field = DummyEnum.HIGH
|
|
37
|
-
cmd.date_field = datetime.date(2025, 2, 14)
|
|
38
|
-
cmd.uuid_field = uuid.UUID("12345678-1234-5678-1234-567812345678")
|
|
39
|
-
# Set note_uuid and command_uuid for effect methods.
|
|
40
|
-
cmd.note_uuid = "note-123"
|
|
41
|
-
cmd.command_uuid = "cmd-456"
|
|
42
|
-
return cmd
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def test_dirty_keys(dummy_command_instance: DummyCommand) -> None:
|
|
46
|
-
"""Test that the dirty_keys property correctly tracks all fields that are set (via constructor and subsequent assignment)."""
|
|
47
|
-
keys = set(dummy_command_instance._dirty_keys)
|
|
48
|
-
expected_keys = {"int_field", "str_field", "enum_field", "date_field", "uuid_field"}
|
|
49
|
-
assert expected_keys == keys
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_values_transformation(dummy_command_instance: DummyCommand) -> None:
|
|
53
|
-
"""
|
|
54
|
-
Test that the values property applies type-specific transformations:
|
|
55
|
-
- Enums are replaced by their .value.
|
|
56
|
-
- Date/datetime fields are converted to ISO formatted strings.
|
|
57
|
-
- UUID fields are converted to strings.
|
|
58
|
-
- Other types are returned as-is.
|
|
59
|
-
"""
|
|
60
|
-
vals = dummy_command_instance.values
|
|
61
|
-
assert vals["int_field"] == 10
|
|
62
|
-
assert vals["str_field"] == "hello"
|
|
63
|
-
# For enum_field, should return its .value.
|
|
64
|
-
assert vals["enum_field"] == DummyEnum.HIGH.value
|
|
65
|
-
# For date_field, should return an ISO string.
|
|
66
|
-
|
|
67
|
-
assert (
|
|
68
|
-
vals["date_field"] == dummy_command_instance.date_field.isoformat()
|
|
69
|
-
if dummy_command_instance.date_field
|
|
70
|
-
else None
|
|
71
|
-
)
|
|
72
|
-
# For uuid_field, should return a string.
|
|
73
|
-
assert vals["uuid_field"] == str(dummy_command_instance.uuid_field)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def test_constantized_key(dummy_command_instance: DummyCommand) -> None:
|
|
77
|
-
"""
|
|
78
|
-
Test that constantized_key transforms the Meta.key from 'dummyCommand'
|
|
79
|
-
into an uppercase, underscore-separated string ('DUMMY_COMMAND').
|
|
80
|
-
"""
|
|
81
|
-
assert dummy_command_instance.constantized_key() == "DUMMY_COMMAND"
|
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import shutil
|
|
3
|
-
import string
|
|
4
|
-
import threading
|
|
5
|
-
from contextlib import chdir
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from decimal import Decimal
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, cast
|
|
10
|
-
from urllib.parse import urlparse
|
|
11
|
-
|
|
12
|
-
import pytest
|
|
13
|
-
import requests
|
|
14
|
-
import websocket
|
|
15
|
-
from pydantic import ValidationError
|
|
16
|
-
from typer.testing import CliRunner
|
|
17
|
-
|
|
18
|
-
import settings
|
|
19
|
-
from canvas_cli.apps.plugin.plugin import _build_package, plugin_url
|
|
20
|
-
from canvas_cli.main import app
|
|
21
|
-
from canvas_sdk.commands import (
|
|
22
|
-
AssessCommand,
|
|
23
|
-
DiagnoseCommand,
|
|
24
|
-
GoalCommand,
|
|
25
|
-
HistoryOfPresentIllnessCommand,
|
|
26
|
-
MedicationStatementCommand,
|
|
27
|
-
PlanCommand,
|
|
28
|
-
PrescribeCommand,
|
|
29
|
-
QuestionnaireCommand,
|
|
30
|
-
ReasonForVisitCommand,
|
|
31
|
-
StopMedicationCommand,
|
|
32
|
-
UpdateGoalCommand,
|
|
33
|
-
)
|
|
34
|
-
from canvas_sdk.commands.base import _BaseCommand
|
|
35
|
-
from canvas_sdk.commands.constants import ClinicalQuantity, Coding
|
|
36
|
-
|
|
37
|
-
runner = CliRunner()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class WrongType:
|
|
41
|
-
"""A type to yield ValidationErrors in tests."""
|
|
42
|
-
|
|
43
|
-
wrong_field: str
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class MaskedValue:
|
|
47
|
-
"""A class to mask sensitive values in tests."""
|
|
48
|
-
|
|
49
|
-
def __init__(self, value: str) -> None:
|
|
50
|
-
self.value = value
|
|
51
|
-
|
|
52
|
-
def __repr__(self) -> str:
|
|
53
|
-
return "MaskedValue(********)"
|
|
54
|
-
|
|
55
|
-
def __str___(self) -> str:
|
|
56
|
-
return "*******"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def get_field_type_unformatted(field_props: dict[str, Any]) -> str:
|
|
60
|
-
"""Get the unformatted field type from the field properties."""
|
|
61
|
-
if t := field_props.get("type"):
|
|
62
|
-
return field_props.get("format") or t
|
|
63
|
-
|
|
64
|
-
first_in_union: dict = field_props.get("anyOf", field_props.get("allOf"))[0]
|
|
65
|
-
if "$ref" in first_in_union:
|
|
66
|
-
return first_in_union["$ref"].split("#/$defs/")[-1]
|
|
67
|
-
return first_in_union.get("format") or first_in_union["type"]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def get_field_type(field_props: dict) -> str:
|
|
71
|
-
"""Get the field type from the field properties."""
|
|
72
|
-
return get_field_type_unformatted(field_props).replace("-", "").replace("array", "list")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def random_string() -> str:
|
|
76
|
-
"""Generate a random string."""
|
|
77
|
-
return "".join(random.choices(string.ascii_uppercase + string.digits, k=7))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def fake(field_props: dict, Command: type[_BaseCommand]) -> Any:
|
|
81
|
-
"""Generate a fake value for a field."""
|
|
82
|
-
t = get_field_type(field_props)
|
|
83
|
-
match t:
|
|
84
|
-
case "string":
|
|
85
|
-
return random_string()
|
|
86
|
-
case "integer":
|
|
87
|
-
return random.randint(1, 10)
|
|
88
|
-
case "datetime":
|
|
89
|
-
return datetime.now()
|
|
90
|
-
case "boolean":
|
|
91
|
-
return random.choice([True, False])
|
|
92
|
-
case "number":
|
|
93
|
-
return Decimal(random.randrange(1, 200))
|
|
94
|
-
case "array":
|
|
95
|
-
num_items = random.randint(0, 5)
|
|
96
|
-
item_props = field_props["anyOf"][0]["items"]
|
|
97
|
-
return [fake(item_props, Command) for i in range(num_items)]
|
|
98
|
-
case "list":
|
|
99
|
-
num_items = random.randint(0, field_props.get("maxItems", 5))
|
|
100
|
-
item_props = field_props.get("items")
|
|
101
|
-
return [fake(item_props, Command) for i in range(num_items)] if item_props else []
|
|
102
|
-
case "Coding":
|
|
103
|
-
return Coding(system=random_string(), code=random_string(), display=random_string())
|
|
104
|
-
case "ClinicalQuantity":
|
|
105
|
-
return ClinicalQuantity(representative_ndc="ndc", ncpdp_quantity_qualifier_code="code")
|
|
106
|
-
case "WrongType":
|
|
107
|
-
return WrongType()
|
|
108
|
-
if t[0].isupper():
|
|
109
|
-
return random.choice(list(getattr(Command, t)))
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def raises_wrong_type_error(
|
|
113
|
-
Command: type[_BaseCommand],
|
|
114
|
-
field: str,
|
|
115
|
-
) -> None:
|
|
116
|
-
"""Test that the correct error is raised when the wrong type is passed to a field."""
|
|
117
|
-
field_props = Command.model_json_schema()["properties"][field]
|
|
118
|
-
field_type = get_field_type(field_props)
|
|
119
|
-
|
|
120
|
-
wrong_field_type = "WrongType"
|
|
121
|
-
|
|
122
|
-
with pytest.raises(ValidationError) as e1:
|
|
123
|
-
err_kwargs = {field: fake({"type": wrong_field_type}, Command)}
|
|
124
|
-
Command(**err_kwargs)
|
|
125
|
-
err_msg1 = repr(e1.value)
|
|
126
|
-
|
|
127
|
-
valid_kwargs = {field: fake(field_props, Command)}
|
|
128
|
-
cmd = Command(**valid_kwargs)
|
|
129
|
-
err_value = fake({"type": wrong_field_type}, Command)
|
|
130
|
-
with pytest.raises(ValidationError) as e2:
|
|
131
|
-
setattr(cmd, field, err_value)
|
|
132
|
-
err_msg2 = repr(e2.value)
|
|
133
|
-
|
|
134
|
-
assert "validation error" in err_msg1
|
|
135
|
-
assert f"{Command.__name__}\n{field}" in err_msg1
|
|
136
|
-
|
|
137
|
-
assert "validation error" in err_msg2
|
|
138
|
-
assert f"{Command.__name__}\n{field}" in err_msg1
|
|
139
|
-
|
|
140
|
-
field_type = (
|
|
141
|
-
"dictionary" if field_type == "Coding" or field_type == "ClinicalQuantity" else field_type
|
|
142
|
-
)
|
|
143
|
-
if field_type == "number":
|
|
144
|
-
assert "Input should be an instance of Decimal" in err_msg1
|
|
145
|
-
assert "Input should be an instance of Decimal" in err_msg2
|
|
146
|
-
elif field_type[0].isupper():
|
|
147
|
-
assert f"Input should be an instance of {Command.__name__}.{field_type}" in err_msg1
|
|
148
|
-
assert f"Input should be an instance of {Command.__name__}.{field_type}" in err_msg2
|
|
149
|
-
else:
|
|
150
|
-
assert f"Input should be a valid {field_type}" in err_msg1
|
|
151
|
-
assert f"Input should be a valid {field_type}" in err_msg2
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def raises_none_error_for_effect_method(
|
|
155
|
-
Command: type[_BaseCommand],
|
|
156
|
-
method: str,
|
|
157
|
-
) -> None:
|
|
158
|
-
"""Test that the correct error is raised when a required field is None for an effect method."""
|
|
159
|
-
cmd_name = Command.__name__
|
|
160
|
-
cmd_name_article = "an" if cmd_name.startswith(("A", "E", "I", "O", "U")) else "a"
|
|
161
|
-
|
|
162
|
-
cmd = Command()
|
|
163
|
-
method_required_fields = cmd._get_effect_method_required_fields(method)
|
|
164
|
-
with pytest.raises(ValidationError) as e:
|
|
165
|
-
getattr(cmd, method)()
|
|
166
|
-
e_msg = repr(e.value)
|
|
167
|
-
missing_fields = [field for field in method_required_fields if getattr(cmd, field) is None]
|
|
168
|
-
num_errs = len(missing_fields)
|
|
169
|
-
assert f"{num_errs} validation error{'s' if num_errs > 1 else ''} for {cmd_name}" in e_msg
|
|
170
|
-
|
|
171
|
-
for f in missing_fields:
|
|
172
|
-
assert (
|
|
173
|
-
f"Field '{f}' is required to {method.replace('_', ' ')} {cmd_name_article} {cmd_name} [type=missing, input_value=None, input_type=NoneType]"
|
|
174
|
-
in e_msg
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def write_protocol_code(
|
|
179
|
-
note_uuid: str, plugin_name: str, commands: list[type[_BaseCommand]]
|
|
180
|
-
) -> None:
|
|
181
|
-
"""Test that the protocol code is written correctly."""
|
|
182
|
-
imports = ", ".join([c.__name__ for c in commands])
|
|
183
|
-
effects = ", ".join([f"{c.__name__}(note_uuid='{note_uuid}').originate()" for c in commands])
|
|
184
|
-
|
|
185
|
-
protocol_code = f"""from canvas_sdk.commands import {imports}
|
|
186
|
-
from canvas_sdk.events import EventType
|
|
187
|
-
from canvas_sdk.protocols import BaseProtocol
|
|
188
|
-
|
|
189
|
-
class Protocol(BaseProtocol):
|
|
190
|
-
RESPONDS_TO = EventType.Name(EventType.ENCOUNTER_CREATED)
|
|
191
|
-
def compute(self):
|
|
192
|
-
return [{effects}]
|
|
193
|
-
"""
|
|
194
|
-
|
|
195
|
-
with chdir(Path("./custom-plugins")):
|
|
196
|
-
runner.invoke(app, "init", input=plugin_name)
|
|
197
|
-
|
|
198
|
-
with open(f"./custom-plugins/{plugin_name}/protocols/my_protocol.py", "w") as protocol:
|
|
199
|
-
protocol.write(protocol_code)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def install_plugin(plugin_name: str, token: MaskedValue) -> None:
|
|
203
|
-
"""Install a plugin."""
|
|
204
|
-
with open(_build_package(Path(f"./custom-plugins/{plugin_name}")), "rb") as package:
|
|
205
|
-
response = requests.post(
|
|
206
|
-
plugin_url(cast(str, settings.INTEGRATION_TEST_URL)),
|
|
207
|
-
data={"is_enabled": True},
|
|
208
|
-
files={"package": package},
|
|
209
|
-
headers={"Authorization": f"Bearer {token.value}"},
|
|
210
|
-
)
|
|
211
|
-
response.raise_for_status()
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def trigger_plugin_event(token: MaskedValue) -> None:
|
|
215
|
-
"""Trigger a plugin event."""
|
|
216
|
-
response = requests.post(
|
|
217
|
-
f"{settings.INTEGRATION_TEST_URL}/api/Note/",
|
|
218
|
-
headers={
|
|
219
|
-
"Authorization": f"Bearer {token.value}",
|
|
220
|
-
"Content-Type": "application/json",
|
|
221
|
-
"Accept": "application/json",
|
|
222
|
-
},
|
|
223
|
-
json={
|
|
224
|
-
"patient": 2,
|
|
225
|
-
"provider": 1,
|
|
226
|
-
"note_type": "office",
|
|
227
|
-
"note_type_version": 1,
|
|
228
|
-
"lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
|
|
229
|
-
},
|
|
230
|
-
)
|
|
231
|
-
response.raise_for_status()
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def get_original_note_body_commands(new_note_id: int, token: MaskedValue) -> list[str]:
|
|
235
|
-
"""Get the commands from the original note body."""
|
|
236
|
-
response = requests.get(
|
|
237
|
-
f"{settings.INTEGRATION_TEST_URL}/api/Note/{new_note_id}",
|
|
238
|
-
headers={
|
|
239
|
-
"Authorization": f"Bearer {token.value}",
|
|
240
|
-
"Content-Type": "application/json",
|
|
241
|
-
"Accept": "application/json",
|
|
242
|
-
},
|
|
243
|
-
)
|
|
244
|
-
response.raise_for_status()
|
|
245
|
-
|
|
246
|
-
original_note = response.json()
|
|
247
|
-
|
|
248
|
-
body = original_note["body"]
|
|
249
|
-
return [
|
|
250
|
-
line["value"]
|
|
251
|
-
for line in body
|
|
252
|
-
if "data" in line
|
|
253
|
-
and "commandUuid" in line["data"]
|
|
254
|
-
and "id" in line["data"]
|
|
255
|
-
and line["type"] == "command"
|
|
256
|
-
]
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def clean_up_files_and_plugins(plugin_name: str, token: MaskedValue) -> None:
|
|
260
|
-
"""Clean up the files and plugins."""
|
|
261
|
-
# clean up
|
|
262
|
-
if Path(f"./custom-plugins/{plugin_name}").exists():
|
|
263
|
-
shutil.rmtree(Path(f"./custom-plugins/{plugin_name}"))
|
|
264
|
-
|
|
265
|
-
# disable
|
|
266
|
-
response = requests.patch(
|
|
267
|
-
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
268
|
-
data={"is_enabled": False},
|
|
269
|
-
headers={
|
|
270
|
-
"Authorization": f"Bearer {token.value}",
|
|
271
|
-
},
|
|
272
|
-
)
|
|
273
|
-
response.raise_for_status()
|
|
274
|
-
|
|
275
|
-
# delete
|
|
276
|
-
response = requests.delete(
|
|
277
|
-
plugin_url(cast(str, settings.INTEGRATION_TEST_URL), plugin_name),
|
|
278
|
-
headers={"Authorization": f"Bearer {token.value}"},
|
|
279
|
-
)
|
|
280
|
-
response.raise_for_status()
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
# For reuse with the protocol code
|
|
284
|
-
COMMANDS: list[type[_BaseCommand]] = [
|
|
285
|
-
AssessCommand,
|
|
286
|
-
DiagnoseCommand,
|
|
287
|
-
GoalCommand,
|
|
288
|
-
HistoryOfPresentIllnessCommand,
|
|
289
|
-
MedicationStatementCommand,
|
|
290
|
-
PlanCommand,
|
|
291
|
-
PrescribeCommand,
|
|
292
|
-
QuestionnaireCommand,
|
|
293
|
-
ReasonForVisitCommand,
|
|
294
|
-
StopMedicationCommand,
|
|
295
|
-
UpdateGoalCommand,
|
|
296
|
-
]
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def create_new_note(token: MaskedValue) -> dict:
|
|
300
|
-
"""Create a new note."""
|
|
301
|
-
headers = {
|
|
302
|
-
"Authorization": f"Bearer {token.value}",
|
|
303
|
-
"Content-Type": "application/json",
|
|
304
|
-
"Accept": "application/json",
|
|
305
|
-
}
|
|
306
|
-
data = {
|
|
307
|
-
"patient": 1,
|
|
308
|
-
"provider": 1,
|
|
309
|
-
"note_type": "office",
|
|
310
|
-
"note_type_version": 1,
|
|
311
|
-
"lastModifiedBySessionKey": "8fee3c03a525cebee1d8a6b8e63dd4dg",
|
|
312
|
-
}
|
|
313
|
-
response = requests.post(
|
|
314
|
-
f"{settings.INTEGRATION_TEST_URL}/api/Note/", headers=headers, json=data
|
|
315
|
-
)
|
|
316
|
-
response.raise_for_status()
|
|
317
|
-
return response.json()
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def get_token() -> MaskedValue:
|
|
321
|
-
"""Get a valid token."""
|
|
322
|
-
response = requests.post(
|
|
323
|
-
f"{settings.INTEGRATION_TEST_URL}/auth/token/",
|
|
324
|
-
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
325
|
-
data={
|
|
326
|
-
"grant_type": "client_credentials",
|
|
327
|
-
"client_id": settings.INTEGRATION_TEST_CLIENT_ID,
|
|
328
|
-
"client_secret": settings.INTEGRATION_TEST_CLIENT_SECRET,
|
|
329
|
-
},
|
|
330
|
-
)
|
|
331
|
-
response.raise_for_status()
|
|
332
|
-
|
|
333
|
-
return MaskedValue(response.json()["access_token"])
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def wait_for_log(
|
|
337
|
-
host: str, token: str, message: str
|
|
338
|
-
) -> tuple[threading.Event, threading.Thread, websocket.WebSocketApp]:
|
|
339
|
-
"""Wait for a specific log message."""
|
|
340
|
-
hostname = cast(str, urlparse(host).hostname)
|
|
341
|
-
instance = hostname.removesuffix(".canvasmedical.com")
|
|
342
|
-
|
|
343
|
-
websocket_uri = f"wss://logs.console.canvasmedical.com/{instance}?token={token}"
|
|
344
|
-
|
|
345
|
-
connected_event = threading.Event()
|
|
346
|
-
message_received_event = threading.Event()
|
|
347
|
-
|
|
348
|
-
def _on_message(ws: websocket.WebSocket, received_message: str) -> None:
|
|
349
|
-
try:
|
|
350
|
-
if "Log stream connected" in received_message:
|
|
351
|
-
connected_event.set()
|
|
352
|
-
if message.lower() in received_message.lower():
|
|
353
|
-
message_received_event.set()
|
|
354
|
-
ws.close()
|
|
355
|
-
except Exception as ex:
|
|
356
|
-
print(f"Error processing message: {ex}")
|
|
357
|
-
|
|
358
|
-
def _on_error(ws: websocket.WebSocket, error: str) -> None:
|
|
359
|
-
print(f"WebSocket error: {error}")
|
|
360
|
-
|
|
361
|
-
ws = websocket.WebSocketApp(
|
|
362
|
-
websocket_uri,
|
|
363
|
-
on_message=_on_message,
|
|
364
|
-
on_error=_on_error,
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
thread = threading.Thread(target=ws.run_forever)
|
|
368
|
-
thread.start()
|
|
369
|
-
|
|
370
|
-
timeout_not_hit = connected_event.wait(timeout=5.0)
|
|
371
|
-
if not timeout_not_hit:
|
|
372
|
-
ws.close()
|
|
373
|
-
assert timeout_not_hit, "connection timeout hit"
|
|
374
|
-
|
|
375
|
-
return message_received_event, thread, ws
|
|
File without changes
|