Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: local development improvements #102

Merged
merged 16 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions canvas_cli/apps/emit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from canvas_cli.apps.emit.emit import emit

__all__ = ("emit",)
67 changes: 67 additions & 0 deletions canvas_cli/apps/emit/emit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import json
import random
from pathlib import Path
from typing import Annotated

import grpc
import typer

from canvas_generated.messages.events_pb2 import Event as PluginRunnerEvent
from canvas_generated.messages.events_pb2 import EventType as PluginRunnerEventType
from canvas_generated.services.plugin_runner_pb2_grpc import PluginRunnerStub


def emit(
event_fixture: str,
plugin_runner_port: Annotated[
str, typer.Option(help="Port of your locally running plugin runner")
] = "50051",
) -> None:
"""
Send an event fixture to your locally running plugin-runner process, and print any resultant effects.

Valid fixture files are newline-delimited JSON, with each containing the keys `EventType`, `target`, and `context`. Some fixture files are included in the canvas-plugins repo.
"""
# If an event fixture file exists at the specified path, use it.
# Otherwise, see if it represents an event that we have a Canvas-provided
# fixture for and use that.
event_fixture_path = Path(event_fixture)

if not event_fixture_path.exists():
candidate_built_in_fixture_path = (
Path(__file__).resolve().parent / "event_fixtures" / f"{event_fixture}.ndjson"
)
if candidate_built_in_fixture_path.exists():
event_fixture_path = candidate_built_in_fixture_path
else:
print(f"ERROR: No file found at location {event_fixture}.")
print(f"ERROR: No built-in fixture file found named {event_fixture}.ndjson.")
return

# Grab a random event from the fixture file ndjson
lines = event_fixture_path.read_text().splitlines()
myline = random.choice(lines)
event_data = json.loads(myline)
event = PluginRunnerEvent(
type=PluginRunnerEventType.Value(event_data["EventType"]),
target=event_data["target"],
context=event_data["context"],
)
with grpc.insecure_channel(f"localhost:{plugin_runner_port}") as channel:
stub = PluginRunnerStub(channel)
responses = stub.HandleEvent(event)

at_least_one_effect = False
try:
for response in responses:
for effect in response.effects:
at_least_one_effect = True
print(effect)

if not at_least_one_effect:
print("SUCCESS: No effects returned.")
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.UNAVAILABLE:
print(
f"ERROR: Couldn't make a connection to a plugin runner process at localhost:{plugin_runner_port}. Is it running?"
)
aduane marked this conversation as resolved.
Show resolved Hide resolved
File renamed without changes.
3 changes: 3 additions & 0 deletions canvas_cli/apps/run_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from canvas_cli.apps.run_plugins.run_plugins import run_plugin, run_plugins

__all__ = ("run_plugins", "run_plugin")
16 changes: 16 additions & 0 deletions canvas_cli/apps/run_plugins/run_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from plugin_runner.plugin_runner import run_server


def run_plugin(plugin_directory: str) -> None:
"""
Run the specified plugin for local development.
"""
return run_plugins([plugin_directory])


def run_plugins(plugin_directories: list[str]) -> None:
"""
Run the specified plugins for local development.
"""
run_server(plugin_directories)
return
46 changes: 8 additions & 38 deletions canvas_cli/main.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import importlib.metadata
import json
import random
from pathlib import Path
from typing import Annotated, Optional
from typing import Optional

import grpc
import typer

from canvas_cli.apps import plugin
from canvas_cli.apps.emit import emit
from canvas_cli.apps.logs import logs as logs_command
from canvas_cli.apps.run_plugins import run_plugin, run_plugins
from canvas_cli.utils.context import context
from canvas_generated.messages.events_pb2 import Event as PluginRunnerEvent
from canvas_generated.messages.events_pb2 import EventType as PluginRunnerEventType
from canvas_generated.services.plugin_runner_pb2_grpc import PluginRunnerStub

APP_NAME = "canvas_cli"

Expand All @@ -28,6 +24,11 @@
app.command(short_help="List all plugins from a Canvas instance")(plugin.list)
app.command(short_help="Validate the Canvas Manifest json file")(plugin.validate_manifest)
app.command(short_help="Listen and print log streams from a Canvas instance")(logs_command)
app.command(
short_help="Send an event fixture to your locally running plugin-runner process, and print any resultant effects."
)(emit)
app.command(short_help="Run the specified plugins for local development.")(run_plugins)
app.command(short_help="Run the specified plugin for local development.")(run_plugin)

# Our current version
__version__ = importlib.metadata.version("canvas")
Expand Down Expand Up @@ -59,37 +60,6 @@ def get_or_create_config_file() -> Path:
return config_path


@app.command()
def emit(
event_fixture: str,
plugin_runner_port: Annotated[
str, typer.Option(help="Port of your locally running plugin runner")
] = "50051",
) -> None:
"""
Grab an event from a fixture file and send it your locally running plugin-runner process.
Any resultant effects will be printed.

Valid fixture files are newline-delimited JSON, with each containing the keys `EventType`, `target`, and `context`. Some fixture files are included in the canvas-plugins repo.
"""
# Grab a random event from the fixture file ndjson
lines = Path(event_fixture).read_text().splitlines()
myline = random.choice(lines)
event_data = json.loads(myline)
event = PluginRunnerEvent(
type=PluginRunnerEventType.Value(event_data["EventType"]),
target=event_data["target"],
context=event_data["context"],
)
with grpc.insecure_channel(f"localhost:{plugin_runner_port}") as channel:
stub = PluginRunnerStub(channel)
responses = stub.HandleEvent(event)

for response in responses:
for effect in response.effects:
print(effect)


@app.callback()
def main(
version: Optional[bool] = typer.Option(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{% for _ in cookiecutter.__project_slug %}={% endfor %}
{{ cookiecutter.__project_slug }}
{% for _ in cookiecutter.__project_slug %}={% endfor %}

Expand Down
4 changes: 2 additions & 2 deletions canvas_generated/messages/events_pb2.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions canvas_generated/messages/events_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class EventType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
PROTOCOL_OVERRIDE_DELETED: _ClassVar[EventType]
TASK_COMMENT_UPDATED: _ClassVar[EventType]
TASK_COMMENT_DELETED: _ClassVar[EventType]
DEVICE_CREATED: _ClassVar[EventType]
DEVICE_UPDATED: _ClassVar[EventType]
OBSERVATION_CREATED: _ClassVar[EventType]
OBSERVATION_UPDATED: _ClassVar[EventType]
PRE_COMMAND_ORIGINATE: _ClassVar[EventType]
POST_COMMAND_ORIGINATE: _ClassVar[EventType]
PRE_COMMAND_UPDATE: _ClassVar[EventType]
Expand Down Expand Up @@ -628,6 +632,10 @@ PROTOCOL_OVERRIDE_UPDATED: EventType
PROTOCOL_OVERRIDE_DELETED: EventType
TASK_COMMENT_UPDATED: EventType
TASK_COMMENT_DELETED: EventType
DEVICE_CREATED: EventType
DEVICE_UPDATED: EventType
OBSERVATION_CREATED: EventType
OBSERVATION_UPDATED: EventType
PRE_COMMAND_ORIGINATE: EventType
POST_COMMAND_ORIGINATE: EventType
PRE_COMMAND_UPDATE: EventType
Expand Down
2 changes: 1 addition & 1 deletion canvas_sdk/v1/data/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def q_object(system: str, codes: Container[str]) -> Q:
"""
This method can be overridden if a Q object with different filtering options is needed.
"""
return Q(codings__system=system, codings_code_in=codes)
return Q(codings__system=system, codings__code__in=codes)


class ValueSetLookupByNameQuerySet(ValueSetLookupQuerySet):
Expand Down
27 changes: 27 additions & 0 deletions canvas_sdk/v1/data/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django.db import models

from canvas_sdk.v1.data.patient import Patient
from canvas_sdk.v1.data.user import CanvasUser


class Command(models.Model):
"""Command."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_commands_command_001"

id = models.UUIDField()
dbid = models.BigIntegerField(primary_key=True)
created = models.DateTimeField()
modified = models.DateTimeField()
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
state = models.CharField()
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING)
note_id = models.BigIntegerField()
schema_key = models.TextField()
data = models.JSONField()
origination_source = models.CharField()
44 changes: 44 additions & 0 deletions canvas_sdk/v1/data/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.db import models

from canvas_sdk.v1.data import Patient
from canvas_sdk.v1.data.user import CanvasUser


class Device(models.Model):
"""Device."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_device_001"

id = models.UUIDField()
dbid = models.BigIntegerField(primary_key=True)
created = models.DateTimeField()
modified = models.DateTimeField()
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="devices")
note_id = models.BigIntegerField()
deleted = models.BooleanField()
labeled_contains_NRL = models.BooleanField()
assigning_authority = models.CharField()
scoping_entity = models.CharField()
udi = models.CharField()
di = models.CharField()
issuing_agency = models.CharField()
lot_number = models.CharField()
brand_name = models.CharField()
mri_safety_status = models.CharField()
version_model_number = models.CharField()
company_name = models.CharField()
gmdnPTName = models.TextField()
status = models.CharField()
expiration_date = models.DateField()
expiration_date_original = models.CharField()
serial_number = models.CharField()
manufacturing_date_original = models.CharField()
manufacturing_date = models.DateField()
manufacturer = models.CharField()
procedure_id = models.BigIntegerField()
117 changes: 117 additions & 0 deletions canvas_sdk/v1/data/observation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from django.db import models

from canvas_sdk.v1.data.base import CommittableModelManager, ValueSetLookupQuerySet
from canvas_sdk.v1.data.patient import Patient
from canvas_sdk.v1.data.user import CanvasUser


class ObservationQuerySet(ValueSetLookupQuerySet):
"""ObservationQuerySet."""

pass


class Observation(models.Model):
"""Observation."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_observation_001"

objects = CommittableModelManager.from_queryset(ObservationQuerySet)()

id = models.UUIDField()
dbid = models.BigIntegerField(primary_key=True)
created = models.DateTimeField()
modified = models.DateTimeField()
originator = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
nmpsilva marked this conversation as resolved.
Show resolved Hide resolved
committer = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
entered_in_error = models.ForeignKey(CanvasUser, on_delete=models.DO_NOTHING)
deleted = models.BooleanField()
patient = models.ForeignKey(Patient, on_delete=models.DO_NOTHING, related_name="observations")
is_member_of = models.ForeignKey(
"self", on_delete=models.DO_NOTHING, null=True, related_name="members"
)
category = models.CharField()
units = models.TextField()
value = models.TextField()
note_id = models.BigIntegerField()
name = models.TextField()
effective_datetime = models.DateTimeField()


class ObservationCoding(models.Model):
"""ObservationCoding."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_observationcoding_001"

dbid = models.BigIntegerField(primary_key=True)
system = models.CharField()
version = models.CharField()
code = models.CharField()
display = models.CharField()
user_selected = models.BooleanField()
observation = models.ForeignKey(
Observation, on_delete=models.DO_NOTHING, related_name="codings"
)


class ObservationComponent(models.Model):
"""ObservationComponent."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_observationcomponent_001"

dbid = models.BigIntegerField(primary_key=True)
created = models.DateTimeField()
modified = models.DateTimeField()
observation = models.ForeignKey(
Observation, on_delete=models.DO_NOTHING, related_name="components"
)
value_quantity = models.TextField()
value_quantity_unit = models.TextField()
name = models.TextField()


class ObservationComponentCoding(models.Model):
"""ObservationComponentCoding."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_observationcomponentcoding_001"

dbid = models.BigIntegerField(primary_key=True)
system = models.CharField()
version = models.CharField()
code = models.CharField()
display = models.CharField()
user_selected = models.BooleanField()
observation_component = models.ForeignKey(
ObservationComponent, on_delete=models.DO_NOTHING, related_name="codings"
)


class ObservationValueCoding(models.Model):
"""ObservationValueCoding."""

class Meta:
managed = False
app_label = "canvas_sdk"
db_table = "canvas_sdk_data_api_observationvaluecoding_001"

dbid = models.BigIntegerField(primary_key=True)
system = models.CharField()
version = models.CharField()
code = models.CharField()
display = models.CharField()
user_selected = models.BooleanField()
observation = models.ForeignKey(
Observation, on_delete=models.DO_NOTHING, related_name="value_codings"
)
Loading
Loading