outerbounds 0.3.176rc4__py3-none-any.whl → 0.3.176rc6__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.
- outerbounds/apps/app_cli.py +1 -1
- outerbounds/apps/code_package/code_packager.py +64 -66
- {outerbounds-0.3.176rc4.dist-info → outerbounds-0.3.176rc6.dist-info}/METADATA +3 -3
- {outerbounds-0.3.176rc4.dist-info → outerbounds-0.3.176rc6.dist-info}/RECORD +6 -6
- {outerbounds-0.3.176rc4.dist-info → outerbounds-0.3.176rc6.dist-info}/WHEEL +0 -0
- {outerbounds-0.3.176rc4.dist-info → outerbounds-0.3.176rc6.dist-info}/entry_points.txt +0 -0
outerbounds/apps/app_cli.py
CHANGED
@@ -594,7 +594,7 @@ def list(ctx, project, branch, name, tags, format, auth_type):
|
|
594
594
|
"--tag",
|
595
595
|
"tags",
|
596
596
|
multiple=True,
|
597
|
-
type=
|
597
|
+
type=KVDictType,
|
598
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
599
|
)
|
600
600
|
@click.pass_context
|
@@ -7,6 +7,7 @@ from io import BytesIO
|
|
7
7
|
from typing import List, Tuple, Dict, Any, Optional, Callable, Union
|
8
8
|
|
9
9
|
from metaflow.datastore.content_addressed_store import ContentAddressedStore
|
10
|
+
from metaflow.util import to_unicode
|
10
11
|
from metaflow.metaflow_config import (
|
11
12
|
DATASTORE_SYSROOT_S3,
|
12
13
|
DATASTORE_SYSROOT_AZURE,
|
@@ -18,6 +19,58 @@ from metaflow.metaflow_config import (
|
|
18
19
|
CODE_PACKAGE_PREFIX = "apps-code-packages"
|
19
20
|
|
20
21
|
|
22
|
+
# this is os.walk(follow_symlinks=True) with cycle detection
|
23
|
+
def walk_without_cycles(top_root):
|
24
|
+
seen = set()
|
25
|
+
|
26
|
+
def _recurse(root):
|
27
|
+
for parent, dirs, files in os.walk(root):
|
28
|
+
for d in dirs:
|
29
|
+
path = os.path.join(parent, d)
|
30
|
+
if os.path.islink(path):
|
31
|
+
# Breaking loops: never follow the same symlink twice
|
32
|
+
#
|
33
|
+
# NOTE: this also means that links to sibling links are
|
34
|
+
# not followed. In this case:
|
35
|
+
#
|
36
|
+
# x -> y
|
37
|
+
# y -> oo
|
38
|
+
# oo/real_file
|
39
|
+
#
|
40
|
+
# real_file is only included twice, not three times
|
41
|
+
reallink = os.path.realpath(path)
|
42
|
+
if reallink not in seen:
|
43
|
+
seen.add(reallink)
|
44
|
+
for x in _recurse(path):
|
45
|
+
yield x
|
46
|
+
yield parent, files
|
47
|
+
|
48
|
+
for x in _recurse(top_root):
|
49
|
+
yield x
|
50
|
+
|
51
|
+
|
52
|
+
def symlink_friendly_walk(root, exclude_hidden=True, suffixes=None):
|
53
|
+
if suffixes is None:
|
54
|
+
suffixes = []
|
55
|
+
root = to_unicode(root) # handle files/folder with non ascii chars
|
56
|
+
prefixlen = len("%s/" % os.path.dirname(root))
|
57
|
+
for (
|
58
|
+
path,
|
59
|
+
files,
|
60
|
+
) in walk_without_cycles(root):
|
61
|
+
if exclude_hidden and "/." in path:
|
62
|
+
continue
|
63
|
+
# path = path[2:] # strip the ./ prefix
|
64
|
+
# if path and (path[0] == '.' or './' in path):
|
65
|
+
# continue
|
66
|
+
for fname in files:
|
67
|
+
if (fname[0] == "." and fname in suffixes) or (
|
68
|
+
fname[0] != "." and any(fname.endswith(suffix) for suffix in suffixes)
|
69
|
+
):
|
70
|
+
p = os.path.join(path, fname)
|
71
|
+
yield p, p[prefixlen:]
|
72
|
+
|
73
|
+
|
21
74
|
class CodePackager:
|
22
75
|
"""
|
23
76
|
A datastore-agnostic class for packaging code.
|
@@ -384,11 +437,13 @@ class CodePackager:
|
|
384
437
|
|
385
438
|
@staticmethod
|
386
439
|
def directory_walker(
|
387
|
-
root,
|
440
|
+
root,
|
441
|
+
exclude_hidden=True,
|
442
|
+
suffixes=None,
|
388
443
|
) -> List[Tuple[str, str]]:
|
389
444
|
"""
|
390
445
|
Walk a directory and yield tuples of (file_path, relative_arcname) for files
|
391
|
-
that match the given suffix filters.
|
446
|
+
that match the given suffix filters. It will follow symlinks, but not cycles.
|
392
447
|
|
393
448
|
This function is similar to MetaflowPackage._walk and handles symlinks safely.
|
394
449
|
|
@@ -400,8 +455,6 @@ class CodePackager:
|
|
400
455
|
Whether to exclude hidden files and directories (those starting with '.')
|
401
456
|
suffixes : List[str], optional
|
402
457
|
List of file suffixes to include (e.g. ['.py', '.txt'])
|
403
|
-
follow_symlinks : bool, default True
|
404
|
-
Whether to follow symlinks (with cycle detection)
|
405
458
|
|
406
459
|
Returns
|
407
460
|
-------
|
@@ -410,61 +463,12 @@ class CodePackager:
|
|
410
463
|
- file_path is the full path to the file
|
411
464
|
- relative_arcname is the path to use within the archive
|
412
465
|
"""
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
# Calculate the prefix length to strip from paths
|
420
|
-
prefixlen = len(os.path.dirname(root)) + 1 # +1 for the trailing slash
|
421
|
-
|
422
|
-
# Use a set to track visited symlinks to avoid cycles
|
423
|
-
seen = set()
|
424
|
-
|
425
|
-
def _walk_without_cycles(walk_root):
|
426
|
-
for parent, dirs, files in os.walk(walk_root, followlinks=follow_symlinks):
|
427
|
-
# If not following symlinks, we're done
|
428
|
-
if not follow_symlinks:
|
429
|
-
yield parent, files
|
430
|
-
continue
|
431
|
-
|
432
|
-
# When following symlinks, we need to check for cycles
|
433
|
-
for d_idx in range(
|
434
|
-
len(dirs) - 1, -1, -1
|
435
|
-
): # Iterate backwards to safely remove
|
436
|
-
d = dirs[d_idx]
|
437
|
-
path = os.path.join(parent, d)
|
438
|
-
if os.path.islink(path):
|
439
|
-
# Break cycles by never following the same symlink twice
|
440
|
-
reallink = os.path.realpath(path)
|
441
|
-
if reallink in seen:
|
442
|
-
# Remove from dirs to avoid following it
|
443
|
-
dirs.pop(d_idx)
|
444
|
-
else:
|
445
|
-
seen.add(reallink)
|
446
|
-
|
447
|
-
yield parent, files
|
448
|
-
|
449
|
-
# Build the list of path tuples
|
450
|
-
result = []
|
451
|
-
for path, files in _walk_without_cycles(root):
|
452
|
-
# Skip hidden directories if requested
|
453
|
-
if exclude_hidden and "/." in path:
|
454
|
-
continue
|
455
|
-
|
456
|
-
for fname in files:
|
457
|
-
# Skip hidden files if requested, unless they have a specified suffix
|
458
|
-
if (
|
459
|
-
(fname[0] == "." and fname in suffixes)
|
460
|
-
or (fname[0] != "." or not exclude_hidden)
|
461
|
-
and any(fname.endswith(suffix) for suffix in suffixes)
|
462
|
-
):
|
463
|
-
file_path = os.path.join(path, fname)
|
464
|
-
rel_path = file_path[prefixlen:]
|
465
|
-
result.append((file_path, rel_path))
|
466
|
-
|
467
|
-
return result
|
466
|
+
files = []
|
467
|
+
for file_path, rel_path in symlink_friendly_walk(
|
468
|
+
root, exclude_hidden, suffixes
|
469
|
+
):
|
470
|
+
files.append((file_path, rel_path))
|
471
|
+
return files
|
468
472
|
|
469
473
|
@staticmethod
|
470
474
|
def default_package_create(
|
@@ -515,7 +519,6 @@ class CodePackager:
|
|
515
519
|
path,
|
516
520
|
exclude_hidden=True,
|
517
521
|
suffixes=suffixes,
|
518
|
-
follow_symlinks=True,
|
519
522
|
):
|
520
523
|
tar.add(
|
521
524
|
file_path,
|
@@ -546,13 +549,12 @@ class CodePackager:
|
|
546
549
|
suffixes: Optional[List[str]] = None,
|
547
550
|
exclude_hidden: bool = True,
|
548
551
|
metadata: Optional[Dict[str, Any]] = None,
|
549
|
-
follow_symlinks: bool = True,
|
550
552
|
) -> bytes:
|
551
553
|
"""
|
552
554
|
Package a directory and all of its contents that match the given suffixes.
|
553
555
|
|
554
556
|
This is a convenience method that works similarly to MetaflowPackage._walk
|
555
|
-
to package a directory for deployment.
|
557
|
+
to package a directory for deployment. Will default follow_symlinks.
|
556
558
|
|
557
559
|
Parameters
|
558
560
|
----------
|
@@ -564,9 +566,6 @@ class CodePackager:
|
|
564
566
|
Whether to exclude hidden files and directories
|
565
567
|
metadata : Dict[str, Any], optional
|
566
568
|
Metadata to include in the package
|
567
|
-
follow_symlinks : bool, default True
|
568
|
-
Whether to follow symlinks when walking the directory
|
569
|
-
|
570
569
|
Returns
|
571
570
|
-------
|
572
571
|
bytes
|
@@ -602,7 +601,6 @@ class CodePackager:
|
|
602
601
|
directory_path,
|
603
602
|
exclude_hidden=exclude_hidden,
|
604
603
|
suffixes=suffixes,
|
605
|
-
follow_symlinks=follow_symlinks,
|
606
604
|
):
|
607
605
|
# Remove debug print statement
|
608
606
|
tar.add(file_path, arcname=rel_path, recursive=False, filter=no_mtime)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: outerbounds
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.176rc6
|
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.
|
33
|
-
Requires-Dist: ob-metaflow-stubs (==6.0.3.
|
32
|
+
Requires-Dist: ob-metaflow-extensions (==1.1.163rc7)
|
33
|
+
Requires-Dist: ob-metaflow-stubs (==6.0.3.176rc6)
|
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,13 +40,13 @@ 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=
|
43
|
+
outerbounds/apps/app_cli.py,sha256=hIyDEWDAwx5kVgSgjm0_p5GT-3LdKi51q45jwwktN_A,23456
|
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
46
|
outerbounds/apps/capsule.py,sha256=C0xxsGNmsBiwTdybS38jYfLn8VzMp0mQLI6pssha1UY,16965
|
47
47
|
outerbounds/apps/cli_to_config.py,sha256=hV6rfPgCiAX03O363GkvdjSIJBt3-oSbL6F2sTUucFE,3195
|
48
48
|
outerbounds/apps/code_package/__init__.py,sha256=8McF7pgx8ghvjRnazp2Qktlxi9yYwNiwESSQrk-2oW8,68
|
49
|
-
outerbounds/apps/code_package/code_packager.py,sha256=
|
49
|
+
outerbounds/apps/code_package/code_packager.py,sha256=RWvM5BKjgLhu7icsO_n5SSYC57dwyST0dWpoWF88ovU,22881
|
50
50
|
outerbounds/apps/code_package/examples.py,sha256=aF8qKIJxCVv_ugcShQjqUsXKKKMsm1oMkQIl8w3QKuw,4016
|
51
51
|
outerbounds/apps/config_schema.yaml,sha256=R1sSKFroqCSkcJScdAKU2vCvhgdFt_PJNZzecHB1oe0,8629
|
52
52
|
outerbounds/apps/dependencies.py,sha256=SqvdFQdFZZW0wXX_CHMHCrfE0TwaRkTvGCRbQ2Mx3q0,3935
|
@@ -72,7 +72,7 @@ outerbounds/utils/metaflowconfig.py,sha256=l2vJbgPkLISU-XPGZFaC8ZKmYFyJemlD6bwB-
|
|
72
72
|
outerbounds/utils/schema.py,sha256=lMUr9kNgn9wy-sO_t_Tlxmbt63yLeN4b0xQXbDUDj4A,2331
|
73
73
|
outerbounds/utils/utils.py,sha256=4Z8cszNob_8kDYCLNTrP-wWads_S_MdL3Uj3ju4mEsk,501
|
74
74
|
outerbounds/vendor.py,sha256=gRLRJNXtZBeUpPEog0LOeIsl6GosaFFbCxUvR4bW6IQ,5093
|
75
|
-
outerbounds-0.3.
|
76
|
-
outerbounds-0.3.
|
77
|
-
outerbounds-0.3.
|
78
|
-
outerbounds-0.3.
|
75
|
+
outerbounds-0.3.176rc6.dist-info/METADATA,sha256=hfETPwYSaPJHpV1DZB2KKpZJxj9kI8pExlBeTdL50h8,1846
|
76
|
+
outerbounds-0.3.176rc6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
77
|
+
outerbounds-0.3.176rc6.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
|
78
|
+
outerbounds-0.3.176rc6.dist-info/RECORD,,
|
File without changes
|
File without changes
|