outerbounds 0.3.175rc0__py3-none-any.whl → 0.3.176rc1__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.
@@ -13,14 +13,11 @@ from .app_config import (
13
13
  AuthType,
14
14
  )
15
15
  from .cli_to_config import build_config_from_options
16
- from .utils import (
17
- CommaSeparatedListType,
18
- KVPairType,
19
- )
16
+ from .utils import CommaSeparatedListType, KVPairType, KVDictType
20
17
  from . import experimental
21
18
  from .validations import deploy_validations
22
19
  from .code_package import CodePackager
23
- from .capsule import Capsule
20
+ from .capsule import Capsule, list_and_filter_capsules
24
21
  import shlex
25
22
  import time
26
23
  import uuid
@@ -30,6 +27,8 @@ LOGGER_TIMESTAMP = "magenta"
30
27
  LOGGER_COLOR = "green"
31
28
  LOGGER_BAD_COLOR = "red"
32
29
 
30
+ NativeList = list
31
+
33
32
 
34
33
  def _logger(
35
34
  body="", system_msg=False, head="", bad=False, timestamp=True, nl=True, color=None
@@ -75,6 +74,36 @@ def _pre_create_debug(app_config: AppConfig, capsule: Capsule, state_dir: str):
75
74
  )
76
75
 
77
76
 
77
+ def print_table(data, headers):
78
+ """Print data in a formatted table."""
79
+ if not data:
80
+ return
81
+
82
+ # Calculate column widths
83
+ col_widths = [len(h) for h in headers]
84
+
85
+ # Calculate actual widths based on data
86
+ for row in data:
87
+ for i, cell in enumerate(row):
88
+ col_widths[i] = max(col_widths[i], len(str(cell)))
89
+
90
+ # Print header
91
+ header_row = " | ".join(
92
+ [headers[i].ljust(col_widths[i]) for i in range(len(headers))]
93
+ )
94
+ click.secho("-" * len(header_row), fg="yellow")
95
+ click.secho(header_row, fg="yellow", bold=True)
96
+ click.secho("-" * len(header_row), fg="yellow")
97
+
98
+ # Print data rows
99
+ for row in data:
100
+ formatted_row = " | ".join(
101
+ [str(row[i]).ljust(col_widths[i]) for i in range(len(row))]
102
+ )
103
+ click.secho(formatted_row, fg="green", bold=True)
104
+ click.secho("-" * len(header_row), fg="yellow")
105
+
106
+
78
107
  @click.group()
79
108
  def cli():
80
109
  """Outerbounds CLI tool."""
@@ -231,6 +260,18 @@ def common_deploy_options(func):
231
260
  help="Maximum number of replicas to deploy",
232
261
  default=None,
233
262
  )
263
+ @click.option(
264
+ "--description",
265
+ type=str,
266
+ help="The description of the app to deploy.",
267
+ default=None,
268
+ )
269
+ @click.option(
270
+ "--app-type",
271
+ type=str,
272
+ help="The type of app to deploy.",
273
+ default=None,
274
+ )
234
275
  @wraps(func)
235
276
  def wrapper(*args, **kwargs):
236
277
  return func(*args, **kwargs)
@@ -478,6 +519,139 @@ def deploy(ctx, command, **options):
478
519
  raise e
479
520
 
480
521
 
522
+ def _parse_capsule_table(filtered_capsules):
523
+ headers = ["Name", "ID", "Ready", "App Type", "Port", "Tags", "URL"]
524
+ table_data = []
525
+
526
+ for capsule in filtered_capsules:
527
+ spec = capsule.get("spec", {})
528
+ status = capsule.get("status", {})
529
+ cap_id = capsule.get("id")
530
+
531
+ display_name = spec.get("displayName", "")
532
+ ready = str(status.get("readyToServeTraffic", False))
533
+ auth_type = spec.get("authConfig", {}).get("authType", "")
534
+ port = str(spec.get("port", ""))
535
+ tags_str = ", ".join(
536
+ [f"{tag['key']}={tag['value']}" for tag in spec.get("tags", [])]
537
+ )
538
+ url = status.get("accessInfo", {}).get("outOfClusterURL", "")
539
+
540
+ table_data.append(
541
+ [
542
+ display_name,
543
+ cap_id,
544
+ ready,
545
+ auth_type,
546
+ port,
547
+ tags_str,
548
+ f"https://{url}",
549
+ ]
550
+ )
551
+ return headers, table_data
552
+
553
+
554
+ @app.command(help="List apps in the Outerbounds Platform.")
555
+ @click.option("--project", type=str, help="Filter apps by project")
556
+ @click.option("--branch", type=str, help="Filter apps by branch")
557
+ @click.option("--name", type=str, help="Filter apps by name")
558
+ @click.option(
559
+ "--tag",
560
+ "tags",
561
+ type=KVDictType,
562
+ help="Filter apps by tag. Format KEY=VALUE. Example --tag foo=bar --tag x=y. If multiple tags are provided, the app must match all of them.",
563
+ multiple=True,
564
+ )
565
+ @click.option(
566
+ "--format",
567
+ type=click.Choice(["json", "text"]),
568
+ help="Format the output",
569
+ default="text",
570
+ )
571
+ @click.option(
572
+ "--auth-type", type=click.Choice(AuthType.enums()), help="Filter apps by Auth type"
573
+ )
574
+ @click.pass_context
575
+ def list(ctx, project, branch, name, tags, format, auth_type):
576
+ """List apps in the Outerbounds Platform."""
577
+
578
+ filtered_capsules = list_and_filter_capsules(
579
+ ctx.obj.api_url, ctx.obj.perimeter, project, branch, name, tags, auth_type, None
580
+ )
581
+ if format == "json":
582
+ click.echo(json.dumps(filtered_capsules, indent=4))
583
+ else:
584
+ headers, table_data = _parse_capsule_table(filtered_capsules)
585
+ print_table(table_data, headers)
586
+
587
+
588
+ @app.command(help="Delete an app/apps from the Outerbounds Platform.")
589
+ @click.option("--name", type=str, help="Filter app to delete by name")
590
+ @click.option("--id", "cap_id", type=str, help="Filter app to delete by id")
591
+ @click.option("--project", type=str, help="Filter apps to delete by project")
592
+ @click.option("--branch", type=str, help="Filter apps to delete by branch")
593
+ @click.option(
594
+ "--tag",
595
+ "tags",
596
+ multiple=True,
597
+ type=KVPairType,
598
+ help="Filter apps to delete by tag. Format KEY=VALUE. Example --tag foo=bar --tag x=y. If multiple tags are provided, the app must match all of them.",
599
+ )
600
+ @click.pass_context
601
+ def delete(ctx, name, cap_id, project, branch, tags):
602
+
603
+ """Delete an app/apps from the Outerbounds Platform."""
604
+ # Atleast one of the args need to be provided
605
+ if not any(
606
+ [
607
+ name is not None,
608
+ cap_id is not None,
609
+ project is not None,
610
+ branch is not None,
611
+ len(tags) != 0,
612
+ ]
613
+ ):
614
+ raise AppConfigError(
615
+ "Atleast one of the options need to be provided. You can use --name, --id, --project, --branch, --tag"
616
+ )
617
+
618
+ filtered_capsules = list_and_filter_capsules(
619
+ ctx.obj.api_url, ctx.obj.perimeter, project, branch, name, tags, None, cap_id
620
+ )
621
+
622
+ headers, table_data = _parse_capsule_table(filtered_capsules)
623
+ click.secho("The following apps will be deleted:", fg="red", bold=True)
624
+ print_table(table_data, headers)
625
+
626
+ # Confirm the deletion
627
+ confirm = click.prompt(
628
+ click.style(
629
+ "💊 Are you sure you want to delete these apps?", fg="red", bold=True
630
+ ),
631
+ default="no",
632
+ type=click.Choice(["yes", "no"]),
633
+ )
634
+ if confirm == "no":
635
+ exit(1)
636
+
637
+ def item_show_func(x):
638
+ if not x:
639
+ return None
640
+ name = x.get("spec", {}).get("displayName", "")
641
+ id = x.get("id", "")
642
+ return click.style("💊 deleting %s [%s]" % (name, id), fg="red", bold=True)
643
+
644
+ with click.progressbar(
645
+ filtered_capsules,
646
+ label=click.style("💊 Deleting apps...", fg="red", bold=True),
647
+ fill_char=click.style("█", fg="red", bold=True),
648
+ empty_char=click.style("░", fg="red", bold=True),
649
+ item_show_func=item_show_func,
650
+ ) as bar:
651
+ for capsule in bar:
652
+ Capsule.delete(capsule.get("id"), ctx.obj.api_url, ctx.obj.perimeter)
653
+
654
+
481
655
  @app.command(help="Run an app locally (for testing).")
482
656
  @common_run_options
483
657
  @click.pass_context
@@ -172,10 +172,16 @@ class CapsuleInput:
172
172
  _scheduling_config["computePools"] = [
173
173
  {"name": x} for x in app_config.get_state("compute_pools")
174
174
  ]
175
-
175
+ _description = app_config.get_state("description")
176
+ _app_type = app_config.get_state("app_type")
177
+ _final_info = {}
178
+ if _description:
179
+ _final_info["description"] = _description
180
+ if _app_type:
181
+ _final_info["endpointType"] = _app_type
176
182
  return {
177
183
  "perimeter": app_config.get_state("perimeter"),
178
- # "environmentPath": "environments/python3.9",
184
+ **_final_info,
179
185
  "codePackagePath": app_config.get_state("code_package_url"),
180
186
  "image": app_config.get_state("image"),
181
187
  "resourceIntegrations": [
@@ -264,9 +270,10 @@ def get_capsule(capsule_id: str, api_url: str, request_headers: dict):
264
270
 
265
271
 
266
272
  def delete_capsule(capsule_id: str, api_url: str, request_headers: dict):
273
+ _url = os.path.join(api_url, capsule_id)
267
274
  response = safe_requests_wrapper(
268
275
  requests.delete,
269
- os.path.join(api_url, capsule_id),
276
+ _url,
270
277
  headers=request_headers,
271
278
  retryable_status_codes=[409], # todo : verify me
272
279
  )
@@ -278,12 +285,112 @@ def delete_capsule(capsule_id: str, api_url: str, request_headers: dict):
278
285
  return response.json()
279
286
 
280
287
 
288
+ def list_capsules(api_url: str, request_headers: dict):
289
+ response = safe_requests_wrapper(
290
+ requests.get,
291
+ api_url,
292
+ headers=request_headers,
293
+ )
294
+ if response.status_code >= 400:
295
+ raise TODOException(
296
+ f"Failed to list capsules: {response.status_code} {response.text}"
297
+ )
298
+ return response.json()
299
+
300
+
301
+ def list_and_filter_capsules(
302
+ api_url, perimeter, project, branch, name, tags, auth_type, capsule_id
303
+ ):
304
+ capsules = Capsule.list(api_url, perimeter)
305
+
306
+ def _tags_match(tags, key, value):
307
+ for t in tags:
308
+ if t["key"] == key and t["value"] == value:
309
+ return True
310
+ return False
311
+
312
+ def _all_tags_match(tags, tags_to_match):
313
+ for t in tags_to_match:
314
+ if _tags_match(tags, t["key"], t["value"]):
315
+ return True
316
+ return False
317
+
318
+ def _filter_capsules(capsules, project, branch, name, tags, auth_type, capsule_id):
319
+ _filtered_capsules = []
320
+ for capsule in capsules:
321
+ set_tags = capsule.get("spec", {}).get("tags", [])
322
+ display_name = capsule.get("spec", {}).get("displayName", None)
323
+ set_id = capsule.get("id", None)
324
+ set_auth_type = (
325
+ capsule.get("spec", {}).get("authConfig", {}).get("authType", None)
326
+ )
327
+
328
+ if auth_type and set_auth_type != auth_type:
329
+ continue
330
+ if project and not _tags_match(set_tags, "project", project):
331
+ continue
332
+ if branch and not _tags_match(set_tags, "branch", branch):
333
+ continue
334
+ if name and display_name != name:
335
+ continue
336
+ if tags and not _all_tags_match(set_tags, tags):
337
+ continue
338
+ if capsule_id and set_id != capsule_id:
339
+ continue
340
+
341
+ _filtered_capsules.append(capsule)
342
+ return _filtered_capsules
343
+
344
+ return _filter_capsules(
345
+ capsules, project, branch, name, tags, auth_type, capsule_id
346
+ )
347
+
348
+
281
349
  class Capsule:
282
350
 
283
351
  status: CapsuleStateMachine
284
352
 
285
353
  identifier = None
286
354
 
355
+ @classmethod
356
+ def list(cls, base_url: str, perimeter: str):
357
+ base_url = cls._create_base_url(base_url, perimeter)
358
+ from metaflow.metaflow_config import SERVICE_HEADERS
359
+
360
+ request_headers = {
361
+ **{"Content-Type": "application/json", "Connection": "keep-alive"},
362
+ **(SERVICE_HEADERS or {}),
363
+ }
364
+ _capsules = list_capsules(base_url, request_headers)
365
+ if "capsules" not in _capsules:
366
+ raise TODOException(f"Failed to list capsules")
367
+ return _capsules.get("capsules", [])
368
+
369
+ @classmethod
370
+ def delete(cls, identifier: str, base_url: str, perimeter: str):
371
+ base_url = cls._create_base_url(base_url, perimeter)
372
+ from metaflow.metaflow_config import SERVICE_HEADERS
373
+
374
+ request_headers = {
375
+ **{"Content-Type": "application/json", "Connection": "keep-alive"},
376
+ **(SERVICE_HEADERS or {}),
377
+ }
378
+ return delete_capsule(identifier, base_url, request_headers)
379
+
380
+ @classmethod
381
+ def _create_base_url(
382
+ cls,
383
+ base_url: str,
384
+ perimeter: str,
385
+ ):
386
+ return os.path.join(
387
+ base_url,
388
+ "v1",
389
+ "perimeters",
390
+ perimeter,
391
+ "capsules",
392
+ )
393
+
287
394
  # TODO: Current default timeout is very large of 5 minutes. Ideally we should have finished the deployed in less than 1 minutes.
288
395
  def __init__(
289
396
  self,
@@ -293,12 +400,9 @@ class Capsule:
293
400
  debug_dir: Optional[str] = None,
294
401
  ):
295
402
  self._app_config = app_config
296
- self._base_url = os.path.join(
403
+ self._base_url = self._create_base_url(
297
404
  base_url,
298
- "v1",
299
- "perimeters",
300
405
  app_config.get_state("perimeter"),
301
- "capsules",
302
406
  )
303
407
  self._create_timeout = create_timeout
304
408
  self._debug_dir = debug_dir
@@ -365,9 +469,3 @@ class Capsule:
365
469
  state_machine.check_for_debug(self._debug_dir)
366
470
 
367
471
  return capsule_response
368
-
369
- def list(self):
370
- return list_capsules(self._base_url, self._request_headers)
371
-
372
- def delete(self):
373
- return delete_capsule(self.identifier, self._base_url, self._request_headers)
@@ -6,7 +6,7 @@ def build_config_from_options(options):
6
6
  config = {}
7
7
 
8
8
  # Set basic fields
9
- for key in ["name", "port", "image", "compute_pools"]:
9
+ for key in ["name", "port", "image", "compute_pools", "description", "app_type"]:
10
10
  if options.get(key):
11
11
  config[key] = options[key]
12
12
 
@@ -33,11 +33,20 @@ properties:
33
33
  example:
34
34
  - foo: bar
35
35
  - x: y
36
+ description: # Only used in `deploy` command
37
+ allow_union: true
38
+ type: string
39
+ description: The description of the app to deploy.
40
+ example: "This is a description of my app."
41
+ app_type: # Only used in `deploy` command
42
+ allow_union: true
43
+ type: string
44
+ description: The User defined type of app to deploy. Its only used for bookkeeping purposes.
45
+ example: "MyCustomAgent"
36
46
  image: # Only used in `deploy` command
37
47
  allow_union: true # We will overrwite the image if specified on the CLI.
38
48
  type: string
39
49
  description: The Docker image to deploy with the App.
40
- example: "python:3.10-slim"
41
50
  secrets: # Used in `run` command
42
51
  allow_union: true
43
52
  type: array
outerbounds/apps/utils.py CHANGED
@@ -18,6 +18,31 @@ class MaximumRetriesExceeded(Exception):
18
18
  return f"Maximum retries exceeded for {self.url}[{self.method}] {self.status_code} {self.text}"
19
19
 
20
20
 
21
+ class KeyValueDictPair(click.ParamType):
22
+ name = "KV-DICT-PAIR"
23
+
24
+ def convert(self, value, param, ctx):
25
+ # Parse a string of the form KEY=VALUE into a dict {KEY: VALUE}
26
+ if len(value.split("=", 1)) != 2:
27
+ self.fail(
28
+ f"Invalid format for {value}. Expected format: KEY=VALUE", param, ctx
29
+ )
30
+
31
+ key, _value = value.split("=", 1)
32
+ try:
33
+ return {"key": key, "value": json.loads(_value)}
34
+ except json.JSONDecodeError:
35
+ return {"key": key, "value": _value}
36
+ except Exception as e:
37
+ self.fail(f"Invalid value for {value}. Error: {e}", param, ctx)
38
+
39
+ def __str__(self):
40
+ return repr(self)
41
+
42
+ def __repr__(self):
43
+ return "KV-PAIR"
44
+
45
+
21
46
  class KeyValuePair(click.ParamType):
22
47
  name = "KV-PAIR"
23
48
 
@@ -155,6 +180,7 @@ KVPairType = KeyValuePair()
155
180
  MetaflowArtifactType = MountMetaflowArtifact()
156
181
  SecretMountType = MountSecret()
157
182
  CommaSeparatedListType = CommaSeparatedList()
183
+ KVDictType = KeyValueDictPair()
158
184
 
159
185
 
160
186
  class TODOException(Exception):
@@ -410,7 +410,7 @@ def kill_process(config_dir=None, profile=None, port=-1, name=""):
410
410
  default=os.environ.get("METAFLOW_PROFILE", ""),
411
411
  help="The named metaflow profile in which your workstation exists",
412
412
  )
413
- def list(config_dir=None, profile=None):
413
+ def list_local(config_dir=None, profile=None):
414
414
  if "WORKSTATION_ID" not in os.environ:
415
415
  click.secho(
416
416
  "All outerbounds app commands can only be run from a workstation.",
@@ -7,6 +7,7 @@ from . import (
7
7
  tutorials_cli,
8
8
  fast_bakery_cli,
9
9
  secrets_cli,
10
+ flowprojects_cli,
10
11
  )
11
12
 
12
13
 
@@ -20,6 +21,7 @@ from . import (
20
21
  tutorials_cli.cli,
21
22
  fast_bakery_cli.cli,
22
23
  secrets_cli.cli,
24
+ flowprojects_cli.cli,
23
25
  ],
24
26
  )
25
27
  def cli(**kwargs):
@@ -0,0 +1,137 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ import requests
5
+
6
+ from ..utils import metaflowconfig
7
+ from outerbounds._vendor import click
8
+
9
+
10
+ @click.group()
11
+ def cli(**kwargs):
12
+ pass
13
+
14
+
15
+ @cli.group(help="Commands for pushing Deployments metadata.", hidden=True)
16
+ def flowproject(**kwargs):
17
+ pass
18
+
19
+
20
+ @flowproject.command()
21
+ @click.option(
22
+ "-d",
23
+ "--config-dir",
24
+ default=os.path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
25
+ help="Path to Metaflow configuration directory",
26
+ show_default=True,
27
+ )
28
+ @click.option(
29
+ "-p",
30
+ "--profile",
31
+ default=os.environ.get("METAFLOW_PROFILE", ""),
32
+ help="The named metaflow profile in which your workstation exists",
33
+ )
34
+ @click.option("--id", help="The ID for this deployment")
35
+ def get_metadata(config_dir, profile, id):
36
+ api_url = metaflowconfig.get_sanitized_url_from_config(
37
+ config_dir, profile, "OBP_API_SERVER"
38
+ )
39
+ perimeter = _get_perimeter()
40
+ headers = _get_request_headers()
41
+
42
+ project, branch = _parse_id(id)
43
+
44
+ # GET the latest flowproject config in order to modify it
45
+ # /v1/perimeters/:perimeter/:project/:branch/flowprojects/latest
46
+ response = requests.get(
47
+ url=f"{api_url}/v1/perimeters/{perimeter}/projects/{project}/branches/{branch}/latestflowproject",
48
+ headers=headers,
49
+ )
50
+ if response.status_code >= 500:
51
+ raise Exception("API request failed.")
52
+
53
+ body = response.json()
54
+ if response.status_code >= 400:
55
+ raise Exception("request failed: %s" % body)
56
+
57
+ out = json.dumps(body)
58
+
59
+ print(out, file=sys.stdout)
60
+
61
+
62
+ @flowproject.command()
63
+ @click.option(
64
+ "-d",
65
+ "--config-dir",
66
+ default=os.path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
67
+ help="Path to Metaflow configuration directory",
68
+ show_default=True,
69
+ )
70
+ @click.option(
71
+ "-p",
72
+ "--profile",
73
+ default=os.environ.get("METAFLOW_PROFILE", ""),
74
+ help="The named metaflow profile in which your workstation exists",
75
+ )
76
+ @click.argument("json_str")
77
+ def set_metadata(config_dir, profile, json_str):
78
+ api_url = metaflowconfig.get_sanitized_url_from_config(
79
+ config_dir, profile, "OBP_API_SERVER"
80
+ )
81
+
82
+ perimeter = _get_perimeter()
83
+ headers = _get_request_headers()
84
+ payload = json.loads(json_str)
85
+
86
+ # POST the updated flowproject config
87
+ # /v1/perimeters/:perimeter/flowprojects
88
+ response = requests.post(
89
+ url=f"{api_url}/v1/perimeters/{perimeter}/flowprojects",
90
+ json=payload,
91
+ headers=headers,
92
+ )
93
+ if response.status_code >= 500:
94
+ raise Exception("API request failed. %s" % response.text)
95
+
96
+ if response.status_code >= 400:
97
+ raise Exception("request failed: %s" % response.text)
98
+ body = response.json()
99
+
100
+ print(body, file=sys.stdout)
101
+
102
+
103
+ def _get_request_headers():
104
+ headers = {"Content-Type": "application/json", "Connection": "keep-alive"}
105
+ try:
106
+ from metaflow.metaflow_config import SERVICE_HEADERS
107
+
108
+ headers = {**headers, **(SERVICE_HEADERS or {})}
109
+ except ImportError:
110
+ headers = headers
111
+
112
+ return headers
113
+
114
+
115
+ def _get_perimeter():
116
+ # Get current perimeter
117
+ from metaflow_extensions.outerbounds.remote_config import init_config # type: ignore
118
+
119
+ conf = init_config()
120
+ if "OBP_PERIMETER" in conf:
121
+ perimeter = conf["OBP_PERIMETER"]
122
+ else:
123
+ # if the perimeter is not in metaflow config, try to get it from the environment
124
+ perimeter = os.environ.get("OBP_PERIMETER", None)
125
+ if perimeter is None:
126
+ raise Exception("Perimeter not found in config, but is required.")
127
+
128
+ return perimeter
129
+
130
+
131
+ def _parse_id(id: str):
132
+ parts = id.split("/")
133
+ if len(parts) != 2:
134
+ raise Exception("ID should consist of two parts: project/branch")
135
+
136
+ project, branch = parts
137
+ return project, branch
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: outerbounds
3
- Version: 0.3.175rc0
3
+ Version: 0.3.176rc1
4
4
  Summary: More Data Science, Less Administration
5
5
  License: Proprietary
6
6
  Keywords: data science,machine learning,MLOps
@@ -29,8 +29,8 @@ Requires-Dist: google-cloud-secret-manager (>=2.20.0,<3.0.0) ; extra == "gcp"
29
29
  Requires-Dist: google-cloud-storage (>=2.14.0,<3.0.0) ; extra == "gcp"
30
30
  Requires-Dist: metaflow-checkpoint (==0.2.1)
31
31
  Requires-Dist: ob-metaflow (==2.15.14.1)
32
- Requires-Dist: ob-metaflow-extensions (==1.1.162rc0)
33
- Requires-Dist: ob-metaflow-stubs (==6.0.3.175rc0)
32
+ Requires-Dist: ob-metaflow-extensions (==1.1.163rc1)
33
+ Requires-Dist: ob-metaflow-stubs (==6.0.3.176rc1)
34
34
  Requires-Dist: opentelemetry-distro (>=0.41b0) ; extra == "otel"
35
35
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.20.0) ; extra == "otel"
36
36
  Requires-Dist: opentelemetry-instrumentation-requests (>=0.41b0) ; extra == "otel"
@@ -40,26 +40,27 @@ outerbounds/_vendor/yaml/scanner.py,sha256=ZcI8IngR56PaQ0m27WU2vxCqmDCuRjz-hr7pi
40
40
  outerbounds/_vendor/yaml/serializer.py,sha256=8wFZRy9SsQSktF_f9OOroroqsh4qVUe53ry07P9UgCc,4368
41
41
  outerbounds/_vendor/yaml/tokens.py,sha256=JBSu38wihGr4l73JwbfMA7Ks1-X84g8-NskTz7KwPmA,2578
42
42
  outerbounds/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- outerbounds/apps/app_cli.py,sha256=oUsPGDr3jnW5l7h4Mp1I4WnRZAanXN1se9_aQ1ybhNQ,17559
43
+ outerbounds/apps/app_cli.py,sha256=kAKanVOVdBKI82ihAkRL2XcCLYyA-qkr_4FPS9xY-XI,23377
44
44
  outerbounds/apps/app_config.py,sha256=KBmW9grhiuG9XZG-R0GZkM-024cjj6ztGzOX_2wZW34,11291
45
45
  outerbounds/apps/artifacts.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- outerbounds/apps/capsule.py,sha256=n23uJi9877IVnArgqdq_ES_c2vAql8rzVm7jlTYUr1M,13351
47
- outerbounds/apps/cli_to_config.py,sha256=IjPHeH1lC7l9WPkNLQIWU7obRS3G0G8aTNVZemUvmuw,3168
46
+ outerbounds/apps/capsule.py,sha256=JssY9i43p_7NZlW7VfP4rET3dQt7V-ZYZQmCbcZCZ3U,16650
47
+ outerbounds/apps/cli_to_config.py,sha256=hV6rfPgCiAX03O363GkvdjSIJBt3-oSbL6F2sTUucFE,3195
48
48
  outerbounds/apps/code_package/__init__.py,sha256=8McF7pgx8ghvjRnazp2Qktlxi9yYwNiwESSQrk-2oW8,68
49
49
  outerbounds/apps/code_package/code_packager.py,sha256=SQDBXKwizzpag5GpwoZpvvkyPOodRSQwk2ecAAfO0HI,23316
50
50
  outerbounds/apps/code_package/examples.py,sha256=aF8qKIJxCVv_ugcShQjqUsXKKKMsm1oMkQIl8w3QKuw,4016
51
- outerbounds/apps/config_schema.yaml,sha256=D-qopf3mGZusa4n5GIbrssoJHS3v96_yponFEM127b4,8275
51
+ outerbounds/apps/config_schema.yaml,sha256=bN7mXlVddqN8G4jq6qBpVXl_qFJdOYrmj8E4OY23Rr8,8641
52
52
  outerbounds/apps/dependencies.py,sha256=SqvdFQdFZZW0wXX_CHMHCrfE0TwaRkTvGCRbQ2Mx3q0,3935
53
53
  outerbounds/apps/deployer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  outerbounds/apps/experimental/__init__.py,sha256=12L_FzZyzv162uo4I6cmlrxat7feUtIu_kxbObTJZTA,3059
55
55
  outerbounds/apps/secrets.py,sha256=27qf04lOBqRjvcswj0ldHOmntP2T6SEjtMJtkJQ_GUg,6100
56
- outerbounds/apps/utils.py,sha256=JymjsgpU0osF-eDvstFS9zkM7bqliJdqAEV7kAjqxCM,7298
56
+ outerbounds/apps/utils.py,sha256=xKaSJRmAYToyWvantixLES1vdi5vLLdoQ6yZXAv9IBw,8089
57
57
  outerbounds/apps/validations.py,sha256=AVEw9eCvkzqq1m5ZC8btaWrSR6kWYKzarELfrASuAwQ,1117
58
58
  outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
59
59
  outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
60
- outerbounds/command_groups/apps_cli.py,sha256=weXYgUbTVIxMSweLVdod_C1laiB32YKwYhf22OfqQJE,20815
61
- outerbounds/command_groups/cli.py,sha256=de4_QY1UeoKX6y-IXIbmklAi6bz0DsdBSmAoCg6lq1o,482
60
+ outerbounds/command_groups/apps_cli.py,sha256=ecXyLhGxjbct62iqviP9qBX8s4d-XG56ICpTM2h2etk,20821
61
+ outerbounds/command_groups/cli.py,sha256=I8b0zyY_xhZfmaLgJcgR98YXxgirkUK7xsjknEd4nfc,534
62
62
  outerbounds/command_groups/fast_bakery_cli.py,sha256=5kja7v6C651XAY6dsP_IkBPJQgfU4hA4S9yTOiVPhW0,6213
63
+ outerbounds/command_groups/flowprojects_cli.py,sha256=gFAA_zUIyhD092Hd7IW5InuIxOqdwRJsHgyWQjy8LZw,3792
63
64
  outerbounds/command_groups/local_setup_cli.py,sha256=tuuqJRXQ_guEwOuQSIf9wkUU0yg8yAs31myGViAK15s,36364
64
65
  outerbounds/command_groups/perimeters_cli.py,sha256=iF_Uw7ROiSctf6FgoJEy30iDBLVE1j9FKuR3shgJRmc,19050
65
66
  outerbounds/command_groups/secrets_cli.py,sha256=Vgn_aiTo76a0s5hCJhNWEOrCVhyYeivD08ooQxz0y7c,2952
@@ -71,7 +72,7 @@ outerbounds/utils/metaflowconfig.py,sha256=l2vJbgPkLISU-XPGZFaC8ZKmYFyJemlD6bwB-
71
72
  outerbounds/utils/schema.py,sha256=lMUr9kNgn9wy-sO_t_Tlxmbt63yLeN4b0xQXbDUDj4A,2331
72
73
  outerbounds/utils/utils.py,sha256=4Z8cszNob_8kDYCLNTrP-wWads_S_MdL3Uj3ju4mEsk,501
73
74
  outerbounds/vendor.py,sha256=gRLRJNXtZBeUpPEog0LOeIsl6GosaFFbCxUvR4bW6IQ,5093
74
- outerbounds-0.3.175rc0.dist-info/METADATA,sha256=c69bE_dY1O_vbcTLpQ0FWj2xL0rF1sIvuI-e3A8MTXA,1846
75
- outerbounds-0.3.175rc0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
76
- outerbounds-0.3.175rc0.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
77
- outerbounds-0.3.175rc0.dist-info/RECORD,,
75
+ outerbounds-0.3.176rc1.dist-info/METADATA,sha256=fldMIpG3wOYVBJ1_q1trcVRlHP4HwndipT4Yo73ly_4,1846
76
+ outerbounds-0.3.176rc1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
77
+ outerbounds-0.3.176rc1.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
78
+ outerbounds-0.3.176rc1.dist-info/RECORD,,