outerbounds 0.3.176rc4__py3-none-any.whl → 0.3.176rc5__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.
@@ -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=KVPairType,
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, exclude_hidden=True, suffixes=None, follow_symlinks=True
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
- if suffixes is None:
414
- suffixes = []
415
-
416
- # Convert root to unicode to handle files/folders with non-ascii chars
417
- root = str(root)
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.176rc4
3
+ Version: 0.3.176rc5
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.163rc6)
33
- Requires-Dist: ob-metaflow-stubs (==6.0.3.176rc4)
32
+ Requires-Dist: ob-metaflow-extensions (==1.1.162)
33
+ Requires-Dist: ob-metaflow-stubs (==6.0.3.176rc5)
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=zbzFOYpRkgFVtBhcZGl0sn8BkL6w0uc9PNMx25CECfA,23456
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=SQDBXKwizzpag5GpwoZpvvkyPOodRSQwk2ecAAfO0HI,23316
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.176rc4.dist-info/METADATA,sha256=bF2r5YAzE_L92TOuSGmgpNuFvqQb73q-boJXB_cF6Gs,1846
76
- outerbounds-0.3.176rc4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
77
- outerbounds-0.3.176rc4.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
78
- outerbounds-0.3.176rc4.dist-info/RECORD,,
75
+ outerbounds-0.3.176rc5.dist-info/METADATA,sha256=Q0yvihZjFKi-Kgeg9rLGGprYHgFGKMyQOP9glmbTD4c,1843
76
+ outerbounds-0.3.176rc5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
77
+ outerbounds-0.3.176rc5.dist-info/entry_points.txt,sha256=7ye0281PKlvqxu15rjw60zKg2pMsXI49_A8BmGqIqBw,47
78
+ outerbounds-0.3.176rc5.dist-info/RECORD,,