canvas 0.57.1__py3-none-any.whl → 0.58.1__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.57.1.dist-info → canvas-0.58.1.dist-info}/METADATA +4 -1
- {canvas-0.57.1.dist-info → canvas-0.58.1.dist-info}/RECORD +27 -20
- canvas_cli/apps/run_plugins/run_plugins.py +90 -4
- canvas_cli/templates/plugins/default/cookiecutter.json +2 -1
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/pyproject.toml +16 -0
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py +21 -0
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{CANVAS_MANIFEST.json → {{ cookiecutter.__package_name }}/CANVAS_MANIFEST.json } +2 -2
- canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__package_name }}/protocols/__init__.py +0 -0
- canvas_sdk/test_utils/__init__.py +0 -0
- canvas_sdk/test_utils/factories/__init__.py +4 -0
- canvas_sdk/test_utils/factories/patient.py +39 -0
- canvas_sdk/test_utils/factories/user.py +13 -0
- canvas_sdk/v1/data/patient.py +67 -47
- canvas_sdk/v1/data/user.py +2 -2
- canvas_sdk/v1/data/utils.py +16 -0
- settings.py +9 -6
- {canvas-0.57.1.dist-info → canvas-0.58.1.dist-info}/WHEEL +0 -0
- {canvas-0.57.1.dist-info → canvas-0.58.1.dist-info}/entry_points.txt +0 -0
- /canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{protocols → tests}/__init__.py +0 -0
- /canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{README.md → {{ cookiecutter.__package_name }}/README.md} +0 -0
- /canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{protocols → {{ cookiecutter.__package_name }}/protocols}/my_protocol.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: canvas
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.58.1
|
|
4
4
|
Summary: SDK to customize event-driven actions in your Canvas instance
|
|
5
5
|
Author-email: Canvas Team <engineering@canvasmedical.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -13,6 +13,7 @@ Requires-Dist: django-stubs[compatible-mypy]<6,>=5.1.1
|
|
|
13
13
|
Requires-Dist: django-timezone-utils<0.16,>=0.15.0
|
|
14
14
|
Requires-Dist: django<6,>=5.1.1
|
|
15
15
|
Requires-Dist: env-tools<3,>=2.4.0
|
|
16
|
+
Requires-Dist: factory-boy>=3.3.3
|
|
16
17
|
Requires-Dist: frozendict>=2.4.6
|
|
17
18
|
Requires-Dist: grpcio<2,>=1.60.1
|
|
18
19
|
Requires-Dist: ipython<9,>=8.21.0
|
|
@@ -32,6 +33,8 @@ Requires-Dist: statsd<5,>=4.0.1
|
|
|
32
33
|
Requires-Dist: typer
|
|
33
34
|
Requires-Dist: typing-extensions<4.13,>=4.8
|
|
34
35
|
Requires-Dist: websocket-client<2,>=1.7.0
|
|
36
|
+
Provides-Extra: test-utils
|
|
37
|
+
Requires-Dist: pytest-canvas; extra == 'test-utils'
|
|
35
38
|
Description-Content-Type: text/markdown
|
|
36
39
|
|
|
37
40
|
[](https://codecov.io/gh/canvas-medical/canvas-plugins)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
settings.py,sha256=
|
|
1
|
+
settings.py,sha256=8CGtEpfAZ43FM6XPNJ8CgGwhWU9xn5mmncD7HZWi-Vk,6321
|
|
2
2
|
canvas_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
canvas_cli/main.py,sha256=INnlb8THwC0kbUJY94FVFq4UtDRGRyBBm6jASTzV0mU,3111
|
|
4
4
|
canvas_cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,18 +70,7 @@ canvas_cli/apps/logs/logs.py,sha256=BFpZ-2OF2Rs1EMLePo5UjqC9fKQeqm8qZobNTFNCL_M,
|
|
|
70
70
|
canvas_cli/apps/plugin/__init__.py,sha256=GB6hBwbajm5cOs-DbJh3q6smPfAaIMa99tSbkvDtbqs,341
|
|
71
71
|
canvas_cli/apps/plugin/plugin.py,sha256=vlnd61nPt98NnYUPAeZMogcRPgv1pdDqBtey13H_Tug,20817
|
|
72
72
|
canvas_cli/apps/run_plugins/__init__.py,sha256=iAMgX_6D3CdjQodGx_azwhSjouaxquOm8Z8QVXnlTFE,117
|
|
73
|
-
canvas_cli/apps/run_plugins/run_plugins.py,sha256=
|
|
74
|
-
canvas_cli/templates/plugins/application/cookiecutter.json,sha256=cI4Wpj68TkKeBP3P16PrjKacTHzsTIpl_rDdzyUpwz4,129
|
|
75
|
-
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json,sha256=-mfqhpljpF4eE8g05pIEnZMt_bDUdhgIBtuQd-devv8,945
|
|
76
|
-
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
77
|
-
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
|
-
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/my_application.py,sha256=rTUUycVOZEj5B4CoBqWMl9T4xPIyTqxsWGzXfAd6huY,584
|
|
79
|
-
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/assets/python-logo.png,sha256=QAipNLQVwVK_pRO-PpsF3kISrkFUmifyxsjUGzokpNo,20637
|
|
80
|
-
canvas_cli/templates/plugins/default/cookiecutter.json,sha256=dWEB3wJ8U4bko8jX26PgLLg_jgWlafLTNqsGnY1PUcg,124
|
|
81
|
-
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json,sha256=3a5PsKPZcENxRd0FYG9AnXNOKlpzP-hQua4HohIL3v0,638
|
|
82
|
-
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
83
|
-
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
-
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/protocols/my_protocol.py,sha256=fKLLcOIwvSWenW8-7tr8VqnF4Iox_5wU9V-Qw9UySsA,2381
|
|
73
|
+
canvas_cli/apps/run_plugins/run_plugins.py,sha256=5JbO0l4f2xtJbSTomJgKUqj416sT9GcDF0TrDg2JZYQ,3275
|
|
85
74
|
canvas_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
75
|
canvas_cli/utils/context/__init__.py,sha256=HhYvI-hydP0mV18nJiU7uo5gk0yN7EYNgouxieoGDOE,102
|
|
87
76
|
canvas_cli/utils/context/context.py,sha256=wk4TxlslF52uD9nXcEZ1eY8L1rcEHk7k-6YBVwaWnVY,5191
|
|
@@ -223,6 +212,10 @@ canvas_sdk/questionnaires/__init__.py,sha256=Pe3R-RY70AOllljO_caNAPpHpJfA3ML5zmw
|
|
|
223
212
|
canvas_sdk/questionnaires/utils.py,sha256=avDMUcQ5XmLfhlBpPJNMtNrVqrPy7g7HtqbSzztuIeQ,3666
|
|
224
213
|
canvas_sdk/templates/__init__.py,sha256=LtUmLDsGSTq249T6sA7PUzkl2OfCAyvtIOGPNHr6wTo,83
|
|
225
214
|
canvas_sdk/templates/utils.py,sha256=J-wDQ9ijOr_jz1SJxjpyZgR7JwXqtGMSS5IperLi2qE,1475
|
|
215
|
+
canvas_sdk/test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
216
|
+
canvas_sdk/test_utils/factories/__init__.py,sha256=mtd247ZUaW3-w5RfuXGMC2iM8XYLnxXUIzbDWqSvo7o,171
|
|
217
|
+
canvas_sdk/test_utils/factories/patient.py,sha256=KKHUcgqu3r3sOc21gQ5p__71zBLm9QZysW5TSnJ631U,1138
|
|
218
|
+
canvas_sdk/test_utils/factories/user.py,sha256=QLxilvK22fHSsGFxe1j354VdMrdquO8nyWApdXXdS8Q,306
|
|
226
219
|
canvas_sdk/utils/__init__.py,sha256=PADveJMf-BtOapK5Cczy-nOuZzVh-HT2H6sfJW_SK9U,198
|
|
227
220
|
canvas_sdk/utils/db.py,sha256=wOZORBfyBVxQkay5vyEtQ4i0KVCfASFGREmecHMR8DE,409
|
|
228
221
|
canvas_sdk/utils/http.py,sha256=BUpDcc8s_MVViiM9Y3FOAMZWW8bj2a8zIuQCl8bXt_s,10729
|
|
@@ -261,7 +254,7 @@ canvas_sdk/v1/data/message.py,sha256=EKc2kkWGuzrgBH3sDUa9jiNpRP7J1ZdWE6BnPhY2gvs
|
|
|
261
254
|
canvas_sdk/v1/data/note.py,sha256=ZZN0CoQbXmXxzo1cb7bpiXBcy7kLpTzZueGgdd6H95Q,9301
|
|
262
255
|
canvas_sdk/v1/data/observation.py,sha256=l1EXGQ8FBBInxQthsmY1Oms9v73ni_J7SAP5M8Hmkvg,3881
|
|
263
256
|
canvas_sdk/v1/data/organization.py,sha256=gt0KZC1hklwWf57vCxs2K0qPnLpMyyNFWNL7-WZ6AqI,1159
|
|
264
|
-
canvas_sdk/v1/data/patient.py,sha256=
|
|
257
|
+
canvas_sdk/v1/data/patient.py,sha256=7D_bxeCAoA_I8-2ZLzAMkCrV9ziZ_-IND9uOq0Wp83I,10515
|
|
265
258
|
canvas_sdk/v1/data/patient_consent.py,sha256=zx1C__E6On-MC7OnTgEeP50tn77R6UXw15wC125jDog,2704
|
|
266
259
|
canvas_sdk/v1/data/payment_collection.py,sha256=h17qmQkCCwSdukIeeYMqBHZnpxpahOa03ckMHmgZV-I,954
|
|
267
260
|
canvas_sdk/v1/data/payor_specific_charge.py,sha256=TtByy7-QF00PfONC-QDo2kkxRAOdLaRjkhssQBPvZjw,770
|
|
@@ -275,8 +268,8 @@ canvas_sdk/v1/data/service_provider.py,sha256=LQfQ0esg97NpmSYnftsWqYUJyJIQOZKmpZ
|
|
|
275
268
|
canvas_sdk/v1/data/staff.py,sha256=iVqBXwANp1qYEL39DIpveKbuqd1nw1oSDQ917xw8xgk,8302
|
|
276
269
|
canvas_sdk/v1/data/task.py,sha256=9jSqr7e7ODJTs4hanWsOD-1Rb8gByJOuLta90V9V_hY,3480
|
|
277
270
|
canvas_sdk/v1/data/team.py,sha256=J4s98sS7UuUw9jJCe-_7eikr1dV4SQARDzK5oxmsO_o,2832
|
|
278
|
-
canvas_sdk/v1/data/user.py,sha256=
|
|
279
|
-
canvas_sdk/v1/data/utils.py,sha256=
|
|
271
|
+
canvas_sdk/v1/data/user.py,sha256=82jnqVrYeWo2eBXY_MH2r75LZo5EKgY9kalahrUl0_s,511
|
|
272
|
+
canvas_sdk/v1/data/utils.py,sha256=t_Q2wpTnvJBx3AJeGD_7ymLNQG63J3E3LU5uq_O99bQ,899
|
|
280
273
|
canvas_sdk/value_set/__init__.py,sha256=YYXr5tEQlnwMgTXJbOG963tKSPiUOM4mUkX8wuiqp7U,17
|
|
281
274
|
canvas_sdk/value_set/custom.py,sha256=smf5fnPwuW-7H_B49CUfLWnq1QH-PhItvYw0siVsu-o,20173
|
|
282
275
|
canvas_sdk/value_set/hcc2018.py,sha256=MMudhqOOgvedh8-dDfC_JbHJn-sNRbk8Q357agD_Pww,2198496
|
|
@@ -317,7 +310,21 @@ protobufs/canvas_generated/messages/plugins.proto,sha256=xJyEeTwM6wWja3vGECLsIzf
|
|
|
317
310
|
protobufs/canvas_generated/services/plugin_runner.proto,sha256=PZ0Ts11b9tdA5Gkg2M05JVEKAm0R4LFEwrGRS-TQ16E,466
|
|
318
311
|
pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
319
312
|
pubsub/pubsub.py,sha256=PHIvJ5SD3M-jQSYeGGSj1FuG6CvP6BQffAoGax9Uudk,1423
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
313
|
+
canvas_cli/templates/plugins/application/cookiecutter.json,sha256=cI4Wpj68TkKeBP3P16PrjKacTHzsTIpl_rDdzyUpwz4,129
|
|
314
|
+
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/CANVAS_MANIFEST.json,sha256=-mfqhpljpF4eE8g05pIEnZMt_bDUdhgIBtuQd-devv8,945
|
|
315
|
+
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
316
|
+
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
317
|
+
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/applications/my_application.py,sha256=rTUUycVOZEj5B4CoBqWMl9T4xPIyTqxsWGzXfAd6huY,584
|
|
318
|
+
canvas_cli/templates/plugins/application/{{ cookiecutter.__project_slug }}/assets/python-logo.png,sha256=QAipNLQVwVK_pRO-PpsF3kISrkFUmifyxsjUGzokpNo,20637
|
|
319
|
+
canvas_cli/templates/plugins/default/cookiecutter.json,sha256=LbcuMaWxrLats1BffuDkk2192qNTgA-NU6sAnsFyj8M,201
|
|
320
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/pyproject.toml,sha256=ogQ94GnE-Aj2KJbiTevwnomqKdZUeBZfIOTcU_KPPdI,422
|
|
321
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
322
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/tests/test_models.py,sha256=IWe3IarNQuS350owh87X1qi-PcpaxzBBNCStr8LC3Co,980
|
|
323
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__package_name }}/CANVAS_MANIFEST.json,sha256=1keK1aJ-rJu4hm5ypqJT82hooCUTUOQ10dhzQ-mab7c,638
|
|
324
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__package_name }}/README.md,sha256=3QKoJQq3YmdplGnDOBMsLCJ3Ya1_aKjoz-QiWc4QfjA,291
|
|
325
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__package_name }}/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
326
|
+
canvas_cli/templates/plugins/default/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__package_name }}/protocols/my_protocol.py,sha256=fKLLcOIwvSWenW8-7tr8VqnF4Iox_5wU9V-Qw9UySsA,2381
|
|
327
|
+
canvas-0.58.1.dist-info/METADATA,sha256=3X9kEv7rwoBL0FHJVrtoGNhJYPYU_lojLmyJxgVY6n0,4758
|
|
328
|
+
canvas-0.58.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
329
|
+
canvas-0.58.1.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
|
|
330
|
+
canvas-0.58.1.dist-info/RECORD,,
|
|
@@ -1,16 +1,102 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import django.db
|
|
6
|
+
import typer
|
|
7
|
+
from django.core.management import call_command
|
|
8
|
+
|
|
9
|
+
import settings
|
|
1
10
|
from plugin_runner.plugin_runner import main as run_server
|
|
2
11
|
|
|
3
12
|
|
|
4
|
-
|
|
13
|
+
@contextmanager
|
|
14
|
+
def _patch_default_db_connection() -> Generator[None, None, None]:
|
|
15
|
+
"""
|
|
16
|
+
Temporarily patch the 'default' Django DB connection to allow db writes.
|
|
17
|
+
"""
|
|
18
|
+
original_default = django.db.connections["default"]
|
|
19
|
+
try:
|
|
20
|
+
temp_handler = django.db.utils.ConnectionHandler(
|
|
21
|
+
{"default": settings.SQLITE_WRITE_MODE_DATABASE}
|
|
22
|
+
)
|
|
23
|
+
django.db.connections["default"] = temp_handler["default"]
|
|
24
|
+
yield
|
|
25
|
+
finally:
|
|
26
|
+
django.db.connections["default"] = original_default
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _run_db_seed_file(path: Path) -> None:
|
|
30
|
+
"""Run the database setup file to initialize the database."""
|
|
31
|
+
code = path.read_text()
|
|
32
|
+
exec_globals = {"__name__": "__main__"}
|
|
33
|
+
exec(code, exec_globals)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _reset_db(seed_file: Path | None = None) -> None:
|
|
37
|
+
"""Reset the database."""
|
|
38
|
+
settings.SQLITE_DB_PATH.unlink(missing_ok=True)
|
|
39
|
+
|
|
40
|
+
with _patch_default_db_connection():
|
|
41
|
+
call_command("migrate", run_syncdb=True, verbosity=0)
|
|
42
|
+
if seed_file:
|
|
43
|
+
_run_db_seed_file(seed_file)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_plugin(
|
|
47
|
+
plugin_directory: str = typer.Argument(..., help="Path to the plugin directory to run."),
|
|
48
|
+
db_seed_file: Path | None = typer.Option(
|
|
49
|
+
help="Path to the database seed file to use.",
|
|
50
|
+
default=None,
|
|
51
|
+
),
|
|
52
|
+
reset_db: bool = typer.Option(
|
|
53
|
+
help="Reset the database before running the plugin.", default=False
|
|
54
|
+
),
|
|
55
|
+
) -> None:
|
|
5
56
|
"""
|
|
6
57
|
Run the specified plugin for local development.
|
|
7
58
|
"""
|
|
8
|
-
|
|
59
|
+
_run_plugins([plugin_directory], db_seed_file=db_seed_file, reset_db=reset_db)
|
|
9
60
|
|
|
10
61
|
|
|
11
|
-
def run_plugins(
|
|
62
|
+
def run_plugins(
|
|
63
|
+
plugin_directories: list[str],
|
|
64
|
+
db_seed_file: Path | None = typer.Option(
|
|
65
|
+
help="Path to the database seed file to use.",
|
|
66
|
+
default=None,
|
|
67
|
+
),
|
|
68
|
+
reset_db: bool = typer.Option(
|
|
69
|
+
help="Reset the database before running the plugin(s).", default=False
|
|
70
|
+
),
|
|
71
|
+
) -> None:
|
|
12
72
|
"""
|
|
13
73
|
Run the specified plugins for local development.
|
|
14
74
|
"""
|
|
75
|
+
_run_plugins(plugin_directories, db_seed_file=db_seed_file, reset_db=reset_db)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _run_plugins(
|
|
79
|
+
plugin_directories: list[str], db_seed_file: Path | None = None, reset_db: bool = False
|
|
80
|
+
) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Run the specified plugins for local development.
|
|
83
|
+
"""
|
|
84
|
+
if db_seed_file:
|
|
85
|
+
if not db_seed_file.exists():
|
|
86
|
+
raise typer.BadParameter(f"Database setup file not found: {db_seed_file.resolve()}")
|
|
87
|
+
if not db_seed_file.is_file():
|
|
88
|
+
raise typer.BadParameter(
|
|
89
|
+
f"Database setup file is not a regular file: {db_seed_file.resolve()}"
|
|
90
|
+
)
|
|
91
|
+
if not db_seed_file.suffix == ".py":
|
|
92
|
+
raise typer.BadParameter("Database setup file must be a Python script (.py)")
|
|
93
|
+
|
|
94
|
+
if settings.CANVAS_SDK_DB_BACKEND != "sqlite3":
|
|
95
|
+
raise typer.BadParameter(
|
|
96
|
+
"Database backend must be 'sqlite3' for local plugin development. Please unset 'DATABASE_URL' env var."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if db_seed_file or reset_db:
|
|
100
|
+
_reset_db(seed_file=db_seed_file)
|
|
101
|
+
|
|
15
102
|
run_server(plugin_directories)
|
|
16
|
-
return
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"project_name": "My Cool Plugin",
|
|
3
|
-
"__project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '
|
|
3
|
+
"__project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}",
|
|
4
|
+
"__package_name": "{{ cookiecutter.__project_slug.replace('-', '_') }}"
|
|
4
5
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
authors = [
|
|
3
|
+
{email = "engineering@canvasmedical.com", name = "Canvas Team"}
|
|
4
|
+
]
|
|
5
|
+
dependencies = [
|
|
6
|
+
"canvas[test-utils]"
|
|
7
|
+
]
|
|
8
|
+
description = "Some description of your project."
|
|
9
|
+
license = "MIT"
|
|
10
|
+
name = "{{ cookiecutter.__project_slug }}"
|
|
11
|
+
readme = "{{cookiecutter.__package_name }}/README.md"
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
version = "0.1.0"
|
|
14
|
+
|
|
15
|
+
[tool.pytest.ini_options]
|
|
16
|
+
python_files = ["*_tests.py", "test_*.py", "tests.py"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# To run the tests, use the command `pytest` in the terminal or uv run pytest.
|
|
2
|
+
# Each test is wrapped inside a transaction that is rolled back at the end of the test.
|
|
3
|
+
# If you want to modify which files are used for testing, check the [tool.pytest.ini_options] section in pyproject.toml.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.test_utils.factories import PatientFactory
|
|
7
|
+
from canvas_sdk.v1.data.discount import Discount
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# You can use a factory to create a patient instance for testing purposes.
|
|
11
|
+
def test_factory() -> None:
|
|
12
|
+
"""Test that a patient can be created using the PatientFactory."""
|
|
13
|
+
patient = PatientFactory.create()
|
|
14
|
+
assert patient.id is not None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# if a factory is not available, you can create an instance manually with the data model directly.
|
|
18
|
+
def test_model() -> None:
|
|
19
|
+
"""Test that a Discount instance can be created."""
|
|
20
|
+
Discount.objects.create(name="10%", adjustment_group="30", adjustment_code="CO", discount=0.10)
|
|
21
|
+
assert Discount.objects.first().pk is not None
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sdk_version": "0.1.4",
|
|
3
3
|
"plugin_version": "0.0.1",
|
|
4
|
-
"name": "{{ cookiecutter.
|
|
4
|
+
"name": "{{ cookiecutter.__package_name }}",
|
|
5
5
|
"description": "Edit the description in CANVAS_MANIFEST.json",
|
|
6
6
|
"components": {
|
|
7
7
|
"protocols": [
|
|
8
8
|
{
|
|
9
|
-
"class": "{{ cookiecutter.
|
|
9
|
+
"class": "{{ cookiecutter.__package_name }}.protocols.my_protocol:Protocol",
|
|
10
10
|
"description": "A protocol that does xyz..."
|
|
11
11
|
}
|
|
12
12
|
],
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from factory.fuzzy import FuzzyDate
|
|
5
|
+
|
|
6
|
+
from canvas_sdk.test_utils.factories.user import CanvasUserFactory
|
|
7
|
+
from canvas_sdk.v1.data import Patient, PatientAddress
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PatientAddressFactory(factory.django.DjangoModelFactory[PatientAddress]):
|
|
11
|
+
"""Factory for creating a PatientAddress."""
|
|
12
|
+
|
|
13
|
+
class Meta:
|
|
14
|
+
model = PatientAddress
|
|
15
|
+
|
|
16
|
+
line1 = "1234 Main Street"
|
|
17
|
+
line2 = "Apt 3"
|
|
18
|
+
city = "San Francisco"
|
|
19
|
+
district = "Sunset"
|
|
20
|
+
state_code = "CA"
|
|
21
|
+
postal_code = "94112"
|
|
22
|
+
country = "USA"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PatientFactory(factory.django.DjangoModelFactory[Patient]):
|
|
26
|
+
"""Factory for creating a Patient."""
|
|
27
|
+
|
|
28
|
+
class Meta:
|
|
29
|
+
model = Patient
|
|
30
|
+
|
|
31
|
+
birth_date = FuzzyDate(
|
|
32
|
+
start_date=datetime.date.today() - datetime.timedelta(days=100 * 365),
|
|
33
|
+
end_date=datetime.date.today() - datetime.timedelta(days=10 * 365),
|
|
34
|
+
)
|
|
35
|
+
first_name = factory.Faker("first_name")
|
|
36
|
+
middle_name = factory.Faker("first_name")
|
|
37
|
+
last_name = factory.Faker("last_name")
|
|
38
|
+
addresses = factory.RelatedFactory(PatientAddressFactory, "patient")
|
|
39
|
+
user = factory.SubFactory(CanvasUserFactory)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
|
|
3
|
+
from canvas_sdk.v1.data import CanvasUser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CanvasUserFactory(factory.django.DjangoModelFactory[CanvasUser]):
|
|
7
|
+
"""Factory for creating a CanvasUser."""
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
model = CanvasUser
|
|
11
|
+
|
|
12
|
+
email = factory.Faker("email")
|
|
13
|
+
phone_number = factory.Faker("phone_number")
|
canvas_sdk/v1/data/patient.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
import arrow
|
|
6
6
|
from django.contrib.postgres.fields import ArrayField
|
|
@@ -16,10 +16,7 @@ from canvas_sdk.v1.data.common import (
|
|
|
16
16
|
ContactPointSystem,
|
|
17
17
|
ContactPointUse,
|
|
18
18
|
)
|
|
19
|
-
from canvas_sdk.v1.data.utils import create_key
|
|
20
|
-
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from django_stubs_ext.db.models.manager import RelatedManager
|
|
19
|
+
from canvas_sdk.v1.data.utils import create_key, generate_mrn
|
|
23
20
|
|
|
24
21
|
|
|
25
22
|
class SexAtBirth(TextChoices):
|
|
@@ -51,45 +48,65 @@ class Patient(Model):
|
|
|
51
48
|
id = models.CharField(
|
|
52
49
|
max_length=32, db_column="key", unique=True, editable=False, default=create_key
|
|
53
50
|
)
|
|
54
|
-
first_name = models.CharField(max_length=255)
|
|
55
|
-
|
|
51
|
+
first_name = models.CharField(max_length=255, default="", blank=True)
|
|
52
|
+
middle_name = models.CharField(max_length=255, default="", blank=True)
|
|
53
|
+
last_name = models.CharField(max_length=255, default="", blank=True)
|
|
54
|
+
maiden_name = models.CharField(max_length=255, default="", blank=True)
|
|
56
55
|
birth_date = models.DateField()
|
|
57
56
|
business_line = models.ForeignKey(
|
|
58
|
-
"v1.BusinessLine",
|
|
57
|
+
"v1.BusinessLine",
|
|
58
|
+
on_delete=models.DO_NOTHING,
|
|
59
|
+
related_name="patients",
|
|
60
|
+
null=True,
|
|
59
61
|
)
|
|
60
62
|
sex_at_birth = models.CharField(choices=SexAtBirth.choices, max_length=3)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
suffix = models.CharField(max_length=100)
|
|
65
|
-
middle_name = models.CharField(max_length=255)
|
|
66
|
-
maiden_name = models.CharField(max_length=255)
|
|
63
|
+
|
|
64
|
+
prefix = models.CharField(max_length=100, blank=True, default="")
|
|
65
|
+
suffix = models.CharField(max_length=100, blank=True, default="")
|
|
67
66
|
nickname = models.CharField(max_length=255)
|
|
68
|
-
sexual_orientation_term = models.CharField(max_length=255)
|
|
69
|
-
sexual_orientation_code = models.CharField(max_length=255)
|
|
70
|
-
gender_identity_term = models.CharField(max_length=255)
|
|
71
|
-
gender_identity_code = models.CharField(max_length=255)
|
|
72
|
-
preferred_pronouns = models.CharField(max_length=255)
|
|
73
|
-
biological_race_codes = ArrayField(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
sexual_orientation_term = models.CharField(max_length=255, default="", blank=True)
|
|
68
|
+
sexual_orientation_code = models.CharField(max_length=255, default="", blank=True)
|
|
69
|
+
gender_identity_term = models.CharField(max_length=255, default="", blank=True)
|
|
70
|
+
gender_identity_code = models.CharField(max_length=255, default="", blank=True)
|
|
71
|
+
preferred_pronouns = models.CharField(max_length=255, default="", blank=True)
|
|
72
|
+
biological_race_codes = ArrayField(
|
|
73
|
+
models.CharField(max_length=100, default="", blank=True), default=list, blank=True
|
|
74
|
+
)
|
|
75
|
+
last_known_timezone = models.CharField(max_length=32, null=True, blank=True)
|
|
76
|
+
mrn = models.CharField(max_length=9, unique=True, default=generate_mrn)
|
|
77
|
+
active = models.BooleanField(default=True)
|
|
78
|
+
deceased = models.BooleanField(default=False)
|
|
79
|
+
deceased_datetime = models.DateTimeField(null=True, blank=True)
|
|
80
|
+
deceased_cause = models.TextField(default="", blank=True)
|
|
81
|
+
deceased_comment = models.TextField(default="", blank=True)
|
|
82
|
+
other_gender_description = models.CharField(max_length=255, blank=True, default="")
|
|
83
|
+
social_security_number = models.CharField(max_length=9, blank=True, default="")
|
|
84
|
+
administrative_note = models.TextField(null=True, blank=True)
|
|
85
|
+
clinical_note = models.TextField(default="", blank=True)
|
|
86
|
+
mothers_maiden_name = models.CharField(max_length=255, blank=True, default="")
|
|
87
|
+
multiple_birth_indicator = models.BooleanField(null=True, blank=True)
|
|
88
|
+
birth_order = models.BigIntegerField(null=True, blank=True)
|
|
89
|
+
|
|
90
|
+
default_location = models.ForeignKey(
|
|
91
|
+
"v1.PracticeLocation",
|
|
92
|
+
on_delete=models.SET_NULL,
|
|
93
|
+
default=None,
|
|
94
|
+
null=True,
|
|
95
|
+
blank=True,
|
|
96
|
+
related_name="default_patients",
|
|
97
|
+
)
|
|
98
|
+
default_provider = models.ForeignKey(
|
|
99
|
+
"v1.Staff",
|
|
100
|
+
on_delete=models.SET_NULL,
|
|
101
|
+
default=None,
|
|
102
|
+
null=True,
|
|
103
|
+
blank=True,
|
|
104
|
+
related_name="default_patients",
|
|
105
|
+
)
|
|
90
106
|
user = models.ForeignKey("v1.CanvasUser", on_delete=models.DO_NOTHING, null=True)
|
|
91
107
|
|
|
92
|
-
|
|
108
|
+
created = models.DateTimeField(auto_now_add=True)
|
|
109
|
+
modified = models.DateTimeField(auto_now=True)
|
|
93
110
|
|
|
94
111
|
@classmethod
|
|
95
112
|
def find(cls, id: str) -> Patient:
|
|
@@ -179,20 +196,23 @@ class PatientAddress(IdentifiableModel):
|
|
|
179
196
|
class Meta:
|
|
180
197
|
db_table = "canvas_sdk_data_api_patientaddress_001"
|
|
181
198
|
|
|
182
|
-
line1 = models.CharField(max_length=255)
|
|
183
|
-
line2 = models.CharField(max_length=255)
|
|
199
|
+
line1 = models.CharField(max_length=255, default="", blank=True)
|
|
200
|
+
line2 = models.CharField(max_length=255, default="", blank=True)
|
|
184
201
|
city = models.CharField(max_length=255)
|
|
185
|
-
district = models.CharField(max_length=255)
|
|
202
|
+
district = models.CharField(max_length=255, blank=True, default="")
|
|
186
203
|
state_code = models.CharField(max_length=2)
|
|
187
204
|
postal_code = models.CharField(max_length=255)
|
|
188
|
-
use = models.CharField(choices=AddressUse.choices, max_length=10)
|
|
189
|
-
type = models.CharField(choices=AddressType.choices, max_length=10)
|
|
190
|
-
longitude = models.FloatField()
|
|
191
|
-
latitude = models.FloatField()
|
|
192
|
-
start = models.DateField()
|
|
193
|
-
end = models.DateField()
|
|
205
|
+
use = models.CharField(choices=AddressUse.choices, max_length=10, default=AddressUse.HOME)
|
|
206
|
+
type = models.CharField(choices=AddressType.choices, max_length=10, default=AddressType.BOTH)
|
|
207
|
+
longitude = models.FloatField(null=True, blank=True)
|
|
208
|
+
latitude = models.FloatField(null=True, blank=True)
|
|
209
|
+
start = models.DateField(null=True, blank=True)
|
|
210
|
+
end = models.DateField(null=True, blank=True)
|
|
194
211
|
country = models.CharField(max_length=255)
|
|
195
|
-
state = models.CharField(
|
|
212
|
+
state = models.CharField(
|
|
213
|
+
choices=AddressState.choices, max_length=20, default=AddressState.ACTIVE
|
|
214
|
+
)
|
|
215
|
+
|
|
196
216
|
patient = models.ForeignKey(
|
|
197
217
|
"v1.Patient", on_delete=models.DO_NOTHING, related_name="addresses", null=True
|
|
198
218
|
)
|
canvas_sdk/v1/data/user.py
CHANGED
|
@@ -11,8 +11,8 @@ class CanvasUser(Model):
|
|
|
11
11
|
|
|
12
12
|
email = models.EmailField(db_column="email")
|
|
13
13
|
phone_number = models.CharField(db_column="phone_number", max_length=255)
|
|
14
|
-
last_invite_date_time = models.DateTimeField()
|
|
15
|
-
is_portal_registered = models.BooleanField()
|
|
14
|
+
last_invite_date_time = models.DateTimeField(null=True, blank=True)
|
|
15
|
+
is_portal_registered = models.BooleanField(default=False)
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
__exports__ = ("CanvasUser",)
|
canvas_sdk/v1/data/utils.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
1
3
|
import uuid
|
|
2
4
|
from collections.abc import Sequence
|
|
3
5
|
from decimal import Decimal
|
|
@@ -13,4 +15,18 @@ def create_key() -> str:
|
|
|
13
15
|
return uuid.uuid4().hex
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
def generate_mrn(length: int = 9, max_attempts: int = 100) -> str:
|
|
19
|
+
"""Generates a unique Medical Record Number (MRN) of specified length."""
|
|
20
|
+
from canvas_sdk.v1.data import Patient
|
|
21
|
+
|
|
22
|
+
digits = string.digits
|
|
23
|
+
|
|
24
|
+
for _ in range(max_attempts):
|
|
25
|
+
mrn = "".join(random.choices(digits, k=length))
|
|
26
|
+
if not Patient.objects.filter(mrn=mrn).exists():
|
|
27
|
+
return mrn
|
|
28
|
+
|
|
29
|
+
raise RuntimeError(f"Unable to generate a unique MRN after {max_attempts} attempts")
|
|
30
|
+
|
|
31
|
+
|
|
16
32
|
__exports__ = ()
|
settings.py
CHANGED
|
@@ -108,18 +108,21 @@ if CANVAS_SDK_DB_BACKEND == "postgres":
|
|
|
108
108
|
DATABASES = {"default": db_config}
|
|
109
109
|
|
|
110
110
|
elif CANVAS_SDK_DB_BACKEND == "sqlite3":
|
|
111
|
+
SQLITE_DB_PATH = BASE_DIR / "canvas_db.sqlite3"
|
|
112
|
+
SQLITE_WRITE_MODE_DATABASE = {
|
|
113
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
114
|
+
"NAME": str(SQLITE_DB_PATH),
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
DATABASES = {
|
|
112
118
|
"default": {
|
|
113
119
|
"ENGINE": "django.db.backends.sqlite3",
|
|
114
|
-
"NAME": f"file:{
|
|
120
|
+
"NAME": f"file:{SQLITE_DB_PATH}?mode=ro",
|
|
115
121
|
"OPTIONS": {"uri": True},
|
|
116
|
-
}
|
|
117
|
-
"default-write": {
|
|
118
|
-
"ENGINE": "django.db.backends.sqlite3",
|
|
119
|
-
"NAME": str(BASE_DIR / "db.sqlite3"),
|
|
120
|
-
},
|
|
122
|
+
}
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
|
|
123
126
|
else:
|
|
124
127
|
raise ImproperlyConfigured(
|
|
125
128
|
"Unsupported database backend specified in 'CANVAS_SDK_DB_BACKEND' setting. "
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|