canvas 0.56.0__py3-none-any.whl → 0.57.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: canvas
3
- Version: 0.56.0
3
+ Version: 0.57.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
@@ -1,6 +1,6 @@
1
1
  settings.py,sha256=Yu4LE0PcohXgxqO8-W0b5uoXMJ20p-NpUj1lNFCdB8I,6296
2
2
  canvas_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- canvas_cli/main.py,sha256=L6JQkt1yxy30cA3-M9v7JD8WMW4i0M5GPr9kZetAito,2728
3
+ canvas_cli/main.py,sha256=INnlb8THwC0kbUJY94FVFq4UtDRGRyBBm6jASTzV0mU,3111
4
4
  canvas_cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  canvas_cli/apps/auth/__init__.py,sha256=gIwJ2qWvRlLqbiRkudrGqTKV-orlb8OTkG487qoRda4,105
6
6
  canvas_cli/apps/auth/storage.py,sha256=D5zZaZo_kDCxXmyjtHsPJOatD0fP5eO1OoHFdxwCxH4,1656
@@ -67,8 +67,8 @@ canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_CREATED.ndjson,sha256=p5XQozktqGA
67
67
  canvas_cli/apps/emit/event_fixtures/VITAL_SIGN_UPDATED.ndjson,sha256=Er0aUWIYkqbL9JdCaiNBuzizuHiOmFQr5Q2uYTtWj8I,36764
68
68
  canvas_cli/apps/logs/__init__.py,sha256=ehY9SRb6nBw81xZF50yyBlUZJtNR2VeVSNI5sFuWJ7o,64
69
69
  canvas_cli/apps/logs/logs.py,sha256=BFpZ-2OF2Rs1EMLePo5UjqC9fKQeqm8qZobNTFNCL_M,1972
70
- canvas_cli/apps/plugin/__init__.py,sha256=G_nLsu6cdko5OjatnbqUyEboGcNlGGLwpZmCBxMKdfo,236
71
- canvas_cli/apps/plugin/plugin.py,sha256=RqjNdP8Lzy5XaaQFx4eu1AnT2L_KHWQs2XvzRLCTdDs,17551
70
+ canvas_cli/apps/plugin/__init__.py,sha256=GB6hBwbajm5cOs-DbJh3q6smPfAaIMa99tSbkvDtbqs,341
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
73
  canvas_cli/apps/run_plugins/run_plugins.py,sha256=w4JTsXFiHckZahvbSOJAqZo3jWwedxlHLiXct1j_6cY,392
74
74
  canvas_cli/templates/plugins/application/cookiecutter.json,sha256=cI4Wpj68TkKeBP3P16PrjKacTHzsTIpl_rDdzyUpwz4,129
@@ -309,7 +309,7 @@ plugin_runner/exceptions.py,sha256=ltqn56SMTg-T5miSh5hux4ojwx0hZGSWaB7BxyAmcAo,5
309
309
  plugin_runner/generate_allowed_imports.py,sha256=LQuVxL_j5n0Sj-KgR4Q8D9mj0xfuDqzO69kBfZUqwGE,2565
310
310
  plugin_runner/installation.py,sha256=2KTDWWsQ97WIN2k9tC4d50zN77WWK_1D5obXlhLfWw8,8536
311
311
  plugin_runner/load_all_plugins.py,sha256=4T2gW2YljhIx4xfwf1c0F_8oIbE1ubsLj0ShkHRtlVY,5847
312
- plugin_runner/plugin_runner.py,sha256=oGYaYox_yG45N0IQ2DjgMMCpNEP_aGCwClIeadVFJ8Q,26402
312
+ plugin_runner/plugin_runner.py,sha256=RpcZoXXzGEflJGuHJaAOCP24mWb4fG1bLw3ryA6n88k,26623
313
313
  plugin_runner/sandbox.py,sha256=A5iaxQePwA5FERK232MArq2VzZzHKM5ydrfTDFeXcLg,30232
314
314
  protobufs/canvas_generated/messages/effects.proto,sha256=wQBRk0_XN8ssIjDtRttxsEG6pZJbYS8czGzdvpx3g6M,9756
315
315
  protobufs/canvas_generated/messages/events.proto,sha256=21qM3Ct9-iIG_T7O-XoFPWpnVafocYJ1KGiZ2nn1CFM,52047
@@ -317,7 +317,7 @@ protobufs/canvas_generated/messages/plugins.proto,sha256=xJyEeTwM6wWja3vGECLsIzf
317
317
  protobufs/canvas_generated/services/plugin_runner.proto,sha256=PZ0Ts11b9tdA5Gkg2M05JVEKAm0R4LFEwrGRS-TQ16E,466
318
318
  pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
319
319
  pubsub/pubsub.py,sha256=PHIvJ5SD3M-jQSYeGGSj1FuG6CvP6BQffAoGax9Uudk,1423
320
- canvas-0.56.0.dist-info/METADATA,sha256=-OXQE3WANI18Q4p8n403Ijs7sXqBaeuML85LW7ZKT68,4645
321
- canvas-0.56.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
322
- canvas-0.56.0.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
323
- canvas-0.56.0.dist-info/RECORD,,
320
+ canvas-0.57.1.dist-info/METADATA,sha256=MPYJjyR4mKewN9sbDrqzm3N0WSDY1ZY47gIRtCpqUbE,4645
321
+ canvas-0.57.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
322
+ canvas-0.57.1.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
323
+ canvas-0.57.1.dist-info/RECORD,,
@@ -4,8 +4,20 @@ from canvas_cli.apps.plugin.plugin import (
4
4
  init,
5
5
  install,
6
6
  list,
7
+ list_secrets,
8
+ set_secrets,
7
9
  uninstall,
8
10
  validate_manifest,
9
11
  )
10
12
 
11
- __all__ = ("uninstall", "enable", "disable", "init", "validate_manifest", "install", "list")
13
+ __all__ = (
14
+ "list_secrets",
15
+ "set_secrets",
16
+ "disable",
17
+ "enable",
18
+ "init",
19
+ "install",
20
+ "list",
21
+ "uninstall",
22
+ "validate_manifest",
23
+ )
@@ -1,9 +1,12 @@
1
1
  import ast
2
+ import base64
3
+ import builtins
2
4
  import json
3
5
  import tarfile
4
6
  import tempfile
5
7
  from collections.abc import Iterable
6
8
  from pathlib import Path
9
+ from pprint import pprint
7
10
  from typing import Any, cast
8
11
  from urllib.parse import urljoin
9
12
 
@@ -160,17 +163,17 @@ def _get_meta_properties(protocol_path: Path, classname: str) -> dict[str, str]:
160
163
  if isinstance(meta_b.value, ast.Constant):
161
164
  value = meta_b.value.value
162
165
  elif isinstance(meta_b.value, ast.List):
163
- value = [cast(ast.Constant, e).value for e in meta_b.value.elts]
166
+ value = [cast(ast.Constant, e).value for e in meta_b.value.elts] # type: ignore[assignment]
164
167
  elif isinstance(meta_b.value, ast.Dict):
165
168
  keys = meta_b.value.keys
166
169
  values = meta_b.value.values
167
- value = {
170
+ value = { # type: ignore[assignment]
168
171
  cast(ast.Constant, k).value: cast(ast.Constant, values[i]).value
169
172
  for i, k in enumerate(keys)
170
173
  }
171
174
  else:
172
175
  value = None
173
- meta[target_id] = value
176
+ meta[target_id] = value # type: ignore[assignment]
174
177
 
175
178
  return meta
176
179
 
@@ -204,6 +207,18 @@ def get_base_plugin_template_path(plugin_type: str) -> Path:
204
207
  return context.plugin_template_dir / context.default_plugin_template_name
205
208
 
206
209
 
210
+ def parse_secrets(secrets: builtins.list[str]) -> builtins.list[str]:
211
+ """Parse secrets from the command line, expecting them in the format Key=value."""
212
+ parsed_secrets = []
213
+
214
+ for secret in secrets:
215
+ if "=" not in secret:
216
+ raise typer.BadParameter(f"Invalid secret format: '{secret}'. Use key=value.")
217
+ parsed_secrets.append(secret)
218
+
219
+ return parsed_secrets
220
+
221
+
207
222
  def init(
208
223
  plugin_type: str = typer.Argument(
209
224
  "protocol",
@@ -222,6 +237,9 @@ def init(
222
237
 
223
238
  def install(
224
239
  plugin_name: Path = typer.Argument(..., help="Path to plugin to install"),
240
+ secrets: builtins.list[str] = typer.Option(
241
+ [], "--secret", callback=parse_secrets, help="Secrets to set, e.g. Key=value"
242
+ ),
225
243
  host: str | None = typer.Option(
226
244
  callback=get_default_host,
227
245
  help="Canvas instance to connect to",
@@ -243,6 +261,11 @@ def install(
243
261
  else:
244
262
  raise typer.BadParameter(f"Plugin '{plugin_name}' needs to be a valid directory")
245
263
 
264
+ encoded_secrets = []
265
+ for pair in secrets:
266
+ encoded = base64.b64encode(pair.encode()).decode()
267
+ encoded_secrets.append(("secret", encoded))
268
+
246
269
  print(f"Installing plugin: {built_package_path} into {host}")
247
270
 
248
271
  url = plugin_url(host)
@@ -250,10 +273,11 @@ def install(
250
273
  print(f"Posting {built_package_path.absolute()} to {url}")
251
274
 
252
275
  try:
276
+ data = [("is_enabled", True)] + encoded_secrets
253
277
  with open(built_package_path, "rb") as package:
254
278
  r = requests.post(
255
279
  url,
256
- data={"is_enabled": True},
280
+ data=data,
257
281
  files={"package": package},
258
282
  headers={"Authorization": f"Bearer {token}"},
259
283
  )
@@ -270,7 +294,7 @@ def install(
270
294
  package_name := _get_name_from_metadata(host, token, built_package_path)
271
295
  ):
272
296
  print(f"Plugin {package_name} already exists, updating instead...")
273
- update(package_name, built_package_path, is_enabled=True, host=host)
297
+ update(package_name, built_package_path, is_enabled=True, secrets=secrets, host=host)
274
298
  else:
275
299
  print(f"Status code {r.status_code}: {r.text}")
276
300
  raise typer.Exit(1)
@@ -427,6 +451,58 @@ def list(
427
451
  raise typer.Exit(1)
428
452
 
429
453
 
454
+ def list_secrets(
455
+ plugin: str = typer.Argument(..., help="Plugin name to list secrets for"),
456
+ host: str | None = typer.Option(
457
+ callback=get_default_host,
458
+ help="Canvas instance to connect to",
459
+ default=None,
460
+ ),
461
+ ) -> None:
462
+ """List all secrets from a plugin on a Canvas instance."""
463
+ if not host:
464
+ raise typer.BadParameter("Please specify a host or add one to the configuration file")
465
+
466
+ url = plugin_url(host, plugin, "metadata")
467
+
468
+ token = get_or_request_api_token(host)
469
+
470
+ try:
471
+ r = requests.get(
472
+ url,
473
+ headers={"Authorization": f"Bearer {token}"},
474
+ )
475
+ except requests.exceptions.RequestException:
476
+ print(f"Failed to connect to {host}")
477
+ raise typer.Exit(1) from None
478
+
479
+ if r.status_code == requests.codes.ok:
480
+ secrets = r.json().get("secrets", [])
481
+
482
+ if secrets:
483
+ pprint(secrets)
484
+ else:
485
+ print("No secrets configured.")
486
+ else:
487
+ print(f"Status code {r.status_code}: {r.text}")
488
+ raise typer.Exit(1)
489
+
490
+
491
+ def set_secrets(
492
+ plugin: str = typer.Argument(..., help="Plugin name to configure"),
493
+ host: str | None = typer.Option(
494
+ callback=get_default_host,
495
+ help="Canvas instance to connect to",
496
+ default=None,
497
+ ),
498
+ secrets: builtins.list[str] = typer.Argument(
499
+ ..., callback=parse_secrets, help="Secrets to set, e.g. Key=value"
500
+ ),
501
+ ) -> None:
502
+ """Configure plugin secrets on a Canvas instance."""
503
+ update(name=plugin, package_path=None, secrets=secrets, host=host, is_enabled=None)
504
+
505
+
430
506
  def validate_manifest(
431
507
  plugin_name: Path = typer.Argument(..., help="Path to plugin to validate"),
432
508
  ) -> None:
@@ -473,6 +549,9 @@ def update(
473
549
  is_enabled: bool | None = typer.Option(
474
550
  None, "--enable/--disable", show_default=False, help="Enable/disable the plugin"
475
551
  ),
552
+ secrets: builtins.list[str] = typer.Option(
553
+ [], "--secret", callback=parse_secrets, help="Secrets to set, e.g. Key=value"
554
+ ),
476
555
  host: str | None = typer.Option(
477
556
  callback=get_default_host,
478
557
  help="Canvas instance to connect to",
@@ -488,12 +567,27 @@ def update(
488
567
 
489
568
  token = get_or_request_api_token(host)
490
569
 
491
- print(f"Updating plugin {name} from {host} with {is_enabled=}, {package_path=}")
570
+ encoded_secrets = []
571
+ for pair in secrets:
572
+ encoded = base64.b64encode(pair.encode()).decode()
573
+ encoded_secrets.append(("secret", encoded))
574
+
575
+ args = [
576
+ *((f"is_enabled={is_enabled}",) if is_enabled is not None else ()),
577
+ *((f"package_path={package_path}",) if package_path is not None else ()),
578
+ *((f"secrets={','.join([s.split('=')[0] for s in secrets])}",) if secrets else ()),
579
+ ]
580
+
581
+ print(f"Updating plugin {name} from {host}" + (f" with {', '.join(args)}" if args else ""))
492
582
 
493
583
  url = plugin_url(host, name)
494
584
 
495
585
  try:
496
- data = {"is_enabled": is_enabled} if is_enabled is not None else {}
586
+ data = (
587
+ [("is_enabled", is_enabled)] + encoded_secrets
588
+ if is_enabled is not None
589
+ else encoded_secrets
590
+ )
497
591
  headers = {"Authorization": f"Bearer {token}"}
498
592
 
499
593
  if package_path:
@@ -515,7 +609,10 @@ def update(
515
609
  raise typer.Exit(1) from None
516
610
 
517
611
  if r.status_code == requests.codes.ok:
518
- print("New plugin version uploaded! Check logs for more details.")
612
+ if package_path:
613
+ print("New plugin version uploaded! Check logs for more details.")
614
+ elif secrets:
615
+ print("Plugin secrets successfully updated.")
519
616
 
520
617
  else:
521
618
  print(f"Status code {r.status_code}: {r.text}")
canvas_cli/main.py CHANGED
@@ -29,6 +29,16 @@ app.command(
29
29
  app.command(short_help="Run the specified plugins for local development.")(run_plugins)
30
30
  app.command(short_help="Run the specified plugin for local development.")(run_plugin)
31
31
 
32
+ # Config app
33
+ config_app = typer.Typer(help="Manage plugin secrets.", rich_markup_mode=None, add_completion=False)
34
+ app.add_typer(config_app, name="config")
35
+ config_app.command(name="list", short_help="List plugin secrets on a Canvas instance.")(
36
+ plugin.list_secrets
37
+ )
38
+ config_app.command(name="set", short_help="Set plugin secrets on a Canvas instance.")(
39
+ plugin.set_secrets
40
+ )
41
+
32
42
  # Our current version
33
43
  __version__ = importlib.metadata.version("canvas")
34
44
 
@@ -731,7 +731,14 @@ def main(specified_plugin_paths: list[str] | None = None) -> None:
731
731
  port = "50051"
732
732
 
733
733
  executor = ThreadPoolExecutor(max_workers=settings.PLUGIN_RUNNER_MAX_WORKERS)
734
- server = grpc.server(thread_pool=executor)
734
+ server = grpc.server(
735
+ thread_pool=executor,
736
+ options=(
737
+ # set max message lengths to 64mb
738
+ ("grpc.max_receive_message_length", 64 * 1024 * 1024),
739
+ ("grpc.max_send_message_length", 64 * 1024 * 1024),
740
+ ),
741
+ )
735
742
  server.add_insecure_port("127.0.0.1:" + port)
736
743
 
737
744
  add_PluginRunnerServicer_to_server(PluginRunner(), server)