outerbounds 0.3.90__py3-none-any.whl → 0.3.92__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,505 @@
1
+ import json
2
+ import os
3
+ from os import path
4
+ from outerbounds._vendor import click
5
+ import requests
6
+
7
+ from ..utils import metaflowconfig
8
+ from ..utils.schema import (
9
+ CommandStatus,
10
+ OuterboundsCommandResponse,
11
+ OuterboundsCommandStatus,
12
+ )
13
+
14
+
15
+ @click.group()
16
+ def cli(**kwargs):
17
+ pass
18
+
19
+
20
+ @click.group(help="Manage perimeters")
21
+ def app(**kwargs):
22
+ pass
23
+
24
+
25
+ @app.command(help="Start an app using a port and a name")
26
+ @click.option(
27
+ "-d",
28
+ "--config-dir",
29
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
30
+ help="Path to Metaflow configuration directory",
31
+ show_default=True,
32
+ )
33
+ @click.option(
34
+ "-p",
35
+ "--profile",
36
+ default=os.environ.get("METAFLOW_PROFILE", ""),
37
+ help="The named metaflow profile in which your workstation exists",
38
+ )
39
+ @click.option(
40
+ "--port",
41
+ required=True,
42
+ help="Port number where you want to start your app",
43
+ type=int,
44
+ )
45
+ @click.option(
46
+ "--name",
47
+ required=True,
48
+ help="Name of your app",
49
+ type=str,
50
+ )
51
+ @click.option(
52
+ "-o",
53
+ "--output",
54
+ default="",
55
+ help="Show output in the specified format.",
56
+ type=click.Choice(["json", ""]),
57
+ )
58
+ def start(config_dir=None, profile=None, port=-1, name="", output=""):
59
+ start_app_response = OuterboundsCommandResponse()
60
+
61
+ validate_workstation_step = CommandStatus(
62
+ "ValidateRunningOnWorkstation",
63
+ OuterboundsCommandStatus.OK,
64
+ "Command is being run on a workstation.",
65
+ )
66
+
67
+ list_workstations_step = CommandStatus(
68
+ "ListWorkstations",
69
+ OuterboundsCommandStatus.OK,
70
+ "List of workstations fetched.",
71
+ )
72
+
73
+ validate_port_exists = CommandStatus(
74
+ "ValidatePortExists",
75
+ OuterboundsCommandStatus.OK,
76
+ "Port exists on workstation",
77
+ )
78
+
79
+ start_app_step = CommandStatus(
80
+ "StartApp",
81
+ OuterboundsCommandStatus.OK,
82
+ f"App {name} started on port {port}!",
83
+ )
84
+
85
+ if "WORKSTATION_ID" not in os.environ:
86
+ validate_workstation_step.update(
87
+ OuterboundsCommandStatus.FAIL,
88
+ "All outerbounds app commands can only be run from a workstation.",
89
+ "",
90
+ )
91
+ start_app_response.add_step(validate_workstation_step)
92
+ click.secho(
93
+ "All outerbounds app commands can only be run from a workstation.",
94
+ fg="red",
95
+ err=True,
96
+ )
97
+
98
+ if output == "json":
99
+ click.echo(json.dumps(start_app_response.as_dict(), indent=4))
100
+ return
101
+
102
+ try:
103
+ try:
104
+ metaflow_token = metaflowconfig.get_metaflow_token_from_config(
105
+ config_dir, profile
106
+ )
107
+ api_url = metaflowconfig.get_sanitized_url_from_config(
108
+ config_dir, profile, "OBP_API_SERVER"
109
+ )
110
+
111
+ workstations_response = requests.get(
112
+ f"{api_url}/v1/workstations", headers={"x-api-key": metaflow_token}
113
+ )
114
+ workstations_response.raise_for_status()
115
+ start_app_response.add_step(list_workstations_step)
116
+ except:
117
+ click.secho("Failed to list workstations!", fg="red", err=True)
118
+ list_workstations_step.update(
119
+ OuterboundsCommandStatus.FAIL, "Failed to list workstations!", ""
120
+ )
121
+ start_app_response.add_step(list_workstations_step)
122
+ if output == "json":
123
+ click.echo(json.dumps(start_app_response.as_dict(), indent=4))
124
+ return
125
+
126
+ workstations_json = workstations_response.json()["workstations"]
127
+ for workstation in workstations_json:
128
+ if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
129
+ if "named_ports" in workstation["spec"]:
130
+ for named_port in workstation["spec"]["named_ports"]:
131
+ if int(named_port["port"]) == port:
132
+ start_app_response.add_step(validate_port_exists)
133
+ if named_port["enabled"] and named_port["name"] == name:
134
+ click.secho(
135
+ f"App {name} started on port {port}!",
136
+ fg="green",
137
+ err=True,
138
+ )
139
+ click.secho(
140
+ f"App URL: {api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{name}/",
141
+ fg="green",
142
+ err=True,
143
+ )
144
+ start_app_response.add_step(start_app_step)
145
+ if output == "json":
146
+ click.echo(
147
+ json.dumps(
148
+ start_app_response.as_dict(), indent=4
149
+ )
150
+ )
151
+ return
152
+ else:
153
+ try:
154
+ response = requests.put(
155
+ f"{api_url}/v1/workstations/update/{os.environ['WORKSTATION_ID']}/namedports",
156
+ headers={"x-api-key": metaflow_token},
157
+ json={
158
+ "port": port,
159
+ "name": name,
160
+ "enabled": True,
161
+ },
162
+ )
163
+
164
+ response.raise_for_status()
165
+ click.secho(
166
+ f"App {name} started on port {port}!",
167
+ fg="green",
168
+ err=True,
169
+ )
170
+ except:
171
+ click.secho(
172
+ f"Failed to start app {name} on port {port}!",
173
+ fg="red",
174
+ err=True,
175
+ )
176
+ start_app_step.update(
177
+ OuterboundsCommandStatus.FAIL,
178
+ f"Failed to start app {name} on port {port}!",
179
+ "",
180
+ )
181
+
182
+ start_app_response.add_step(start_app_step)
183
+ if output == "json":
184
+ click.echo(
185
+ json.dumps(
186
+ start_app_response.as_dict(), indent=4
187
+ )
188
+ )
189
+ return
190
+
191
+ click.secho(
192
+ f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
193
+ fg="red",
194
+ err=True,
195
+ )
196
+ validate_port_exists.update(
197
+ OuterboundsCommandStatus.FAIL,
198
+ f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
199
+ "",
200
+ )
201
+ start_app_response.add_step(validate_port_exists)
202
+ if output == "json":
203
+ click.echo(json.dumps(start_app_response.as_dict(), indent=4))
204
+ except Exception as e:
205
+ click.secho(f"Failed to start app {name} on port {port}!", fg="red", err=True)
206
+ start_app_step.update(
207
+ OuterboundsCommandStatus.FAIL,
208
+ f"Failed to start app {name} on port {port}!",
209
+ "",
210
+ )
211
+ start_app_response.add_step(start_app_step)
212
+ if output == "json":
213
+ click.secho(json.dumps(start_app_response.as_dict(), indent=4))
214
+
215
+
216
+ @app.command(help="Stop an app using its port number")
217
+ @click.option(
218
+ "-d",
219
+ "--config-dir",
220
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
221
+ help="Path to Metaflow configuration directory",
222
+ show_default=True,
223
+ )
224
+ @click.option(
225
+ "-p",
226
+ "--profile",
227
+ default=os.environ.get("METAFLOW_PROFILE", ""),
228
+ help="The named metaflow profile in which your workstation exists",
229
+ )
230
+ @click.option(
231
+ "--port",
232
+ required=True,
233
+ help="Port number where you want to start your app.",
234
+ type=int,
235
+ )
236
+ @click.option(
237
+ "-o",
238
+ "--output",
239
+ default="",
240
+ help="Show output in the specified format.",
241
+ type=click.Choice(["json", ""]),
242
+ )
243
+ def stop(config_dir=None, profile=None, port=-1, output=""):
244
+ stop_app_response = OuterboundsCommandResponse()
245
+
246
+ validate_workstation_step = CommandStatus(
247
+ "ValidateRunningOnWorkstation",
248
+ OuterboundsCommandStatus.OK,
249
+ "Command is being run on a workstation.",
250
+ )
251
+
252
+ list_workstations_step = CommandStatus(
253
+ "ListWorkstations",
254
+ OuterboundsCommandStatus.OK,
255
+ "List of workstations fetched.",
256
+ )
257
+
258
+ validate_port_exists = CommandStatus(
259
+ "ValidatePortExists",
260
+ OuterboundsCommandStatus.OK,
261
+ "Port exists on workstation",
262
+ )
263
+
264
+ stop_app_step = CommandStatus(
265
+ "StopApp",
266
+ OuterboundsCommandStatus.OK,
267
+ f"App stopped on port {port}!",
268
+ )
269
+
270
+ if "WORKSTATION_ID" not in os.environ:
271
+ validate_workstation_step.update(
272
+ OuterboundsCommandStatus.FAIL,
273
+ "All outerbounds app commands can only be run from a workstation.",
274
+ "",
275
+ )
276
+ stop_app_response.add_step(validate_workstation_step)
277
+ click.secho(
278
+ "All outerbounds app commands can only be run from a workstation.",
279
+ fg="red",
280
+ err=True,
281
+ )
282
+
283
+ if output == "json":
284
+ click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
285
+ return
286
+
287
+ try:
288
+ try:
289
+ metaflow_token = metaflowconfig.get_metaflow_token_from_config(
290
+ config_dir, profile
291
+ )
292
+ api_url = metaflowconfig.get_sanitized_url_from_config(
293
+ config_dir, profile, "OBP_API_SERVER"
294
+ )
295
+
296
+ workstations_response = requests.get(
297
+ f"{api_url}/v1/workstations", headers={"x-api-key": metaflow_token}
298
+ )
299
+ workstations_response.raise_for_status()
300
+ stop_app_response.add_step(list_workstations_step)
301
+ except:
302
+ click.secho("Failed to list workstations!", fg="red", err=True)
303
+ list_workstations_step.update(
304
+ OuterboundsCommandStatus.FAIL, "Failed to list workstations!", ""
305
+ )
306
+ stop_app_response.add_step(list_workstations_step)
307
+ if output == "json":
308
+ click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
309
+ return
310
+
311
+ workstations_json = workstations_response.json()["workstations"]
312
+ for workstation in workstations_json:
313
+ if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
314
+ if "named_ports" in workstation["spec"]:
315
+ for named_port in workstation["spec"]["named_ports"]:
316
+ if int(named_port["port"]) == port:
317
+ stop_app_response.add_step(validate_port_exists)
318
+ if not named_port["enabled"]:
319
+ click.secho(
320
+ f"App stopped on port {port}!", fg="green", err=True
321
+ )
322
+ stop_app_response.add_step(stop_app_step)
323
+ if output == "json":
324
+ click.echo(
325
+ json.dumps(
326
+ stop_app_response.as_dict(), indent=4
327
+ )
328
+ )
329
+ return
330
+ else:
331
+ try:
332
+ response = requests.put(
333
+ f"{api_url}/v1/workstations/update/{os.environ['WORKSTATION_ID']}/namedports",
334
+ headers={"x-api-key": metaflow_token},
335
+ json={
336
+ "port": port,
337
+ "name": named_port["name"],
338
+ "enabled": False,
339
+ },
340
+ )
341
+ response.raise_for_status()
342
+ click.secho(
343
+ f"App stopped on port {port}!",
344
+ fg="green",
345
+ err=True,
346
+ )
347
+ except:
348
+ click.secho(
349
+ f"Failed to stop app on port {port}!",
350
+ fg="red",
351
+ err=True,
352
+ )
353
+ stop_app_step.update(
354
+ OuterboundsCommandStatus.FAIL,
355
+ f"Failed to stop app on port {port}!",
356
+ "",
357
+ )
358
+
359
+ stop_app_response.add_step(stop_app_step)
360
+ if output == "json":
361
+ click.echo(
362
+ json.dumps(
363
+ stop_app_response.as_dict(), indent=4
364
+ )
365
+ )
366
+ return
367
+
368
+ click.secho(
369
+ f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
370
+ fg="red",
371
+ err=True,
372
+ )
373
+ validate_port_exists.update(
374
+ OuterboundsCommandStatus.FAIL,
375
+ f"Port {port} not found on workstation {os.environ['WORKSTATION_ID']}",
376
+ "",
377
+ )
378
+ stop_app_response.add_step(validate_port_exists)
379
+ if output == "json":
380
+ click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
381
+ except Exception as e:
382
+ click.secho(f"Failed to stop app on port {port}!", fg="red", err=True)
383
+ stop_app_step.update(
384
+ OuterboundsCommandStatus.FAIL, f"Failed to stop on port {port}!", ""
385
+ )
386
+ stop_app_response.add_step(stop_app_step)
387
+ if output == "json":
388
+ click.echo(json.dumps(stop_app_response.as_dict(), indent=4))
389
+
390
+
391
+ @app.command(help="Stop an app using its port number")
392
+ @click.option(
393
+ "-d",
394
+ "--config-dir",
395
+ default=path.expanduser(os.environ.get("METAFLOW_HOME", "~/.metaflowconfig")),
396
+ help="Path to Metaflow configuration directory",
397
+ show_default=True,
398
+ )
399
+ @click.option(
400
+ "-p",
401
+ "--profile",
402
+ default=os.environ.get("METAFLOW_PROFILE", ""),
403
+ help="The named metaflow profile in which your workstation exists",
404
+ )
405
+ @click.option(
406
+ "-o",
407
+ "--output",
408
+ default="",
409
+ help="Show output in the specified format.",
410
+ type=click.Choice(["json", ""]),
411
+ )
412
+ def list(config_dir=None, profile=None, output=""):
413
+ list_app_response = OuterboundsCommandResponse()
414
+
415
+ validate_workstation_step = CommandStatus(
416
+ "ValidateRunningOnWorkstation",
417
+ OuterboundsCommandStatus.OK,
418
+ "Command is being run on a workstation.",
419
+ )
420
+
421
+ list_workstations_step = CommandStatus(
422
+ "ListWorkstations",
423
+ OuterboundsCommandStatus.OK,
424
+ "List of workstations fetched.",
425
+ )
426
+
427
+ if "WORKSTATION_ID" not in os.environ:
428
+ validate_workstation_step.update(
429
+ OuterboundsCommandStatus.FAIL,
430
+ "All outerbounds app commands can only be run from a workstation.",
431
+ "",
432
+ )
433
+ list_app_response.add_step(validate_workstation_step)
434
+ click.secho(
435
+ "All outerbounds app commands can only be run from a workstation.",
436
+ fg="red",
437
+ err=True,
438
+ )
439
+
440
+ if output == "json":
441
+ click.echo(json.dumps(list_app_response.as_dict(), indent=4))
442
+ return
443
+
444
+ try:
445
+ try:
446
+ metaflow_token = metaflowconfig.get_metaflow_token_from_config(
447
+ config_dir, profile
448
+ )
449
+ api_url = metaflowconfig.get_sanitized_url_from_config(
450
+ config_dir, profile, "OBP_API_SERVER"
451
+ )
452
+
453
+ workstations_response = requests.get(
454
+ f"{api_url}/v1/workstations", headers={"x-api-key": metaflow_token}
455
+ )
456
+ workstations_response.raise_for_status()
457
+ list_app_response.add_step(list_workstations_step)
458
+ except:
459
+ click.secho("Failed to list workstations!", fg="red", err=True)
460
+ list_workstations_step.update(
461
+ OuterboundsCommandStatus.FAIL, "Failed to list workstations!", ""
462
+ )
463
+ list_app_response.add_step(list_workstations_step)
464
+ if output == "json":
465
+ click.echo(json.dumps(list_app_response.as_dict(), indent=4))
466
+ return
467
+
468
+ workstations_json = workstations_response.json()["workstations"]
469
+ for workstation in workstations_json:
470
+ if workstation["instance_id"] == os.environ["WORKSTATION_ID"]:
471
+ if "named_ports" in workstation["spec"]:
472
+ for named_port in workstation["spec"]["named_ports"]:
473
+ if named_port["enabled"]:
474
+ click.secho(
475
+ f"App Name: {named_port['name']}", fg="green", err=True
476
+ )
477
+ click.secho(
478
+ f"App Port on Workstation: {named_port['port']}",
479
+ fg="green",
480
+ err=True,
481
+ )
482
+ click.secho(f"App Status: Deployed", fg="green", err=True)
483
+ click.secho(
484
+ f"App URL: {api_url.replace('api', 'ui')}/apps/{os.environ['WORKSTATION_ID']}/{named_port['name']}/",
485
+ fg="green",
486
+ err=True,
487
+ )
488
+ else:
489
+ click.secho(
490
+ f"App Port on Workstation: {named_port['port']}",
491
+ fg="yellow",
492
+ err=True,
493
+ )
494
+ click.secho(
495
+ f"App Status: Not Deployed", fg="yellow", err=True
496
+ )
497
+
498
+ click.echo("\n", err=True)
499
+ except Exception as e:
500
+ click.secho(f"Failed to list apps!", fg="red", err=True)
501
+ if output == "json":
502
+ click.echo(json.dumps(list_app_response.as_dict(), indent=4))
503
+
504
+
505
+ cli.add_command(app, name="app")
@@ -2,11 +2,17 @@ from outerbounds._vendor import click
2
2
  from . import local_setup_cli
3
3
  from . import workstations_cli
4
4
  from . import perimeters_cli
5
+ from . import apps_cli
5
6
 
6
7
 
7
8
  @click.command(
8
9
  cls=click.CommandCollection,
9
- sources=[local_setup_cli.cli, workstations_cli.cli, perimeters_cli.cli],
10
+ sources=[
11
+ local_setup_cli.cli,
12
+ workstations_cli.cli,
13
+ perimeters_cli.cli,
14
+ apps_cli.cli,
15
+ ],
10
16
  )
11
17
  def cli(**kwargs):
12
18
  pass
@@ -59,14 +59,14 @@ class OuterboundsCommandResponse:
59
59
  if step.status == OuterboundsCommandStatus.FAIL:
60
60
  self.status = OuterboundsCommandStatus.FAIL
61
61
  self._code = 500
62
- self._message = "We found one or more errors with your installation."
62
+ self._message = "Encountered an error when trying to run command."
63
63
  elif (
64
64
  step.status == OuterboundsCommandStatus.WARN
65
65
  and self.status != OuterboundsCommandStatus.FAIL
66
66
  ):
67
67
  self.status = OuterboundsCommandStatus.WARN
68
68
  self._code = 200
69
- self._message = "We found one or more warnings with your installation."
69
+ self._message = "Encountered one or more warnings when running the command."
70
70
 
71
71
  def as_dict(self):
72
72
  self._data["steps"] = [step.as_dict() for step in self._steps]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: outerbounds
3
- Version: 0.3.90
3
+ Version: 0.3.92
4
4
  Summary: More Data Science, Less Administration
5
5
  License: Proprietary
6
6
  Keywords: data science,machine learning,MLOps
@@ -24,9 +24,9 @@ Requires-Dist: google-api-core (>=2.16.1,<3.0.0) ; extra == "gcp"
24
24
  Requires-Dist: google-auth (>=2.27.0,<3.0.0) ; extra == "gcp"
25
25
  Requires-Dist: google-cloud-secret-manager (>=2.20.0,<3.0.0) ; extra == "gcp"
26
26
  Requires-Dist: google-cloud-storage (>=2.14.0,<3.0.0) ; extra == "gcp"
27
- Requires-Dist: ob-metaflow (==2.12.12.0)
28
- Requires-Dist: ob-metaflow-extensions (==1.1.79)
29
- Requires-Dist: ob-metaflow-stubs (==5.2)
27
+ Requires-Dist: ob-metaflow (==2.12.17.1)
28
+ Requires-Dist: ob-metaflow-extensions (==1.1.82)
29
+ Requires-Dist: ob-metaflow-stubs (==5.5)
30
30
  Requires-Dist: opentelemetry-distro (==0.41b0)
31
31
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.20.0)
32
32
  Requires-Dist: opentelemetry-instrumentation-requests (==0.41b0)
@@ -41,17 +41,18 @@ outerbounds/_vendor/yaml/serializer.py,sha256=8wFZRy9SsQSktF_f9OOroroqsh4qVUe53r
41
41
  outerbounds/_vendor/yaml/tokens.py,sha256=JBSu38wihGr4l73JwbfMA7Ks1-X84g8-NskTz7KwPmA,2578
42
42
  outerbounds/cli_main.py,sha256=e9UMnPysmc7gbrimq2I4KfltggyU7pw59Cn9aEguVcU,74
43
43
  outerbounds/command_groups/__init__.py,sha256=QPWtj5wDRTINDxVUL7XPqG3HoxHNvYOg08EnuSZB2Hc,21
44
- outerbounds/command_groups/cli.py,sha256=sorDdQvmTPqIwfvgtuNLILelimXu5CknFnWQFsYFGHs,286
44
+ outerbounds/command_groups/apps_cli.py,sha256=XdGRitYWyKGDZXlGSbcP7U6F3DclqzgBG7ZGuZK8tzY,19361
45
+ outerbounds/command_groups/cli.py,sha256=3NyxnczfANtxKh-KnJHGWAEC5akdXux_hylfGXTs2A0,362
45
46
  outerbounds/command_groups/local_setup_cli.py,sha256=tuuqJRXQ_guEwOuQSIf9wkUU0yg8yAs31myGViAK15s,36364
46
47
  outerbounds/command_groups/perimeters_cli.py,sha256=mrJfFIRYFOjuiz-9h4OKg2JT8Utmbs72z6wvPzDss3s,18685
47
48
  outerbounds/command_groups/workstations_cli.py,sha256=V5Jbj1cVb4IRllI7fOgNgL6OekRpuFDv6CEhDb4xC6w,22016
48
49
  outerbounds/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  outerbounds/utils/kubeconfig.py,sha256=yvcyRXGR4AhQuqUDqmbGxEOHw5ixMFV0AZIDg1LI_Qo,7981
50
51
  outerbounds/utils/metaflowconfig.py,sha256=l2vJbgPkLISU-XPGZFaC8ZKmYFyJemlD6bwB-EKUsAw,5770
51
- outerbounds/utils/schema.py,sha256=cNlgjmteLPbDzSEUSQDsq8txdhMGyezSmM83jU3aa0w,2329
52
+ outerbounds/utils/schema.py,sha256=lMUr9kNgn9wy-sO_t_Tlxmbt63yLeN4b0xQXbDUDj4A,2331
52
53
  outerbounds/utils/utils.py,sha256=4Z8cszNob_8kDYCLNTrP-wWads_S_MdL3Uj3ju4mEsk,501
53
54
  outerbounds/vendor.py,sha256=gRLRJNXtZBeUpPEog0LOeIsl6GosaFFbCxUvR4bW6IQ,5093
54
- outerbounds-0.3.90.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
55
- outerbounds-0.3.90.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
56
- outerbounds-0.3.90.dist-info/METADATA,sha256=an69uvqpICHuiBfJVZJJrZU474ehFkjnnilY1jRLYFg,1632
57
- outerbounds-0.3.90.dist-info/RECORD,,
55
+ outerbounds-0.3.92.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
56
+ outerbounds-0.3.92.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
57
+ outerbounds-0.3.92.dist-info/METADATA,sha256=enOUPH2DJjaqvzpywbTNnD1aosIzZq8JlsBne9w84o8,1632
58
+ outerbounds-0.3.92.dist-info/RECORD,,