metaflow 2.15.17__py2.py3-none-any.whl → 2.15.19__py2.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.
Files changed (39) hide show
  1. metaflow/_vendor/imghdr/__init__.py +180 -0
  2. metaflow/cli.py +12 -0
  3. metaflow/cmd/develop/stub_generator.py +19 -2
  4. metaflow/metaflow_config.py +0 -2
  5. metaflow/plugins/__init__.py +3 -0
  6. metaflow/plugins/airflow/airflow.py +6 -0
  7. metaflow/plugins/argo/argo_workflows.py +316 -287
  8. metaflow/plugins/argo/exit_hooks.py +209 -0
  9. metaflow/plugins/aws/aws_utils.py +1 -1
  10. metaflow/plugins/aws/step_functions/step_functions.py +6 -0
  11. metaflow/plugins/cards/card_cli.py +20 -1
  12. metaflow/plugins/cards/card_creator.py +24 -1
  13. metaflow/plugins/cards/card_datastore.py +8 -36
  14. metaflow/plugins/cards/card_decorator.py +57 -1
  15. metaflow/plugins/cards/card_modules/convert_to_native_type.py +5 -2
  16. metaflow/plugins/cards/card_modules/test_cards.py +16 -0
  17. metaflow/plugins/cards/metadata.py +22 -0
  18. metaflow/plugins/exit_hook/__init__.py +0 -0
  19. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  20. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  21. metaflow/plugins/pypi/conda_environment.py +8 -4
  22. metaflow/plugins/pypi/micromamba.py +9 -1
  23. metaflow/plugins/secrets/__init__.py +3 -0
  24. metaflow/plugins/secrets/secrets_decorator.py +9 -173
  25. metaflow/plugins/secrets/secrets_func.py +60 -0
  26. metaflow/plugins/secrets/secrets_spec.py +101 -0
  27. metaflow/plugins/secrets/utils.py +74 -0
  28. metaflow/runner/metaflow_runner.py +16 -1
  29. metaflow/runtime.py +45 -0
  30. metaflow/version.py +1 -1
  31. {metaflow-2.15.17.data → metaflow-2.15.19.data}/data/share/metaflow/devtools/Tiltfile +27 -2
  32. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/METADATA +2 -2
  33. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/RECORD +39 -30
  34. {metaflow-2.15.17.data → metaflow-2.15.19.data}/data/share/metaflow/devtools/Makefile +0 -0
  35. {metaflow-2.15.17.data → metaflow-2.15.19.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  36. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/WHEEL +0 -0
  37. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/entry_points.txt +0 -0
  38. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/licenses/LICENSE +0 -0
  39. {metaflow-2.15.17.dist-info → metaflow-2.15.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,180 @@
1
+ """Recognize image file formats based on their first few bytes."""
2
+
3
+ from os import PathLike
4
+ import warnings
5
+
6
+ __all__ = ["what"]
7
+
8
+
9
+ warnings._deprecated(__name__, remove=(3, 13))
10
+
11
+
12
+ #-------------------------#
13
+ # Recognize image headers #
14
+ #-------------------------#
15
+
16
+ def what(file, h=None):
17
+ """Return the type of image contained in a file or byte stream."""
18
+ f = None
19
+ try:
20
+ if h is None:
21
+ if isinstance(file, (str, PathLike)):
22
+ f = open(file, 'rb')
23
+ h = f.read(32)
24
+ else:
25
+ location = file.tell()
26
+ h = file.read(32)
27
+ file.seek(location)
28
+ for tf in tests:
29
+ res = tf(h, f)
30
+ if res:
31
+ return res
32
+ finally:
33
+ if f: f.close()
34
+ return None
35
+
36
+
37
+ #---------------------------------#
38
+ # Subroutines per image file type #
39
+ #---------------------------------#
40
+
41
+ tests = []
42
+
43
+ def test_jpeg(h, f):
44
+ """Test for JPEG data with JFIF or Exif markers; and raw JPEG."""
45
+ if h[6:10] in (b'JFIF', b'Exif'):
46
+ return 'jpeg'
47
+ elif h[:4] == b'\xff\xd8\xff\xdb':
48
+ return 'jpeg'
49
+
50
+ tests.append(test_jpeg)
51
+
52
+ def test_png(h, f):
53
+ """Verify if the image is a PNG."""
54
+ if h.startswith(b'\211PNG\r\n\032\n'):
55
+ return 'png'
56
+
57
+ tests.append(test_png)
58
+
59
+ def test_gif(h, f):
60
+ """Verify if the image is a GIF ('87 or '89 variants)."""
61
+ if h[:6] in (b'GIF87a', b'GIF89a'):
62
+ return 'gif'
63
+
64
+ tests.append(test_gif)
65
+
66
+ def test_tiff(h, f):
67
+ """Verify if the image is a TIFF (can be in Motorola or Intel byte order)."""
68
+ if h[:2] in (b'MM', b'II'):
69
+ return 'tiff'
70
+
71
+ tests.append(test_tiff)
72
+
73
+ def test_rgb(h, f):
74
+ """test for the SGI image library."""
75
+ if h.startswith(b'\001\332'):
76
+ return 'rgb'
77
+
78
+ tests.append(test_rgb)
79
+
80
+ def test_pbm(h, f):
81
+ """Verify if the image is a PBM (portable bitmap)."""
82
+ if len(h) >= 3 and \
83
+ h[0] == ord(b'P') and h[1] in b'14' and h[2] in b' \t\n\r':
84
+ return 'pbm'
85
+
86
+ tests.append(test_pbm)
87
+
88
+ def test_pgm(h, f):
89
+ """Verify if the image is a PGM (portable graymap)."""
90
+ if len(h) >= 3 and \
91
+ h[0] == ord(b'P') and h[1] in b'25' and h[2] in b' \t\n\r':
92
+ return 'pgm'
93
+
94
+ tests.append(test_pgm)
95
+
96
+ def test_ppm(h, f):
97
+ """Verify if the image is a PPM (portable pixmap)."""
98
+ if len(h) >= 3 and \
99
+ h[0] == ord(b'P') and h[1] in b'36' and h[2] in b' \t\n\r':
100
+ return 'ppm'
101
+
102
+ tests.append(test_ppm)
103
+
104
+ def test_rast(h, f):
105
+ """test for the Sun raster file."""
106
+ if h.startswith(b'\x59\xA6\x6A\x95'):
107
+ return 'rast'
108
+
109
+ tests.append(test_rast)
110
+
111
+ def test_xbm(h, f):
112
+ """Verify if the image is a X bitmap (X10 or X11)."""
113
+ if h.startswith(b'#define '):
114
+ return 'xbm'
115
+
116
+ tests.append(test_xbm)
117
+
118
+ def test_bmp(h, f):
119
+ """Verify if the image is a BMP file."""
120
+ if h.startswith(b'BM'):
121
+ return 'bmp'
122
+
123
+ tests.append(test_bmp)
124
+
125
+ def test_webp(h, f):
126
+ """Verify if the image is a WebP."""
127
+ if h.startswith(b'RIFF') and h[8:12] == b'WEBP':
128
+ return 'webp'
129
+
130
+ tests.append(test_webp)
131
+
132
+ def test_exr(h, f):
133
+ """verify is the image ia a OpenEXR fileOpenEXR."""
134
+ if h.startswith(b'\x76\x2f\x31\x01'):
135
+ return 'exr'
136
+
137
+ tests.append(test_exr)
138
+
139
+ #--------------------#
140
+ # Small test program #
141
+ #--------------------#
142
+
143
+ def test():
144
+ import sys
145
+ recursive = 0
146
+ if sys.argv[1:] and sys.argv[1] == '-r':
147
+ del sys.argv[1:2]
148
+ recursive = 1
149
+ try:
150
+ if sys.argv[1:]:
151
+ testall(sys.argv[1:], recursive, 1)
152
+ else:
153
+ testall(['.'], recursive, 1)
154
+ except KeyboardInterrupt:
155
+ sys.stderr.write('\n[Interrupted]\n')
156
+ sys.exit(1)
157
+
158
+ def testall(list, recursive, toplevel):
159
+ import sys
160
+ import os
161
+ for filename in list:
162
+ if os.path.isdir(filename):
163
+ print(filename + '/:', end=' ')
164
+ if recursive or toplevel:
165
+ print('recursing down:')
166
+ import glob
167
+ names = glob.glob(os.path.join(glob.escape(filename), '*'))
168
+ testall(names, recursive, 0)
169
+ else:
170
+ print('*** directory (use -r) ***')
171
+ else:
172
+ print(filename + ':', end=' ')
173
+ sys.stdout.flush()
174
+ try:
175
+ print(what(filename))
176
+ except OSError:
177
+ print('*** not found ***')
178
+
179
+ if __name__ == '__main__':
180
+ test()
metaflow/cli.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import inspect
3
+ import os
3
4
  import sys
4
5
  import traceback
5
6
  from datetime import datetime
@@ -242,6 +243,14 @@ def version(obj):
242
243
  type=click.Choice(["local"] + [m.TYPE for m in ENVIRONMENTS]),
243
244
  help="Execution environment type",
244
245
  )
246
+ @click.option(
247
+ "--force-rebuild-environments/--no-force-rebuild-environments",
248
+ is_flag=True,
249
+ default=False,
250
+ hidden=True,
251
+ type=bool,
252
+ help="Explicitly rebuild the execution environments",
253
+ )
245
254
  # See comment for --quiet
246
255
  @click.option(
247
256
  "--datastore",
@@ -300,6 +309,7 @@ def start(
300
309
  quiet=False,
301
310
  metadata=None,
302
311
  environment=None,
312
+ force_rebuild_environments=False,
303
313
  datastore=None,
304
314
  datastore_root=None,
305
315
  decospecs=None,
@@ -435,6 +445,8 @@ def start(
435
445
  ctx.obj.environment = [
436
446
  e for e in ENVIRONMENTS + [MetaflowEnvironment] if e.TYPE == environment
437
447
  ][0](ctx.obj.flow)
448
+ # set force rebuild flag for environments that support it.
449
+ ctx.obj.environment._force_rebuild = force_rebuild_environments
438
450
  ctx.obj.environment.validate_environment(ctx.obj.logger, datastore)
439
451
 
440
452
  ctx.obj.event_logger = LOGGING_SIDECARS[event_logger](
@@ -7,7 +7,6 @@ import pathlib
7
7
  import re
8
8
  import time
9
9
  import typing
10
-
11
10
  from datetime import datetime
12
11
  from io import StringIO
13
12
  from types import ModuleType
@@ -335,6 +334,8 @@ class StubGenerator:
335
334
 
336
335
  # Imports that are needed at the top of the file
337
336
  self._imports = set() # type: Set[str]
337
+
338
+ self._sub_module_imports = set() # type: Set[Tuple[str, str]]``
338
339
  # Typing imports (behind if TYPE_CHECKING) that are needed at the top of the file
339
340
  self._typing_imports = set() # type: Set[str]
340
341
  # Typevars that are defined
@@ -643,6 +644,21 @@ class StubGenerator:
643
644
  "deployer"
644
645
  ] = (self._current_module_name + "." + name)
645
646
 
647
+ # Handle TypedDict gracefully for Python 3.7 compatibility
648
+ # _TypedDictMeta is not available in Python 3.7
649
+ typed_dict_meta = getattr(typing, "_TypedDictMeta", None)
650
+ if typed_dict_meta is not None and isinstance(clazz, typed_dict_meta):
651
+ self._sub_module_imports.add(("typing", "TypedDict"))
652
+ total_flag = getattr(clazz, "__total__", False)
653
+ buff = StringIO()
654
+ # Emit the TypedDict base and total flag
655
+ buff.write(f"class {name}(TypedDict, total={total_flag}):\n")
656
+ # Write out each field from __annotations__
657
+ for field_name, field_type in clazz.__annotations__.items():
658
+ ann = self._get_element_name_with_module(field_type)
659
+ buff.write(f"{TAB}{field_name}: {ann}\n")
660
+ return buff.getvalue()
661
+
646
662
  buff = StringIO()
647
663
  # Class prototype
648
664
  buff.write("class " + name.split(".")[-1] + "(")
@@ -987,7 +1003,6 @@ class StubGenerator:
987
1003
  ]
988
1004
 
989
1005
  docs = split_docs(raw_doc, section_boundaries)
990
-
991
1006
  parameters, no_arg_version = parse_params_from_doc(docs["param_doc"])
992
1007
 
993
1008
  if docs["add_to_current_doc"]:
@@ -1515,6 +1530,8 @@ class StubGenerator:
1515
1530
  f.write("import " + module + "\n")
1516
1531
  if module == "typing":
1517
1532
  imported_typing = True
1533
+ for module, sub_module in self._sub_module_imports:
1534
+ f.write(f"from {module} import {sub_module}\n")
1518
1535
  if self._typing_imports:
1519
1536
  if not imported_typing:
1520
1537
  f.write("import typing\n")
@@ -214,8 +214,6 @@ CARD_GSROOT = from_conf(
214
214
  )
215
215
  CARD_NO_WARNING = from_conf("CARD_NO_WARNING", False)
216
216
 
217
- SKIP_CARD_DUALWRITE = from_conf("SKIP_CARD_DUALWRITE", False)
218
-
219
217
  RUNTIME_CARD_RENDER_INTERVAL = from_conf("RUNTIME_CARD_RENDER_INTERVAL", 60)
220
218
 
221
219
  # Azure storage account URL
@@ -69,6 +69,7 @@ FLOW_DECORATORS_DESC = [
69
69
  ("trigger_on_finish", ".events_decorator.TriggerOnFinishDecorator"),
70
70
  ("pypi_base", ".pypi.pypi_decorator.PyPIFlowDecorator"),
71
71
  ("conda_base", ".pypi.conda_decorator.CondaFlowDecorator"),
72
+ ("exit_hook", ".exit_hook.exit_hook_decorator.ExitHookDecorator"),
72
73
  ]
73
74
 
74
75
  # Add environments here
@@ -240,6 +241,7 @@ from .cards.card_modules.test_cards import (
240
241
  TestTimeoutCard,
241
242
  TestRefreshCard,
242
243
  TestRefreshComponentCard,
244
+ TestImageCard,
243
245
  )
244
246
 
245
247
  CARDS = [
@@ -258,6 +260,7 @@ CARDS = [
258
260
  DefaultCardJSON,
259
261
  TestRefreshCard,
260
262
  TestRefreshComponentCard,
263
+ TestImageCard,
261
264
  ]
262
265
  merge_lists(CARDS, MF_EXTERNAL_CARDS, "type")
263
266
 
@@ -654,6 +654,12 @@ class Airflow(object):
654
654
  "to Airflow is not supported currently."
655
655
  )
656
656
 
657
+ if self.flow._flow_decorators.get("exit_hook"):
658
+ raise AirflowException(
659
+ "Deploying flows with the @exit_hook decorator "
660
+ "to Airflow is not currently supported."
661
+ )
662
+
657
663
  # Visit every node of the flow and recursively build the state machine.
658
664
  def _visit(node, workflow, exit_node=None):
659
665
  kube_deco = dict(