metaflow-netflixext 1.3.4.dev0__tar.gz → 1.3.4.dev2__tar.gz

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 (65) hide show
  1. {metaflow_netflixext-1.3.4.dev0/metaflow_netflixext.egg-info → metaflow_netflixext-1.3.4.dev2}/PKG-INFO +1 -1
  2. metaflow_netflixext-1.3.4.dev2/metaflow_extensions/netflix_ext/plugins/coverage/__init__.py +0 -0
  3. metaflow_netflixext-1.3.4.dev2/metaflow_extensions/netflix_ext/plugins/coverage/coveragerc.py +108 -0
  4. metaflow_netflixext-1.3.4.dev2/metaflow_extensions/netflix_ext/plugins/coverage/generate_coveragerc.py +96 -0
  5. metaflow_netflixext-1.3.4.dev2/metaflow_extensions/netflix_ext/plugins/coverage/setup_coverage.py +103 -0
  6. metaflow_netflixext-1.3.4.dev2/metaflow_extensions/netflix_ext/plugins/coverage/upload_coverage_files.py +83 -0
  7. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2/metaflow_netflixext.egg-info}/PKG-INFO +1 -1
  8. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_netflixext.egg-info/SOURCES.txt +5 -0
  9. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/LICENSE +0 -0
  10. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/README.md +0 -0
  11. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/__init__.py +0 -0
  12. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/__init__.py +0 -0
  13. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/constants.py +0 -0
  14. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/current_stub_generator.py +0 -0
  15. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/debug_cmd.py +0 -0
  16. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/debug_script_generator.py +0 -0
  17. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/debug_stub_generator.py +0 -0
  18. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/debug_utils.py +0 -0
  19. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/jupyter_instructions_markdown.py +0 -0
  20. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/debug/jupyter_title_markdown.py +0 -0
  21. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/environment/__init__.py +0 -0
  22. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/environment/environment_cmd.py +0 -0
  23. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/environment/utils.py +0 -0
  24. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/cmd/mfextinit_netflixext.py +0 -0
  25. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/config/mfextinit_netflixext.py +0 -0
  26. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/exceptions/__init__.py +0 -0
  27. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/exceptions/decorators.py +0 -0
  28. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/exceptions/http_helpers.py +0 -0
  29. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/generate_vendor.py +0 -0
  30. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/http_helpers.py +0 -0
  31. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/__init__.py +0 -0
  32. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda.py +0 -0
  33. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_common_decorator.py +0 -0
  34. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_environment.py +0 -0
  35. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_flow_decorator.py +0 -0
  36. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_flow_mutator.py +0 -0
  37. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_lock_micromamba_server.py +0 -0
  38. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/conda_step_decorator.py +0 -0
  39. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/env_descr.py +0 -0
  40. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/envsresolver.py +0 -0
  41. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/parsers.py +0 -0
  42. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/pypi_package_builder.py +0 -0
  43. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/remote_bootstrap.py +0 -0
  44. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/__init__.py +0 -0
  45. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/builder_envs_resolver.py +0 -0
  46. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/conda_lock_resolver.py +0 -0
  47. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/conda_resolver.py +0 -0
  48. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/micromamba_server_resolver.py +0 -0
  49. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/pip_resolver.py +0 -0
  50. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resolvers/pylock_toml_resolver.py +0 -0
  51. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-32x32.png +0 -0
  52. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-64x64.png +0 -0
  53. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/resources/logo-svg.svg +0 -0
  54. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/terminal_menu.py +0 -0
  55. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/conda/utils.py +0 -0
  56. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/environment_cli.py +0 -0
  57. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/plugins/mfextinit_netflixext.py +0 -0
  58. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/toplevel/mfextinit_netflixext.py +0 -0
  59. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/toplevel/netflixext_toplevel.py +0 -0
  60. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_extensions/netflix_ext/toplevel/netflixext_version.py +0 -0
  61. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_netflixext.egg-info/dependency_links.txt +0 -0
  62. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_netflixext.egg-info/requires.txt +0 -0
  63. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/metaflow_netflixext.egg-info/top_level.txt +0 -0
  64. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/setup.cfg +0 -0
  65. {metaflow_netflixext-1.3.4.dev0 → metaflow_netflixext-1.3.4.dev2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaflow-netflixext
3
- Version: 1.3.4.dev0
3
+ Version: 1.3.4.dev2
4
4
  Summary: Metaflow extensions from Netflix
5
5
  Author: Netflix Metaflow Developers
6
6
  Author-email: metaflow-dev@netflix.com
@@ -0,0 +1,108 @@
1
+ # this file is named coveragerc.py since metaflow does not have a way to package hidden files
2
+
3
+ [run]
4
+ branch = True
5
+ parallel = True
6
+ concurrency = multiprocessing,thread
7
+
8
+ [html]
9
+ show_contexts = True
10
+
11
+ [report]
12
+ omit =
13
+ /apps/bdi-venv*/*
14
+ /**/site-packages/*
15
+ /**/vendor/*
16
+ /**/vendored/*
17
+ /**/_vendor/*
18
+ /**/build/*
19
+ /**/pip-install-*/*
20
+ /**/pip-req-build-*/*
21
+ /**/_remote_module_non_scriptable.py
22
+ /**/__autograph_generated_*.py
23
+ /**/metaflow-function-*/*
24
+ metaflow/plugins/airflow/*
25
+ metaflow/plugins/argo/*
26
+ metaflow/plugins/azure/*
27
+ metaflow/plugins/gcp/*
28
+ metaflow/plugins/kubernetes/*
29
+ coveragerc.py
30
+ setup.py
31
+
32
+ include_namespace_packages = True
33
+ show_missing = False
34
+ ignore_errors = False
35
+
36
+ [paths]
37
+ ; Specific paths must come before general paths
38
+ ; /root/metaflow/.mf_code/metaflow_extensions is more specific than /root/metaflow
39
+ metaflow_extensions =
40
+ .mf_code/metaflow_extensions
41
+ metaflow_extensions
42
+ nflx-metaflow-functions/metaflow_extensions
43
+ nflx-metaflow-serving/metaflow_extensions
44
+ nflx-metaflow/metaflow_extensions
45
+ nflx-fastdata/metaflow_extensions
46
+ mli-metaflow-custom-repo/nflx-metaflow-functions/metaflow_extensions
47
+ mli-metaflow-custom-repo/nflx-metaflow-serving/metaflow_extensions
48
+ mli-metaflow-custom-repo/nflx-metaflow/metaflow_extensions
49
+ mli-metaflow-custom-repo/nflx-fastdata/metaflow_extensions
50
+ metaflow-repo/test/extensions/packages/card_via_extinit/metaflow_extensions
51
+ metaflow-repo/test/extensions/packages/card_via_init/metaflow_extensions
52
+ metaflow-repo/test/extensions/packages/card_via_ns_subpackage/metaflow_extensions
53
+ /*/.mf_code/metaflow_extensions
54
+ /*/*/.mf_code/metaflow_extensions
55
+ /**/.mf_code/metaflow_extensions
56
+ /root/metaflow/.mf_code/metaflow_extensions
57
+ /data/tmp/*/env/install/lib/*/site-packages/metaflow_extensions
58
+ /apps/*/lib/*/site-packages/metaflow_extensions
59
+
60
+ ; Test flow files are flattened into test_flows_flat to match remote execution structure
61
+ ; First line is canonical location (where files exist), rest are aliases (execution paths)
62
+ test_flows_flat =
63
+ .mf_code/test_flows_flat
64
+ /root/metaflow
65
+ /*/metaflow
66
+
67
+ .mf_code =
68
+ .mf_code
69
+ /*/.mf_code
70
+ /*/*/.mf_code
71
+ /**/.mf_code
72
+
73
+ metaflow =
74
+ .mf_code/metaflow
75
+ metaflow
76
+ metaflow-repo/metaflow
77
+ /*/.mf_code/metaflow
78
+ /*/*/.mf_code/metaflow
79
+ /**/.mf_code/metaflow
80
+ /*/metaflow
81
+ /*/*/metaflow
82
+ /**/metaflow
83
+ /data/tmp/*/env/install/lib/*/site-packages/metaflow
84
+ /apps/*/lib/*/site-packages/metaflow
85
+
86
+ test =
87
+ .mf_code/test
88
+ test
89
+ mli-metaflow-custom-repo/test
90
+ metaflow-repo/test
91
+ /*/metaflow/test
92
+ /*/*/metaflow/test
93
+ /**/metaflow/test
94
+
95
+ ; Spin tests are at test/unit/spin in OSS metaflow but executed at test/plugins/spin/spin
96
+ test_spin =
97
+ .mf_code/test/unit/spin
98
+ /root/metaflow/test/plugins/spin/spin
99
+ /*/metaflow/test/plugins/spin/spin
100
+
101
+ cwd =
102
+ .
103
+ mli-metaflow-custom-repo
104
+ /data/tmp/*/user/corp/mli-metaflow-custom
105
+ /*/metaflow
106
+ /*/*/metaflow
107
+ /**/.mf_code
108
+ /**/metaflow
@@ -0,0 +1,96 @@
1
+ import configparser
2
+ import coverage
3
+ import re
4
+ import os
5
+ from metaflow_extensions.netflix_ext.plugins.coverage.setup_coverage import COVERAGE_RCFILE
6
+
7
+
8
+ def get_tmp_dirs(pattern, coverage_rcfile, data_file=".coverage"):
9
+ cov = coverage.Coverage(data_file=data_file, config_file=coverage_rcfile)
10
+ cov.load()
11
+ cov_data = cov.get_data()
12
+ files_covered = cov_data.measured_files()
13
+ tmp_dirs = set()
14
+ pattern = re.compile(pattern)
15
+ for file_path in files_covered:
16
+ match = pattern.match(file_path)
17
+ if match:
18
+ tmp_dirs.add(match.group(1))
19
+ return list(tmp_dirs)
20
+
21
+
22
+ def remap_tmp_dirs(cov, coverage_rcfile, data_file):
23
+ """
24
+ We need to update the [paths] config to remap all the temporary directories.
25
+ This function iterates over all the files and uses a regex match to
26
+ find the relevent paths that need remapping.
27
+ """
28
+ config = cov.config
29
+ trampoline_dirs = get_tmp_dirs(
30
+ r"^(/data/tmp/[^/]+/)([^/]+\.py)$", coverage_rcfile, data_file
31
+ )
32
+ config.paths["_escape_trampolines"] = ["_escape_trampolines"] + trampoline_dirs
33
+ ttk_dirs = get_tmp_dirs(
34
+ r"(?P<dir>.*?/_ttk_[^/]+)/ray_train_func\.py$", coverage_rcfile, data_file
35
+ )
36
+ print(f"DEBUG: Found {len(ttk_dirs)} TTK directories: {ttk_dirs}")
37
+ config.paths["trainingplatform"] = [
38
+ ".mf_code/metaflow_extensions/nflx/plugins/trainingplatform"
39
+ ] + ttk_dirs
40
+ ray_dirs = get_tmp_dirs(
41
+ r"(?P<dir>.*?/_ray_pkg_[^/]+)/.+", coverage_rcfile, data_file
42
+ )
43
+ config.paths["ray"] = ["."] + ray_dirs
44
+ # Initialize training_platform if it doesn't exist, then add ray_dirs
45
+ if "training_platform" not in config.paths:
46
+ config.paths["training_platform"] = []
47
+ config.paths["training_platform"] = config.paths["training_platform"] + ray_dirs
48
+ formatted_dirs = get_tmp_dirs(
49
+ r"(?P<dir>.*?)/[^/]+_(check|test)_flow\.py$", coverage_rcfile, data_file
50
+ )
51
+ config.paths["_formatted_flows"] = ["_formatted_flows"] + formatted_dirs
52
+
53
+
54
+ def save_updated_coveragerc(config, coverage_rcfile, output_file):
55
+ # Read template as text to preserve all sections and comments
56
+ with open(coverage_rcfile, "r") as f:
57
+ template_content = f.read()
58
+
59
+ # Parse template to get existing paths
60
+ config_parser = configparser.ConfigParser()
61
+ config_parser.read_string(template_content)
62
+
63
+ # Update [paths] section: preserve template paths, add/update dynamic ones
64
+ if not config_parser.has_section("paths"):
65
+ config_parser.add_section("paths")
66
+
67
+ # Merge: Start with all template paths, then add/update dynamic ones
68
+ merged_paths = {}
69
+
70
+ # First, preserve all template paths
71
+ if config_parser.has_section("paths"):
72
+ for key in config_parser.options("paths"):
73
+ value = config_parser.get("paths", key)
74
+ # ConfigParser returns multi-line values with newlines already
75
+ merged_paths[key] = value
76
+
77
+ # Then add or override with dynamic paths from config.paths
78
+ for key, path_list in config.paths.items():
79
+ # Skip empty path lists to avoid IndexError in coverage.py
80
+ if path_list:
81
+ merged_paths[key] = "\n".join(path_list)
82
+
83
+ # Write merged paths back (skip empty values)
84
+ for key, value in merged_paths.items():
85
+ if value.strip(): # Only write non-empty paths
86
+ config_parser.set("paths", key, value)
87
+
88
+ with open(output_file, "w") as configfile:
89
+ config_parser.write(configfile)
90
+ print(f"Updated .coveragerc file written to {output_file}")
91
+
92
+
93
+ if __name__ == "__main__":
94
+ cov = coverage.Coverage(data_file=".coverage", config_file=COVERAGE_RCFILE)
95
+ remap_tmp_dirs(cov, COVERAGE_RCFILE, ".coverage")
96
+ save_updated_coveragerc(cov.config, COVERAGE_RCFILE, ".coveragerc")
@@ -0,0 +1,103 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ from typing import Union, TYPE_CHECKING
6
+ from metaflow.util import which
7
+
8
+ if TYPE_CHECKING:
9
+ import sh
10
+
11
+ MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
12
+ COVERAGE_RCFILE = os.path.join(MODULE_DIR, "coveragerc.py")
13
+ SITECUSTOMIZE_FILE = os.path.join(MODULE_DIR, "sitecustomize.py")
14
+
15
+
16
+ def get_local_coverage_dir():
17
+ METAFLOW_COVERAGE_S3_PATH = os.environ.get("METAFLOW_COVERAGE_S3_PATH")
18
+ assert METAFLOW_COVERAGE_S3_PATH, "METAFLOW_COVERAGE_S3_PATH is not set"
19
+ group_tag = os.path.basename(METAFLOW_COVERAGE_S3_PATH)
20
+ coverage_dir = os.path.join("/tmp", group_tag)
21
+ os.makedirs(coverage_dir, exist_ok=True)
22
+ return coverage_dir
23
+
24
+
25
+ def get_sitepackages_dir(python_binary: str):
26
+ return (
27
+ subprocess.run(
28
+ [python_binary, "-c", "import site; print(site.getsitepackages()[0])"],
29
+ check=True,
30
+ stdout=subprocess.PIPE,
31
+ )
32
+ .stdout.decode()
33
+ .strip()
34
+ )
35
+
36
+
37
+ def setup_sitecustomize(python_binary: str):
38
+ site_packages_dir = get_sitepackages_dir(python_binary)
39
+ sitecustomize_file = os.path.join(site_packages_dir, "sitecustomize.py")
40
+ local_coverage_dir = get_local_coverage_dir()
41
+
42
+ # We also copy the .coveragerc file to the site-packages directory.
43
+ # This keeps the .coveragerc file in the same directory as the sitecustomize.py file
44
+ # so that when the env is saved and restored we will have both.
45
+ coverage_rcfile = os.path.join(site_packages_dir, ".coveragerc")
46
+ if not os.path.exists(coverage_rcfile):
47
+ shutil.copy(COVERAGE_RCFILE, coverage_rcfile)
48
+
49
+ # We harden out the environment variables as the fallback so that it will work with
50
+ # subprocesses that are launched without inheriting the environment.
51
+ lines = [
52
+ "import os",
53
+ "import site",
54
+ "site_packages_dir = site.getsitepackages()[0]",
55
+ f"os.environ['COVERAGE_FILE'] = os.environ.get('COVERAGE_FILE') or os.path.join('{local_coverage_dir}', f'.coverage')",
56
+ f"os.environ['COVERAGE_PROCESS_START'] = os.environ.get('COVERAGE_PROCESS_START') or os.path.join(site_packages_dir, '.coveragerc')",
57
+ f"os.environ['COVERAGE_RCFILE'] = os.environ.get('COVERAGE_RCFILE') or os.path.join(site_packages_dir, '.coveragerc')",
58
+ f"os.environ['METAFLOW_COVERAGE_S3_PATH'] = os.environ.get('METAFLOW_COVERAGE_S3_PATH') or '{os.environ['METAFLOW_COVERAGE_S3_PATH']}'",
59
+ "import coverage",
60
+ "coverage.process_startup()",
61
+ "coverage_context = os.environ.get('METAFLOW_COVERAGE_CONTEXT')",
62
+ "if coverage_context and coverage.Coverage.current():",
63
+ " coverage.Coverage.current().switch_context(coverage_context)",
64
+ ]
65
+ if not os.path.exists(sitecustomize_file):
66
+ with open(sitecustomize_file, "w") as target_file:
67
+ target_file.write("\n".join(lines) + "\n")
68
+
69
+
70
+ def maybe_install_coverage(python_binary: str):
71
+ try:
72
+ subprocess.run(
73
+ [python_binary, "-c", "import coverage"],
74
+ check=True,
75
+ stdout=subprocess.PIPE,
76
+ stderr=subprocess.PIPE,
77
+ )
78
+ except subprocess.CalledProcessError:
79
+ subprocess.run([python_binary, "-m", "pip", "install", "coverage"], check=True)
80
+
81
+
82
+ def setup_coverage(sh_python: Union[str, "sh.Command"]):
83
+ sh_python = str(sh_python)
84
+ assert os.environ.get(
85
+ "METAFLOW_COVERAGE_S3_PATH"
86
+ ), "METAFLOW_COVERAGE_S3_PATH is not set"
87
+
88
+ python_binaries = set(
89
+ ["python"]
90
+ + os.environ.get("MF_BDI_PYTHONPATH", "").split()
91
+ + os.environ.get("MFPY", "").split()
92
+ + [sh_python]
93
+ )
94
+ for python_binary in python_binaries:
95
+ if not python_binary or not which(python_binary):
96
+ continue
97
+ maybe_install_coverage(python_binary)
98
+ setup_sitecustomize(python_binary)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ sh_python = sys.argv[1] if len(sys.argv) > 1 else ""
103
+ setup_coverage(sh_python)
@@ -0,0 +1,83 @@
1
+ import coverage
2
+ import glob
3
+ import gzip
4
+ import hashlib
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import uuid
9
+
10
+ from .setup_coverage import get_local_coverage_dir, COVERAGE_RCFILE
11
+
12
+
13
+ def hash_file(file_path: str) -> str:
14
+ hasher = hashlib.sha256()
15
+ with open(file_path, "rb") as f:
16
+ for chunk in iter(lambda: f.read(4096), b""):
17
+ hasher.update(chunk)
18
+ return hasher.hexdigest()
19
+
20
+
21
+ def gzip_file(source_path: str, destination_path: str) -> None:
22
+ with open(source_path, "rb") as f_in:
23
+ with gzip.open(destination_path, "wb") as f_out:
24
+ shutil.copyfileobj(f_in, f_out)
25
+
26
+
27
+ def has_arcs(file_path: str) -> bool:
28
+ try:
29
+ cov = coverage.Coverage(data_file=file_path)
30
+ cov.load()
31
+ return cov.get_data().has_arcs
32
+ except:
33
+ # sometimes the coverage file is corrupted and cannot be loaded, eg:
34
+ # - doesn't seem to be a coverage data file
35
+ # - database disk image is malformed
36
+ # so we just ignore it
37
+ return False
38
+
39
+
40
+ def combine_coverage_files(local_dir):
41
+ combined_coverage_file = f"{local_dir}/.coverage"
42
+ if os.path.exists(combined_coverage_file):
43
+ shutil.move(combined_coverage_file, f"{combined_coverage_file}.{uuid.uuid4()}")
44
+ files = [f for f in glob.glob(f"{local_dir}/.coverage.*") if has_arcs(f)]
45
+ if not files:
46
+ return
47
+ cov = coverage.Coverage(
48
+ data_file=combined_coverage_file, config_file=COVERAGE_RCFILE
49
+ )
50
+ try:
51
+ cov.combine(data_paths=files, keep=True)
52
+ except coverage.exceptions.NoDataError:
53
+ return
54
+ cov.save()
55
+ assert os.path.exists(combined_coverage_file)
56
+
57
+
58
+ def upload_coverage_files(coverage_s3_path: str) -> None:
59
+ coverage_s3_path = coverage_s3_path.rstrip("/")
60
+ local_coverage_dir = get_local_coverage_dir()
61
+ combine_coverage_files(local_coverage_dir)
62
+
63
+ combined_coverage_file = f"{local_coverage_dir}/.coverage"
64
+ file_hash = hash_file(combined_coverage_file)
65
+
66
+ gzipped_file = f"{local_coverage_dir}/.coverage.{file_hash}.gz"
67
+ gzip_file(combined_coverage_file, gzipped_file)
68
+
69
+ subprocess.run(
70
+ f"aws s3 cp {gzipped_file} {coverage_s3_path}/",
71
+ shell=True,
72
+ check=True,
73
+ cwd=local_coverage_dir,
74
+ )
75
+
76
+
77
+ if __name__ == "__main__":
78
+ import sys
79
+
80
+ if len(sys.argv) != 2:
81
+ print("Usage: python script.py <COVERAGE_S3_PATH>")
82
+ else:
83
+ upload_coverage_files(sys.argv[1])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaflow-netflixext
3
- Version: 1.3.4.dev0
3
+ Version: 1.3.4.dev2
4
4
  Summary: Metaflow extensions from Netflix
5
5
  Author: Netflix Metaflow Developers
6
6
  Author-email: metaflow-dev@netflix.com
@@ -48,6 +48,11 @@ metaflow_extensions/netflix_ext/plugins/conda/resolvers/pylock_toml_resolver.py
48
48
  metaflow_extensions/netflix_ext/plugins/conda/resources/logo-32x32.png
49
49
  metaflow_extensions/netflix_ext/plugins/conda/resources/logo-64x64.png
50
50
  metaflow_extensions/netflix_ext/plugins/conda/resources/logo-svg.svg
51
+ metaflow_extensions/netflix_ext/plugins/coverage/__init__.py
52
+ metaflow_extensions/netflix_ext/plugins/coverage/coveragerc.py
53
+ metaflow_extensions/netflix_ext/plugins/coverage/generate_coveragerc.py
54
+ metaflow_extensions/netflix_ext/plugins/coverage/setup_coverage.py
55
+ metaflow_extensions/netflix_ext/plugins/coverage/upload_coverage_files.py
51
56
  metaflow_extensions/netflix_ext/toplevel/mfextinit_netflixext.py
52
57
  metaflow_extensions/netflix_ext/toplevel/netflixext_toplevel.py
53
58
  metaflow_extensions/netflix_ext/toplevel/netflixext_version.py