From bedc1791f1976dc9b3016d6d5fc6fbe3470fac55 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 13:56:33 -0400 Subject: [PATCH 01/70] added single package for data-prep-kit with extra[dev,ray,spak] Signed-off-by: Maroun Touma --- data-processing-lib/Makefile | 5 +++ data-processing-lib/pyproject.toml | 44 ++++++++++++++++++++++ data-processing-lib/requirements-dev.txt | 9 +++++ data-processing-lib/requirements-ray.txt | 3 ++ data-processing-lib/requirements-spark.txt | 2 + data-processing-lib/requirements.txt | 6 +++ 6 files changed, 69 insertions(+) create mode 100644 data-processing-lib/pyproject.toml create mode 100644 data-processing-lib/requirements-dev.txt create mode 100644 data-processing-lib/requirements-ray.txt create mode 100644 data-processing-lib/requirements-spark.txt create mode 100644 data-processing-lib/requirements.txt diff --git a/data-processing-lib/Makefile b/data-processing-lib/Makefile index a70a05ff8..d0d1305ac 100644 --- a/data-processing-lib/Makefile +++ b/data-processing-lib/Makefile @@ -55,3 +55,8 @@ set-versions: @# Help: Recursively $@ in all subdirs @$(MAKE) RULE=$@ .recurse + +build-pkg-dist:: + $(MAKE) .defaults.build-dist BUILD_WHEEL_ARG=-w + +publish-dist :: .check-env .defaults.publish-dist diff --git a/data-processing-lib/pyproject.toml b/data-processing-lib/pyproject.toml new file mode 100644 index 000000000..9dbbc7f27 --- /dev/null +++ b/data-processing-lib/pyproject.toml @@ -0,0 +1,44 @@ +[project] +name = "data_prep_toolkit" +version = "0.2.2.dev0" +keywords = ["data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] +requires-python = ">=3.10" +description = "Data Preparation Toolkit Library for Ray and Python" +license = {text = "Apache-2.0"} +readme = {file = "README.md", content-type = "text/markdown"} +authors = [ + { name = "David Wood", email = "dawood@us.ibm.com" }, + { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, +] + +dynamic = ["dependencies", "optional-dependencies"] + +[project_urls] +Repository = "https://github.com/IBM/data-prep-kit" +Issues = "https://github.com/IBM/data-prep-kit/issues" +Documentation = "https://ibm.github.io/data-prep-kit/" +"Transform project" = "https://github.com/IBM/data-prep-kit/tree/dev/transforms/universal/noop" + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic.dependencies] +file = ["requirements.txt"] + +[tool.setuptools.dynamic.optional-dependencies] +dev = { file = ["requirements-dev.txt"]} +ray = { file = ["requirements-ray.txt"]} +spark = { file = ["requirements-spark.txt"]} + +[tool.setuptools.packages.find] +where = ["python/src", "ray/src", "spark/src"] + + +[tool.pytest.ini_options] +# Currently we use low coverage since we have to run tests separately (see makefile) +#addopts = "--cov --cov-report term-missing --cov-fail-under 25" +markers = ["unit: unit tests", "integration: integration tests"] + +[tool.coverage.run] +include = ["src/*"] diff --git a/data-processing-lib/requirements-dev.txt b/data-processing-lib/requirements-dev.txt new file mode 100644 index 000000000..326d62c8e --- /dev/null +++ b/data-processing-lib/requirements-dev.txt @@ -0,0 +1,9 @@ +twine +pytest>=7.3.2 +pytest-dotenv>=0.5.2 +pytest-env>=1.0.0 +pre-commit>=3.3.2 +pytest-cov>=4.1.0 +pytest-mock>=3.10.0 +moto==5.0.5 +markupsafe==2.0.1 diff --git a/data-processing-lib/requirements-ray.txt b/data-processing-lib/requirements-ray.txt new file mode 100644 index 000000000..aafa3caeb --- /dev/null +++ b/data-processing-lib/requirements-ray.txt @@ -0,0 +1,3 @@ +ray[default]==2.24.0 +fastapi>=0.110.2 +pillow>=10.3.0 diff --git a/data-processing-lib/requirements-spark.txt b/data-processing-lib/requirements-spark.txt new file mode 100644 index 000000000..f38f033da --- /dev/null +++ b/data-processing-lib/requirements-spark.txt @@ -0,0 +1,2 @@ +pyspark>=3.5.2 +psutil>=6.0.0 diff --git a/data-processing-lib/requirements.txt b/data-processing-lib/requirements.txt new file mode 100644 index 000000000..7b363f2b5 --- /dev/null +++ b/data-processing-lib/requirements.txt @@ -0,0 +1,6 @@ + numpy < 1.29.0 + pyarrow==16.1.0 + boto3==1.34.69 + argparse + mmh3 + psutil From c6514d52916e7ea902e47d14941b73028d68cfb0 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 14:03:24 -0400 Subject: [PATCH 02/70] Crate a single package with [all,ray,code_quality,etc.] Signed-off-by: Maroun Touma --- .make.defaults | 2 +- transforms/Makefile | 29 ++++++ .../python/README.md => README-list.md} | 5 + .../code/code2parquet/python/pyproject.toml | 13 ++- .../code/code2parquet/python/requirements.txt | 3 + .../code/code_quality/python/pyproject.toml | 14 ++- .../code/code_quality/python/requirements.txt | 3 + .../header_cleanser/python/pyproject.toml | 9 +- .../header_cleanser/python/requirements.txt | 3 + .../code/license_select/python/pyproject.toml | 8 +- .../license_select/python/requirements.txt | 1 + .../proglang_select/python/pyproject.toml | 8 +- .../proglang_select/python/requirements.txt | 1 + .../language/doc_chunk/python/pyproject.toml | 9 +- .../doc_chunk/python/requirements.txt | 3 + .../doc_quality/python/pyproject.toml | 8 +- .../doc_quality/python/requirements.txt | 2 + .../language/lang_id/python/pyproject.toml | 15 +-- .../language/lang_id/python/requirements.txt | 5 + .../pii_redactor/python/pyproject.toml | 11 +-- .../pii_redactor/python/requirements.txt | 5 + .../text_encoder/python/pyproject.toml | 8 +- .../text_encoder/python/requirements.txt | 2 + transforms/packaging/.gitignore | 5 - transforms/packaging/.make.packaging | 83 ---------------- transforms/packaging/Makefile | 60 ------------ transforms/packaging/README.md | 55 ----------- transforms/packaging/python/Makefile | 89 ----------------- transforms/packaging/python/pyproject.toml | 39 -------- .../packaging/python/requirements.all.txt | 51 ---------- .../packaging/python/requirements.lang1.txt | 32 ------- transforms/packaging/ray/Makefile | 66 ------------- transforms/packaging/ray/README.md | 41 -------- transforms/packaging/ray/pyproject.toml | 40 -------- transforms/packaging/ray/requirements.txt | 21 ---- transforms/pyproject.toml | 96 +++++++++++++++++++ transforms/requirements-ray.txt | 9 ++ transforms/requirements.txt | 1 + .../universal/doc_id/python/pyproject.toml | 7 +- .../universal/doc_id/python/requirements.txt | 1 + .../universal/ededup/python/pyproject.toml | 10 +- .../universal/ededup/python/requirements.txt | 3 + .../universal/fdedup/ray/pyproject.toml | 4 +- .../universal/filter/python/pyproject.toml | 13 ++- .../universal/filter/python/requirements.txt | 3 + .../universal/profiler/python/pyproject.toml | 9 +- .../profiler/python/requirements.txt | 5 + .../universal/resize/python/pyproject.toml | 7 +- .../universal/resize/python/requirements.txt | 1 + .../tokenization/python/pyproject.toml | 10 +- .../tokenization/python/requirements.txt | 2 + 51 files changed, 274 insertions(+), 656 deletions(-) rename transforms/{packaging/python/README.md => README-list.md} (94%) create mode 100644 transforms/code/code2parquet/python/requirements.txt create mode 100644 transforms/code/code_quality/python/requirements.txt create mode 100644 transforms/code/header_cleanser/python/requirements.txt create mode 100644 transforms/code/license_select/python/requirements.txt create mode 100644 transforms/code/proglang_select/python/requirements.txt create mode 100644 transforms/language/doc_chunk/python/requirements.txt create mode 100644 transforms/language/doc_quality/python/requirements.txt create mode 100644 transforms/language/lang_id/python/requirements.txt create mode 100644 transforms/language/pii_redactor/python/requirements.txt create mode 100644 transforms/language/text_encoder/python/requirements.txt delete mode 100644 transforms/packaging/.gitignore delete mode 100644 transforms/packaging/.make.packaging delete mode 100644 transforms/packaging/Makefile delete mode 100644 transforms/packaging/README.md delete mode 100644 transforms/packaging/python/Makefile delete mode 100644 transforms/packaging/python/pyproject.toml delete mode 100644 transforms/packaging/python/requirements.all.txt delete mode 100644 transforms/packaging/python/requirements.lang1.txt delete mode 100644 transforms/packaging/ray/Makefile delete mode 100644 transforms/packaging/ray/README.md delete mode 100644 transforms/packaging/ray/pyproject.toml delete mode 100644 transforms/packaging/ray/requirements.txt create mode 100644 transforms/pyproject.toml create mode 100644 transforms/requirements-ray.txt create mode 100644 transforms/requirements.txt create mode 100644 transforms/universal/doc_id/python/requirements.txt create mode 100644 transforms/universal/ededup/python/requirements.txt create mode 100644 transforms/universal/filter/python/requirements.txt create mode 100644 transforms/universal/profiler/python/requirements.txt create mode 100644 transforms/universal/resize/python/requirements.txt create mode 100644 transforms/universal/tokenization/python/requirements.txt diff --git a/.make.defaults b/.make.defaults index f9f58500f..4e07d84b3 100644 --- a/.make.defaults +++ b/.make.defaults @@ -627,7 +627,7 @@ MINIO_ADMIN_PWD= localminiosecretkey rm -rf dist || true rm -rf src/*egg-info || true ${PIP} install --upgrade build - ${PYTHON} -m build + ${PYTHON} -m build $(BUILD_WHEEL_ARG) # Publish the distribution in the dist directory, usually created with .defaults.build-dist target .PHONY: .defaults.publish-dist diff --git a/transforms/Makefile b/transforms/Makefile index 2f8fa27b1..5ff1f5111 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -79,3 +79,32 @@ workflow-upload:: set-versions:: @# Help: Recursively make $@ in all subdirs @$(MAKE) RULE=$@ .recurse + + +build-pkg-dist:: + ## Most transforms today don't have a package name.... Need to fix that + ## In the meantime, we will copy everything to a single folder + -rm -fr src + mkdir src + # Copy all the src folders recursively (not clear if they have subfolders) + for x in $(shell find . | grep '[ray| python]/src$$') ; do \ + echo $$x ; \ + if [ -d "$$x" ]; then \ + cp -r $$x/* src ; \ + fi \ + done + # Only needs to build the whl + $(MAKE) BUILD_WHEEL_ARG=-w .defaults.build-dist + +test-pkg-dist:: + -rm -fr venv + python -m venv venv + source venv/bin/activate && $(PYTHON) -m pip install '$(REPOROOT)/data-processing-lib/dist/data_prep_toolkit-$(DPK_VERSION)-py3-none-any.whl[dev,ray]' + source venv/bin/activate && $(PYTHON) -m pip install 'dist/data_prep_toolkit_transforms-$(DPK_TRANSFORMS_VERSION)-py3-none-any.whl[all]' + for T in $(shell find . | grep '[ray| python]/test$$') ; do \ + echo "running unit test on: $$T" ; \ + source venv/bin/activate && $(PYTEST) $$T; \ + done; + @# Help: Setup environment and run unit tests for all transforms + + diff --git a/transforms/packaging/python/README.md b/transforms/README-list.md similarity index 94% rename from transforms/packaging/python/README.md rename to transforms/README-list.md index 20eb0dff0..99885ad34 100644 --- a/transforms/packaging/python/README.md +++ b/transforms/README-list.md @@ -5,9 +5,14 @@ The [transforms](https://github.com/IBM/data-prep-kit/blob/dev/transforms/README.md) are delivered as a standard pyton library available on pypi and can be installed using pip install: `python -m pip install data-prep-toolkit-transforms` +or +`python -m pip install data-prep-toolkit-transforms[ray]` + installing the python transforms will also install `data-prep-toolkit` +installing the ray transforms will also install `data-prep-toolkit[ray]` + ## List of Transforms in current package Note: This list includes the transforms that were part of the release starting with data-prep-toolkit-transforms:0.2.1. This list may not always reflect up to date information. Users are encourage to raise an issue in git when they discover missing components or packages that are listed below but not in the current release they get from pypi. diff --git a/transforms/code/code2parquet/python/pyproject.toml b/transforms/code/code2parquet/python/pyproject.toml index 34a668bf0..16c63f7d3 100644 --- a/transforms/code/code2parquet/python/pyproject.toml +++ b/transforms/code/code2parquet/python/pyproject.toml @@ -9,11 +9,14 @@ authors = [ { name = "David Wood", email = "dawood@us.ibm.com" }, { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "parameterized", - "pandas", -] +dynamic = ["dependencies"] + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]}] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] diff --git a/transforms/code/code2parquet/python/requirements.txt b/transforms/code/code2parquet/python/requirements.txt new file mode 100644 index 000000000..758ab56fe --- /dev/null +++ b/transforms/code/code2parquet/python/requirements.txt @@ -0,0 +1,3 @@ +data-prep-toolkit==0.2.2.dev0 +parameterized +pandas diff --git a/transforms/code/code_quality/python/pyproject.toml b/transforms/code/code_quality/python/pyproject.toml index 58e2affa7..22313fc2a 100644 --- a/transforms/code/code_quality/python/pyproject.toml +++ b/transforms/code/code_quality/python/pyproject.toml @@ -8,11 +8,15 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Shivdeep Singh", email = "shivdeep.singh@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "bs4==0.0.2", - "transformers==4.38.2", -] +dynamic = ["dependencies"] + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] diff --git a/transforms/code/code_quality/python/requirements.txt b/transforms/code/code_quality/python/requirements.txt new file mode 100644 index 000000000..106e56f74 --- /dev/null +++ b/transforms/code/code_quality/python/requirements.txt @@ -0,0 +1,3 @@ +data-prep-toolkit==0.2.2.dev0 +bs4==0.0.2 +transformers==4.38.2 diff --git a/transforms/code/header_cleanser/python/pyproject.toml b/transforms/code/header_cleanser/python/pyproject.toml index c4326b4a0..2e24466f0 100644 --- a/transforms/code/header_cleanser/python/pyproject.toml +++ b/transforms/code/header_cleanser/python/pyproject.toml @@ -8,15 +8,16 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Yash kalathiya", email = "yashkalathiya164@gmail.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "scancode-toolkit==32.1.0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/code/header_cleanser/python/requirements.txt b/transforms/code/header_cleanser/python/requirements.txt new file mode 100644 index 000000000..bed2168c1 --- /dev/null +++ b/transforms/code/header_cleanser/python/requirements.txt @@ -0,0 +1,3 @@ +data-prep-toolkit==0.2.2.dev0 +scancode-toolkit==32.1.0 ; platform_system != 'Darwin' + diff --git a/transforms/code/license_select/python/pyproject.toml b/transforms/code/license_select/python/pyproject.toml index 1058b0440..0d7857d12 100644 --- a/transforms/code/license_select/python/pyproject.toml +++ b/transforms/code/license_select/python/pyproject.toml @@ -9,14 +9,16 @@ authors = [ { name = "Shivdeep Singh", email = "shivdeep.singh@ibm.com" }, { name = "Mark Lewis", email = "mark_lewis@uk.ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.1.dev0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/code/license_select/python/requirements.txt b/transforms/code/license_select/python/requirements.txt new file mode 100644 index 000000000..e14cde7ab --- /dev/null +++ b/transforms/code/license_select/python/requirements.txt @@ -0,0 +1 @@ +data-prep-toolkit==0.2.2.dev0 \ No newline at end of file diff --git a/transforms/code/proglang_select/python/pyproject.toml b/transforms/code/proglang_select/python/pyproject.toml index 25aa5fdcf..9745a48c3 100644 --- a/transforms/code/proglang_select/python/pyproject.toml +++ b/transforms/code/proglang_select/python/pyproject.toml @@ -8,14 +8,16 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Shivdeep Singh", email = "shivdeep.singh@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/code/proglang_select/python/requirements.txt b/transforms/code/proglang_select/python/requirements.txt new file mode 100644 index 000000000..e14cde7ab --- /dev/null +++ b/transforms/code/proglang_select/python/requirements.txt @@ -0,0 +1 @@ +data-prep-toolkit==0.2.2.dev0 \ No newline at end of file diff --git a/transforms/language/doc_chunk/python/pyproject.toml b/transforms/language/doc_chunk/python/pyproject.toml index 7705779b0..1a3bd333f 100644 --- a/transforms/language/doc_chunk/python/pyproject.toml +++ b/transforms/language/doc_chunk/python/pyproject.toml @@ -10,16 +10,15 @@ authors = [ { name = "Panos Vagenas", email = "pva@zurich.ibm.com" }, { name = "Christoph Auer", email = "cau@zurich.ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "docling-core==1.3.0", - "llama-index-core>=0.11.0,<0.12.0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/language/doc_chunk/python/requirements.txt b/transforms/language/doc_chunk/python/requirements.txt new file mode 100644 index 000000000..8e8c1bebb --- /dev/null +++ b/transforms/language/doc_chunk/python/requirements.txt @@ -0,0 +1,3 @@ +data-prep-toolkit==0.2.2.dev0 +docling-core==1.3.0 +llama-index-core>=0.11.0,<0.12.0 diff --git a/transforms/language/doc_quality/python/pyproject.toml b/transforms/language/doc_quality/python/pyproject.toml index 8ebec8fe3..12c712ae9 100644 --- a/transforms/language/doc_quality/python/pyproject.toml +++ b/transforms/language/doc_quality/python/pyproject.toml @@ -8,14 +8,16 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Daiki Tsuzuku", email = "dtsuzuku@jp.ibm.com" } ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + + [project.optional-dependencies] dev = [ diff --git a/transforms/language/doc_quality/python/requirements.txt b/transforms/language/doc_quality/python/requirements.txt new file mode 100644 index 000000000..f2f9d6200 --- /dev/null +++ b/transforms/language/doc_quality/python/requirements.txt @@ -0,0 +1,2 @@ + +data-prep-toolkit==0.2.2.dev0 diff --git a/transforms/language/lang_id/python/pyproject.toml b/transforms/language/lang_id/python/pyproject.toml index 54c874a36..1b9107019 100644 --- a/transforms/language/lang_id/python/pyproject.toml +++ b/transforms/language/lang_id/python/pyproject.toml @@ -8,13 +8,14 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Daiki Tsuzuku", email = "dtsuzuku@jp.ibm.com" } ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "fasttext==0.9.2", - "langcodes==3.3.0", - "huggingface-hub >= 0.21.4, <1.0.0", - "numpy==1.26.4", -] +dynamic = ["dependencies"] + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] diff --git a/transforms/language/lang_id/python/requirements.txt b/transforms/language/lang_id/python/requirements.txt new file mode 100644 index 000000000..111465be0 --- /dev/null +++ b/transforms/language/lang_id/python/requirements.txt @@ -0,0 +1,5 @@ +data-prep-toolkit==0.2.2.dev0 +fasttext==0.9.2 +langcodes==3.3.0 +huggingface-hub >= 0.21.4, <1.0.0 +numpy==1.26.4 diff --git a/transforms/language/pii_redactor/python/pyproject.toml b/transforms/language/pii_redactor/python/pyproject.toml index 55d4e8970..7045b6ec0 100644 --- a/transforms/language/pii_redactor/python/pyproject.toml +++ b/transforms/language/pii_redactor/python/pyproject.toml @@ -8,18 +8,15 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Sowmya.L.R", email = "lrsowmya@gmail.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "presidio-analyzer>=2.2.355", - "presidio-anonymizer>=2.2.355", - "flair>=0.14.0", - "pandas>=2.2.2", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/language/pii_redactor/python/requirements.txt b/transforms/language/pii_redactor/python/requirements.txt new file mode 100644 index 000000000..99e423ce1 --- /dev/null +++ b/transforms/language/pii_redactor/python/requirements.txt @@ -0,0 +1,5 @@ +data-prep-toolkit==0.2.2.dev0 +presidio-analyzer>=2.2.355 +presidio-anonymizer>=2.2.355 +flair>=0.14.0 +pandas>=2.2.2 diff --git a/transforms/language/text_encoder/python/pyproject.toml b/transforms/language/text_encoder/python/pyproject.toml index e9f84fefd..0dd0ac44c 100644 --- a/transforms/language/text_encoder/python/pyproject.toml +++ b/transforms/language/text_encoder/python/pyproject.toml @@ -10,15 +10,15 @@ authors = [ { name = "Panos Vagenas", email = "pva@zurich.ibm.com" }, { name = "Peter Staar", email = "taa@zurich.ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "sentence-transformers==3.0.1", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/language/text_encoder/python/requirements.txt b/transforms/language/text_encoder/python/requirements.txt new file mode 100644 index 000000000..be8c0a880 --- /dev/null +++ b/transforms/language/text_encoder/python/requirements.txt @@ -0,0 +1,2 @@ +data-prep-toolkit==0.2.2.dev0 +sentence-transformers==3.0.1 diff --git a/transforms/packaging/.gitignore b/transforms/packaging/.gitignore deleted file mode 100644 index 863607847..000000000 --- a/transforms/packaging/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -**/src -**/dist -**/*.egg-info -**/build - diff --git a/transforms/packaging/.make.packaging b/transforms/packaging/.make.packaging deleted file mode 100644 index 5268889d0..000000000 --- a/transforms/packaging/.make.packaging +++ /dev/null @@ -1,83 +0,0 @@ -ifndef T_SET -T_SET=all -endif - - -venv: - $(MAKE) .defaults.create-venv - -test:: test-src - -clean:: .transforms.clean - -rm -fr src - -image:: .transforms.python-image - -run-ut:: - source venv/bin/activate; \ - if [ -e requirements.test.txt ]; then \ - $(PYTHON) -m pip install -r requirements.test.txt ; \ - fi; \ - for T in $(TRANSFORMS_NAMES); do \ - echo running unit test on: $$T ; \ - $(PYTEST) $(REPOROOT)/transforms/$$T/$(PACKAGING_RUN_TIME)/test; \ - done; - @# Help: Setup environment and run unit tests for all transforms - - -setup: .transforms.setup venv - $(MAKE) src - source venv/bin/activate; \ - $(PYTHON) -m pip install . - @# Help: Do any default transform setup before running make src and setting up a test environment - - -requirements: - if [ -e requirements.$(T_SET).txt ]; then \ - cp requirements.$(T_SET).txt requirements.txt ; \ - fi - -pkg-name: - if [ $(TRANSFORM_PKG) ]; then \ - cat pyproject.toml | sed -e \ - 's/^name[ ]*=.*/name = "'${TRANSFORM_PKG}'"/' \ - > tt.toml; \ - mv tt.toml pyproject.toml; \ - fi - -is-patch: - if [ $(IS_PATCH) ]; then \ - cat pyproject.toml | sed -e \ - 's/^version[ ]*=[ ]*"\(.*\).dev.*/version = "\1"/' \ - > tt.toml; \ - mv tt.toml pyproject.toml; \ - fi - -##################################################### -# to build a patched release, use make IS_PATCH=1 src -##################################################### -src: - mkdir src - make requirements - make pkg-name - make is-patch - for T in $(shell echo $(TRANSFORMS_NAMES)); do \ - echo copy src from $$T ; \ - cp -R $(REPOROOT)/transforms/$$T/$(PACKAGING_RUN_TIME)/src/* src ; \ - rm -fr *.egg-info ; \ - rm -fr dist ; \ - rm -fr build ; \ - done; - @# Help: Setup src folder and remove old distribution. to setup for a patched release use: make IS_PATCH=1 $@ - - -build:: build-dist - -publish:: publish-dist - -build-dist:: src .defaults.build-dist - @# Help: build the distribution for publishing to pypi. to build a patch release (no .devN) use: make IS_PATCH=1 $@ - -publish-dist:: .defaults.publish-dist - - diff --git a/transforms/packaging/Makefile b/transforms/packaging/Makefile deleted file mode 100644 index aa75d525e..000000000 --- a/transforms/packaging/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -REPOROOT=../../ -# Use make help, to see the available rules -include ../../.make.defaults - -setup:: - -clean:: - # Clean up workflows common virtual environment. - rm -rf venv || true - rm -rf *.back || true - @# Help: Recursively make $@ all subdirs - $(MAKE) RULE=$@ .recurse - -src:: - @# Help: Recursively setup $@ in all subdirs - $(MAKE) RULE=$@ .recurse - -setup:: - -build:: - -build-dist:: - @# Help: Recursively build distributions in all subdirs - $(MAKE) RULE=$@ .recurse - -publish-dist:: - @# Help: Recursively publish distributions in all subdirs - $(MAKE) RULE=$@ .recurse - -venv:: - -image:: - -publish:: - -test-image:: - -test:: - -test-src:: - @# Help: Recursively make $@ in all subdirs - $(MAKE) RULE=$@ .recurse - -kind-load-image:: - -docker-load-image:: - -docker-save-image:: - -workflow-venv:: - -workflow-test:: - -workflow-build:: - -workflow-upload:: - -set-versions:: - @# Help: Recursively make $@ in all subdirs - @$(MAKE) RULE=$@ .recurse diff --git a/transforms/packaging/README.md b/transforms/packaging/README.md deleted file mode 100644 index e0d23ad52..000000000 --- a/transforms/packaging/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Transforms Pacakges for both Python and Ray - -Most available Transforms can be published to pypi as a single package. A detailed list of available Python transforms is available at this [link](python/README.md). Similarly the following [link](ray/README.md) provide a derailed list and installation instructions for Ray transforms - - - -## Clone folder and update version number -```` -git clone https://github.com/IBM/data-prep-kit.git package-release -cd package-release -```` -in `.make.versions`, Set the values for DPK_MAJOR_VERSION, DPK_MINOR_VERSION and DPK_MICRO_VERSION to specify the DPK library to use and as appropriate, set the value for `DPK_TRANSFORMS_VERSION` that will be used to tag the latest version released to pypi - -`make set-versions` - -## Creating src folder - -Given that the transforms do not currently have their own name spaces, the first step is to copy all the transforms to the same src folder prior to running unit tests of the individual transforms and/or building the distribution: - - -```` -cd transforms/packaging -make clean -make src -```` - -## Build and Test - -This procedure will run all the UT for each individual transforms using a single package configuration: - -```` -cd transforms/packaging -make clean -make src -make test-src -```` - -## Build and Deploy - -This procedure will buid two wheels: one for the python transforms and one for the ray transforms. - -```` -cd transforms/packaging -make clean -make src -make build-dist -```` - -To publish the wheels to pypi.org, run: - -`make publish-dist` - - - - diff --git a/transforms/packaging/python/Makefile b/transforms/packaging/python/Makefile deleted file mode 100644 index 6a0a355de..000000000 --- a/transforms/packaging/python/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Define the root of the local git clone for the common rules to be able -# know where they are running from. -REPOROOT=../../.. -# Include a library of common .transform.* targets which most -# transforms should be able to reuse. However, feel free -# to override/redefine the rules below. - -# $(REPOROOT)/.make.versions file contains the versions - -include $(REPOROOT)/transforms/.make.transforms -include ../.make.packaging - -PACKAGING_RUN_TIME=python - -ifeq ($(T_SET), all) -# Cannot combine language/html2parquet with pdf2parquet due to: -#The conflict is caused by: -# docling-ibm-models 1.1.7 depends on lxml<5.0.0 and >=4.9.1 -# trafilatura 1.12.0 depends on lxml>=5.2.2; platform_system != "Darwin" or python_version > "3.8" -TRANSFORMS_NAMES = code/code_quality \ - code/code2parquet \ - code/header_cleanser \ - code/proglang_select \ - language/doc_chunk \ - language/doc_quality \ - language/lang_id \ - language/pdf2parquet \ - language/pii_redactor \ - language/text_encoder \ - universal/tokenization \ - universal/ededup \ - /universal/doc_id \ - universal/filter \ - universal/resize -TRANSFORM_PKG = "data_prep_toolkit_transforms" -endif - -ifeq ($(T_SET), lang1) -TRANSFORMS_NAMES = language/doc_quality \ - language/lang_id \ - language/text_encoder \ - language/html2parquet \ - universal/tokenization \ - universal/ededup \ - /universal/doc_id \ - universal/filter \ - universal/resize -TRANSFORM_PKG = "data_prep_toolkit_transforms_lang1" -endif - -# distribution versions is the same as image version. -set-versions: - $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions - -test-src:: - $(MAKE) src - $(MAKE) .transforms.python-venv - $(MAKE) run-ut - @# Help: Do any default transform setup before running make src and setting up a test environment - -test-with-pypi: - $(MAKE) src - $(MAKE) .defaults.create-venv - source venv/bin/activate; \ - $(PYTHON) -m pip install . - $(MAKE) run-ut - @# Help: Load dependencies from pypi and run all unit tests: final step in verification BEFORE deploying to pypi) - - -test-wheel: - -rm -fr venv - $(MAKE) .defaults.create-venv - source venv/bin/activate; \ - $(PYTHON) -m pip install dist/*.whl - $(MAKE) run-ut - @# Help: Load wheel from local folder and run all unit tests - - - -test-latest-patch: - $(MAKE) clean - $(MAKE) .defaults.create-venv - source venv/bin/activate; \ - $(PYTHON) -m pip install $(TRANSFORM_PKG) - $(MAKE) run-ut - @# Help: Load wheel from pypi and run all unit tests: final step in verification AFTER deploying to pypi) - - - diff --git a/transforms/packaging/python/pyproject.toml b/transforms/packaging/python/pyproject.toml deleted file mode 100644 index 8d760515a..000000000 --- a/transforms/packaging/python/pyproject.toml +++ /dev/null @@ -1,39 +0,0 @@ -[project] -name = "data_prep_toolkit_transforms" -version = "0.2.2.dev0" -requires-python = ">=3.10,<3.13" -keywords = ["transforms", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] -description = "Data Preparation Toolkit Transforms" -license = {text = "Apache-2.0"} -readme = {file = "README.md", content-type = "text/markdown"} -authors = [ - { name = "Maroun Touma", email = "touma@us.ibm.com" }, -] -dynamic = ["dependencies"] - -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" - -[tool.setuptools.dynamic] -dependencies = {file = ["requirements.txt"]} - -[options] -package_dir = ["src"] - -[options.packages.find] -where = ["src/"] - -[tool.pytest.ini_options] -# Currently we use low coverage since we have to run tests separately (see makefile) -#addopts = "--cov --cov-report term-missing --cov-fail-under 25" -markers = ["unit: unit tests", "integration: integration tests"] - -[tool.coverage.run] -include = ["src/*"] - - - - - - diff --git a/transforms/packaging/python/requirements.all.txt b/transforms/packaging/python/requirements.all.txt deleted file mode 100644 index c1246fba9..000000000 --- a/transforms/packaging/python/requirements.all.txt +++ /dev/null @@ -1,51 +0,0 @@ -data-prep-toolkit>=0.2.1 -# code quality -bs4==0.0.2 -transformers==4.38.2 -#pdf2parquet -docling-core==1.3.0 -docling-ibm-models==1.1.7 -deepsearch-glm==0.21.0 -docling==1.11.0, -filetype >=1.2.0, <2.0.0 -#Doc chunking -docling-core==1.3.0, -llama-index-core>=0.11.0,<0.12.0, -#filter -duckdb>=0.10.1 -#langid -fasttext==0.9.2 -langcodes==3.3.0 -huggingface-hub >= 0.21.4, <1.0.0 -numpy==1.26.4 -#fdedup -mmh3>=4.1.0 -xxhash==3.4.1 -tqdm==4.66.3 -scipy>=1.12.0, <2.0.0 -# ededup -mmh3>=4.1.0 -xxhash==3.4.1 -#code2parquet -pandas -parameterized -#header cleanser -scancode-toolkit==32.1.0 ; platform_system != 'Darwin' -#text_encoder -sentence-transformers==3.0.1 -# PII-redactor -presidio-analyzer>=2.2.355 -presidio-anonymizer>=2.2.355 -flair>=0.14.0 -pandas>=2.2.2 -#html2parquet -#INFO: pip is looking at multiple versions of trafilatura to determine which version is compatible with other requirements. This could take a while. -#The conflict is caused by: -# docling-ibm-models 1.1.7 depends on lxml<5.0.0 and >=4.9.1 -# trafilatura 1.12.0 depends on lxml>=5.2.2; platform_system != "Darwin" or python_version > "3.8" -#trafilatura==1.12.0 -#tokenization -transformers==4.38.2 - - - diff --git a/transforms/packaging/python/requirements.lang1.txt b/transforms/packaging/python/requirements.lang1.txt deleted file mode 100644 index 1c7289f64..000000000 --- a/transforms/packaging/python/requirements.lang1.txt +++ /dev/null @@ -1,32 +0,0 @@ -data-prep-toolkit>=0.2.1 -#filter -duckdb>=0.10.1 -#langid -fasttext==0.9.2 -langcodes==3.3.0 -huggingface-hub >= 0.21.4, <1.0.0 -numpy==1.26.4 -#fdedup -mmh3>=4.1.0 -xxhash==3.4.1 -tqdm==4.66.3 -scipy==1.12.0 -# ededup -mmh3>=4.1.0, -xxhash==3.4.1 -#text_encoder -sentence-transformers>=3.0.1 -#html2parquet -trafilatura==1.12.0 -#tokenization -transformers==4.38.2 - -#ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. -#data-prep-toolkit-transforms 0.2.2.dev0 requires duckdb==0.10.1, but you have duckdb 1.1.0 which is incompatible. -#data-prep-toolkit-transforms 0.2.2.dev0 requires sentence-transformers==3.0.1, but you have sentence-transformers 3.1.1 which is incompatible. - - - - - - diff --git a/transforms/packaging/ray/Makefile b/transforms/packaging/ray/Makefile deleted file mode 100644 index 0a1d6d911..000000000 --- a/transforms/packaging/ray/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -# Define the root of the local git clone for the common rules to be able -# know where they are running from. -REPOROOT=../../.. -# Include a library of common .transform.* targets which most -# transforms should be able to reuse. However, feel free -# to override/redefine the rules below. - -# $(REPOROOT)/.make.versions file contains the versions - -include $(REPOROOT)/transforms/.make.transforms -include ../.make.packaging - -PACKAGING_RUN_TIME=ray - -# Excluded from build -# ./code/malware/ray - -set-versions: - $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions - - -## Ray Transforms: `find . -name src | grep ray/src` -TRANSFORMS_NAMES = code/proglang_select \ - code/header_cleanser \ - code/code_quality \ - code/repo_level_ordering \ - code/code2parquet \ - language/doc_chunk \ - language/doc_quality \ - language/lang_id \ - language/text_encoder \ - language/pii_redactor \ - language/pdf2parquet \ - universal/fdedup \ - universal/tokenization \ - universal/ededup \ - universal/profiler \ - universal/doc_id \ - universal/filter \ - universal/resize - -# doc chunk has conflict dependencies with pdf2parquet that need to be resolved -# doc_chunk depends on docling>=1.8.2,<2.0.0 -# pdf2parquet depends on docling==1.7.0 - - -test-src:: - $(MAKE) src - $(MAKE) -C ../python src - make .transforms.ray-venv - $(MAKE) run-ut - @# Help: Do any default transform setup before running make src and setting up a test environment - -test-with-python-pypi: - $(MAKE) clean - $(MAKE) .defaults.create-venv - source venv/bin/activate && cd ../ray && $(MAKE) src && $(PYTHON) -m pip install . - $(MAKE) test-src - -test-with-pypi: - $(MAKE) clean - $(MAKE) .defaults.create-venv - source venv/bin/activate; \ - $(PYTHON) -m pip install data_prep_toolkit_transforms_ray==$(DPK_TRANSFORMS_VERSION) - $(MAKE) test-src - diff --git a/transforms/packaging/ray/README.md b/transforms/packaging/ray/README.md deleted file mode 100644 index b7d4cf2eb..000000000 --- a/transforms/packaging/ray/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# DPK Ray Transforms - -## installation - -The [transforms](https://github.com/IBM/data-prep-kit/blob/dev/transforms/README.md) are delivered as a standard pyton library available on pypi and can be installed using pip install: - -`python -m pip install data-prep-toolkit-transforms-ray` - -installing the Ray transforms will also install `data_prep_toolkit_transforms` and `data-prep-toolkit-ray` - -## List of Ray Transforms availabe in current package - -Note: This list includes the transforms that are part of the current release for 0.2.1.dev3 and will be maintained on best effort but may may not be always up to date. users are encourage to raise an issue in git when they discover missing components - -* code - * [code2parquet](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/code2parquet/ray/README.md) - * [proglang_select](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/proglang_select/ray/README.md) - * [header_cleanser (Not available on MacOS)](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/code2parquet/ray/README.md) - * [code_quality](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/code_quality/ray/README.md) - * [repo_level_ordering](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/repo_level_ordering/ray/README.md) -* language - * [doc_quality](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/doc_quality/ray/README.md) - * [doc_chunk](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/doc_chunk/ray/README.md) - * [lang_id](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/lang_id/ray/README.md) - * [text_encoder](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/text_encoder/ray/README.md) - * [pdf2parquet](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/pdf2parquet/ray/README.md) - * [pii_redactor](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/pii_redactor/ray/README.md) -* universal - * [fdedup](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/fdedup/ray/README.md) - * [tokenization](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/tokenization/ray/README.md) - * [ededup](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/ededup/ray/README.md) - * [profiler](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/profiler/ray/README.md) - * [doc_id](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/doc_id/ray/README.md) - * [filter](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/filter/ray/README.md) - * [resize](https://github.com/IBM/data-prep-kit/blob/dev/transforms/code/resize/ray/README.md) - - - - - - diff --git a/transforms/packaging/ray/pyproject.toml b/transforms/packaging/ray/pyproject.toml deleted file mode 100644 index 2f02d4c51..000000000 --- a/transforms/packaging/ray/pyproject.toml +++ /dev/null @@ -1,40 +0,0 @@ -[project] -name = "data_prep_toolkit_transforms_ray" -version = "0.2.2.dev0" -requires-python = ">=3.10,<3.13" -keywords = ["transforms", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] -description = "Data Preparation Toolkit Transforms using Ray" -license = {text = "Apache-2.0"} -readme = {file = "README.md", content-type = "text/markdown"} -authors = [ - { name = "Maroun Touma", email = "touma@us.ibm.com" }, -] -dynamic = ["dependencies"] - -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" - -[tool.setuptools.dynamic] -dependencies = {file = ["requirements.txt"]} - - -[options] -package_dir = ["src"] - -[options.packages.find] -where = ["src/"] - -[tool.pytest.ini_options] -# Currently we use low coverage since we have to run tests separately (see makefile) -#addopts = "--cov --cov-report term-missing --cov-fail-under 25" -markers = ["unit: unit tests", "integration: integration tests"] - -[tool.coverage.run] -include = ["src/*"] - - - - - - diff --git a/transforms/packaging/ray/requirements.txt b/transforms/packaging/ray/requirements.txt deleted file mode 100644 index 632bbe670..000000000 --- a/transforms/packaging/ray/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -data-prep-toolkit-ray>=0.2.2.dev0 -data-prep-toolkit-transforms>=0.2.2.dev0 -scancode-toolkit==32.1.0 ; platform_system != 'Darwin' -parameterized -tqdm==4.66.3 -mmh3==4.1.0 -xxhash==3.4.1 -tqdm==4.66.3 -#The conflict is caused by: -# ray fdedup depends on scipy==1.12.0 -# docling 1.7.0 depends on scipy<2.0.0 and >=1.14.1 -scipy>=1.12.0 -networkx==3.3 -colorlog==6.8.2 -func-timeout==4.3.5 -pandas==2.2.2 -emerge-viz==2.0.0 - - - - diff --git a/transforms/pyproject.toml b/transforms/pyproject.toml new file mode 100644 index 000000000..30d9f39e9 --- /dev/null +++ b/transforms/pyproject.toml @@ -0,0 +1,96 @@ +[project] +name = "data_prep_toolkit_transforms" +version = "0.2.2.dev0" +requires-python = ">=3.10" +keywords = ["transforms", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] +description = "Data Preparation Toolkit Transforms using Ray" +license = {text = "Apache-2.0"} +readme = {file = "README-list.md", content-type = "text/markdown"} +authors = [ + { name = "Maroun Touma", email = "touma@us.ibm.com" }, +] +dynamic = ["dependencies","optional-dependencies"] + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + + +[tool.setuptools.dynamic.dependencies] +file = ["requirements.txt"] + +[tool.setuptools.dynamic.optional-dependencies] +dev = { file = ["requirements-dev.txt"]} +ray = { file = ["requirements-ray.txt"]} +all = { file = [ +"code/proglang_select/python/requirements.txt", +"code/header_cleanser/python/requirements.txt", +"code/license_select/python/requirements.txt", +"code/code_quality/python/requirements.txt", +"code/code2parquet/python/requirements.txt", + +"language/doc_quality/python/requirements.txt", +"language/doc_chunk/python/requirements.txt", +##### Cannot have html2parquet until we solve +## docling-ibm-models 1.1.7 depends on lxml<5.0.0 and >=4.9.1 +## trafilatura 1.12.0 depends on lxml>=5.2.2; platform_system != "Darwin" or python_version > "3.8" +## "language/html2parquet/python/requirements.txt", +"language/pii_redactor/python/requirements.txt", +"language/lang_id/python/requirements.txt", +"language/text_encoder/python/requirements.txt", +"language/pdf2parquet/python/requirements.txt", + +"universal/hap/python/requirements.txt", +"universal/tokenization/python/requirements.txt", +"universal/ededup/python/requirements.txt", +"universal/profiler/python/requirements.txt", +"universal/doc_id/python/requirements.txt", +"universal/filter/python/requirements.txt", +"universal/resize/python/requirements.txt" +]} + +# pyproject.toml must be in a parent and cannot be in sibling +# i.e. Cannot access '../code/proglang_select/python/.. + +proglang_select = { file = ["code/proglang_select/python/requirements.txt"]} +header_cleanser = {file = ["code/header_cleanser/python/requirements.txt"]} +license_select = { file = ["code/license_select/python/requirements.txt"]} +code_quality = { file = ["code/code_quality/python/requirements.txt"]} +code2parquet = {file = ["code/code2parquet/python/requirements.txt"]} + +doc_quality = { file = ["language/doc_quality/python/requirements.txt"]} +doc_chunk = { file = ["language/doc_chunk/python/requirements.txt"]} +html2parquet = { file = ["language/html2parquet/python/requirements.txt"]} +pii_redactor = { file = ["language/pii_redactor/python/requirements.txt"]} +lang_id = { file = ["language/lang_id/python/requirements.txt"]} +text_encoder = { file = ["language/text_encoder/python/requirements.txt"]} +pdf2parquet = { file = ["language/pdf2parquet/python/requirements.txt"]} + +hap = { file = ["universal/hap/python/requirements.txt"]} +tokenization = { file = ["universal/tokenization/python/requirements.txt"]} +ededup = { file = ["universal/ededup/python/requirements.txt"]} +profiler = { file = ["universal/profiler/python/requirements.txt"]} +doc_id = { file = ["universal/doc_id/python/requirements.txt"]} +filter = { file = ["universal/filter/python/requirements.txt"]} +resize = { file = ["universal/resize/python/requirements.txt"]} + +# Does not seem to work for our custom layout +# copy all files to a single src and let automatic discovery find them + +[options] +package_dir = ["src","test"] + +[options.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +# Currently we use low coverage since we have to run tests separately (see makefile) +#addopts = "--cov --cov-report term-missing --cov-fail-under 25" +markers = ["unit: unit tests", "integration: integration tests"] + + + + + + + diff --git a/transforms/requirements-ray.txt b/transforms/requirements-ray.txt new file mode 100644 index 000000000..4eadbf121 --- /dev/null +++ b/transforms/requirements-ray.txt @@ -0,0 +1,9 @@ +data-prep-toolkit[ray]>=0.2.2.dev0 +networkx==3.3 +colorlog==6.8.2 +func-timeout==4.3.5 +emerge-viz==2.0.0 + + + + diff --git a/transforms/requirements.txt b/transforms/requirements.txt new file mode 100644 index 000000000..d30f01bd3 --- /dev/null +++ b/transforms/requirements.txt @@ -0,0 +1 @@ +data-prep-toolkit>=0.2.2.dev0 \ No newline at end of file diff --git a/transforms/universal/doc_id/python/pyproject.toml b/transforms/universal/doc_id/python/pyproject.toml index 46d3f79f8..b9d45b803 100644 --- a/transforms/universal/doc_id/python/pyproject.toml +++ b/transforms/universal/doc_id/python/pyproject.toml @@ -9,14 +9,15 @@ authors = [ { name = "David Wood", email = "dawood@us.ibm.com" }, { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0" -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/universal/doc_id/python/requirements.txt b/transforms/universal/doc_id/python/requirements.txt new file mode 100644 index 000000000..e14cde7ab --- /dev/null +++ b/transforms/universal/doc_id/python/requirements.txt @@ -0,0 +1 @@ +data-prep-toolkit==0.2.2.dev0 \ No newline at end of file diff --git a/transforms/universal/ededup/python/pyproject.toml b/transforms/universal/ededup/python/pyproject.toml index 59d0d72ee..fecad1683 100644 --- a/transforms/universal/ededup/python/pyproject.toml +++ b/transforms/universal/ededup/python/pyproject.toml @@ -9,16 +9,16 @@ authors = [ { name = "David Wood", email = "dawood@us.ibm.com" }, { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "mmh3==4.1.0", - "xxhash==3.4.1", -] + +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/universal/ededup/python/requirements.txt b/transforms/universal/ededup/python/requirements.txt new file mode 100644 index 000000000..d01c93d95 --- /dev/null +++ b/transforms/universal/ededup/python/requirements.txt @@ -0,0 +1,3 @@ +data-prep-toolkit==0.2.2.dev0 +mmh3>=4.1.0 +xxhash==3.4.1 diff --git a/transforms/universal/fdedup/ray/pyproject.toml b/transforms/universal/fdedup/ray/pyproject.toml index 3f2c8ba51..d6d36f9c0 100644 --- a/transforms/universal/fdedup/ray/pyproject.toml +++ b/transforms/universal/fdedup/ray/pyproject.toml @@ -11,10 +11,10 @@ authors = [ ] dependencies = [ "data-prep-toolkit-ray==0.2.2.dev0", - "mmh3==4.1.0", + "mmh3>=4.1.0", "xxhash==3.4.1", "tqdm==4.66.3", - "scipy==1.12.0" + "scipy>=1.12.0, <2.0.0" ] [build-system] diff --git a/transforms/universal/filter/python/pyproject.toml b/transforms/universal/filter/python/pyproject.toml index b9d781573..0cc80e7ae 100644 --- a/transforms/universal/filter/python/pyproject.toml +++ b/transforms/universal/filter/python/pyproject.toml @@ -8,10 +8,15 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Constantin Adam", email = "cmadam@us.ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "duckdb==0.10.1", -] + +dynamic = ["dependencies"] + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] diff --git a/transforms/universal/filter/python/requirements.txt b/transforms/universal/filter/python/requirements.txt new file mode 100644 index 000000000..9d1711c3b --- /dev/null +++ b/transforms/universal/filter/python/requirements.txt @@ -0,0 +1,3 @@ + +data-prep-toolkit==0.2.2.dev0 +duckdb>=0.10.1 diff --git a/transforms/universal/profiler/python/pyproject.toml b/transforms/universal/profiler/python/pyproject.toml index 4bc90209f..290e89a15 100644 --- a/transforms/universal/profiler/python/pyproject.toml +++ b/transforms/universal/profiler/python/pyproject.toml @@ -8,16 +8,15 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "mmh3==4.1.0", - "xxhash==3.4.1", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/universal/profiler/python/requirements.txt b/transforms/universal/profiler/python/requirements.txt new file mode 100644 index 000000000..d164794c7 --- /dev/null +++ b/transforms/universal/profiler/python/requirements.txt @@ -0,0 +1,5 @@ + +data-prep-toolkit==0.2.2.dev0 +mmh3==4.1.0 +xxhash==3.4.1 + diff --git a/transforms/universal/resize/python/pyproject.toml b/transforms/universal/resize/python/pyproject.toml index 2396e5b23..6dd64f3bf 100644 --- a/transforms/universal/resize/python/pyproject.toml +++ b/transforms/universal/resize/python/pyproject.toml @@ -9,14 +9,15 @@ authors = [ { name = "David Wood", email = "dawood@us.ibm.com" }, { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", -] +dynamic = ["dependencies"] [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/universal/resize/python/requirements.txt b/transforms/universal/resize/python/requirements.txt new file mode 100644 index 000000000..e14cde7ab --- /dev/null +++ b/transforms/universal/resize/python/requirements.txt @@ -0,0 +1 @@ +data-prep-toolkit==0.2.2.dev0 \ No newline at end of file diff --git a/transforms/universal/tokenization/python/pyproject.toml b/transforms/universal/tokenization/python/pyproject.toml index f69787b3d..b45336701 100644 --- a/transforms/universal/tokenization/python/pyproject.toml +++ b/transforms/universal/tokenization/python/pyproject.toml @@ -9,11 +9,6 @@ readme = {file = "README.md", content-type = "text/markdown"} authors = [ { name = "Xuan-Hong Dang", email = "xuan-hong.dang@ibm.com"}, ] -dependencies = [ - "data-prep-toolkit==0.2.2.dev0", - "transformers==4.38.2", -] - [project_urls] Repository = "https://github.com/IBM/data-prep-kit" @@ -21,10 +16,15 @@ Issues = "https://github.com/IBM/data-prep-kit/issues" Documentation = "https://ibm.github.io/data-prep-kit/" "Transform project" = "https://github.com/IBM/data-prep-kit/tree/dev/transforms/universal/tokenization" +dynamic = ["dependencies"] + [build-system] requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/universal/tokenization/python/requirements.txt b/transforms/universal/tokenization/python/requirements.txt new file mode 100644 index 000000000..269257538 --- /dev/null +++ b/transforms/universal/tokenization/python/requirements.txt @@ -0,0 +1,2 @@ +data-prep-toolkit==0.2.2.dev0 +transformers==4.38.2 From 552b5fc11740bc715df2d734082c9eaab75cb52b Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 15:10:48 -0400 Subject: [PATCH 03/70] fix typos --- transforms/code/code2parquet/python/pyproject.toml | 6 +----- transforms/code/code_quality/python/pyproject.toml | 4 ---- transforms/code/license_select/python/pyproject.toml | 2 +- transforms/code/license_select/ray/pyproject.toml | 6 +++--- transforms/language/lang_id/python/pyproject.toml | 3 --- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/transforms/code/code2parquet/python/pyproject.toml b/transforms/code/code2parquet/python/pyproject.toml index 16c63f7d3..b08504bef 100644 --- a/transforms/code/code2parquet/python/pyproject.toml +++ b/transforms/code/code2parquet/python/pyproject.toml @@ -16,11 +16,7 @@ requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] -dependencies = {file = ["requirements.txt"]}] - -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" +dependencies = {file = ["requirements.txt"]} [project.optional-dependencies] dev = [ diff --git a/transforms/code/code_quality/python/pyproject.toml b/transforms/code/code_quality/python/pyproject.toml index 22313fc2a..46f59bc6c 100644 --- a/transforms/code/code_quality/python/pyproject.toml +++ b/transforms/code/code_quality/python/pyproject.toml @@ -18,10 +18,6 @@ build-backend = "setuptools.build_meta" dependencies = {file = ["requirements.txt"]} -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" - [project.optional-dependencies] dev = [ "twine", diff --git a/transforms/code/license_select/python/pyproject.toml b/transforms/code/license_select/python/pyproject.toml index 0d7857d12..1404bb205 100644 --- a/transforms/code/license_select/python/pyproject.toml +++ b/transforms/code/license_select/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_license_select_transform_python" -version = "0.2.1.dev0" +version = "0.2.2.dev0" requires-python = ">=3.10" description = "License Select Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/code/license_select/ray/pyproject.toml b/transforms/code/license_select/ray/pyproject.toml index 89b4b9ea5..3295f2427 100644 --- a/transforms/code/license_select/ray/pyproject.toml +++ b/transforms/code/license_select/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_license_select_transform_ray" -version = "0.2.1.dev0" +version = "0.2.2.dev0" requires-python = ">=3.10" description = "License Select Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Mark Lewis", email = "mark_lewis@uk.ibm.com" }, ] dependencies = [ - "dpk-license-select-transform-python==0.2.1.dev0", - "data-prep-toolkit-ray==0.2.1.dev0", + "dpk-license-select-transform-python==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev0", ] [build-system] diff --git a/transforms/language/lang_id/python/pyproject.toml b/transforms/language/lang_id/python/pyproject.toml index 1b9107019..ba256765f 100644 --- a/transforms/language/lang_id/python/pyproject.toml +++ b/transforms/language/lang_id/python/pyproject.toml @@ -17,9 +17,6 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]} -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" [project.optional-dependencies] dev = [ From 05b9c85a859af639b649401968afff33de5b8dc5 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 15:24:57 -0400 Subject: [PATCH 04/70] fix typo Signed-off-by: Maroun Touma --- transforms/universal/filter/python/pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/transforms/universal/filter/python/pyproject.toml b/transforms/universal/filter/python/pyproject.toml index 0cc80e7ae..f2dadffa6 100644 --- a/transforms/universal/filter/python/pyproject.toml +++ b/transforms/universal/filter/python/pyproject.toml @@ -18,10 +18,6 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]} -[build-system] -requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] -build-backend = "setuptools.build_meta" - [project.optional-dependencies] dev = [ "twine", From 37454afcb1f7ba2430fbbeaa70758cafb7690ebe Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 15:35:34 -0400 Subject: [PATCH 05/70] Added requirements.txt to dockerfile Signed-off-by: Maroun Touma --- transforms/code/code2parquet/python/Dockerfile | 1 + transforms/code/code_quality/python/Dockerfile | 1 + transforms/code/header_cleanser/python/Dockerfile | 1 + transforms/code/license_select/python/Dockerfile | 1 + transforms/code/proglang_select/python/Dockerfile | 1 + transforms/language/doc_chunk/python/Dockerfile | 1 + transforms/language/doc_quality/python/Dockerfile | 1 + transforms/language/lang_id/python/Dockerfile | 1 + transforms/language/pii_redactor/python/Dockerfile | 1 + transforms/language/text_encoder/python/Dockerfile | 3 ++- transforms/universal/doc_id/python/Dockerfile | 2 +- transforms/universal/ededup/python/Dockerfile | 1 + transforms/universal/filter/python/Dockerfile | 1 + transforms/universal/profiler/python/Dockerfile | 1 + transforms/universal/resize/python/Dockerfile | 1 + transforms/universal/tokenization/python/Dockerfile | 1 + 16 files changed, 17 insertions(+), 2 deletions(-) diff --git a/transforms/code/code2parquet/python/Dockerfile b/transforms/code/code2parquet/python/Dockerfile index b36b6a6c4..f94301a9c 100644 --- a/transforms/code/code2parquet/python/Dockerfile +++ b/transforms/code/code2parquet/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy the main() entry point to the image diff --git a/transforms/code/code_quality/python/Dockerfile b/transforms/code/code_quality/python/Dockerfile index 76cf1de30..b25a57ca1 100644 --- a/transforms/code/code_quality/python/Dockerfile +++ b/transforms/code/code_quality/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . #COPY requirements.txt requirements.txt diff --git a/transforms/code/header_cleanser/python/Dockerfile b/transforms/code/header_cleanser/python/Dockerfile index c2e215904..84831bcd2 100644 --- a/transforms/code/header_cleanser/python/Dockerfile +++ b/transforms/code/header_cleanser/python/Dockerfile @@ -27,6 +27,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy source data diff --git a/transforms/code/license_select/python/Dockerfile b/transforms/code/license_select/python/Dockerfile index 6831306c3..2fa9f9426 100644 --- a/transforms/code/license_select/python/Dockerfile +++ b/transforms/code/license_select/python/Dockerfile @@ -18,6 +18,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy source data diff --git a/transforms/code/proglang_select/python/Dockerfile b/transforms/code/proglang_select/python/Dockerfile index a94d9d960..3186862f0 100644 --- a/transforms/code/proglang_select/python/Dockerfile +++ b/transforms/code/proglang_select/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy the main() entry point to the image diff --git a/transforms/language/doc_chunk/python/Dockerfile b/transforms/language/doc_chunk/python/Dockerfile index 8efb3845b..d399a77ed 100644 --- a/transforms/language/doc_chunk/python/Dockerfile +++ b/transforms/language/doc_chunk/python/Dockerfile @@ -21,6 +21,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install ${PIP_INSTALL_EXTRA_ARGS} --no-cache-dir -e . # copy transform main() entry point to the image diff --git a/transforms/language/doc_quality/python/Dockerfile b/transforms/language/doc_quality/python/Dockerfile index 78b769dd7..10dca4999 100644 --- a/transforms/language/doc_quality/python/Dockerfile +++ b/transforms/language/doc_quality/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy transform main() entry point to the image diff --git a/transforms/language/lang_id/python/Dockerfile b/transforms/language/lang_id/python/Dockerfile index 131748480..f1bcc1bdd 100644 --- a/transforms/language/lang_id/python/Dockerfile +++ b/transforms/language/lang_id/python/Dockerfile @@ -25,6 +25,7 @@ USER dpk COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # clean up apt diff --git a/transforms/language/pii_redactor/python/Dockerfile b/transforms/language/pii_redactor/python/Dockerfile index 64b92e1b6..437bf8220 100644 --- a/transforms/language/pii_redactor/python/Dockerfile +++ b/transforms/language/pii_redactor/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy transform main() entry point to the image diff --git a/transforms/language/text_encoder/python/Dockerfile b/transforms/language/text_encoder/python/Dockerfile index 676968fee..86023a440 100644 --- a/transforms/language/text_encoder/python/Dockerfile +++ b/transforms/language/text_encoder/python/Dockerfile @@ -19,7 +19,8 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . # END OF STEPS destined for a data-prep-kit base image -COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install ${PIP_INSTALL_EXTRA_ARGS} --no-cache-dir -e . # copy transform main() entry point to the image diff --git a/transforms/universal/doc_id/python/Dockerfile b/transforms/universal/doc_id/python/Dockerfile index 16a9c0e66..6f478cb33 100644 --- a/transforms/universal/doc_id/python/Dockerfile +++ b/transforms/universal/doc_id/python/Dockerfile @@ -18,7 +18,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml COPY --chown=dpk:root README.md README.md - +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy source data diff --git a/transforms/universal/ededup/python/Dockerfile b/transforms/universal/ededup/python/Dockerfile index d3d47e7a4..df9f3ce64 100644 --- a/transforms/universal/ededup/python/Dockerfile +++ b/transforms/universal/ededup/python/Dockerfile @@ -18,6 +18,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml COPY --chown=dpk:root README.md README.md +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . diff --git a/transforms/universal/filter/python/Dockerfile b/transforms/universal/filter/python/Dockerfile index 6f60d2813..5df52a36e 100644 --- a/transforms/universal/filter/python/Dockerfile +++ b/transforms/universal/filter/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy the main() entry point to the image diff --git a/transforms/universal/profiler/python/Dockerfile b/transforms/universal/profiler/python/Dockerfile index a744fc9cd..9aa921f5e 100644 --- a/transforms/universal/profiler/python/Dockerfile +++ b/transforms/universal/profiler/python/Dockerfile @@ -18,6 +18,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml COPY --chown=dpk:root README.md README.md +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . diff --git a/transforms/universal/resize/python/Dockerfile b/transforms/universal/resize/python/Dockerfile index 303e67840..9caa3565c 100644 --- a/transforms/universal/resize/python/Dockerfile +++ b/transforms/universal/resize/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:users src/ src/ COPY --chown=dpk:users pyproject.toml pyproject.toml COPY --chown=dpk:users README.md Readme.md +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . # copy the main() entry point to the image diff --git a/transforms/universal/tokenization/python/Dockerfile b/transforms/universal/tokenization/python/Dockerfile index a1fd159c7..a9a96e52d 100644 --- a/transforms/universal/tokenization/python/Dockerfile +++ b/transforms/universal/tokenization/python/Dockerfile @@ -19,6 +19,7 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml +COPY --chown=dpk:root requirements.txt requirements.txt RUN pip install --no-cache-dir -e . #COPY requirements.txt requirements.txt From 46ba7953f60940d218694c4655af7243e076fd54 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 16:02:13 -0400 Subject: [PATCH 06/70] added requirements.txt to context --- .make.defaults | 1 + 1 file changed, 1 insertion(+) diff --git a/.make.defaults b/.make.defaults index 4e07d84b3..a81c3fb39 100644 --- a/.make.defaults +++ b/.make.defaults @@ -234,6 +234,7 @@ __check_defined = \ mkdir ${LIB_NAME} cp -p -R ${LIB_PATH}/src ${LIB_NAME} cp -p -R ${LIB_PATH}/pyproject.toml ${LIB_NAME} + cp -p -R ${LIB_PATH}/requirements.txt ${LIB_NAME} cp -p -R ${LIB_PATH}/README.md ${LIB_NAME} # Build and image using the local Dockerfile and make the data-processing-lib/python From d52701b5963de3b9a0094686b5c1859a85088f4f Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 16:55:53 -0400 Subject: [PATCH 07/70] pip install python requirments from txt file Signed-off-by: Maroun Touma --- .make.defaults | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.make.defaults b/.make.defaults index a81c3fb39..e1287ffc4 100644 --- a/.make.defaults +++ b/.make.defaults @@ -302,7 +302,10 @@ endif if [ ! -z "$(EXTRA_INDEX_URL)" ]; then \ extra_url='--extra-index-url $(EXTRA_INDEX_URL)'; \ fi; \ - pip install $(PIP_INSTALL_EXTRA_ARGS) $${extra_url} -e $(PYTHON_PROJECT_DIR); + if [ -e $(PYTHON_PROJECT_DIR)/requirements.txt ]; then \ + pip install -r $(PYTHON_PROJECT_DIR)/requirements.txt; \ + fi; \ + pip install $(PIP_INSTALL_EXTRA_ARGS) $${extra_url} -e $(PYTHON_PROJECT_DIR) @echo Done installing source from $(PYTHON_PROJECT_DIR) into venv # Install local requirements last as it generally includes our lib source From 8e77eb7614775ad678b64563e6eb7ce3cb1dc98e Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 17:06:09 -0400 Subject: [PATCH 08/70] copy requiremwnts.txt if prsent Signed-off-by: Maroun Touma --- .make.defaults | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.make.defaults b/.make.defaults index e1287ffc4..bf46962ea 100644 --- a/.make.defaults +++ b/.make.defaults @@ -234,7 +234,7 @@ __check_defined = \ mkdir ${LIB_NAME} cp -p -R ${LIB_PATH}/src ${LIB_NAME} cp -p -R ${LIB_PATH}/pyproject.toml ${LIB_NAME} - cp -p -R ${LIB_PATH}/requirements.txt ${LIB_NAME} + -cp -p -R ${LIB_PATH}/requirements.txt ${LIB_NAME} cp -p -R ${LIB_PATH}/README.md ${LIB_NAME} # Build and image using the local Dockerfile and make the data-processing-lib/python From 4e62fb026d09b04e473ba8885c49836553f00966 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 17:56:34 -0400 Subject: [PATCH 09/70] try to install requirements file directly. Signed-off-by: Maroun Touma --- transforms/universal/tokenization/python/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/transforms/universal/tokenization/python/Dockerfile b/transforms/universal/tokenization/python/Dockerfile index a9a96e52d..e1eea7e40 100644 --- a/transforms/universal/tokenization/python/Dockerfile +++ b/transforms/universal/tokenization/python/Dockerfile @@ -20,11 +20,9 @@ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=dpk:root src/ src/ COPY --chown=dpk:root pyproject.toml pyproject.toml COPY --chown=dpk:root requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -e . -#COPY requirements.txt requirements.txt -#RUN pip install --no-cache-dir -r requirements.txt - # copy the main() entry point to the image COPY ./src/tokenization_transform_python.py . From 0ada456b00aea99c2408e4e9519a42807306c607 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 18:14:30 -0400 Subject: [PATCH 10/70] Apply same fix as for python image. For some reason, in this image we need to install the rquirements.txt seperately Signed-off-by: Maroun Touma --- transforms/universal/tokenization/ray/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/transforms/universal/tokenization/ray/Dockerfile b/transforms/universal/tokenization/ray/Dockerfile index 0199e23b8..8b7e78c27 100644 --- a/transforms/universal/tokenization/ray/Dockerfile +++ b/transforms/universal/tokenization/ray/Dockerfile @@ -13,11 +13,9 @@ COPY --chown=ray:users data-processing-lib-python/ data-processing-lib-python/ RUN cd data-processing-lib-python && pip install --no-cache-dir -e . COPY --chown=ray:users data-processing-lib-ray/ data-processing-lib-ray/ RUN cd data-processing-lib-ray && pip install --no-cache-dir -e . -COPY --chown=ray:users python-transform/ python-transform -RUN cd python-transform && pip install --no-cache-dir -e . +COPY --chown=ray:users python-transform/ python-transform +RUN cd python-transform && pip install --no-cache-dir -r requirements.txt && pip install --no-cache-dir -e . -#COPY requirements.txt requirements.txt -#RUN pip install --no-cache-dir -r requirements.txt COPY --chown=ray:users src/ src/ COPY --chown=ray:users pyproject.toml pyproject.toml From 81fdd0f52c390fa15c8fb304e88e47860522ee67 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Sun, 6 Oct 2024 19:09:33 -0400 Subject: [PATCH 11/70] prevent kfp-workflow from installing local requirements found in /transforms folder Signed-off-by: Maroun Touma --- .make.defaults | 5 +++++ transforms/.make.workflows | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.make.defaults b/.make.defaults index bf46962ea..3b5016237 100644 --- a/.make.defaults +++ b/.make.defaults @@ -348,6 +348,11 @@ endif .defaults.ray-lib-src-venv:: .defaults.create-venv .defaults.install-ray-lib-src-venv .defaults.install-local-requirements-venv @# Help: Create the venv and install Ray library source, local dependencies and adjacent python source if present. +# Install local requirements last as it generally includes our lib source +.PHONY: .defaults.kfp-venv +.defaults.kfp-venv:: .defaults.create-venv .defaults.install-ray-lib-src-venv + @# Help: Create the venv and install Ray library source, local dependencies and adjacent python source if present. + # Install all source from the repo for a ray runtime transform into an existing venv # And if there is an adjacent python dir (as for transforms), then also install that source .PHONY: .defaults.install-ray-lib-src-venv diff --git a/transforms/.make.workflows b/transforms/.make.workflows index adbf721e6..d9b9217b1 100644 --- a/transforms/.make.workflows +++ b/transforms/.make.workflows @@ -52,7 +52,7 @@ endif ${WORKFLOW_VENV_ACTIVATE}: ${REPOROOT}/.make.versions ${REPOROOT}/kfp/kfp_ray_components/requirements.txt ${DPK_RAY_LIB_DIR} ${KFP_LIB_SRC_FILES} ${KFP_LIB_CONFIG_FILE} ${KFP_SHARED_LIB_SRC_FILES} rm -rf ${REPOROOT}/transforms/venv - $(MAKE) -C ${REPOROOT}/transforms .defaults.ray-lib-src-venv + $(MAKE) -C ${REPOROOT}/transforms .defaults.kfp-venv . ${WORKFLOW_VENV_ACTIVATE}; \ pip install -e $(REPOROOT)/kfp/kfp_support_lib/shared_workflow_support; \ pip install -e $(REPOROOT)/kfp/kfp_support_lib/$(WORKFLOW_SUPPORT_LIB); \ From df9988687b7f31d7d23362fcb1416d3adfff5c8b Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 11:15:50 -0400 Subject: [PATCH 12/70] change author name for pyproject.tml Signed-off-by: Maroun Touma --- data-processing-lib/pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data-processing-lib/pyproject.toml b/data-processing-lib/pyproject.toml index 9dbbc7f27..d9f23f2fb 100644 --- a/data-processing-lib/pyproject.toml +++ b/data-processing-lib/pyproject.toml @@ -7,8 +7,7 @@ description = "Data Preparation Toolkit Library for Ray and Python" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} authors = [ - { name = "David Wood", email = "dawood@us.ibm.com" }, - { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, + { name = "Maroun Touma", email = "touma@us.ibm.com" }, ] dynamic = ["dependencies", "optional-dependencies"] From df67d4119d211d18ec6ee4f09772c265e341d3e8 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 11:55:20 -0400 Subject: [PATCH 13/70] Changed everything to dev1 Signed-off-by: Maroun Touma --- .make.versions | 2 +- data-processing-lib/Makefile | 2 ++ data-processing-lib/pyproject.toml | 4 ++-- data-processing-lib/python/pyproject.toml | 2 +- data-processing-lib/ray/pyproject.toml | 4 ++-- data-processing-lib/spark/pyproject.toml | 4 ++-- .../kfp_v1_workflow_support/pyproject.toml | 4 ++-- .../kfp_v2_workflow_support/pyproject.toml | 4 ++-- .../shared_workflow_support/pyproject.toml | 4 ++-- transforms/Makefile | 2 ++ transforms/code/code2parquet/python/pyproject.toml | 2 +- transforms/code/code2parquet/python/requirements.txt | 2 +- transforms/code/code2parquet/ray/pyproject.toml | 6 +++--- transforms/code/code_quality/python/pyproject.toml | 2 +- transforms/code/code_quality/python/requirements.txt | 2 +- transforms/code/code_quality/ray/pyproject.toml | 6 +++--- transforms/code/header_cleanser/python/pyproject.toml | 2 +- transforms/code/header_cleanser/python/requirements.txt | 2 +- transforms/code/header_cleanser/ray/pyproject.toml | 6 +++--- transforms/code/license_select/python/pyproject.toml | 4 ++-- transforms/code/license_select/python/requirements.txt | 2 +- transforms/code/license_select/ray/pyproject.toml | 8 ++++---- transforms/code/malware/python/pyproject.toml | 4 ++-- transforms/code/malware/ray/pyproject.toml | 6 +++--- transforms/code/proglang_select/python/pyproject.toml | 2 +- transforms/code/proglang_select/python/requirements.txt | 2 +- transforms/code/proglang_select/ray/pyproject.toml | 6 +++--- transforms/code/repo_level_ordering/ray/pyproject.toml | 4 ++-- transforms/language/doc_chunk/python/pyproject.toml | 2 +- transforms/language/doc_chunk/python/requirements.txt | 2 +- transforms/language/doc_chunk/ray/pyproject.toml | 6 +++--- transforms/language/doc_quality/python/pyproject.toml | 2 +- transforms/language/doc_quality/python/requirements.txt | 2 +- transforms/language/doc_quality/ray/pyproject.toml | 6 +++--- transforms/language/html2parquet/python/pyproject.toml | 2 +- transforms/language/html2parquet/python/requirements.txt | 2 +- transforms/language/lang_id/python/pyproject.toml | 2 +- transforms/language/lang_id/python/requirements.txt | 2 +- transforms/language/lang_id/ray/pyproject.toml | 6 +++--- transforms/language/pdf2parquet/python/pyproject.toml | 2 +- transforms/language/pdf2parquet/python/requirements.txt | 2 +- transforms/language/pdf2parquet/ray/pyproject.toml | 2 +- transforms/language/pdf2parquet/ray/requirements.txt | 4 ++-- transforms/language/text_encoder/python/pyproject.toml | 2 +- transforms/language/text_encoder/python/requirements.txt | 2 +- transforms/language/text_encoder/ray/pyproject.toml | 6 +++--- transforms/pyproject.toml | 4 ++-- transforms/requirements-ray.txt | 2 +- transforms/requirements.txt | 2 +- transforms/universal/doc_id/python/pyproject.toml | 2 +- transforms/universal/doc_id/python/requirements.txt | 2 +- transforms/universal/doc_id/ray/pyproject.toml | 6 +++--- transforms/universal/doc_id/spark/pyproject.toml | 4 ++-- transforms/universal/ededup/python/pyproject.toml | 2 +- transforms/universal/ededup/python/requirements.txt | 2 +- transforms/universal/ededup/ray/pyproject.toml | 6 +++--- transforms/universal/fdedup/ray/pyproject.toml | 4 ++-- transforms/universal/filter/python/pyproject.toml | 2 +- transforms/universal/filter/python/requirements.txt | 2 +- transforms/universal/filter/ray/pyproject.toml | 6 +++--- transforms/universal/filter/spark/pyproject.toml | 4 ++-- transforms/universal/hap/python/pyproject.toml | 4 ++-- transforms/universal/hap/python/requirements.txt | 2 +- transforms/universal/noop/python/pyproject.toml | 4 ++-- transforms/universal/noop/ray/pyproject.toml | 6 +++--- transforms/universal/noop/spark/pyproject.toml | 6 +++--- transforms/universal/profiler/python/pyproject.toml | 4 ++-- transforms/universal/profiler/python/requirements.txt | 2 +- transforms/universal/profiler/ray/pyproject.toml | 6 +++--- transforms/universal/profiler/spark/pyproject.toml | 8 ++++---- transforms/universal/resize/python/pyproject.toml | 2 +- transforms/universal/resize/python/requirements.txt | 2 +- transforms/universal/resize/ray/pyproject.toml | 6 +++--- transforms/universal/resize/spark/pyproject.toml | 8 ++++---- transforms/universal/tokenization/python/pyproject.toml | 2 +- transforms/universal/tokenization/python/requirements.txt | 2 +- transforms/universal/tokenization/ray/pyproject.toml | 6 +++--- 77 files changed, 139 insertions(+), 135 deletions(-) diff --git a/.make.versions b/.make.versions index 0b06a8bae..8076ce860 100644 --- a/.make.versions +++ b/.make.versions @@ -19,7 +19,7 @@ DPK_MINOR_VERSION=2 DPK_MICRO_VERSION=2 # The suffix is generally always set in the main/development branch and only nulled out when creating release branches. # It can be manually incremented, for example, to allow publishing a new intermediate version wheel to pypi. -DPK_VERSION_SUFFIX=.dev0 +DPK_VERSION_SUFFIX=.dev1 DPK_VERSION=$(DPK_MAJOR_VERSION).$(DPK_MINOR_VERSION).$(DPK_MICRO_VERSION)$(DPK_VERSION_SUFFIX) diff --git a/data-processing-lib/Makefile b/data-processing-lib/Makefile index d0d1305ac..5b5b93e73 100644 --- a/data-processing-lib/Makefile +++ b/data-processing-lib/Makefile @@ -22,6 +22,7 @@ REPOROOT=.. # Get some common rules for the whole repo include $(REPOROOT)/.make.defaults +include $(REPOROOT)/.make.versions ########## ########## ########## ########## ########## ########## ########## ########## # Global rules that are generally to be implemented in the sub-directories and can @@ -53,6 +54,7 @@ publish:: set-versions: @# Help: Recursively $@ in all subdirs + $(MAKE) TOML_VERSION=$(DPK_LIB_VERSION) .defaults.update-toml @$(MAKE) RULE=$@ .recurse diff --git a/data-processing-lib/pyproject.toml b/data-processing-lib/pyproject.toml index d9f23f2fb..db42ecf0d 100644 --- a/data-processing-lib/pyproject.toml +++ b/data-processing-lib/pyproject.toml @@ -1,8 +1,8 @@ [project] name = "data_prep_toolkit" -version = "0.2.2.dev0" +version = "0.2.2.dev1" keywords = ["data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] -requires-python = ">=3.10" +requires-python = ">=3.10,<3.13" description = "Data Preparation Toolkit Library for Ray and Python" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} diff --git a/data-processing-lib/python/pyproject.toml b/data-processing-lib/python/pyproject.toml index 595a1805a..f00d45a0a 100644 --- a/data-processing-lib/python/pyproject.toml +++ b/data-processing-lib/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" keywords = ["data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] description = "Data Preparation Toolkit Library" diff --git a/data-processing-lib/ray/pyproject.toml b/data-processing-lib/ray/pyproject.toml index a7f476560..1e8c335cc 100644 --- a/data-processing-lib/ray/pyproject.toml +++ b/data-processing-lib/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" keywords = ["data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] requires-python = ">=3.10,<3.13" description = "Data Preparation Toolkit Library for Ray" @@ -11,7 +11,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit>=0.2.2.dev0", + "data-prep-toolkit>=0.2.2.dev1", "ray[default]==2.36.1", # These two are to fix security issues identified by quay.io "fastapi>=0.110.2", diff --git a/data-processing-lib/spark/pyproject.toml b/data-processing-lib/spark/pyproject.toml index e8d0c8285..54feb9a03 100644 --- a/data-processing-lib/spark/pyproject.toml +++ b/data-processing-lib/spark/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit_spark" -version = "0.2.2.dev0" +version = "0.2.2.dev1" keywords = ["data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] requires-python = ">=3.10,<3.13" description = "Data Preparation Toolkit Library for Spark" @@ -11,7 +11,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "data-prep-toolkit==0.2.2.dev0", + "data-prep-toolkit==0.2.2.dev1", "pyspark>=3.5.2", "psutil>=6.0.0" ] diff --git a/kfp/kfp_support_lib/kfp_v1_workflow_support/pyproject.toml b/kfp/kfp_support_lib/kfp_v1_workflow_support/pyproject.toml index c0331b38b..d62342c5f 100644 --- a/kfp/kfp_support_lib/kfp_v1_workflow_support/pyproject.toml +++ b/kfp/kfp_support_lib/kfp_v1_workflow_support/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit_kfp_v1" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Data Preparation Kit Library. KFP support" license = {text = "Apache-2.0"} @@ -13,7 +13,7 @@ authors = [ ] dependencies = [ "kfp==1.8.22", - "data-prep-toolkit-kfp-shared==0.2.2.dev0", + "data-prep-toolkit-kfp-shared==0.2.2.dev1", ] [build-system] diff --git a/kfp/kfp_support_lib/kfp_v2_workflow_support/pyproject.toml b/kfp/kfp_support_lib/kfp_v2_workflow_support/pyproject.toml index 220c56ad8..3dab7eac9 100644 --- a/kfp/kfp_support_lib/kfp_v2_workflow_support/pyproject.toml +++ b/kfp/kfp_support_lib/kfp_v2_workflow_support/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit_kfp_v2" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Data Preparation Kit Library. KFP support" license = {text = "Apache-2.0"} @@ -14,7 +14,7 @@ authors = [ dependencies = [ "kfp==2.8.0", "kfp-kubernetes==1.2.0", - "data-prep-toolkit-kfp-shared==0.2.2.dev0", + "data-prep-toolkit-kfp-shared==0.2.2.dev1", ] [build-system] diff --git a/kfp/kfp_support_lib/shared_workflow_support/pyproject.toml b/kfp/kfp_support_lib/shared_workflow_support/pyproject.toml index 726d7fd5d..b2dc963d7 100644 --- a/kfp/kfp_support_lib/shared_workflow_support/pyproject.toml +++ b/kfp/kfp_support_lib/shared_workflow_support/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "data_prep_toolkit_kfp_shared" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Data Preparation Kit Library. KFP support" license = {text = "Apache-2.0"} @@ -14,7 +14,7 @@ authors = [ dependencies = [ "requests", "kubernetes", - "data-prep-toolkit-ray==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/Makefile b/transforms/Makefile index 5ff1f5111..80ababae9 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -1,6 +1,7 @@ REPOROOT=../ # Use make help, to see the available rules include ../.make.defaults +include .make.transforms setup:: @# Help: Recursively make $@ all subdirs @@ -78,6 +79,7 @@ workflow-upload:: set-versions:: @# Help: Recursively make $@ in all subdirs + $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions @$(MAKE) RULE=$@ .recurse diff --git a/transforms/code/code2parquet/python/pyproject.toml b/transforms/code/code2parquet/python/pyproject.toml index b08504bef..0c115efc3 100644 --- a/transforms/code/code2parquet/python/pyproject.toml +++ b/transforms/code/code2parquet/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_code2parquet_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "code2parquet Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/code/code2parquet/python/requirements.txt b/transforms/code/code2parquet/python/requirements.txt index 758ab56fe..45f677e77 100644 --- a/transforms/code/code2parquet/python/requirements.txt +++ b/transforms/code/code2parquet/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 parameterized pandas diff --git a/transforms/code/code2parquet/ray/pyproject.toml b/transforms/code/code2parquet/ray/pyproject.toml index 3f8808037..120d080dc 100644 --- a/transforms/code/code2parquet/ray/pyproject.toml +++ b/transforms/code/code2parquet/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_code2parquet_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "code2parquet Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-ray==0.2.2.dev0", - "dpk-code2parquet-transform-python==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", + "dpk-code2parquet-transform-python==0.2.2.dev1", "parameterized", "pandas", ] diff --git a/transforms/code/code_quality/python/pyproject.toml b/transforms/code/code_quality/python/pyproject.toml index 46f59bc6c..b217060f5 100644 --- a/transforms/code/code_quality/python/pyproject.toml +++ b/transforms/code/code_quality/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_code_quality_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Code Quality Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/code/code_quality/python/requirements.txt b/transforms/code/code_quality/python/requirements.txt index 106e56f74..4ee249788 100644 --- a/transforms/code/code_quality/python/requirements.txt +++ b/transforms/code/code_quality/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 bs4==0.0.2 transformers==4.38.2 diff --git a/transforms/code/code_quality/ray/pyproject.toml b/transforms/code/code_quality/ray/pyproject.toml index 78ded1ce0..457678a6e 100644 --- a/transforms/code/code_quality/ray/pyproject.toml +++ b/transforms/code/code_quality/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_code_quality_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Code Quality Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Shivdeep Singh", email = "shivdeep.singh@ibm.com" }, ] dependencies = [ - "dpk-code-quality-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-code-quality-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/code/header_cleanser/python/pyproject.toml b/transforms/code/header_cleanser/python/pyproject.toml index 2e24466f0..79dee12a1 100644 --- a/transforms/code/header_cleanser/python/pyproject.toml +++ b/transforms/code/header_cleanser/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_header_cleanser_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "License and Copyright Removal Transform for Python" license = {text = "Apache-2.0"} diff --git a/transforms/code/header_cleanser/python/requirements.txt b/transforms/code/header_cleanser/python/requirements.txt index bed2168c1..4502a5fdb 100644 --- a/transforms/code/header_cleanser/python/requirements.txt +++ b/transforms/code/header_cleanser/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 scancode-toolkit==32.1.0 ; platform_system != 'Darwin' diff --git a/transforms/code/header_cleanser/ray/pyproject.toml b/transforms/code/header_cleanser/ray/pyproject.toml index 7509027a1..f99feaba7 100644 --- a/transforms/code/header_cleanser/ray/pyproject.toml +++ b/transforms/code/header_cleanser/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_header_cleanser_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "License and copyright removal Transform for Ray" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Yash kalathiya", email = "yashkalathiya164@gmail.com" }, ] dependencies = [ - "dpk-header-cleanser-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-header-cleanser-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", "scancode-toolkit==32.1.0", ] diff --git a/transforms/code/license_select/python/pyproject.toml b/transforms/code/license_select/python/pyproject.toml index 1404bb205..740c5ccbb 100644 --- a/transforms/code/license_select/python/pyproject.toml +++ b/transforms/code/license_select/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_license_select_transform_python" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "License Select Python Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} diff --git a/transforms/code/license_select/python/requirements.txt b/transforms/code/license_select/python/requirements.txt index e14cde7ab..82723b6ef 100644 --- a/transforms/code/license_select/python/requirements.txt +++ b/transforms/code/license_select/python/requirements.txt @@ -1 +1 @@ -data-prep-toolkit==0.2.2.dev0 \ No newline at end of file +data-prep-toolkit==0.2.2.dev1 \ No newline at end of file diff --git a/transforms/code/license_select/ray/pyproject.toml b/transforms/code/license_select/ray/pyproject.toml index 3295f2427..0d0634ef3 100644 --- a/transforms/code/license_select/ray/pyproject.toml +++ b/transforms/code/license_select/ray/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_license_select_transform_ray" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "License Select Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} @@ -10,8 +10,8 @@ authors = [ { name = "Mark Lewis", email = "mark_lewis@uk.ibm.com" }, ] dependencies = [ - "dpk-license-select-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-license-select-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/code/malware/python/pyproject.toml b/transforms/code/malware/python/pyproject.toml index 256a10b79..53af8fa4e 100644 --- a/transforms/code/malware/python/pyproject.toml +++ b/transforms/code/malware/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_malware_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Malware Python Transform" license = {text = "Apache-2.0"} @@ -9,7 +9,7 @@ authors = [ { name = "Takuya Goto", email = "tkyg@jp.ibm.com" }, ] dependencies = [ - "data-prep-toolkit==0.2.2.dev0", + "data-prep-toolkit==0.2.2.dev1", "clamd==1.0.2", ] diff --git a/transforms/code/malware/ray/pyproject.toml b/transforms/code/malware/ray/pyproject.toml index cf454e856..6abc2af60 100644 --- a/transforms/code/malware/ray/pyproject.toml +++ b/transforms/code/malware/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_malware_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Malware Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Takuya Goto", email = "tkyg@jp.ibm.com" }, ] dependencies = [ - "dpk-malware-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-malware-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/code/proglang_select/python/pyproject.toml b/transforms/code/proglang_select/python/pyproject.toml index 9745a48c3..b120e5064 100644 --- a/transforms/code/proglang_select/python/pyproject.toml +++ b/transforms/code/proglang_select/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_proglang_select_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Programming Language Selection Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/code/proglang_select/python/requirements.txt b/transforms/code/proglang_select/python/requirements.txt index e14cde7ab..82723b6ef 100644 --- a/transforms/code/proglang_select/python/requirements.txt +++ b/transforms/code/proglang_select/python/requirements.txt @@ -1 +1 @@ -data-prep-toolkit==0.2.2.dev0 \ No newline at end of file +data-prep-toolkit==0.2.2.dev1 \ No newline at end of file diff --git a/transforms/code/proglang_select/ray/pyproject.toml b/transforms/code/proglang_select/ray/pyproject.toml index 1730ab04f..a74372f49 100644 --- a/transforms/code/proglang_select/ray/pyproject.toml +++ b/transforms/code/proglang_select/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_proglang_select_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Programming Language Selection Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Shivdeep Singh", email = "shivdeep.singh@ibm.com" }, ] dependencies = [ - "dpk-proglang-select-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-proglang-select-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/code/repo_level_ordering/ray/pyproject.toml b/transforms/code/repo_level_ordering/ray/pyproject.toml index e87d133e0..f66d2c9d1 100644 --- a/transforms/code/repo_level_ordering/ray/pyproject.toml +++ b/transforms/code/repo_level_ordering/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_repo_level_order_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "repo_level_order Ray Transform" license = {text = "Apache-2.0"} @@ -11,7 +11,7 @@ authors = [ { name = "Shanmukha Guttula", email = "shagutt1@in.ibm.com" }, ] dependencies = [ - "data-prep-toolkit-ray==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", "networkx==3.3", "colorlog==6.8.2", "func-timeout==4.3.5", diff --git a/transforms/language/doc_chunk/python/pyproject.toml b/transforms/language/doc_chunk/python/pyproject.toml index 1a3bd333f..eeff859f0 100644 --- a/transforms/language/doc_chunk/python/pyproject.toml +++ b/transforms/language/doc_chunk/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_chunk_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "chunk documents Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/doc_chunk/python/requirements.txt b/transforms/language/doc_chunk/python/requirements.txt index 8e8c1bebb..d532510ba 100644 --- a/transforms/language/doc_chunk/python/requirements.txt +++ b/transforms/language/doc_chunk/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 docling-core==1.3.0 llama-index-core>=0.11.0,<0.12.0 diff --git a/transforms/language/doc_chunk/ray/pyproject.toml b/transforms/language/doc_chunk/ray/pyproject.toml index 6bab175b8..ac3f5218e 100644 --- a/transforms/language/doc_chunk/ray/pyproject.toml +++ b/transforms/language/doc_chunk/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_chunk_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "chunk documents Ray Transform" license = {text = "Apache-2.0"} @@ -11,8 +11,8 @@ authors = [ { name = "Christoph Auer", email = "cau@zurich.ibm.com" }, ] dependencies = [ - "dpk-doc-chunk-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-doc-chunk-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/language/doc_quality/python/pyproject.toml b/transforms/language/doc_quality/python/pyproject.toml index 12c712ae9..c4c9b2805 100644 --- a/transforms/language/doc_quality/python/pyproject.toml +++ b/transforms/language/doc_quality/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_quality_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Document Quality Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/doc_quality/python/requirements.txt b/transforms/language/doc_quality/python/requirements.txt index f2f9d6200..25bf48702 100644 --- a/transforms/language/doc_quality/python/requirements.txt +++ b/transforms/language/doc_quality/python/requirements.txt @@ -1,2 +1,2 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 diff --git a/transforms/language/doc_quality/ray/pyproject.toml b/transforms/language/doc_quality/ray/pyproject.toml index 0588c1997..a4aba9a3a 100644 --- a/transforms/language/doc_quality/ray/pyproject.toml +++ b/transforms/language/doc_quality/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_quality_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Document Quality Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Daiki Tsuzuku", email = "dtsuzuku@jp.ibm.com" } ] dependencies = [ - "dpk-doc_quality-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0" + "dpk-doc_quality-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1" ] [build-system] diff --git a/transforms/language/html2parquet/python/pyproject.toml b/transforms/language/html2parquet/python/pyproject.toml index 9cb33f5c3..0f78a62dd 100644 --- a/transforms/language/html2parquet/python/pyproject.toml +++ b/transforms/language/html2parquet/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_html2parquet_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "HTML2PARQUET Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/html2parquet/python/requirements.txt b/transforms/language/html2parquet/python/requirements.txt index 69d487445..8b507cedd 100644 --- a/transforms/language/html2parquet/python/requirements.txt +++ b/transforms/language/html2parquet/python/requirements.txt @@ -1,2 +1,2 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 trafilatura==1.12.0 diff --git a/transforms/language/lang_id/python/pyproject.toml b/transforms/language/lang_id/python/pyproject.toml index ba256765f..35406abc3 100644 --- a/transforms/language/lang_id/python/pyproject.toml +++ b/transforms/language/lang_id/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_lang_id_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Language Identification Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/lang_id/python/requirements.txt b/transforms/language/lang_id/python/requirements.txt index 111465be0..d195ebfbb 100644 --- a/transforms/language/lang_id/python/requirements.txt +++ b/transforms/language/lang_id/python/requirements.txt @@ -1,4 +1,4 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 fasttext==0.9.2 langcodes==3.3.0 huggingface-hub >= 0.21.4, <1.0.0 diff --git a/transforms/language/lang_id/ray/pyproject.toml b/transforms/language/lang_id/ray/pyproject.toml index ac4558675..60ff39947 100644 --- a/transforms/language/lang_id/ray/pyproject.toml +++ b/transforms/language/lang_id/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_lang_id_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Language Identification Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Daiki Tsuzuku", email = "dtsuzuku@jp.ibm.com" } ] dependencies = [ - "dpk-lang_id-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-lang_id-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/language/pdf2parquet/python/pyproject.toml b/transforms/language/pdf2parquet/python/pyproject.toml index 804f2ff7c..c069300b5 100644 --- a/transforms/language/pdf2parquet/python/pyproject.toml +++ b/transforms/language/pdf2parquet/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_pdf2parquet_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "PDF2PARQUET Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/pdf2parquet/python/requirements.txt b/transforms/language/pdf2parquet/python/requirements.txt index e275fdc2a..d959b9e38 100644 --- a/transforms/language/pdf2parquet/python/requirements.txt +++ b/transforms/language/pdf2parquet/python/requirements.txt @@ -1,4 +1,4 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 docling-core==1.3.0 docling-ibm-models==1.1.7 deepsearch-glm==0.21.0 diff --git a/transforms/language/pdf2parquet/ray/pyproject.toml b/transforms/language/pdf2parquet/ray/pyproject.toml index 39939adae..5699dcc8c 100644 --- a/transforms/language/pdf2parquet/ray/pyproject.toml +++ b/transforms/language/pdf2parquet/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_pdf2parquet_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "PDF2PARQUET Ray Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/pdf2parquet/ray/requirements.txt b/transforms/language/pdf2parquet/ray/requirements.txt index 1db94ff1c..1577d024f 100644 --- a/transforms/language/pdf2parquet/ray/requirements.txt +++ b/transforms/language/pdf2parquet/ray/requirements.txt @@ -1,5 +1,5 @@ -dpk-pdf2parquet-transform-python==0.2.2.dev0 -data-prep-toolkit-ray==0.2.2.dev0 +dpk-pdf2parquet-transform-python==0.2.2.dev1 +data-prep-toolkit-ray==0.2.2.dev1 docling-core==1.3.0 docling-ibm-models==1.1.7 deepsearch-glm==0.21.0 diff --git a/transforms/language/text_encoder/python/pyproject.toml b/transforms/language/text_encoder/python/pyproject.toml index 0dd0ac44c..65cb16b5b 100644 --- a/transforms/language/text_encoder/python/pyproject.toml +++ b/transforms/language/text_encoder/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_text_encoder_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Text Encoder Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/text_encoder/python/requirements.txt b/transforms/language/text_encoder/python/requirements.txt index be8c0a880..aab7681dc 100644 --- a/transforms/language/text_encoder/python/requirements.txt +++ b/transforms/language/text_encoder/python/requirements.txt @@ -1,2 +1,2 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 sentence-transformers==3.0.1 diff --git a/transforms/language/text_encoder/ray/pyproject.toml b/transforms/language/text_encoder/ray/pyproject.toml index 2735856aa..95e29638f 100644 --- a/transforms/language/text_encoder/ray/pyproject.toml +++ b/transforms/language/text_encoder/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_text_encoder_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Text Encoder Ray Transform" license = {text = "Apache-2.0"} @@ -11,8 +11,8 @@ authors = [ { name = "Peter Staar", email = "taa@zurich.ibm.com" }, ] dependencies = [ - "dpk-text_encoder-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-text_encoder-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/pyproject.toml b/transforms/pyproject.toml index 30d9f39e9..4e9a61fbc 100644 --- a/transforms/pyproject.toml +++ b/transforms/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "data_prep_toolkit_transforms" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" keywords = ["transforms", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] description = "Data Preparation Toolkit Transforms using Ray" license = {text = "Apache-2.0"} diff --git a/transforms/requirements-ray.txt b/transforms/requirements-ray.txt index 4eadbf121..00e6a157f 100644 --- a/transforms/requirements-ray.txt +++ b/transforms/requirements-ray.txt @@ -1,4 +1,4 @@ -data-prep-toolkit[ray]>=0.2.2.dev0 +data-prep-toolkit[ray]>=0.2.2.dev1 networkx==3.3 colorlog==6.8.2 func-timeout==4.3.5 diff --git a/transforms/requirements.txt b/transforms/requirements.txt index d30f01bd3..93631f7d4 100644 --- a/transforms/requirements.txt +++ b/transforms/requirements.txt @@ -1 +1 @@ -data-prep-toolkit>=0.2.2.dev0 \ No newline at end of file +data-prep-toolkit>=0.2.2.dev1 \ No newline at end of file diff --git a/transforms/universal/doc_id/python/pyproject.toml b/transforms/universal/doc_id/python/pyproject.toml index b9d45b803..ad4fba0ab 100644 --- a/transforms/universal/doc_id/python/pyproject.toml +++ b/transforms/universal/doc_id/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_id_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "ededup Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/universal/doc_id/python/requirements.txt b/transforms/universal/doc_id/python/requirements.txt index e14cde7ab..82723b6ef 100644 --- a/transforms/universal/doc_id/python/requirements.txt +++ b/transforms/universal/doc_id/python/requirements.txt @@ -1 +1 @@ -data-prep-toolkit==0.2.2.dev0 \ No newline at end of file +data-prep-toolkit==0.2.2.dev1 \ No newline at end of file diff --git a/transforms/universal/doc_id/ray/pyproject.toml b/transforms/universal/doc_id/ray/pyproject.toml index 836454098..03530dff2 100644 --- a/transforms/universal/doc_id/ray/pyproject.toml +++ b/transforms/universal/doc_id/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_id_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "docid Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "dpk_doc_id_transform_python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0" + "dpk_doc_id_transform_python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1" ] [build-system] diff --git a/transforms/universal/doc_id/spark/pyproject.toml b/transforms/universal/doc_id/spark/pyproject.toml index 485174834..636bbf26e 100644 --- a/transforms/universal/doc_id/spark/pyproject.toml +++ b/transforms/universal/doc_id/spark/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_doc_id_transform_spark" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Doc ID Spark Transform" license = {text = "Apache-2.0"} @@ -10,7 +10,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-spark==0.2.2.dev0", + "data-prep-toolkit-spark==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/ededup/python/pyproject.toml b/transforms/universal/ededup/python/pyproject.toml index fecad1683..21bfdad41 100644 --- a/transforms/universal/ededup/python/pyproject.toml +++ b/transforms/universal/ededup/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_ededup_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "ededup Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/universal/ededup/python/requirements.txt b/transforms/universal/ededup/python/requirements.txt index d01c93d95..84b4ac832 100644 --- a/transforms/universal/ededup/python/requirements.txt +++ b/transforms/universal/ededup/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 mmh3>=4.1.0 xxhash==3.4.1 diff --git a/transforms/universal/ededup/ray/pyproject.toml b/transforms/universal/ededup/ray/pyproject.toml index 886832947..84a892180 100644 --- a/transforms/universal/ededup/ray/pyproject.toml +++ b/transforms/universal/ededup/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_ededup_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "ededup Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-ray==0.2.2.dev0", - "dpk_ededup_transform_python==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", + "dpk_ededup_transform_python==0.2.2.dev1", "tqdm==4.66.3", ] diff --git a/transforms/universal/fdedup/ray/pyproject.toml b/transforms/universal/fdedup/ray/pyproject.toml index d6d36f9c0..54fd83a00 100644 --- a/transforms/universal/fdedup/ray/pyproject.toml +++ b/transforms/universal/fdedup/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_fdedup_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "fdedup Ray Transform" license = {text = "Apache-2.0"} @@ -10,7 +10,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-ray==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", "mmh3>=4.1.0", "xxhash==3.4.1", "tqdm==4.66.3", diff --git a/transforms/universal/filter/python/pyproject.toml b/transforms/universal/filter/python/pyproject.toml index f2dadffa6..b93a601e1 100644 --- a/transforms/universal/filter/python/pyproject.toml +++ b/transforms/universal/filter/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_filter_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Filter Transform for Python" license = {text = "Apache-2.0"} diff --git a/transforms/universal/filter/python/requirements.txt b/transforms/universal/filter/python/requirements.txt index 9d1711c3b..56be59c0a 100644 --- a/transforms/universal/filter/python/requirements.txt +++ b/transforms/universal/filter/python/requirements.txt @@ -1,3 +1,3 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 duckdb>=0.10.1 diff --git a/transforms/universal/filter/ray/pyproject.toml b/transforms/universal/filter/ray/pyproject.toml index 5c63a90ff..9d2f84325 100644 --- a/transforms/universal/filter/ray/pyproject.toml +++ b/transforms/universal/filter/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_filter_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Filter Transform for Ray" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Constantin Adam", email = "cmadam@us.ibm.com" }, ] dependencies = [ - "dpk-filter-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-filter-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/filter/spark/pyproject.toml b/transforms/universal/filter/spark/pyproject.toml index a8a0174b6..54a49893e 100644 --- a/transforms/universal/filter/spark/pyproject.toml +++ b/transforms/universal/filter/spark/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_filter_transform_spark" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Filter Spark Transform" license = {text = "Apache-2.0"} @@ -9,7 +9,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-spark==0.2.2.dev0", + "data-prep-toolkit-spark==0.2.2.dev1", ] [project.optional-dependencies] diff --git a/transforms/universal/hap/python/pyproject.toml b/transforms/universal/hap/python/pyproject.toml index 17f5346d2..fd775091e 100644 --- a/transforms/universal/hap/python/pyproject.toml +++ b/transforms/universal/hap/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_hap_transform_python" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "HAP Python Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} diff --git a/transforms/universal/hap/python/requirements.txt b/transforms/universal/hap/python/requirements.txt index 5062a733c..efdb8662b 100644 --- a/transforms/universal/hap/python/requirements.txt +++ b/transforms/universal/hap/python/requirements.txt @@ -1,4 +1,4 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 nltk==3.9.1 transformers==4.38.2 torch==2.4.1 diff --git a/transforms/universal/noop/python/pyproject.toml b/transforms/universal/noop/python/pyproject.toml index 9b1675a69..81a46383c 100644 --- a/transforms/universal/noop/python/pyproject.toml +++ b/transforms/universal/noop/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_noop_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "NOOP Python Transform" license = {text = "Apache-2.0"} @@ -10,7 +10,7 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit==0.2.2.dev0", + "data-prep-toolkit==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/noop/ray/pyproject.toml b/transforms/universal/noop/ray/pyproject.toml index c4120753f..c73f5c67a 100644 --- a/transforms/universal/noop/ray/pyproject.toml +++ b/transforms/universal/noop/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_noop_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "NOOP Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "dpk-noop-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-noop-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/noop/spark/pyproject.toml b/transforms/universal/noop/spark/pyproject.toml index 633ee66bd..5068ffa2f 100644 --- a/transforms/universal/noop/spark/pyproject.toml +++ b/transforms/universal/noop/spark/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_noop_transform_spark" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "NOOP Spark Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "dpk-noop-transform-python==0.2.2.dev0", - "data-prep-toolkit-spark==0.2.2.dev0", + "dpk-noop-transform-python==0.2.2.dev1", + "data-prep-toolkit-spark==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/profiler/python/pyproject.toml b/transforms/universal/profiler/python/pyproject.toml index 290e89a15..e1e36f80a 100644 --- a/transforms/universal/profiler/python/pyproject.toml +++ b/transforms/universal/profiler/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_profiler_transform_python" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "profiler Python Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} diff --git a/transforms/universal/profiler/python/requirements.txt b/transforms/universal/profiler/python/requirements.txt index d164794c7..638e1b7b5 100644 --- a/transforms/universal/profiler/python/requirements.txt +++ b/transforms/universal/profiler/python/requirements.txt @@ -1,5 +1,5 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 mmh3==4.1.0 xxhash==3.4.1 diff --git a/transforms/universal/profiler/ray/pyproject.toml b/transforms/universal/profiler/ray/pyproject.toml index bacba9abb..0b3ef4b55 100644 --- a/transforms/universal/profiler/ray/pyproject.toml +++ b/transforms/universal/profiler/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_profiler_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "profiler Ray Transform" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "data-prep-toolkit-ray==0.2.2.dev0", - "dpk_profiler_transform_python==0.2.2.dev0", + "data-prep-toolkit-ray==0.2.2.dev1", + "dpk_profiler_transform_python==0.2.2.dev1", "tqdm==4.66.3", ] diff --git a/transforms/universal/profiler/spark/pyproject.toml b/transforms/universal/profiler/spark/pyproject.toml index 9cb3106bd..34003b539 100644 --- a/transforms/universal/profiler/spark/pyproject.toml +++ b/transforms/universal/profiler/spark/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_profiler_transform_spark" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "Profiler Spark Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} @@ -9,8 +9,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "dpk-profiler-transform-python==0.2.2.dev0", - "data-prep-toolkit-spark==0.2.2.dev0", + "dpk-profiler-transform-python==0.2.2.dev1", + "data-prep-toolkit-spark==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/resize/python/pyproject.toml b/transforms/universal/resize/python/pyproject.toml index 6dd64f3bf..f393b5b0e 100644 --- a/transforms/universal/resize/python/pyproject.toml +++ b/transforms/universal/resize/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_resize_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "resize Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/universal/resize/python/requirements.txt b/transforms/universal/resize/python/requirements.txt index e14cde7ab..82723b6ef 100644 --- a/transforms/universal/resize/python/requirements.txt +++ b/transforms/universal/resize/python/requirements.txt @@ -1 +1 @@ -data-prep-toolkit==0.2.2.dev0 \ No newline at end of file +data-prep-toolkit==0.2.2.dev1 \ No newline at end of file diff --git a/transforms/universal/resize/ray/pyproject.toml b/transforms/universal/resize/ray/pyproject.toml index 249e40c7d..38043bb7e 100644 --- a/transforms/universal/resize/ray/pyproject.toml +++ b/transforms/universal/resize/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_resize_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Resize Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "dpk-resize-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-resize-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/resize/spark/pyproject.toml b/transforms/universal/resize/spark/pyproject.toml index 77687ca7e..6b6d0f50b 100644 --- a/transforms/universal/resize/spark/pyproject.toml +++ b/transforms/universal/resize/spark/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_resize_transform_spark" -version = "0.2.2.dev0" -requires-python = ">=3.10" +version = "0.2.2.dev1" +requires-python = ">=3.10,<3.13" description = "Resize Spark Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsk@ibm.com" }, ] dependencies = [ - "dpk-resize-transform-python==0.2.2.dev0", - "data-prep-toolkit-spark==0.2.2.dev0", + "dpk-resize-transform-python==0.2.2.dev1", + "data-prep-toolkit-spark==0.2.2.dev1", ] [build-system] diff --git a/transforms/universal/tokenization/python/pyproject.toml b/transforms/universal/tokenization/python/pyproject.toml index b45336701..51e3cbff9 100644 --- a/transforms/universal/tokenization/python/pyproject.toml +++ b/transforms/universal/tokenization/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_tokenization_transform_python" keywords = ["tokenizer", "data", "data preprocessing", "data preparation", "llm", "generative", "ai", "fine-tuning", "llmapps" ] -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Tokenization Transform for Python" license = {text = "Apache-2.0"} diff --git a/transforms/universal/tokenization/python/requirements.txt b/transforms/universal/tokenization/python/requirements.txt index 269257538..d64bcef48 100644 --- a/transforms/universal/tokenization/python/requirements.txt +++ b/transforms/universal/tokenization/python/requirements.txt @@ -1,2 +1,2 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 transformers==4.38.2 diff --git a/transforms/universal/tokenization/ray/pyproject.toml b/transforms/universal/tokenization/ray/pyproject.toml index aa109bbc1..a1ef73dd8 100644 --- a/transforms/universal/tokenization/ray/pyproject.toml +++ b/transforms/universal/tokenization/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_tokenization_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "Tokenization Transform for Ray" license = {text = "Apache-2.0"} @@ -9,8 +9,8 @@ authors = [ { name = "Xuan-Hong Dang", email = "xuan-hong.dang@ibm.com"}, ] dependencies = [ - "dpk-tokenization-transform-python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk-tokenization-transform-python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", ] [build-system] From 2cd32446cabd26b11fb359d4dff445b3d48cf99a Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 12:02:15 -0400 Subject: [PATCH 14/70] update from dev after merge of html2parquet --- transforms/language/html2parquet/ray/pyproject.toml | 2 +- transforms/language/html2parquet/ray/requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/transforms/language/html2parquet/ray/pyproject.toml b/transforms/language/html2parquet/ray/pyproject.toml index d1153a91a..52365dc3a 100644 --- a/transforms/language/html2parquet/ray/pyproject.toml +++ b/transforms/language/html2parquet/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_html2parquet_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10" description = "HTML2PARQUET Python Transform" license = {text = "Apache-2.0"} diff --git a/transforms/language/html2parquet/ray/requirements.txt b/transforms/language/html2parquet/ray/requirements.txt index dc2111e9e..d4c7abc1b 100644 --- a/transforms/language/html2parquet/ray/requirements.txt +++ b/transforms/language/html2parquet/ray/requirements.txt @@ -1,3 +1,3 @@ -dpk-html2parquet-transform-python==0.2.2.dev0 -data-prep-toolkit-ray==0.2.2.dev0 +dpk-html2parquet-transform-python==0.2.2.dev1 +data-prep-toolkit-ray==0.2.2.dev1 trafilatura==1.12.0 \ No newline at end of file From 5b4572bb482d26a1e95449b540ba678a4c80bd3e Mon Sep 17 00:00:00 2001 From: Shahrokh Daijavad Date: Mon, 7 Oct 2024 09:57:30 -0700 Subject: [PATCH 15/70] Update README.md Consistency of installing new pip installs --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aeec4ef70..bd717aa82 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,11 @@ conda install gcc_linux-64 conda install gxx_linux-64 ``` -Next, install the data prep toolkit library. This library installs both the python and ray versions of the transforms. +Next, install the data prep toolkit library. This library installs both the python and ray versions of the transforms. For better management of dependencies, it is recommended to install the same tagged version of both the library and the transform. ```bash -pip3 install data-prep-toolkit-transforms-ray +pip3 install data-prep-toolkit[ray]==0.2.2 +pip3 install data-prep-toolkit-transforms[ray]==0.2.2 pip3 install jupyterlab ipykernel ipywidgets ## install custom kernel From e641e49d3bc27c12b57ae005f568b456d397adf9 Mon Sep 17 00:00:00 2001 From: Shahrokh Daijavad Date: Mon, 7 Oct 2024 09:59:02 -0700 Subject: [PATCH 16/70] Update quick-start.md New pip install option for Ray --- doc/quick-start/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/quick-start/quick-start.md b/doc/quick-start/quick-start.md index b7167df77..47d5a1f1b 100644 --- a/doc/quick-start/quick-start.md +++ b/doc/quick-start/quick-start.md @@ -59,7 +59,7 @@ or **Deploy the latest releases of the data prep toolkit library, all python transforms and all ray transforms** ```shell -pip3 install data-prep-toolkit-transforms-ray +pip3 install data-prep-toolkit-transforms[ray] ``` ## Running transforms From 0e7b710e5e24fa4c3fdb2a46b80e269c58904173 Mon Sep 17 00:00:00 2001 From: Shahrokh Daijavad Date: Mon, 7 Oct 2024 10:03:42 -0700 Subject: [PATCH 17/70] Update README.md Use "all" option for the new pip install --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd717aa82..31d238833 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Next, install the data prep toolkit library. This library installs both the pyth ```bash pip3 install data-prep-toolkit[ray]==0.2.2 -pip3 install data-prep-toolkit-transforms[ray]==0.2.2 +pip3 install data-prep-toolkit-transforms[ray,all]==0.2.2 pip3 install jupyterlab ipykernel ipywidgets ## install custom kernel From c09dfc09a2a23784eb4f4d27880a8a03a64fa2f1 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 15:37:33 -0400 Subject: [PATCH 18/70] remove pii_redactor from all configuration. Can still be installed individually for testing Signed-off-by: Maroun Touma --- transforms/Makefile | 4 +- .../language/html2parquet/ray/pyproject.toml | 2 +- .../universal/hap/python/output/metadata.json | 50 ------------------ .../universal/hap/python/output/test1.parquet | Bin 79822 -> 0 bytes 4 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 transforms/universal/hap/python/output/metadata.json delete mode 100644 transforms/universal/hap/python/output/test1.parquet diff --git a/transforms/Makefile b/transforms/Makefile index 80ababae9..4adfe7d8c 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -1,7 +1,6 @@ REPOROOT=../ # Use make help, to see the available rules include ../.make.defaults -include .make.transforms setup:: @# Help: Recursively make $@ all subdirs @@ -79,7 +78,7 @@ workflow-upload:: set-versions:: @# Help: Recursively make $@ in all subdirs - $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions +# $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions @$(MAKE) RULE=$@ .recurse @@ -97,6 +96,7 @@ build-pkg-dist:: done # Only needs to build the whl $(MAKE) BUILD_WHEEL_ARG=-w .defaults.build-dist + -rm -fr src test-pkg-dist:: -rm -fr venv diff --git a/transforms/language/html2parquet/ray/pyproject.toml b/transforms/language/html2parquet/ray/pyproject.toml index 52365dc3a..5d2af9043 100644 --- a/transforms/language/html2parquet/ray/pyproject.toml +++ b/transforms/language/html2parquet/ray/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "dpk_html2parquet_transform_ray" version = "0.2.2.dev1" -requires-python = ">=3.10" +requires-python = ">=3.10,<3.13" description = "HTML2PARQUET Python Transform" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} diff --git a/transforms/universal/hap/python/output/metadata.json b/transforms/universal/hap/python/output/metadata.json deleted file mode 100644 index 062fee162..000000000 --- a/transforms/universal/hap/python/output/metadata.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "pipeline": "pipeline_id", - "job details": { - "job category": "preprocessing", - "job name": "hap", - "job type": "pure python", - "job id": "job_id", - "start_time": "2024-10-03 21:38:20", - "end_time": "2024-10-03 21:38:29", - "status": "success" - }, - "code": { - "github": "github", - "commit_hash": "12345", - "path": "path" - }, - "job_input_params": { - "model_name_or_path": "ibm-granite/granite-guardian-hap-38m", - "annotation_column": "hap_score", - "doc_text_column": "contents", - "inference_engine": "CPU", - "max_length": 512, - "batch_size": 128, - "checkpointing": false, - "max_files": -1, - "random_samples": -1, - "files_to_use": [ - ".parquet" - ], - "num_processors": 0 - }, - "job_output_stats": { - "source_files": 2, - "source_size": 12124594, - "transform execution exception": 1, - "result_files": 1, - "result_size": 79822, - "processing_time": 6.932, - "source_doc_count": 50, - "result_doc_count": 50 - }, - "source": { - "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/test-data/input", - "type": "path" - }, - "target": { - "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/output", - "type": "path" - } -} \ No newline at end of file diff --git a/transforms/universal/hap/python/output/test1.parquet b/transforms/universal/hap/python/output/test1.parquet deleted file mode 100644 index c9483e34d47dd71af90b1a6694c55fb01ea95453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79822 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%0AOYVoBqAqsAu5`It?D>xkBUN&XG1tLk-G_c?YDV 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 T3O17~|6s{tFqkXKUFZJ+T6-k& From 87b5d8509305fc0851252bb66817db6df8cb2c3a Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 16:01:54 -0400 Subject: [PATCH 19/70] comment out [all] dependencies on pii Signed-off-by: Maroun Touma --- transforms/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transforms/pyproject.toml b/transforms/pyproject.toml index 4e9a61fbc..48ca02703 100644 --- a/transforms/pyproject.toml +++ b/transforms/pyproject.toml @@ -35,7 +35,8 @@ all = { file = [ ## docling-ibm-models 1.1.7 depends on lxml<5.0.0 and >=4.9.1 ## trafilatura 1.12.0 depends on lxml>=5.2.2; platform_system != "Darwin" or python_version > "3.8" ## "language/html2parquet/python/requirements.txt", -"language/pii_redactor/python/requirements.txt", +##### pii_redactor seem to be failing UT +## "language/pii_redactor/python/requirements.txt", "language/lang_id/python/requirements.txt", "language/text_encoder/python/requirements.txt", "language/pdf2parquet/python/requirements.txt", From 037af86f2ff1074865e703fde8d0feb32a5b439e Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 7 Oct 2024 18:12:53 -0400 Subject: [PATCH 20/70] final round of testing with whl file" Signed-off-by: Maroun Touma --- data-processing-lib/Makefile | 3 ++- transforms/Makefile | 1 + transforms/language/pii_redactor/python/pyproject.toml | 2 +- transforms/language/pii_redactor/python/requirements.txt | 2 +- transforms/language/pii_redactor/ray/pyproject.toml | 6 +++--- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/data-processing-lib/Makefile b/data-processing-lib/Makefile index 5b5b93e73..6750f11b4 100644 --- a/data-processing-lib/Makefile +++ b/data-processing-lib/Makefile @@ -61,4 +61,5 @@ set-versions: build-pkg-dist:: $(MAKE) .defaults.build-dist BUILD_WHEEL_ARG=-w -publish-dist :: .check-env .defaults.publish-dist +publish-dist :: .defaults.publish-dist + diff --git a/transforms/Makefile b/transforms/Makefile index 4adfe7d8c..275526687 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -109,4 +109,5 @@ test-pkg-dist:: done; @# Help: Setup environment and run unit tests for all transforms +publish-dist :: .defaults.publish-dist diff --git a/transforms/language/pii_redactor/python/pyproject.toml b/transforms/language/pii_redactor/python/pyproject.toml index 7045b6ec0..3f9ddaaad 100644 --- a/transforms/language/pii_redactor/python/pyproject.toml +++ b/transforms/language/pii_redactor/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_pii_redactor_transform_python" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "PII redactor Transform for Python" license = {text = "Apache-2.0"} diff --git a/transforms/language/pii_redactor/python/requirements.txt b/transforms/language/pii_redactor/python/requirements.txt index 99e423ce1..6969b83b9 100644 --- a/transforms/language/pii_redactor/python/requirements.txt +++ b/transforms/language/pii_redactor/python/requirements.txt @@ -1,4 +1,4 @@ -data-prep-toolkit==0.2.2.dev0 +data-prep-toolkit==0.2.2.dev1 presidio-analyzer>=2.2.355 presidio-anonymizer>=2.2.355 flair>=0.14.0 diff --git a/transforms/language/pii_redactor/ray/pyproject.toml b/transforms/language/pii_redactor/ray/pyproject.toml index 4283df428..1ef96511a 100644 --- a/transforms/language/pii_redactor/ray/pyproject.toml +++ b/transforms/language/pii_redactor/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_pii_redactor_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10,<3.13" description = "PII Redactor Ray Transform" license = {text = "Apache-2.0"} @@ -10,8 +10,8 @@ authors = [ { name = "Boris Lublinsky", email = "blublinsky@ibm.com" }, ] dependencies = [ - "dpk_pii_redactor_transform_python==0.2.2.dev0", - "data-prep-toolkit-ray==0.2.2.dev0", + "dpk_pii_redactor_transform_python==0.2.2.dev1", + "data-prep-toolkit-ray==0.2.2.dev1", "presidio-analyzer>=2.2.355", "presidio-anonymizer>=2.2.355", "flair>=0.14.0", From bf4bc80f4072c457df9aa536ed60f7cde9f40e5e Mon Sep 17 00:00:00 2001 From: Shahrokh Daijavad Date: Mon, 7 Oct 2024 17:07:41 -0700 Subject: [PATCH 21/70] Update Run_your_first_transform_colab.ipynb Need to change to the new format of [ray] --- examples/notebooks/Run_your_first_transform_colab.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/notebooks/Run_your_first_transform_colab.ipynb b/examples/notebooks/Run_your_first_transform_colab.ipynb index 8b99b8f36..0caed5bda 100644 --- a/examples/notebooks/Run_your_first_transform_colab.ipynb +++ b/examples/notebooks/Run_your_first_transform_colab.ipynb @@ -35,7 +35,7 @@ }, "outputs": [], "source": [ - "! pip install --default-timeout=100 data-prep-toolkit-transforms-ray\n" + "! pip install --default-timeout=100 data-prep-toolkit-transforms[ray]\n" ] }, { From 2fbbcd989666ec50ee2e8a3e6e62fa1be1930aed Mon Sep 17 00:00:00 2001 From: ian-cho <42691703+ian-cho@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:07:16 +0900 Subject: [PATCH 22/70] Add files via upload --- transforms/universal/hap/ray/Dockerfile | 42 ++++++++++++ transforms/universal/hap/ray/Makefile | 58 ++++++++++++++++ transforms/universal/hap/ray/README.md | 20 ++++++ .../universal/hap/ray/output/metadata.json | 50 ++++++++++++++ .../universal/hap/ray/output/test1.parquet | Bin 0 -> 79822 bytes transforms/universal/hap/ray/pyproject.toml | 48 +++++++++++++ transforms/universal/hap/ray/requirements.txt | 6 ++ transforms/universal/hap/ray/src/__init__.py | 0 .../universal/hap/ray/src/hap_local_ray.py | 57 ++++++++++++++++ .../universal/hap/ray/src/hap_s3_ray.py | 64 ++++++++++++++++++ .../hap/ray/src/hap_transform_ray.py | 39 +++++++++++ .../hap/ray/test-data/expected/metadata.json | 50 ++++++++++++++ .../hap/ray/test-data/expected/test1.parquet | Bin 0 -> 79822 bytes .../hap/ray/test-data/input/test1.parquet | Bin 0 -> 109303 bytes .../universal/hap/ray/test/test_hap_ray.py | 40 +++++++++++ 15 files changed, 474 insertions(+) create mode 100644 transforms/universal/hap/ray/Dockerfile create mode 100644 transforms/universal/hap/ray/Makefile create mode 100644 transforms/universal/hap/ray/README.md create mode 100644 transforms/universal/hap/ray/output/metadata.json create mode 100644 transforms/universal/hap/ray/output/test1.parquet create mode 100644 transforms/universal/hap/ray/pyproject.toml create mode 100644 transforms/universal/hap/ray/requirements.txt create mode 100644 transforms/universal/hap/ray/src/__init__.py create mode 100644 transforms/universal/hap/ray/src/hap_local_ray.py create mode 100644 transforms/universal/hap/ray/src/hap_s3_ray.py create mode 100644 transforms/universal/hap/ray/src/hap_transform_ray.py create mode 100644 transforms/universal/hap/ray/test-data/expected/metadata.json create mode 100644 transforms/universal/hap/ray/test-data/expected/test1.parquet create mode 100644 transforms/universal/hap/ray/test-data/input/test1.parquet create mode 100644 transforms/universal/hap/ray/test/test_hap_ray.py diff --git a/transforms/universal/hap/ray/Dockerfile b/transforms/universal/hap/ray/Dockerfile new file mode 100644 index 000000000..42005e9ba --- /dev/null +++ b/transforms/universal/hap/ray/Dockerfile @@ -0,0 +1,42 @@ +ARG BASE_IMAGE=docker.io/rayproject/ray:2.24.0-py310 +FROM ${BASE_IMAGE} + +RUN pip install --upgrade --no-cache-dir pip + +# install pytest +RUN pip install --no-cache-dir pytest + +# Copy and install data processing libraries +# These are expected to be placed in the docker context before this is run (see the make image). +COPY --chown=ray:users data-processing-lib-python/ data-processing-lib-python/ +RUN cd data-processing-lib-python && pip install --no-cache-dir -e . +COPY --chown=ray:users data-processing-lib-ray/ data-processing-lib-ray/ +RUN cd data-processing-lib-ray && pip install --no-cache-dir -e . +COPY --chown=ray:users python-transform/ python-transform/ +RUN cd python-transform && pip install --no-cache-dir -e . + +#COPY requirements.txt requirements.txt +#RUN pip install --no-cache-dir -r requirements.txt + +COPY --chown=ray:users src/ src/ +COPY --chown=ray:users pyproject.toml pyproject.toml +RUN pip install --no-cache-dir -e . + +# copy the main() entry point to the image +COPY ./src/hap_transform_ray.py . + +# copy some of the samples in +COPY ./src/hap_local_ray.py local/ + +# copy test +COPY test/ test/ +COPY test-data/ test-data/ + +# Set environment +ENV PYTHONPATH /home/ray + +# Put these at the end since they seem to upset the docker cache. +ARG BUILD_DATE +ARG GIT_COMMIT +LABEL build-date=$BUILD_DATE +LABEL git-commit=$GIT_COMMIT diff --git a/transforms/universal/hap/ray/Makefile b/transforms/universal/hap/ray/Makefile new file mode 100644 index 000000000..942898ee2 --- /dev/null +++ b/transforms/universal/hap/ray/Makefile @@ -0,0 +1,58 @@ +# Define the root of the local git clone for the common rules to be able +# know where they are running from. +REPOROOT=../../../.. +# Include a library of common .transform.* targets which most +# transforms should be able to reuse. However, feel free +# to override/redefine the rules below. +include $(REPOROOT)/transforms/.make.transforms + +TRANSFORM_NAME=hap + +BASE_IMAGE=${RAY_BASE_IMAGE} +HAP_PYTHON_VERSION= $(DPK_VERSION) + +venv:: .transforms.ray-venv + +install:: pip install -r requirements.txt + +test:: .transforms.ray-test + +clean:: .transforms.clean + +image:: .transforms.ray-image + +test-src:: .transforms.test-src + +setup:: .transforms.setup + +test-image:: .transforms.ray-test-image + +build:: build-dist image + +publish: publish-image + +publish-image:: .transforms.publish-image-ray + +setup:: .transforms.setup + +# distribution versions is the same as image version. +set-versions: + $(MAKE) TRANSFORM_PYTHON_VERSION=$(HAP_PYTHON_VERSION) TOML_VERSION=$(HAP_RAY_VERSION) .transforms.set-versions + +build-dist:: set-versions .defaults.build-dist + +publish-dist:: .defaults.publish-dist + +run-cli-sample: .transforms.run-cli-ray-sample + +run-local-sample: .transforms.run-local-ray-sample + +run-s3-sample: .transforms.run-s3-ray-sample + +minio-start: .minio-start + +kind-load-image:: .transforms.kind-load-image + +docker-load-image: .defaults.docker-load-image + +docker-save-image: .defaults.docker-save-image diff --git a/transforms/universal/hap/ray/README.md b/transforms/universal/hap/ray/README.md new file mode 100644 index 000000000..486ac903f --- /dev/null +++ b/transforms/universal/hap/ray/README.md @@ -0,0 +1,20 @@ +# Hate, Abuse, and Profanity (HAP) Annotation +# HAP Transform for Ray +Please see the set of +[transform project conventions](../../../README.md#transform-project-conventions) +for details on general project conventions, transform configuration, +testing and IDE set up. + +## Summary +This project wraps the [hap transform](../python) with a Ray runtime. + +## Configuration and command line Options + +Configuration and command line options are the same as for the base python transform. + +## Running + +### Launched Command Line Options +In addition to those available to the transform as defined in [here](../python/README.md), +the set of +[ray launcher](../../../../data-processing-lib/doc/ray-launcher-options.md) are available. diff --git a/transforms/universal/hap/ray/output/metadata.json b/transforms/universal/hap/ray/output/metadata.json new file mode 100644 index 000000000..062fee162 --- /dev/null +++ b/transforms/universal/hap/ray/output/metadata.json @@ -0,0 +1,50 @@ +{ + "pipeline": "pipeline_id", + "job details": { + "job category": "preprocessing", + "job name": "hap", + "job type": "pure python", + "job id": "job_id", + "start_time": "2024-10-03 21:38:20", + "end_time": "2024-10-03 21:38:29", + "status": "success" + }, + "code": { + "github": "github", + "commit_hash": "12345", + "path": "path" + }, + "job_input_params": { + "model_name_or_path": "ibm-granite/granite-guardian-hap-38m", + "annotation_column": "hap_score", + "doc_text_column": "contents", + "inference_engine": "CPU", + "max_length": 512, + "batch_size": 128, + "checkpointing": false, + "max_files": -1, + "random_samples": -1, + "files_to_use": [ + ".parquet" + ], + "num_processors": 0 + }, + "job_output_stats": { + "source_files": 2, + "source_size": 12124594, + "transform execution exception": 1, + "result_files": 1, + "result_size": 79822, + "processing_time": 6.932, + "source_doc_count": 50, + "result_doc_count": 50 + }, + "source": { + "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/test-data/input", + "type": "path" + }, + "target": { + "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/output", + "type": "path" + } +} \ No newline at end of file diff --git a/transforms/universal/hap/ray/output/test1.parquet b/transforms/universal/hap/ray/output/test1.parquet new file mode 100644 index 0000000000000000000000000000000000000000..c9483e34d47dd71af90b1a6694c55fb01ea95453 GIT binary patch literal 79822 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%0AOYVoBqAqsAu5`It?D>xkBUN&XG1tLk-G_c?YDV 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 T3O17~|6s{tFqkXKUFZJ+T6-k& literal 0 HcmV?d00001 diff --git a/transforms/universal/hap/ray/pyproject.toml b/transforms/universal/hap/ray/pyproject.toml new file mode 100644 index 000000000..f29ceb683 --- /dev/null +++ b/transforms/universal/hap/ray/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "dpk_hap_transform_python" +version = "0.2.2.dev0" +requires-python = ">=3.10" +description = "HAP Ray Transform" +license = {text = "Apache-2.0"} +readme = {file = "README.md", content-type = "text/markdown"} +authors = [ +{ name = "Ian Cho", email = "iancho.mr@gmail.com" }, +] +dynamic = ["dependencies"] + + +[build-system] +requires = ["setuptools>=68.0.0", "wheel", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + +[project.optional-dependencies] +dev = [ + "twine", + "pytest>=7.3.2", + "pytest-dotenv>=0.5.2", + "pytest-env>=1.0.0", + "pre-commit>=3.3.2", + "pytest-cov>=4.1.0", + "pytest-mock>=3.10.0", + "moto==5.0.5", + "markupsafe==2.0.1", +] + + + +[options] +package_dir = ["src","test"] + +[options.packages.find] +where = ["src/"] + +[tool.pytest.ini_options] +# Currently we use low coverage since we have to run tests separately (see makefile) +#addopts = "--cov --cov-report term-missing --cov-fail-under 25" +markers = ["unit: unit tests", "integration: integration tests"] + +[tool.coverage.run] +include = ["src/*"] diff --git a/transforms/universal/hap/ray/requirements.txt b/transforms/universal/hap/ray/requirements.txt new file mode 100644 index 000000000..36c2b81af --- /dev/null +++ b/transforms/universal/hap/ray/requirements.txt @@ -0,0 +1,6 @@ +data-prep-toolkit-ray==0.2.2.dev0 +dpk-hap-transform-python==0.2.2.dev0 +nltk==3.9.1 +transformers==4.38.2 +torch==2.4.1 +pandas==2.2.2 diff --git a/transforms/universal/hap/ray/src/__init__.py b/transforms/universal/hap/ray/src/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/transforms/universal/hap/ray/src/hap_local_ray.py b/transforms/universal/hap/ray/src/hap_local_ray.py new file mode 100644 index 000000000..d3201e42f --- /dev/null +++ b/transforms/universal/hap/ray/src/hap_local_ray.py @@ -0,0 +1,57 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + + +import ast +import os +import sys + +from data_processing.utils import ParamsUtils +from data_processing_ray.runtime.ray import RayTransformLauncher +from hap_transform_ray import HAPRayTransformConfiguration + + +# create parameters +input_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "../test-data/input")) +output_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "../output")) +local_conf = { + "input_folder": input_folder, + "output_folder": output_folder, +} +code_location = {"github": "github", "commit_hash": "12345", "path": "path"} + +params = { + "data_local_config": ParamsUtils.convert_to_ast(local_conf), + "runtime_pipeline_id": "pipeline_id", + "runtime_job_id": "job_id", + "runtime_code_location": ParamsUtils.convert_to_ast(code_location), +} + + +hap_params = { + "model_name_or_path": 'ibm-granite/granite-guardian-hap-38m', + "annotation_column": "hap_score", + "doc_text_column": "contents", + "inference_engine": "CPU", + "max_length": 512, + "batch_size": 128, +} + + + +if __name__ == "__main__": + # Set the simulated command line args + sys.argv = ParamsUtils.dict_to_req(d=params | hap_params) + # create launcher + launcher = RayTransformLauncher(HAPRayTransformConfiguration()) + # Launch the ray actor(s) to process the input + launcher.launch() diff --git a/transforms/universal/hap/ray/src/hap_s3_ray.py b/transforms/universal/hap/ray/src/hap_s3_ray.py new file mode 100644 index 000000000..fceae0b7c --- /dev/null +++ b/transforms/universal/hap/ray/src/hap_s3_ray.py @@ -0,0 +1,64 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +import os +import sys + +from data_processing.utils import ParamsUtils +from data_processing_ray.runtime.ray import RayTransformLauncher +from hap_transform_ray import HAPRayTransformConfiguration + + +# create launcher +launcher = RayTransformLauncher(HAPRayTransformConfiguration()) +# create parameters +s3_cred = { + "access_key": "localminioaccesskey", + "secret_key": "localminiosecretkey", + "url": "http://localhost:9000", +} + +s3_conf = { + "input_folder": "test/hap/input", + "output_folder": "test/hap/output", +} +worker_options = {"num_cpus": 0.8} +code_location = {"github": "github", "commit_hash": "12345", "path": "path"} +params = { + # where to run + "run_locally": True, + # Data access. Only required parameters are specified + "data_s3_cred": ParamsUtils.convert_to_ast(s3_cred), + "data_s3_config": ParamsUtils.convert_to_ast(s3_conf), + # orchestrator + "runtime_worker_options": ParamsUtils.convert_to_ast(worker_options), + "runtime_num_workers": 3, + "runtime_pipeline_id": "pipeline_id", + "runtime_job_id": "job_id", + "runtime_creation_delay": 0, + "runtime_code_location": ParamsUtils.convert_to_ast(code_location), +} + + +hap_params = { + "model_name_or_path": 'ibm-granite/granite-guardian-hap-38m', + "annotation_column": "hap_score", + "doc_text_column": "contents", + "inference_engine": "CPU", + "max_length": 512, + "batch_size": 128, +} + + +sys.argv = ParamsUtils.dict_to_req(d=params | hap_params) +# launch +launcher.launch() diff --git a/transforms/universal/hap/ray/src/hap_transform_ray.py b/transforms/universal/hap/ray/src/hap_transform_ray.py new file mode 100644 index 000000000..f01fffef5 --- /dev/null +++ b/transforms/universal/hap/ray/src/hap_transform_ray.py @@ -0,0 +1,39 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +from data_processing.utils import get_logger +from data_processing_ray.runtime.ray import RayTransformLauncher +from data_processing_ray.runtime.ray.runtime_configuration import RayTransformRuntimeConfiguration +from hap_transform import HAPTransformConfiguration + + +logger = get_logger(__name__) + + +class HAPRayTransformConfiguration(RayTransformRuntimeConfiguration): + """ + Implements the RayTransformConfiguration for HAP as required by the RayTransformLauncher. + """ + + def __init__(self): + """ + Initialization + :param base_configuration - base configuration class + """ + super().__init__(transform_config=HAPTransformConfiguration()) + + +if __name__ == "__main__": + launcher = RayTransformLauncher(HAPRayTransformConfiguration()) + logger.info("Launching hap transform") + launcher.launch() + diff --git a/transforms/universal/hap/ray/test-data/expected/metadata.json b/transforms/universal/hap/ray/test-data/expected/metadata.json new file mode 100644 index 000000000..062fee162 --- /dev/null +++ b/transforms/universal/hap/ray/test-data/expected/metadata.json @@ -0,0 +1,50 @@ +{ + "pipeline": "pipeline_id", + "job details": { + "job category": "preprocessing", + "job name": "hap", + "job type": "pure python", + "job id": "job_id", + "start_time": "2024-10-03 21:38:20", + "end_time": "2024-10-03 21:38:29", + "status": "success" + }, + "code": { + "github": "github", + "commit_hash": "12345", + "path": "path" + }, + "job_input_params": { + "model_name_or_path": "ibm-granite/granite-guardian-hap-38m", + "annotation_column": "hap_score", + "doc_text_column": "contents", + "inference_engine": "CPU", + "max_length": 512, + "batch_size": 128, + "checkpointing": false, + "max_files": -1, + "random_samples": -1, + "files_to_use": [ + ".parquet" + ], + "num_processors": 0 + }, + "job_output_stats": { + "source_files": 2, + "source_size": 12124594, + "transform execution exception": 1, + "result_files": 1, + "result_size": 79822, + "processing_time": 6.932, + "source_doc_count": 50, + "result_doc_count": 50 + }, + "source": { + "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/test-data/input", + "type": "path" + }, + "target": { + "name": "/Users/ian/Desktop/data-prep-kit/transforms/universal/hap/python/output", + "type": "path" + } +} \ No newline at end of file diff --git a/transforms/universal/hap/ray/test-data/expected/test1.parquet b/transforms/universal/hap/ray/test-data/expected/test1.parquet new file mode 100644 index 0000000000000000000000000000000000000000..c9483e34d47dd71af90b1a6694c55fb01ea95453 GIT binary patch literal 79822 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%0AOYVoBqAqsAu5`It?D>xkBUN&XG1tLk-G_c?YDV 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 T3O17~|6s{tFqkXKUFZJ+T6-k& literal 0 HcmV?d00001 diff --git a/transforms/universal/hap/ray/test-data/input/test1.parquet b/transforms/universal/hap/ray/test-data/input/test1.parquet new file mode 100644 index 0000000000000000000000000000000000000000..5e2f5fe9d5547448a8d2ff3ec3b5b5c51e575455 GIT binary patch literal 109303 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�qEzqW7l_RPN4M5Cx565Et86 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 aqiA)oERvd>{SrnTH0byhFNZK)M)?OEOC*p0 literal 0 HcmV?d00001 diff --git a/transforms/universal/hap/ray/test/test_hap_ray.py b/transforms/universal/hap/ray/test/test_hap_ray.py new file mode 100644 index 000000000..232e1f4ae --- /dev/null +++ b/transforms/universal/hap/ray/test/test_hap_ray.py @@ -0,0 +1,40 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ +import os +from data_processing_ray.runtime.ray import RayTransformLauncher +from data_processing.test_support.launch.transform_test import ( + AbstractTransformLauncherTest, +) +from hap_transform_ray import HAPRayTransformConfiguration + +hap_params = { + "run_locally": True, + "model_name_or_path": 'ibm-granite/granite-guardian-hap-38m', + "annotation_column": "hap_score", + "doc_text_column": "contents", + "inference_engine": "CPU", + "max_length": 512, + "batch_size": 128, +} + + +class TestRayHAPTransform(AbstractTransformLauncherTest): + """ + Extends the super-class to define the test data for the tests defined there. + The name of this class MUST begin with the word Test so that pytest recognizes it as a test class. + """ + def get_test_transform_fixtures(self) -> list[tuple]: + basedir = "../test-data" + basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), basedir)) + launcher = RayTransformLauncher(HAPRayTransformConfiguration()) + fixtures = [(launcher, hap_params, basedir + "/input", basedir + "/expected")] + return fixtures From a2c31ee498790e194035a70c0c12204e95a19636 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Tue, 8 Oct 2024 08:17:09 -0400 Subject: [PATCH 23/70] -sfix ray dependency to 2.36.1 that support 3.12 --- data-processing-lib/requirements-ray.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-processing-lib/requirements-ray.txt b/data-processing-lib/requirements-ray.txt index aafa3caeb..33205cd9d 100644 --- a/data-processing-lib/requirements-ray.txt +++ b/data-processing-lib/requirements-ray.txt @@ -1,3 +1,3 @@ -ray[default]==2.24.0 +ray[default]==2.36.1 fastapi>=0.110.2 pillow>=10.3.0 From 999747d2b5398fe254936e550cd5ed25d4990e13 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 14:01:09 -0400 Subject: [PATCH 24/70] Need to look into this one more Signed-off-by: Maroun Touma --- .../header_cleanser/kfp_ray/{Makefile => Makefile.disable-cicd} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename transforms/code/header_cleanser/kfp_ray/{Makefile => Makefile.disable-cicd} (100%) diff --git a/transforms/code/header_cleanser/kfp_ray/Makefile b/transforms/code/header_cleanser/kfp_ray/Makefile.disable-cicd similarity index 100% rename from transforms/code/header_cleanser/kfp_ray/Makefile rename to transforms/code/header_cleanser/kfp_ray/Makefile.disable-cicd From a1ff406f81bd37f62ee5113a2b38e889d7f268ef Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 15:40:27 -0400 Subject: [PATCH 25/70] Update local files with proper revision number Signed-off-by: Maroun Touma --- transforms/Makefile | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/transforms/Makefile b/transforms/Makefile index 275526687..83eb65d5b 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -1,6 +1,8 @@ REPOROOT=../ # Use make help, to see the available rules include ../.make.defaults +include ./transform.config + setup:: @# Help: Recursively make $@ all subdirs @@ -78,11 +80,30 @@ workflow-upload:: set-versions:: @# Help: Recursively make $@ in all subdirs -# $(MAKE) TRANSFORM_PYTHON_VERSION=$(DPK_TRANSFORMS_VERSION) TOML_VERSION=$(DPK_TRANSFORMS_VERSION) .transforms.set-versions + make set-pkg-version @$(MAKE) RULE=$@ .recurse +set-pkg-version: + -e 's/\("data-prep-toollitpk[_-].*transform[_-]python[=<>~][=]\).*"/\1$(TRANSFORM_PYTHON_VERSION)"/' \ + + + echo $(TRANSFORMS_PKG_VERSION) + cat pyproject.toml | sed -e \ + 's/^version[ ]*=.*/version = "'${TRANSFORMS_PKG_VERSION}'"/' \ + > tt + mv tt pyproject.toml + echo $(DPK_VERSION) + cat requirements.txt | sed -e \ + 's/data-prep-toolkit\([=><~][=]\).*/data-prep-toolkit\1$(DPK_VERSION)/' \ + > tt + mv tt requirements.txt + cat requirements-ray.txt | sed -e \ + 's/data-prep-toolkit\[ray\]\([=><~][=]\).*/data-prep-toolkit\[ray\]\1$(DPK_VERSION)/' \ + > tt + mv tt requirements-ray.txt + -build-pkg-dist:: +build-pkg-dist: ## Most transforms today don't have a package name.... Need to fix that ## In the meantime, we will copy everything to a single folder -rm -fr src @@ -98,7 +119,7 @@ build-pkg-dist:: $(MAKE) BUILD_WHEEL_ARG=-w .defaults.build-dist -rm -fr src -test-pkg-dist:: +test-pkg-dist: -rm -fr venv python -m venv venv source venv/bin/activate && $(PYTHON) -m pip install '$(REPOROOT)/data-processing-lib/dist/data_prep_toolkit-$(DPK_VERSION)-py3-none-any.whl[dev,ray]' From b95fee1154402048e5d6f10679d5df7becdcd4d7 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 15:42:44 -0400 Subject: [PATCH 26/70] Added configuration for package transform Signed-off-by: Maroun Touma --- transforms/transform.config | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 transforms/transform.config diff --git a/transforms/transform.config b/transforms/transform.config new file mode 100644 index 000000000..afe747c21 --- /dev/null +++ b/transforms/transform.config @@ -0,0 +1,17 @@ +# +# This is intended to be included across the Makefiles provided within +# a given transform's directory tree, so must use compatible syntax. +# +################################################################################ +# This defines the name of the transform and is used to match against +# expected files and is used to define the transform's image name. +TRANSFORM_NAME=data-prep-kit-transforms + +################################################################################ +# This defines the transforms' package version number as would be used +# when publishing the wheel. In general, only the micro version +# number should be advanced relative to the DPK_VERSION. +# +# If you change the versions numbers, be sure to run "make set-versions" to +# update version numbers across the transform (e.g., pyproject.toml). +TRANSFORMS_PKG_VERSION=$(DPK_VERSION) From 38a681a60c40f270d358f35ae9aee6ac2f2aa556 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 16:27:22 -0400 Subject: [PATCH 27/70] Fix requirements.txt when referencing python package Signed-off-by: Maroun Touma --- transforms/.make.transforms | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/transforms/.make.transforms b/transforms/.make.transforms index cd6aa84ac..5034b3122 100644 --- a/transforms/.make.transforms +++ b/transforms/.make.transforms @@ -342,4 +342,11 @@ minio-stop: > tt.toml; \ mv tt.toml pyproject.toml; \ fi + if [ -e requirements.txt ]; then \ + cat requirements.txt | sed \ + -e 's/\(dpk[_-].*transform[_-]python[=<>~][=]\).*/\1$(TRANSFORM_PYTHON_VERSION)/' \ + > tt.txt; \ + mv tt.txt requirements.txt; \ + fi + From ca5090e1851c34793639c7dca405df6084c97142 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 16:35:22 -0400 Subject: [PATCH 28/70] rename build arg. Signed-off-by: Maroun Touma --- .make.defaults | 2 +- data-processing-lib/Makefile | 2 +- transforms/Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.make.defaults b/.make.defaults index 1184d583f..e69dd0740 100644 --- a/.make.defaults +++ b/.make.defaults @@ -642,7 +642,7 @@ endif rm -rf dist || true rm -rf src/*egg-info || true ${PIP} install --upgrade build - ${PYTHON} -m build $(BUILD_WHEEL_ARG) + ${PYTHON} -m build $(BUILD_WHEEL_EXTRA_ARG) # Publish the distribution in the dist directory, usually created with .defaults.build-dist target .PHONY: .defaults.publish-dist diff --git a/data-processing-lib/Makefile b/data-processing-lib/Makefile index 6750f11b4..fe3932195 100644 --- a/data-processing-lib/Makefile +++ b/data-processing-lib/Makefile @@ -59,7 +59,7 @@ set-versions: build-pkg-dist:: - $(MAKE) .defaults.build-dist BUILD_WHEEL_ARG=-w + $(MAKE) .defaults.build-dist BUILD_WHEEL_EXTRA_ARG=-w publish-dist :: .defaults.publish-dist diff --git a/transforms/Makefile b/transforms/Makefile index 83eb65d5b..a590ebf8d 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -116,7 +116,7 @@ build-pkg-dist: fi \ done # Only needs to build the whl - $(MAKE) BUILD_WHEEL_ARG=-w .defaults.build-dist + $(MAKE) BUILD_WHEEL_EXTRA_ARG=-w .defaults.build-dist -rm -fr src test-pkg-dist: From aff215255c8793ef981e10475386ecc3db1941b2 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Wed, 9 Oct 2024 21:52:05 -0400 Subject: [PATCH 29/70] Added help for new trargets Signed-off-by: Maroun Touma --- .make.defaults | 1 - transforms/Makefile | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.make.defaults b/.make.defaults index e69dd0740..a7cc023ba 100644 --- a/.make.defaults +++ b/.make.defaults @@ -234,7 +234,6 @@ __check_defined = \ mkdir ${LIB_NAME} cp -p -R ${LIB_PATH}/src ${LIB_NAME} cp -p -R ${LIB_PATH}/pyproject.toml ${LIB_NAME} - -cp -p -R ${LIB_PATH}/requirements.txt ${LIB_NAME} cp -p -R ${LIB_PATH}/README.md ${LIB_NAME} if [ -e ${LIB_PATH}/requirements.txt ]; then \ cp -p ${LIB_PATH}/requirements.txt ${LIB_NAME}; \ diff --git a/transforms/Makefile b/transforms/Makefile index a590ebf8d..63e635898 100644 --- a/transforms/Makefile +++ b/transforms/Makefile @@ -84,10 +84,7 @@ set-versions:: @$(MAKE) RULE=$@ .recurse set-pkg-version: - -e 's/\("data-prep-toollitpk[_-].*transform[_-]python[=<>~][=]\).*"/\1$(TRANSFORM_PYTHON_VERSION)"/' \ - - - echo $(TRANSFORMS_PKG_VERSION) + @# Help: Set tag for this package and its dependencies cat pyproject.toml | sed -e \ 's/^version[ ]*=.*/version = "'${TRANSFORMS_PKG_VERSION}'"/' \ > tt @@ -104,6 +101,7 @@ set-pkg-version: build-pkg-dist: + @# Help: Build package wheel ## Most transforms today don't have a package name.... Need to fix that ## In the meantime, we will copy everything to a single folder -rm -fr src @@ -120,6 +118,7 @@ build-pkg-dist: -rm -fr src test-pkg-dist: + @# Help: Setup environment and run unit tests for all transforms. -rm -fr venv python -m venv venv source venv/bin/activate && $(PYTHON) -m pip install '$(REPOROOT)/data-processing-lib/dist/data_prep_toolkit-$(DPK_VERSION)-py3-none-any.whl[dev,ray]' From 2aed22d4b49a4cf20c3d015a8789b78ae320e081 Mon Sep 17 00:00:00 2001 From: ian-cho <42691703+ian-cho@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:29:38 +0900 Subject: [PATCH 30/70] Update transforms/universal/hap/ray/pyproject.toml Co-authored-by: touma-I --- transforms/universal/hap/ray/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/universal/hap/ray/pyproject.toml b/transforms/universal/hap/ray/pyproject.toml index f29ceb683..ff3fc05f0 100644 --- a/transforms/universal/hap/ray/pyproject.toml +++ b/transforms/universal/hap/ray/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "dpk_hap_transform_python" +name = "dpk_hap_transform_ray" version = "0.2.2.dev0" requires-python = ">=3.10" description = "HAP Ray Transform" From c11da2e964d7ad9683225a87296e77d1e96a22f6 Mon Sep 17 00:00:00 2001 From: Dave Nielsen Date: Thu, 10 Oct 2024 06:56:40 -0700 Subject: [PATCH 31/70] Update README.md spelling correction --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4d372356..b537b7a49 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ python -m ipykernel install --user --name=data-prep-kit --display-name "dataprep Test, your installation. If you are able to import these data-prep-kit libraries successfully in python, your installation has succeeded. ```bash -## start python interpretter +## start python interpreter $ python # import DPK libraries @@ -240,4 +240,4 @@ If you use Data Prep Kit in your research, please cite our paper: primaryClass={cs.AI}, url={https://arxiv.org/abs/2409.18164}, } -``` \ No newline at end of file +``` From 4c7f4a5fcda8ea08e8fd87ea2bbcf42c610530c5 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Thu, 10 Oct 2024 22:02:38 +0300 Subject: [PATCH 32/70] Minor change to kfp workflow. (#684) * Minor chages to kfp workflow. Signed-off-by: Revital Sur * Add workflows to blacklist. Signed-off-by: Revital Sur * Minor fixes to repo_level_ordering kfp. Signed-off-by: Revital Sur * Add Makefiles. Signed-off-by: Revital Sur * Add repo_level_ordering to black list. Signed-off-by: Revital Sur * Remove Makefile.disable-cicd Signed-off-by: Revital Sur * Minor change to trigger kfp workflows. Signed-off-by: Revital Sur * Fix kfp_ray/Makefile. Signed-off-by: Revital Sur --------- Signed-off-by: Revital Sur --- .github/workflows/Makefile | 6 - .../workflows/test-code-code2parquet-kfp.yml | 80 +++++------- .../workflows/test-code-code_quality-kfp.yml | 80 +++++------- .../test-code-header_cleanser-kfp.yml | 80 +++++------- .../test-code-license_select-kfp.yml | 80 +++++------- .github/workflows/test-code-malware-kfp.yml | 80 +++++------- .../test-code-proglang_select-kfp.yml | 80 +++++------- .../test-code-repo_level_ordering-kfp.yml | 80 +++++------- .github/workflows/test-kfp-transform.template | 80 +++++------- .../workflows/test-language-doc_chunk-kfp.yml | 116 ++++++++++++++++++ .../test-language-doc_quality-kfp.yml | 80 +++++------- .../workflows/test-language-lang_id-kfp.yml | 80 +++++------- .../test-language-pdf2parquet-kfp.yml | 116 ++++++++++++++++++ .../test-language-pii_redactor-kfp.yml | 116 ++++++++++++++++++ .../test-language-text_encoder-kfp.yml | 80 +++++------- .../workflows/test-universal-doc_id-kfp.yml | 80 +++++------- .../workflows/test-universal-ededup-kfp.yml | 80 +++++------- .../workflows/test-universal-fdedup-kfp.yml | 80 +++++------- .../workflows/test-universal-filter-kfp.yml | 80 +++++------- .github/workflows/test-universal-noop-kfp.yml | 80 +++++------- .../workflows/test-universal-profiler-kfp.yml | 80 +++++------- .../workflows/test-universal-resize-kfp.yml | 80 +++++------- .../test-universal-tokenization-kfp.yml | 80 +++++------- scripts/check-workflows.sh | 2 +- scripts/k8s-setup/populate_minio.sh | 1 + scripts/workflow_helper.sh | 62 ++++++++++ transforms/.make.workflows | 1 - .../{Makefile.disable-cicd => Makefile} | 0 .../{Makefile.disable-cicd => Makefile} | 2 +- .../kfp_ray/repo_level_order_wf.py | 2 +- .../{Makefile.disable-cicd => Makefile} | 0 31 files changed, 1041 insertions(+), 903 deletions(-) create mode 100644 .github/workflows/test-language-doc_chunk-kfp.yml create mode 100644 .github/workflows/test-language-pdf2parquet-kfp.yml create mode 100644 .github/workflows/test-language-pii_redactor-kfp.yml create mode 100755 scripts/workflow_helper.sh rename transforms/code/license_select/kfp_ray/{Makefile.disable-cicd => Makefile} (100%) rename transforms/code/repo_level_ordering/kfp_ray/{Makefile.disable-cicd => Makefile} (96%) rename transforms/language/text_encoder/kfp_ray/{Makefile.disable-cicd => Makefile} (100%) diff --git a/.github/workflows/Makefile b/.github/workflows/Makefile index 8d0c039dc..8673c0e9c 100644 --- a/.github/workflows/Makefile +++ b/.github/workflows/Makefile @@ -44,12 +44,6 @@ transform-tests: echo No kfp_ray directory for $$dir. Skipping generation of $$yml; \ continue; \ fi; \ - z=$$(echo $${KFP_BLACK_LIST} | grep $$dir); \ - if [ ! -z "$$z" ]; then \ - echo $$dir is black listed. Skipping generation of $$yml; \ - continue; \ - fi; \ - echo Generating $$yml; \ cat test-kfp-transform.template | sed -e "s?@TARGET_TRANSFORM_DIR@?transforms/$${TRANSFORM_SUBDIR}/$$dir?g" > $$yml; \ fi; \ done diff --git a/.github/workflows/test-code-code2parquet-kfp.yml b/.github/workflows/test-code-code2parquet-kfp.yml index 9b0a92042..4cb943469 100644 --- a/.github/workflows/test-code-code2parquet-kfp.yml +++ b/.github/workflows/test-code-code2parquet-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/code2parquet timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/code2parquet/Makefile" -a -e "transforms/code/code2parquet/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/code2parquet workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/code2parquet workflow-test - echo "Run transforms/code/code2parquet completed" - else - echo "Skipping transforms/code/code2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/code2parquet") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/code2parquet + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/code2parquet + fi + else + echo "Skipping transforms/code/code2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/code2parquet timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/code2parquet/Makefile" -a -e "transforms/code/code2parquet/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/code2parquet workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/code2parquet workflow-test - echo "Run transforms/code/code2parquet completed" + transform=$(basename "transforms/code/code2parquet") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/code2parquet + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/code2parquet + fi else - echo "Skipping transforms/code/code2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/code2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-code_quality-kfp.yml b/.github/workflows/test-code-code_quality-kfp.yml index 504923b6b..f177d8a37 100644 --- a/.github/workflows/test-code-code_quality-kfp.yml +++ b/.github/workflows/test-code-code_quality-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/code_quality timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/code_quality/Makefile" -a -e "transforms/code/code_quality/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/code_quality workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/code_quality workflow-test - echo "Run transforms/code/code_quality completed" - else - echo "Skipping transforms/code/code_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/code_quality") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/code_quality + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/code_quality + fi + else + echo "Skipping transforms/code/code_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/code_quality timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/code_quality/Makefile" -a -e "transforms/code/code_quality/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/code_quality workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/code_quality workflow-test - echo "Run transforms/code/code_quality completed" + transform=$(basename "transforms/code/code_quality") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/code_quality + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/code_quality + fi else - echo "Skipping transforms/code/code_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/code_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-header_cleanser-kfp.yml b/.github/workflows/test-code-header_cleanser-kfp.yml index b1f4f1b1b..2b5538806 100644 --- a/.github/workflows/test-code-header_cleanser-kfp.yml +++ b/.github/workflows/test-code-header_cleanser-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/header_cleanser timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/header_cleanser/Makefile" -a -e "transforms/code/header_cleanser/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/header_cleanser workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/header_cleanser workflow-test - echo "Run transforms/code/header_cleanser completed" - else - echo "Skipping transforms/code/header_cleanser kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/header_cleanser") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/header_cleanser + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/header_cleanser + fi + else + echo "Skipping transforms/code/header_cleanser kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/header_cleanser timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/header_cleanser/Makefile" -a -e "transforms/code/header_cleanser/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/header_cleanser workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/header_cleanser workflow-test - echo "Run transforms/code/header_cleanser completed" + transform=$(basename "transforms/code/header_cleanser") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/header_cleanser + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/header_cleanser + fi else - echo "Skipping transforms/code/header_cleanser kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/header_cleanser kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-license_select-kfp.yml b/.github/workflows/test-code-license_select-kfp.yml index 0e953a09a..89e7deae6 100644 --- a/.github/workflows/test-code-license_select-kfp.yml +++ b/.github/workflows/test-code-license_select-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/license_select timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/license_select/Makefile" -a -e "transforms/code/license_select/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/license_select workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/license_select workflow-test - echo "Run transforms/code/license_select completed" - else - echo "Skipping transforms/code/license_select kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/license_select") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/license_select + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/license_select + fi + else + echo "Skipping transforms/code/license_select kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/license_select timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/license_select/Makefile" -a -e "transforms/code/license_select/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/license_select workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/license_select workflow-test - echo "Run transforms/code/license_select completed" + transform=$(basename "transforms/code/license_select") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/license_select + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/license_select + fi else - echo "Skipping transforms/code/license_select kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/license_select kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-malware-kfp.yml b/.github/workflows/test-code-malware-kfp.yml index df580decb..389a04555 100644 --- a/.github/workflows/test-code-malware-kfp.yml +++ b/.github/workflows/test-code-malware-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/malware timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/malware/Makefile" -a -e "transforms/code/malware/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/malware workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/malware workflow-test - echo "Run transforms/code/malware completed" - else - echo "Skipping transforms/code/malware kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/malware") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/malware + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/malware + fi + else + echo "Skipping transforms/code/malware kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/malware timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/malware/Makefile" -a -e "transforms/code/malware/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/malware workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/malware workflow-test - echo "Run transforms/code/malware completed" + transform=$(basename "transforms/code/malware") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/malware + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/malware + fi else - echo "Skipping transforms/code/malware kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/malware kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-proglang_select-kfp.yml b/.github/workflows/test-code-proglang_select-kfp.yml index b7ee58f2a..09fd67520 100644 --- a/.github/workflows/test-code-proglang_select-kfp.yml +++ b/.github/workflows/test-code-proglang_select-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/proglang_select timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/proglang_select/Makefile" -a -e "transforms/code/proglang_select/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/proglang_select workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/proglang_select workflow-test - echo "Run transforms/code/proglang_select completed" - else - echo "Skipping transforms/code/proglang_select kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/proglang_select") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/proglang_select + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/proglang_select + fi + else + echo "Skipping transforms/code/proglang_select kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/proglang_select timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/proglang_select/Makefile" -a -e "transforms/code/proglang_select/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/proglang_select workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/proglang_select workflow-test - echo "Run transforms/code/proglang_select completed" + transform=$(basename "transforms/code/proglang_select") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/proglang_select + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/proglang_select + fi else - echo "Skipping transforms/code/proglang_select kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/proglang_select kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-code-repo_level_ordering-kfp.yml b/.github/workflows/test-code-repo_level_ordering-kfp.yml index 3f11db5bb..a3813bc62 100644 --- a/.github/workflows/test-code-repo_level_ordering-kfp.yml +++ b/.github/workflows/test-code-repo_level_ordering-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/code/repo_level_ordering timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/repo_level_ordering/Makefile" -a -e "transforms/code/repo_level_ordering/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/repo_level_ordering workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/repo_level_ordering workflow-test - echo "Run transforms/code/repo_level_ordering completed" - else - echo "Skipping transforms/code/repo_level_ordering kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/code/repo_level_ordering") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/repo_level_ordering + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/repo_level_ordering + fi + else + echo "Skipping transforms/code/repo_level_ordering kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/code/repo_level_ordering timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/code/repo_level_ordering/Makefile" -a -e "transforms/code/repo_level_ordering/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/code/repo_level_ordering workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/code/repo_level_ordering workflow-test - echo "Run transforms/code/repo_level_ordering completed" + transform=$(basename "transforms/code/repo_level_ordering") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/code/repo_level_ordering + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/code/repo_level_ordering + fi else - echo "Skipping transforms/code/repo_level_ordering kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/code/repo_level_ordering kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-kfp-transform.template b/.github/workflows/test-kfp-transform.template index 29bba2634..f9ace4831 100644 --- a/.github/workflows/test-kfp-transform.template +++ b/.github/workflows/test-kfp-transform.template @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for @TARGET_TRANSFORM_DIR@ timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "@TARGET_TRANSFORM_DIR@/Makefile" -a -e "@TARGET_TRANSFORM_DIR@/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C @TARGET_TRANSFORM_DIR@ workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C @TARGET_TRANSFORM_DIR@ workflow-test - echo "Run @TARGET_TRANSFORM_DIR@ completed" - else - echo "Skipping @TARGET_TRANSFORM_DIR@ kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "@TARGET_TRANSFORM_DIR@") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow @TARGET_TRANSFORM_DIR@ + else + $PWD/scripts/workflow_helper.sh build-workflow @TARGET_TRANSFORM_DIR@ + fi + else + echo "Skipping @TARGET_TRANSFORM_DIR@ kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for @TARGET_TRANSFORM_DIR@ timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "@TARGET_TRANSFORM_DIR@/Makefile" -a -e "@TARGET_TRANSFORM_DIR@/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C @TARGET_TRANSFORM_DIR@ workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C @TARGET_TRANSFORM_DIR@ workflow-test - echo "Run @TARGET_TRANSFORM_DIR@ completed" + transform=$(basename "@TARGET_TRANSFORM_DIR@") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow @TARGET_TRANSFORM_DIR@ + else + $PWD/scripts/workflow_helper.sh build-workflow @TARGET_TRANSFORM_DIR@ + fi else - echo "Skipping @TARGET_TRANSFORM_DIR@ kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping @TARGET_TRANSFORM_DIR@ kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-doc_chunk-kfp.yml b/.github/workflows/test-language-doc_chunk-kfp.yml new file mode 100644 index 000000000..f689f1d1b --- /dev/null +++ b/.github/workflows/test-language-doc_chunk-kfp.yml @@ -0,0 +1,116 @@ +# +# DO NOT EDIT THIS FILE: it is generated from test-transform.template, Edit there and run make to change these files +# +name: Test KFP - transforms/language/doc_chunk + +on: + workflow_dispatch: + push: + branches: + - "dev" + - "releases/**" + tags: + - "*" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/doc_chunk/**" + - "!kfp/**" # This is tested in separate workflow + - "!data-processing-lib/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + pull_request: + branches: + - "dev" + - "releases/**" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/doc_chunk/**" + - "!data-processing-lib/**" # This is tested in separate workflow + - "!kfp/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + +# taken from https://stackoverflow.com/questions/66335225/how-to-cancel-previous-runs-in-the-pr-when-you-push-new-commitsupdate-the-curre +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-kfp-v1: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + - name: Test V1 KFP workflow for transforms/language/doc_chunk + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/doc_chunk/Makefile" -a -e "transforms/language/doc_chunk/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/doc_chunk") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/doc_chunk + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/doc_chunk + fi + else + echo "Skipping transforms/language/doc_chunk kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi + + test-kfp-v2: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV + - name: Test V2 KFP workflow for transforms/language/doc_chunk + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/doc_chunk/Makefile" -a -e "transforms/language/doc_chunk/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/doc_chunk") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/doc_chunk + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/doc_chunk + fi + else + echo "Skipping transforms/language/doc_chunk kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-doc_quality-kfp.yml b/.github/workflows/test-language-doc_quality-kfp.yml index 35811aecb..3bb709515 100644 --- a/.github/workflows/test-language-doc_quality-kfp.yml +++ b/.github/workflows/test-language-doc_quality-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/language/doc_quality timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/doc_quality/Makefile" -a -e "transforms/language/doc_quality/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/doc_quality workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/doc_quality workflow-test - echo "Run transforms/language/doc_quality completed" - else - echo "Skipping transforms/language/doc_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/language/doc_quality") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/doc_quality + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/doc_quality + fi + else + echo "Skipping transforms/language/doc_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/language/doc_quality timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/doc_quality/Makefile" -a -e "transforms/language/doc_quality/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/doc_quality workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/doc_quality workflow-test - echo "Run transforms/language/doc_quality completed" + transform=$(basename "transforms/language/doc_quality") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/doc_quality + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/doc_quality + fi else - echo "Skipping transforms/language/doc_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/language/doc_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-lang_id-kfp.yml b/.github/workflows/test-language-lang_id-kfp.yml index a6cd597b2..da2f3bee3 100644 --- a/.github/workflows/test-language-lang_id-kfp.yml +++ b/.github/workflows/test-language-lang_id-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/language/lang_id timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/lang_id/Makefile" -a -e "transforms/language/lang_id/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/lang_id workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/lang_id workflow-test - echo "Run transforms/language/lang_id completed" - else - echo "Skipping transforms/language/lang_id kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/language/lang_id") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/lang_id + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/lang_id + fi + else + echo "Skipping transforms/language/lang_id kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/language/lang_id timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/lang_id/Makefile" -a -e "transforms/language/lang_id/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/lang_id workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/lang_id workflow-test - echo "Run transforms/language/lang_id completed" + transform=$(basename "transforms/language/lang_id") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/lang_id + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/lang_id + fi else - echo "Skipping transforms/language/lang_id kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/language/lang_id kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-pdf2parquet-kfp.yml b/.github/workflows/test-language-pdf2parquet-kfp.yml new file mode 100644 index 000000000..45b99cdc6 --- /dev/null +++ b/.github/workflows/test-language-pdf2parquet-kfp.yml @@ -0,0 +1,116 @@ +# +# DO NOT EDIT THIS FILE: it is generated from test-transform.template, Edit there and run make to change these files +# +name: Test KFP - transforms/language/pdf2parquet + +on: + workflow_dispatch: + push: + branches: + - "dev" + - "releases/**" + tags: + - "*" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/pdf2parquet/**" + - "!kfp/**" # This is tested in separate workflow + - "!data-processing-lib/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + pull_request: + branches: + - "dev" + - "releases/**" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/pdf2parquet/**" + - "!data-processing-lib/**" # This is tested in separate workflow + - "!kfp/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + +# taken from https://stackoverflow.com/questions/66335225/how-to-cancel-previous-runs-in-the-pr-when-you-push-new-commitsupdate-the-curre +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-kfp-v1: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + - name: Test V1 KFP workflow for transforms/language/pdf2parquet + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/pdf2parquet/Makefile" -a -e "transforms/language/pdf2parquet/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/pdf2parquet") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/pdf2parquet + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/pdf2parquet + fi + else + echo "Skipping transforms/language/pdf2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi + + test-kfp-v2: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV + - name: Test V2 KFP workflow for transforms/language/pdf2parquet + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/pdf2parquet/Makefile" -a -e "transforms/language/pdf2parquet/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/pdf2parquet") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/pdf2parquet + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/pdf2parquet + fi + else + echo "Skipping transforms/language/pdf2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-pii_redactor-kfp.yml b/.github/workflows/test-language-pii_redactor-kfp.yml new file mode 100644 index 000000000..17c42ba2a --- /dev/null +++ b/.github/workflows/test-language-pii_redactor-kfp.yml @@ -0,0 +1,116 @@ +# +# DO NOT EDIT THIS FILE: it is generated from test-transform.template, Edit there and run make to change these files +# +name: Test KFP - transforms/language/pii_redactor + +on: + workflow_dispatch: + push: + branches: + - "dev" + - "releases/**" + tags: + - "*" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/pii_redactor/**" + - "!kfp/**" # This is tested in separate workflow + - "!data-processing-lib/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + pull_request: + branches: + - "dev" + - "releases/**" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/language/pii_redactor/**" + - "!data-processing-lib/**" # This is tested in separate workflow + - "!kfp/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + +# taken from https://stackoverflow.com/questions/66335225/how-to-cancel-previous-runs-in-the-pr-when-you-push-new-commitsupdate-the-curre +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-kfp-v1: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + - name: Test V1 KFP workflow for transforms/language/pii_redactor + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/pii_redactor/Makefile" -a -e "transforms/language/pii_redactor/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/pii_redactor") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/pii_redactor + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/pii_redactor + fi + else + echo "Skipping transforms/language/pii_redactor kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi + + test-kfp-v2: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV + - name: Test V2 KFP workflow for transforms/language/pii_redactor + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/language/pii_redactor/Makefile" -a -e "transforms/language/pii_redactor/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/language/pii_redactor") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/pii_redactor + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/pii_redactor + fi + else + echo "Skipping transforms/language/pii_redactor kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-language-text_encoder-kfp.yml b/.github/workflows/test-language-text_encoder-kfp.yml index 3f74c5861..4129ae677 100644 --- a/.github/workflows/test-language-text_encoder-kfp.yml +++ b/.github/workflows/test-language-text_encoder-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/language/text_encoder timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/text_encoder/Makefile" -a -e "transforms/language/text_encoder/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/text_encoder workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/text_encoder workflow-test - echo "Run transforms/language/text_encoder completed" - else - echo "Skipping transforms/language/text_encoder kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/language/text_encoder") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/text_encoder + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/text_encoder + fi + else + echo "Skipping transforms/language/text_encoder kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/language/text_encoder timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/language/text_encoder/Makefile" -a -e "transforms/language/text_encoder/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/language/text_encoder workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/language/text_encoder workflow-test - echo "Run transforms/language/text_encoder completed" + transform=$(basename "transforms/language/text_encoder") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/language/text_encoder + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/language/text_encoder + fi else - echo "Skipping transforms/language/text_encoder kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/language/text_encoder kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-doc_id-kfp.yml b/.github/workflows/test-universal-doc_id-kfp.yml index f40848f62..b9d4fa04f 100644 --- a/.github/workflows/test-universal-doc_id-kfp.yml +++ b/.github/workflows/test-universal-doc_id-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/doc_id timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/doc_id/Makefile" -a -e "transforms/universal/doc_id/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/doc_id workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/doc_id workflow-test - echo "Run transforms/universal/doc_id completed" - else - echo "Skipping transforms/universal/doc_id kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/doc_id") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/doc_id + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/doc_id + fi + else + echo "Skipping transforms/universal/doc_id kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/doc_id timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/doc_id/Makefile" -a -e "transforms/universal/doc_id/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/doc_id workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/doc_id workflow-test - echo "Run transforms/universal/doc_id completed" + transform=$(basename "transforms/universal/doc_id") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/doc_id + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/doc_id + fi else - echo "Skipping transforms/universal/doc_id kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/doc_id kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-ededup-kfp.yml b/.github/workflows/test-universal-ededup-kfp.yml index b3edeec7d..ef9c4e6d3 100644 --- a/.github/workflows/test-universal-ededup-kfp.yml +++ b/.github/workflows/test-universal-ededup-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/ededup timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/ededup/Makefile" -a -e "transforms/universal/ededup/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/ededup workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/ededup workflow-test - echo "Run transforms/universal/ededup completed" - else - echo "Skipping transforms/universal/ededup kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/ededup") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/ededup + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/ededup + fi + else + echo "Skipping transforms/universal/ededup kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/ededup timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/ededup/Makefile" -a -e "transforms/universal/ededup/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/ededup workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/ededup workflow-test - echo "Run transforms/universal/ededup completed" + transform=$(basename "transforms/universal/ededup") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/ededup + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/ededup + fi else - echo "Skipping transforms/universal/ededup kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/ededup kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-fdedup-kfp.yml b/.github/workflows/test-universal-fdedup-kfp.yml index 8d18552c3..fd37421e7 100644 --- a/.github/workflows/test-universal-fdedup-kfp.yml +++ b/.github/workflows/test-universal-fdedup-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/fdedup timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/fdedup/Makefile" -a -e "transforms/universal/fdedup/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/fdedup workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/fdedup workflow-test - echo "Run transforms/universal/fdedup completed" - else - echo "Skipping transforms/universal/fdedup kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/fdedup") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/fdedup + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/fdedup + fi + else + echo "Skipping transforms/universal/fdedup kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/fdedup timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/fdedup/Makefile" -a -e "transforms/universal/fdedup/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/fdedup workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/fdedup workflow-test - echo "Run transforms/universal/fdedup completed" + transform=$(basename "transforms/universal/fdedup") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/fdedup + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/fdedup + fi else - echo "Skipping transforms/universal/fdedup kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/fdedup kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-filter-kfp.yml b/.github/workflows/test-universal-filter-kfp.yml index fde070bad..eab5179d6 100644 --- a/.github/workflows/test-universal-filter-kfp.yml +++ b/.github/workflows/test-universal-filter-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/filter timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/filter/Makefile" -a -e "transforms/universal/filter/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/filter workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/filter workflow-test - echo "Run transforms/universal/filter completed" - else - echo "Skipping transforms/universal/filter kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/filter") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/filter + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/filter + fi + else + echo "Skipping transforms/universal/filter kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/filter timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/filter/Makefile" -a -e "transforms/universal/filter/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/filter workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/filter workflow-test - echo "Run transforms/universal/filter completed" + transform=$(basename "transforms/universal/filter") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/filter + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/filter + fi else - echo "Skipping transforms/universal/filter kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/filter kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-noop-kfp.yml b/.github/workflows/test-universal-noop-kfp.yml index 0aa16a984..3def4f09c 100644 --- a/.github/workflows/test-universal-noop-kfp.yml +++ b/.github/workflows/test-universal-noop-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/noop timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/noop/Makefile" -a -e "transforms/universal/noop/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/noop workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/noop workflow-test - echo "Run transforms/universal/noop completed" - else - echo "Skipping transforms/universal/noop kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/noop") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/noop + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/noop + fi + else + echo "Skipping transforms/universal/noop kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/noop timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/noop/Makefile" -a -e "transforms/universal/noop/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/noop workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/noop workflow-test - echo "Run transforms/universal/noop completed" + transform=$(basename "transforms/universal/noop") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/noop + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/noop + fi else - echo "Skipping transforms/universal/noop kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/noop kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-profiler-kfp.yml b/.github/workflows/test-universal-profiler-kfp.yml index 7f8451f52..a079d43ec 100644 --- a/.github/workflows/test-universal-profiler-kfp.yml +++ b/.github/workflows/test-universal-profiler-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/profiler timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/profiler/Makefile" -a -e "transforms/universal/profiler/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/profiler workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/profiler workflow-test - echo "Run transforms/universal/profiler completed" - else - echo "Skipping transforms/universal/profiler kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/profiler") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/profiler + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/profiler + fi + else + echo "Skipping transforms/universal/profiler kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/profiler timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/profiler/Makefile" -a -e "transforms/universal/profiler/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/profiler workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/profiler workflow-test - echo "Run transforms/universal/profiler completed" + transform=$(basename "transforms/universal/profiler") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/profiler + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/profiler + fi else - echo "Skipping transforms/universal/profiler kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/profiler kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-resize-kfp.yml b/.github/workflows/test-universal-resize-kfp.yml index 7cee28ff5..aa554372d 100644 --- a/.github/workflows/test-universal-resize-kfp.yml +++ b/.github/workflows/test-universal-resize-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/resize timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/resize/Makefile" -a -e "transforms/universal/resize/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/resize workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/resize workflow-test - echo "Run transforms/universal/resize completed" - else - echo "Skipping transforms/universal/resize kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/resize") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/resize + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/resize + fi + else + echo "Skipping transforms/universal/resize kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/resize timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/resize/Makefile" -a -e "transforms/universal/resize/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/resize workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/resize workflow-test - echo "Run transforms/universal/resize completed" + transform=$(basename "transforms/universal/resize") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/resize + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/resize + fi else - echo "Skipping transforms/universal/resize kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/resize kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/.github/workflows/test-universal-tokenization-kfp.yml b/.github/workflows/test-universal-tokenization-kfp.yml index 8d98b3be3..952043caf 100644 --- a/.github/workflows/test-universal-tokenization-kfp.yml +++ b/.github/workflows/test-universal-tokenization-kfp.yml @@ -56,33 +56,26 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test V1 KFP workflow for transforms/universal/tokenization timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/tokenization/Makefile" -a -e "transforms/universal/tokenization/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/tokenization workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/tokenization workflow-test - echo "Run transforms/universal/tokenization completed" - else - echo "Skipping transforms/universal/tokenization kfp test for lack of Makefile and/or kfp_ray/Makefile" + transform=$(basename "transforms/universal/tokenization") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/tokenization + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/tokenization + fi + else + echo "Skipping transforms/universal/tokenization kfp test for lack of Makefile and/or kfp_ray/Makefile" fi test-kfp-v2: @@ -99,32 +92,25 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test V2 KFP workflow for transforms/universal/tokenization timeout-minutes: 120 run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) if [ -e "transforms/universal/tokenization/Makefile" -a -e "transforms/universal/tokenization/kfp_ray/Makefile" ]; then - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup - #make -C kfp/kfp_support_lib test - make -C transforms/universal/tokenization workflow-build - source $K8S_SETUP_SCRIPTS/common.sh - make -C transforms/universal/tokenization workflow-test - echo "Run transforms/universal/tokenization completed" + transform=$(basename "transforms/universal/tokenization") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/tokenization + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/tokenization + fi else - echo "Skipping transforms/universal/tokenization kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi + echo "Skipping transforms/universal/tokenization kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/scripts/check-workflows.sh b/scripts/check-workflows.sh index afc73a886..d1f934368 100755 --- a/scripts/check-workflows.sh +++ b/scripts/check-workflows.sh @@ -17,7 +17,7 @@ if [ ! -d transforms ]; then echo Please run this script from the top of the repository exit 1 fi -KFP_BLACK_LIST="doc_chunk pdf2parquet pii_redactor" +KFP_BLACK_LIST="doc_chunk pdf2parquet pii_redactor text_encoder license_select repo_level_ordering" while [ $# -ne 0 ]; do case $1 in -show-kfp-black-list) echo $KFP_BLACK_LIST; exit 0; diff --git a/scripts/k8s-setup/populate_minio.sh b/scripts/k8s-setup/populate_minio.sh index a43d7dd58..3b22b37e7 100755 --- a/scripts/k8s-setup/populate_minio.sh +++ b/scripts/k8s-setup/populate_minio.sh @@ -26,6 +26,7 @@ mc cp --recursive ${REPOROOT}/transforms/code/proglang_select/ray/test-data/inpu mc cp --recursive ${REPOROOT}/transforms/code/proglang_select/ray/test-data/languages/ kfp/test/proglang_select/languages mc cp --recursive ${REPOROOT}/transforms/code/malware/ray/test-data/input/ kfp/test/malware/input mc cp --recursive ${REPOROOT}/transforms/code/header_cleanser/ray/test-data/input/ kfp/test/header_cleanser/input +mc cp --recursive ${REPOROOT}/transforms/code/repo_level_ordering/ray/test-data/input/ kfp/test/repo_level_ordering/input # language mc cp --recursive ${REPOROOT}/transforms/language/lang_id/ray/test-data/input/ kfp/test/lang_id/input mc cp --recursive ${REPOROOT}/transforms/language/doc_quality/ray/test-data/input/ kfp/test/doc_quality/input diff --git a/scripts/workflow_helper.sh b/scripts/workflow_helper.sh new file mode 100755 index 000000000..5f89df770 --- /dev/null +++ b/scripts/workflow_helper.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +op=$1 + +install_tools(){ + if [ -z "$KIND_VERSION" ] || [ -z "$HELM_VERSION" ] || [ -z "$KUBECTL_VERSION" ]; then + echo "Missing tools versions" + exit 1 + fi + echo "Installing tools" + + curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 + chmod 777 /tmp/kind + curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 + chmod 700 /tmp/get_helm.sh + HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo + chmod 777 /tmp/helm + curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl + chmod 777 /tmp/kubectl + curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc + chmod +x /tmp/mc +} + +test_workflow(){ + local workflow="$1" + + echo "Testing $workflow" + DEPLOY_KUBEFLOW=1 make -C $workflow setup + make -C $workflow workflow-test + echo "Run workflow completed" +} + +build_workflow(){ + local workflow="$1" + + echo "Build $workflow" + make -C $workflow workflow-build + echo "Build workflow completed" +} + + +usage(){ + echo "command not found" +} + +case "$op" in + install-tools) + echo "install tool required for testing workflow" + install_tools + ;; + test-workflow) + echo "test workflow" + test_workflow $2 + ;; + build-workflow) + echo "build workflow" + build_workflow $2 + ;; + *) + usage + ;; +esac diff --git a/transforms/.make.workflows b/transforms/.make.workflows index 9b7259b60..cd4c2532a 100644 --- a/transforms/.make.workflows +++ b/transforms/.make.workflows @@ -69,4 +69,3 @@ ${WORKFLOW_VENV_ACTIVATE}: ${REPOROOT}/.make.versions ${REPOROOT}/kfp/kfp_ray_co cd ${REPOROOT} && make setup; \ fi . ${WORKFLOW_VENV_ACTIVATE} && ${PYTHON} -m workflow_support.pipeline_utils.pipelines_tests_utils -c "upload" -p ${CURDIR}/${PIPELINE_FILE} -e ${KFP_ENDPOINT} - diff --git a/transforms/code/license_select/kfp_ray/Makefile.disable-cicd b/transforms/code/license_select/kfp_ray/Makefile similarity index 100% rename from transforms/code/license_select/kfp_ray/Makefile.disable-cicd rename to transforms/code/license_select/kfp_ray/Makefile diff --git a/transforms/code/repo_level_ordering/kfp_ray/Makefile.disable-cicd b/transforms/code/repo_level_ordering/kfp_ray/Makefile similarity index 96% rename from transforms/code/repo_level_ordering/kfp_ray/Makefile.disable-cicd rename to transforms/code/repo_level_ordering/kfp_ray/Makefile index 5b2425357..5504137ee 100644 --- a/transforms/code/repo_level_ordering/kfp_ray/Makefile.disable-cicd +++ b/transforms/code/repo_level_ordering/kfp_ray/Makefile @@ -45,7 +45,7 @@ workflow-build: workflow-venv .PHONY: workflow-test workflow-test: workflow-build - $(MAKE) .workflows.test-pipeline TRANSFORM_SRC=${SRC_DIR} PIPELINE_FILE=repo_level_order_transform.py + $(MAKE) .workflows.test-pipeline TRANSFORM_SRC=${SRC_DIR} PIPELINE_FILE=repo_level_order_wf.yaml .PHONY: workflow-upload workflow-upload: workflow-build diff --git a/transforms/code/repo_level_ordering/kfp_ray/repo_level_order_wf.py b/transforms/code/repo_level_ordering/kfp_ray/repo_level_order_wf.py index 7ec37bd9d..6c14abfd6 100644 --- a/transforms/code/repo_level_ordering/kfp_ray/repo_level_order_wf.py +++ b/transforms/code/repo_level_ordering/kfp_ray/repo_level_order_wf.py @@ -24,7 +24,7 @@ EXEC_SCRIPT_NAME: str = "repo_level_order_transform_ray.py" # components -base_kfp_image = "quay.io/dataprep1/data-prep-kit/kfp-data-processing_v2:latest" +base_kfp_image = "quay.io/dataprep1/data-prep-kit/kfp-data-processing:latest" # path to kfp component specifications files component_spec_path = "../../../../kfp/kfp_ray_components/" diff --git a/transforms/language/text_encoder/kfp_ray/Makefile.disable-cicd b/transforms/language/text_encoder/kfp_ray/Makefile similarity index 100% rename from transforms/language/text_encoder/kfp_ray/Makefile.disable-cicd rename to transforms/language/text_encoder/kfp_ray/Makefile From dd04e6dc74ad29ecc9cdeee0e5dadb08703774ad Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 10 Oct 2024 15:14:06 -0400 Subject: [PATCH 33/70] add MultiLock class Signed-off-by: David Wood --- .../src/data_processing/utils/multilock.py | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 data-processing-lib/python/src/data_processing/utils/multilock.py diff --git a/data-processing-lib/python/src/data_processing/utils/multilock.py b/data-processing-lib/python/src/data_processing/utils/multilock.py new file mode 100644 index 000000000..b6f7d942a --- /dev/null +++ b/data-processing-lib/python/src/data_processing/utils/multilock.py @@ -0,0 +1,146 @@ +import abc +import datetime +import fcntl +import os +import tempfile +import threading +import time + + +_tempdir = tempfile.gettempdir() + + +class MultiLock(abc.ABC): + """ + Provides a process- and thread-locked lock. + To use + lock = MultiLock("mylock") + ... + lock.acquire(block=false, timeout=30) + # do something critical + ... + lock.release() + """ + + def __init__(self, name): + """ + Create the lock with the given name. + + :param name: the global name associated with this lock. All processes using the same + name will be part of the same locking cohort. It is up to the caller to define + and coordinate lock names. + """ + if name is None or len(name) == 0: + raise ValueError("lock name must not be None or the empty string") + self.lock_filename = os.path.join(_tempdir, name + ".multilock") + # print(f"lock file name is {self.lock_filename}") + self.fd = None + self.thread_lock = threading.Lock() + + def acquire(self, block=True, timeout=None): + """ + With the block argument set to True (the default), the method call will block until the + lock is in an unlocked state, then set it to locked and return True. + + With the block argument set to False, the method call does not block. If the lock + is currently in a locked state, return False; otherwise set the lock to a locked state and return True. + + When invoked with a positive, floating-point value for timeout, wait for at most the number + of seconds specified by timeout as long as the lock can not be acquired. Invocations with a + negative value for timeout are equivalent to a timeout of zero. Invocations with a timeout + value of None (the default) set the timeout period to infinite. The timeout argument has no practical + implications if the block argument is set to False and is thus ignored. + + Returns True if the lock has been acquired or False if the timeout period has elapsed. + + """ + if self.fd is not None: # Already locked. + return True + + start = time.time() + if block: + locked = self.thread_lock.acquire(block, timeout) + else: + locked = self.thread_lock.acquire(block) + if not locked: + return False + end = time.time() + if not block and timeout > 0: + timeout -= end - start + if timeout <= 0: + self.thread_lock.release() + return False + + # open a file and create a file descriptor + self.fd = os.open(self.lock_filename, os.O_RDWR | os.O_CREAT) + + msg = f"MultiLock last held by process with pid={os.getpid()}\n" + os.write(self.fd, str.encode(msg)) + + # put a lock on an open file + locked = False + waited = 0 + sleep_seconds = 1 + if timeout is not None: + timeout = max(0, timeout) + while not locked and (timeout is None or waited <= timeout): + try: + fcntl.lockf(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + locked = True + except Exception as exc: + # Only get here if lock could not be acquired + # print(f"sleeping {exc=}") + time.sleep(sleep_seconds) + if not block: + break + waited += sleep_seconds + if not locked: + # If we didn't get the lock, then release the file. + os.close(self.fd) + self.fd = None + + self.thread_lock.release() + return locked + + def release(self): + """ + Release an acquired lock. Do nothing if the lock is not acquired. + :return: + """ + if self.fd is not None: + self.thread_lock.acquire() + os.close(self.fd) + self.fd = None + self.thread_lock.release() + + def is_locked(self): + return self.fd is not None + + +def main(block, timeout, sleep): + lock = MultiLock("foo") + if block: + print(f"going to acquire the blocking lock with timeout={timeout}") + else: + print(f"going to acquire the non-blocking lock with timemout={timeout}") + locked = lock.acquire(block=block, timeout=timeout) + start = datetime.datetime.now() + start = start.strftime("%Y-%m-%d %H:%M:%S") + if not locked: + print(f"Could not get lock at {start}") + return + print(f"{start}: I got the lock") + time.sleep(sleep) + lock.release() + end = datetime.datetime.now() + end = end.strftime("%Y-%m-%d %H:%M:%S") + print(f"{end}: lock released") + print(f"lock held from {start} to {end}") + + +if __name__ == "__main__": + sleep = 10 + timeout = 10 + main(True, timeout, sleep) + time.sleep(1) + main(False, timeout, sleep) From a19feec364d8c5cdff856540e205339e5002e5e1 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 10 Oct 2024 16:57:37 -0400 Subject: [PATCH 34/70] fix test-kfp.yml to only make workflow-build on the randomly selected transform Signed-off-by: David Wood --- .github/workflows/test-kfp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-kfp.yml b/.github/workflows/test-kfp.yml index 01deebcfa..89061199c 100644 --- a/.github/workflows/test-kfp.yml +++ b/.github/workflows/test-kfp.yml @@ -106,7 +106,6 @@ jobs: export DEPLOY_KUBEFLOW=1 make -C $K8S_SETUP_SCRIPTS setup make -C kfp/kfp_support_lib test - make -C transforms workflow-build source $K8S_SETUP_SCRIPTS/common.sh while : do @@ -118,6 +117,7 @@ jobs: break fi done + make -C ${transforms[$index]} workflow-build make -C ${transforms[$index]} workflow-test echo "Run ${transforms[$index]} completed" @@ -156,7 +156,6 @@ jobs: export KFPv2=1 make -C $K8S_SETUP_SCRIPTS setup make -C kfp/kfp_support_lib test - make -C transforms workflow-build source $K8S_SETUP_SCRIPTS/common.sh while : do @@ -168,6 +167,7 @@ jobs: break fi done + make -C ${transforms[$index]} workflow-build make -C ${transforms[$index]} workflow-test header_text "Run ${transforms[$index]} completed" build-kfp-components: From 70513f40b96eeaaa01664e1f5eb4c31fb6ab3697 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Thu, 10 Oct 2024 17:32:45 -0400 Subject: [PATCH 35/70] fix broken kfp-1 and kfp-2 testing Signed-off-by: Maroun Touma --- .github/workflows/test-kfp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-kfp.yml b/.github/workflows/test-kfp.yml index 01deebcfa..89061199c 100644 --- a/.github/workflows/test-kfp.yml +++ b/.github/workflows/test-kfp.yml @@ -106,7 +106,6 @@ jobs: export DEPLOY_KUBEFLOW=1 make -C $K8S_SETUP_SCRIPTS setup make -C kfp/kfp_support_lib test - make -C transforms workflow-build source $K8S_SETUP_SCRIPTS/common.sh while : do @@ -118,6 +117,7 @@ jobs: break fi done + make -C ${transforms[$index]} workflow-build make -C ${transforms[$index]} workflow-test echo "Run ${transforms[$index]} completed" @@ -156,7 +156,6 @@ jobs: export KFPv2=1 make -C $K8S_SETUP_SCRIPTS setup make -C kfp/kfp_support_lib test - make -C transforms workflow-build source $K8S_SETUP_SCRIPTS/common.sh while : do @@ -168,6 +167,7 @@ jobs: break fi done + make -C ${transforms[$index]} workflow-build make -C ${transforms[$index]} workflow-test header_text "Run ${transforms[$index]} completed" build-kfp-components: From a81dac033f56d55926bda3786a5a594b7f633f56 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Thu, 10 Oct 2024 19:01:07 -0400 Subject: [PATCH 36/70] We keep running into this.. Need a good solution Signed-off-by: Maroun Touma --- transforms/language/text_encoder/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transforms/language/text_encoder/Makefile b/transforms/language/text_encoder/Makefile index bca6f7e85..29357c272 100644 --- a/transforms/language/text_encoder/Makefile +++ b/transforms/language/text_encoder/Makefile @@ -55,25 +55,25 @@ docker-save-image:: .PHONY: workflow-venv workflow-venv: - if [ -e kfp_ray ]; then \ + if [ -e kfp_ray ] && [ -f kfp_ray/Makefile ]; then \ $(MAKE) -C kfp_ray workflow-venv; \ fi .PHONY: workflow-test workflow-test: - if [ -e kfp_ray ]; then \ + if [ -e kfp_ray ] && [ -f kfp_ray/Makefile ]; then \ $(MAKE) -C kfp_ray workflow-test; \ fi .PHONY: workflow-upload workflow-upload: - if [ -e kfp_ray ]; then \ + if [ -e kfp_ray ] && [ -f kfp_ray/Makefile ]; then \ $(MAKE) -C kfp_ray workflow-upload; \ fi .PHONY: workflow-build workflow-build: - if [ -e kfp_ray ]; then \ + if [ -e kfp_ray ] && [ -f kfp_ray/Makefile ]; then \ $(MAKE) -C kfp_ray workflow-build; \ fi From a4a54ddf0e7eb0b87ab1ca8f0a504f97243a4ed2 Mon Sep 17 00:00:00 2001 From: ian-cho <42691703+ian-cho@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:11:29 +0900 Subject: [PATCH 37/70] Update transforms/universal/hap/ray/Makefile Co-authored-by: touma-I --- transforms/universal/hap/ray/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/universal/hap/ray/Makefile b/transforms/universal/hap/ray/Makefile index 942898ee2..af5d50348 100644 --- a/transforms/universal/hap/ray/Makefile +++ b/transforms/universal/hap/ray/Makefile @@ -37,7 +37,7 @@ setup:: .transforms.setup # distribution versions is the same as image version. set-versions: - $(MAKE) TRANSFORM_PYTHON_VERSION=$(HAP_PYTHON_VERSION) TOML_VERSION=$(HAP_RAY_VERSION) .transforms.set-versions + $(MAKE) TRANSFORM_PYTHON_VERSION=$(HAP_PYTHON_VERSION) TOML_VERSION=$(HAP_PYTHON_VERSION) .transforms.set-versions build-dist:: set-versions .defaults.build-dist From 2bb4cca298895e5c6505d8e1a71e49ee352bc7b4 Mon Sep 17 00:00:00 2001 From: Parameswaran Selvam Date: Thu, 10 Oct 2024 11:51:58 -0700 Subject: [PATCH 38/70] code license failed to apply module cli params Signed-off-by: Parameswaran Selvam --- .../python/src/license_select_transform.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/transforms/code/license_select/python/src/license_select_transform.py b/transforms/code/license_select/python/src/license_select_transform.py index dc4ad2b72..a43d399a3 100644 --- a/transforms/code/license_select/python/src/license_select_transform.py +++ b/transforms/code/license_select/python/src/license_select_transform.py @@ -48,7 +48,6 @@ LICENSE_COLUMN_DEFAULT = "license" LICENSES_KEY = "licenses" - def _get_supported_licenses(license_file: str, data_access: DataAccess) -> list[str]: logger.info(f"Getting supported licenses from file {license_file}") licenses_list = None @@ -120,7 +119,6 @@ def transform(self, table: pa.Table, file_name: str = None) -> tuple[list[pa.Tab new_table = self.transformer.transform(table) return [new_table], {} - class LicenseSelectTransformConfiguration(TransformConfiguration): def __init__(self): super().__init__(name="license_select", transform_class=LicenseSelectTransform) @@ -159,18 +157,25 @@ def add_input_params(self, parser: ArgumentParser) -> None: self.daf.add_input_params(parser) def apply_input_params(self, args: Namespace) -> bool: + if not self.daf.apply_input_params(args): + return False + captured = CLIArgumentProvider.capture_parameters(args, CLI_PREFIX, False) - data_access = self.daf.create_data_access() - deny = captured.get(DENY_LICENSES_KEY, False) - license_list_file = captured.get(LICENSES_FILE_KEY) + license_column_name = captured.get(LICENSE_COLUMN_NAME_KEY) + allow_licenses = captured.get(ALLOW_NO_LICENSE_KEY) + deny_licenses = captured.get(DENY_LICENSES_KEY, False) + licenses_file = captured.get(LICENSES_FILE_KEY) + # Read licenses from allow-list or deny-list - licenses = _get_supported_licenses(license_list_file, data_access) + data_access = self.daf.create_data_access() + licenses = _get_supported_licenses(licenses_file, data_access) + self.params = { LICENSE_SELECT_PARAMS: { - LICENSE_COLUMN_NAME_KEY: captured.get(LICENSE_COLUMN_NAME_KEY), - ALLOW_NO_LICENSE_KEY: captured.get(ALLOW_NO_LICENSE_KEY), + LICENSE_COLUMN_NAME_KEY: license_column_name, + ALLOW_NO_LICENSE_KEY: allow_licenses, + DENY_LICENSES_KEY: deny_licenses, LICENSES_KEY: licenses, - DENY_LICENSES_KEY: deny, } } return True From 2869eeaa5f7f545db6b9a791fc17104f46e4321b Mon Sep 17 00:00:00 2001 From: "Constantin M. Adam" Date: Fri, 11 Oct 2024 09:00:30 -0400 Subject: [PATCH 39/70] Fuzzy dedup modifications (#687) * Driver broadcast of large spark config variables Signed-off-by: Constantin M Adam * Launch k8s spark cluster using spark native k8s API Signed-off-by: Constantin M Adam * Add PyYAML to the base image dependencies Signed-off-by: Constantin M Adam * Updated documentation for get_bcast_params() method Signed-off-by: Constantin M Adam * Updated documentation Signed-off-by: Constantin M Adam --------- Signed-off-by: Constantin M Adam --- data-processing-lib/doc/spark-runtime.md | 8 ++- data-processing-lib/spark/pyproject.toml | 3 +- .../runtime/spark/runtime_configuration.py | 13 ++++ .../runtime/spark/transform_orchestrator.py | 67 +++++++++++++++++-- .../runtime/spark/transform_runtime.py | 20 ++++-- 5 files changed, 95 insertions(+), 16 deletions(-) diff --git a/data-processing-lib/doc/spark-runtime.md b/data-processing-lib/doc/spark-runtime.md index d30ed11b8..62fc0fe9e 100644 --- a/data-processing-lib/doc/spark-runtime.md +++ b/data-processing-lib/doc/spark-runtime.md @@ -41,9 +41,11 @@ of this parameter: ## Transforms -* [SparkTransformRuntimeConfiguration](../spark/src/data_processing_spark/transform/runtime_configuration.py) allows - to configure transform to use PySpark - +* [SparkTransformRuntimeConfiguration](../spark/src/data_processing_spark/runtime/spark/runtime_configuration.py) + allows to configure transform to use PySpark. In addition to its base class + [TransformRuntimeConfiguration](../python//src/data_processing/runtime/runtime_configuration.py) features, + this class includes `get_bcast_params()` method to get very large configuration settings. Before starting the + transform execution, the Spark runtime will broadcast these settings to all the workers. ## Runtime diff --git a/data-processing-lib/spark/pyproject.toml b/data-processing-lib/spark/pyproject.toml index e8d0c8285..bc1cb4f67 100644 --- a/data-processing-lib/spark/pyproject.toml +++ b/data-processing-lib/spark/pyproject.toml @@ -13,7 +13,8 @@ authors = [ dependencies = [ "data-prep-toolkit==0.2.2.dev0", "pyspark>=3.5.2", - "psutil>=6.0.0" + "psutil>=6.0.0", + "PyYAML>=6.0.2" ] [project_urls] diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/runtime_configuration.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/runtime_configuration.py index e0804e1e3..0f788396e 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/runtime_configuration.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/runtime_configuration.py @@ -10,6 +10,9 @@ # limitations under the License. ################################################################################ +from typing import Any + +from data_processing.data_access import DataAccessFactoryBase from data_processing.runtime import TransformRuntimeConfiguration from data_processing.transform import TransformConfiguration from data_processing_spark.runtime.spark import DefaultSparkTransformRuntime @@ -29,6 +32,16 @@ def __init__( super().__init__(transform_config=transform_config) self.runtime_class = runtime_class + def get_bcast_params(self, data_access_factory: DataAccessFactoryBase) -> dict[str, Any]: + """Allows retrieving and broadcasting to all the workers very large + configuration parameters, like the list of document IDs to remove for + fuzzy dedup, or the list of blocked web domains for block listing. This + function is called by the spark runtime after spark initialization, and + before spark_context.parallelize() + :param data_access_factory - creates data_access object to download the large config parameter + """ + return {} + def create_transform_runtime(self) -> DefaultSparkTransformRuntime: """ Create transform runtime with the parameters captured during apply_input_params() diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py index 57a6c58fc..c279f2b73 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py @@ -10,24 +10,69 @@ # limitations under the License. ################################################################################ +import os +import socket import time import traceback from datetime import datetime +import yaml from data_processing.data_access import DataAccessFactoryBase from data_processing.transform import TransformStatistics from data_processing.utils import GB, get_logger from data_processing_spark.runtime.spark import ( + SparkTransformExecutionConfiguration, SparkTransformFileProcessor, SparkTransformRuntimeConfiguration, - SparkTransformExecutionConfiguration, ) from pyspark import SparkConf, SparkContext +from pyspark.sql import SparkSession logger = get_logger(__name__) +def _init_spark(runtime_config: SparkTransformRuntimeConfiguration) -> SparkSession: + server_port_https = int(os.getenv("KUBERNETES_SERVICE_PORT_HTTPS", "-1")) + if server_port_https == -1: + # running locally + spark_config = {"spark.driver.host": "127.0.0.1"} + return SparkSession.builder.appName(runtime_config.get_name()).config(map=spark_config).getOrCreate() + else: + # running in Kubernetes, use spark_profile.yml and + # environment variables for configuration + server_port = os.environ["KUBERNETES_SERVICE_PORT"] + master_url = f"k8s://https://kubernetes.default:{server_port}" + + # Read Spark configuration profile + config_filepath = os.path.abspath( + os.path.join(os.getenv("SPARK_HOME"), "work-dir", "config", "spark_profile.yml") + ) + with open(config_filepath, "r") as config_fp: + spark_config = yaml.safe_load(os.path.expandvars(config_fp.read())) + spark_config["spark.submit.deployMode"] = "client" + + # configure the executor pods from template + executor_pod_template_file = os.path.join( + os.getenv("SPARK_HOME"), + "work-dir", + "src", + "templates", + "spark-executor-pod-template.yml", + ) + spark_config["spark.kubernetes.executor.podTemplateFile"] = executor_pod_template_file + spark_config["spark.kubernetes.container.image.pullPolicy"] = "Always" + + # Pass the driver IP address to the workers for callback + myservice_url = socket.gethostbyname(socket.gethostname()) + spark_config["spark.driver.host"] = myservice_url + spark_config["spark.driver.bindAddress"] = "0.0.0.0" + spark_config["spark.decommission.enabled"] = True + logger.info(f"Launching Spark Session with configuration\n" f"{yaml.dump(spark_config, indent=2)}") + app_name = spark_config.get("spark.app.name", "my-spark-app") + return SparkSession.builder.master(master_url).appName(app_name).config(map=spark_config).getOrCreate() + + def orchestrate( runtime_config: SparkTransformRuntimeConfiguration, execution_configuration: SparkTransformExecutionConfiguration, @@ -45,14 +90,17 @@ def orchestrate( logger.info(f"orchestrator started at {start_ts}") # create data access data_access = data_access_factory.create_data_access() + bcast_params = runtime_config.get_bcast_params(data_access_factory) if data_access is None: logger.error("No DataAccess instance provided - exiting") return 1 # initialize Spark - conf = SparkConf().setAppName(runtime_config.get_name()).set("spark.driver.host", "127.0.0.1") - sc = SparkContext(conf=conf) + spark_session = _init_spark(runtime_config) + sc = spark_session.sparkContext + # broadcast spark_runtime_config = sc.broadcast(runtime_config) daf = sc.broadcast(data_access_factory) + spark_bcast_params = sc.broadcast(bcast_params) def process_partition(iterator): """ @@ -63,6 +111,7 @@ def process_partition(iterator): # local statistics dictionary statistics = TransformStatistics() # create transformer runtime + bcast_params = spark_bcast_params.value d_access_factory = daf.value runtime_conf = spark_runtime_config.value runtime = runtime_conf.create_transform_runtime() @@ -77,8 +126,11 @@ def process_partition(iterator): logger.debug(f"partition {f}") # add additional parameters transform_params = ( - runtime.get_transform_config(partition=int(f[1]), data_access_factory=d_access_factory, - statistics=statistics)) + runtime.get_transform_config( + partition=int(f[1]), data_access_factory=d_access_factory, statistics=statistics + ) + | bcast_params + ) # create transform with partition number file_processor.create_transform(transform_params) first = False @@ -128,7 +180,7 @@ def process_partition(iterator): memory = 0.0 for i in range(executors.size()): memory += executors.toList().apply(i)._2()._1() - resources = {"cpus": cpus, "gpus": 0, "memory": round(memory/GB, 2), "object_store": 0} + resources = {"cpus": cpus, "gpus": 0, "memory": round(memory / GB, 2), "object_store": 0} input_params = runtime_config.get_transform_metadata() | execution_configuration.get_input_params() metadata = { "pipeline": execution_configuration.pipeline_id, @@ -143,7 +195,8 @@ def process_partition(iterator): "execution_stats": { "num partitions": num_partitions, "execution time, min": round((time.time() - start_time) / 60, 3), - } | resources, + } + | resources, "job_output_stats": stats, } logger.debug(f"Saving job metadata: {metadata}.") diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py index f16b09520..7b968b1e9 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py @@ -34,19 +34,29 @@ def get_transform_config( """ Get the dictionary of configuration that will be provided to the transform's initializer. This is the opportunity for this runtime to create a new set of configuration based on the - config/params provided to this instance's initializer. This may include the addition - of new configuration data such as ray shared memory, new actors, etc, that might be needed and - expected by the transform in its initializer and/or transform() methods. + config/params provided to this instance's initializer. + :param partition - the partition assigned to this worker, needed by transforms like doc_id :param data_access_factory - data access factory class being used by the RayOrchestrator. :param statistics - reference to statistics actor :return: dictionary of transform init params """ return self.params + def get_bcast_params(self, data_access_factory: DataAccessFactoryBase) -> dict[str, Any]: + """Allows retrieving and broadcasting to all the workers very large + configuration parameters, like the list of document IDs to remove for + fuzzy dedup, or the list of blocked web domains for block listing. This + function is called by the spark runtime after spark initialization, and + before spark_context.parallelize() + :param data_access_factory - creates data_access object to download the large config parameter + """ + return {} + def compute_execution_stats(self, stats: TransformStatistics) -> None: """ Update/augment the given statistics object with runtime-specific additions/modifications. + This method does not return a value; the job execution statistics are generally reported + as metadata by the Spark Orchestrator. :param stats: output of statistics as aggregated across all calls to all transforms. - :return: job execution statistics. These are generally reported as metadata by the Ray Orchestrator. """ - pass \ No newline at end of file + pass From e2a6d282f6a9ba721df21d333c45dd424cb44fd7 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Fri, 11 Oct 2024 08:15:13 -0500 Subject: [PATCH 40/70] Must set run_locally to true for it to pass the test in cicd workflow Signed-off-by: Maroun Touma --- transforms/universal/hap/ray/src/hap_local_ray.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transforms/universal/hap/ray/src/hap_local_ray.py b/transforms/universal/hap/ray/src/hap_local_ray.py index d3201e42f..e2f4d6e81 100644 --- a/transforms/universal/hap/ray/src/hap_local_ray.py +++ b/transforms/universal/hap/ray/src/hap_local_ray.py @@ -30,6 +30,9 @@ code_location = {"github": "github", "commit_hash": "12345", "path": "path"} params = { + # where to run + "run_locally": True, + "data_local_config": ParamsUtils.convert_to_ast(local_conf), "runtime_pipeline_id": "pipeline_id", "runtime_job_id": "job_id", From 33bae0e87c0d8076cdca551b09e81208e2953008 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Fri, 11 Oct 2024 11:19:57 -0400 Subject: [PATCH 41/70] skip workflow testing if in blacklist Signed-off-by: Maroun Touma --- transforms/code/repo_level_ordering/Makefile | 33 ++++++++++++++----- .../code/repo_level_ordering/transform.config | 2 +- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/transforms/code/repo_level_ordering/Makefile b/transforms/code/repo_level_ordering/Makefile index 04b1cc451..cfcd22d8a 100644 --- a/transforms/code/repo_level_ordering/Makefile +++ b/transforms/code/repo_level_ordering/Makefile @@ -1,6 +1,7 @@ REPOROOT=../../.. # Use make help, to see the available rules include $(REPOROOT)/.make.defaults +include transform.config setup:: @# Help: Recursively make $@ all subdirs @@ -47,25 +48,41 @@ load-image:: .PHONY: workflow-venv workflow-venv: - if [ -e kfp_ray ]; then \ - $(MAKE) -C kfp_ray workflow-venv; \ + @is_blacklisted=$$(cd $(REPOROOT); bash scripts/check-workflows.sh -show-kfp-black-list | grep $(TRANSFORM_NAME)); \ + if [ -z "$$is_blacklisted" ]; \ + then \ + echo $(MAKE) -C kfp_ray $@ ; \ + else \ + echo "Skipping KFP workflow: Transform is blacklisted " ; \ fi .PHONY: workflow-test workflow-test: - if [ -e kfp_ray ]; then \ - $(MAKE) -C kfp_ray workflow-test; \ + @is_blacklisted=$$(cd $(REPOROOT); bash scripts/check-workflows.sh -show-kfp-black-list | grep $(TRANSFORM_NAME)); \ + if [ -z "$$is_blacklisted" ]; \ + then \ + echo $(MAKE) -C kfp_ray $@ ; \ + else \ + echo "Skipping KFP workflow: Transform is blacklisted " ; \ fi .PHONY: workflow-upload workflow-upload: - if [ -e kfp_ray ]; then \ - $(MAKE) -C kfp_ray workflow-upload; \ + @is_blacklisted=$$(cd $(REPOROOT); bash scripts/check-workflows.sh -show-kfp-black-list | grep $(TRANSFORM_NAME)); \ + if [ -z "$$is_blacklisted" ]; \ + then \ + echo $(MAKE) -C kfp_ray $@ ; \ + else \ + echo "Skipping KFP workflow: Transform is blacklisted " ; \ fi .PHONY: workflow-build workflow-build: - if [ -e kfp_ray ]; then \ - $(MAKE) -C kfp_ray workflow-build; \ + is_blacklisted=$$(cd $(REPOROOT); bash scripts/check-workflows.sh -show-kfp-black-list | grep $(TRANSFORM_NAME)); \ + if [ -z "$$is_blacklisted" ]; \ + then \ + echo $(MAKE) -C kfp_ray $@ ; \ + else \ + echo "Skipping KFP workflow: Transform is blacklisted " ; \ fi diff --git a/transforms/code/repo_level_ordering/transform.config b/transforms/code/repo_level_ordering/transform.config index 0d82c6377..99df6130d 100644 --- a/transforms/code/repo_level_ordering/transform.config +++ b/transforms/code/repo_level_ordering/transform.config @@ -5,7 +5,7 @@ ################################################################################ # This defines the name of the transform and is used to match against # expected files and is used to define the transform's image name. -TRANSFORM_NAME=repo_level_order +TRANSFORM_NAME=repo_level_ordering ################################################################################ # This defines the transforms' version number as would be used From e6192a09479cce0320b2342cddb34b4bf59be4ad Mon Sep 17 00:00:00 2001 From: SHAHROKH DAIJAVAD Date: Fri, 11 Oct 2024 10:12:05 -0700 Subject: [PATCH 42/70] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b537b7a49..255437482 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ The matrix below shows the the combination of modules and supported runtimes. Al | [Filter on annotations](transforms/universal/filter/python/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [Profiler](transforms/universal/profiler/ray/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [Resize](transforms/universal/resize/python/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [HAP](transforms/universal/hap/python/README.md) | :white_check_mark: | | | | +| [HAP](transforms/universal/hap/python/README.md) | :white_check_mark: | :white_check_mark: | | | | [Tokenizer](transforms/universal/tokenization/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | **Language-only** | | | | | | [Language identification](transforms/language/lang_id/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | From c76baf5debdc1306ab3732f4a0b408b32a312cb8 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Fri, 11 Oct 2024 16:35:51 -0400 Subject: [PATCH 43/70] adjust revision for ray requirements Signed-off-by: Maroun Touma --- transforms/universal/hap/ray/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transforms/universal/hap/ray/requirements.txt b/transforms/universal/hap/ray/requirements.txt index 36c2b81af..6b7f46c5f 100644 --- a/transforms/universal/hap/ray/requirements.txt +++ b/transforms/universal/hap/ray/requirements.txt @@ -1,5 +1,5 @@ -data-prep-toolkit-ray==0.2.2.dev0 -dpk-hap-transform-python==0.2.2.dev0 +data-prep-toolkit-ray==0.2.2.dev1 +dpk-hap-transform-python==0.2.2.dev1 nltk==3.9.1 transformers==4.38.2 torch==2.4.1 From 842ee84d2ce54fe49d6f56232996501efae90f14 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Fri, 11 Oct 2024 16:36:52 -0400 Subject: [PATCH 44/70] adjust revision tag for ray transform Signed-off-by: Maroun Touma --- transforms/universal/hap/ray/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/universal/hap/ray/pyproject.toml b/transforms/universal/hap/ray/pyproject.toml index ff3fc05f0..412df9413 100644 --- a/transforms/universal/hap/ray/pyproject.toml +++ b/transforms/universal/hap/ray/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dpk_hap_transform_ray" -version = "0.2.2.dev0" +version = "0.2.2.dev1" requires-python = ">=3.10" description = "HAP Ray Transform" license = {text = "Apache-2.0"} From be4bfe40688d2ff5441e61d8e067c8ce5cc91043 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Fri, 11 Oct 2024 21:55:36 -0400 Subject: [PATCH 45/70] user repo_level_order for name instread of repo_level_ordering Signed-off-by: Maroun Touma --- transforms/code/repo_level_ordering/transform.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/code/repo_level_ordering/transform.config b/transforms/code/repo_level_ordering/transform.config index 99df6130d..0d82c6377 100644 --- a/transforms/code/repo_level_ordering/transform.config +++ b/transforms/code/repo_level_ordering/transform.config @@ -5,7 +5,7 @@ ################################################################################ # This defines the name of the transform and is used to match against # expected files and is used to define the transform's image name. -TRANSFORM_NAME=repo_level_ordering +TRANSFORM_NAME=repo_level_order ################################################################################ # This defines the transforms' version number as would be used From 3c53e9b2d9c0d5132ce2e9ba83cfbeddf5257a49 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Sun, 13 Oct 2024 03:55:09 -0500 Subject: [PATCH 46/70] Add KFP workflow for HAP. Signed-off-by: Revital Sur --- .github/workflows/test-universal-hap-kfp.yml | 116 +++++++++ scripts/k8s-setup/populate_minio.sh | 2 +- transforms/universal/hap/kfp_ray/Makefile | 59 +++++ transforms/universal/hap/kfp_ray/hap_wf.py | 227 ++++++++++++++++++ .../hap/kfp_ray/pipeline_definitions.yaml | 44 ++++ 5 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-universal-hap-kfp.yml create mode 100644 transforms/universal/hap/kfp_ray/Makefile create mode 100644 transforms/universal/hap/kfp_ray/hap_wf.py create mode 100644 transforms/universal/hap/kfp_ray/pipeline_definitions.yaml diff --git a/.github/workflows/test-universal-hap-kfp.yml b/.github/workflows/test-universal-hap-kfp.yml new file mode 100644 index 000000000..4e52e0535 --- /dev/null +++ b/.github/workflows/test-universal-hap-kfp.yml @@ -0,0 +1,116 @@ +# +# DO NOT EDIT THIS FILE: it is generated from test-transform.template, Edit there and run make to change these files +# +name: Test KFP - transforms/universal/hap + +on: + workflow_dispatch: + push: + branches: + - "dev" + - "releases/**" + tags: + - "*" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/universal/hap/**" + - "!kfp/**" # This is tested in separate workflow + - "!data-processing-lib/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + pull_request: + branches: + - "dev" + - "releases/**" + paths: + - ".make.*" + - "transforms/.make.workflows" + - "transforms/universal/hap/**" + - "!data-processing-lib/**" # This is tested in separate workflow + - "!kfp/**" # This is tested in separate workflow + - "!**.md" + - "!**/doc/**" + - "!**/images/**" + - "!**.gitignore" + +# taken from https://stackoverflow.com/questions/66335225/how-to-cancel-previous-runs-in-the-pr-when-you-push-new-commitsupdate-the-curre +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-kfp-v1: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + - name: Test V1 KFP workflow for transforms/universal/hap + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/universal/hap/Makefile" -a -e "transforms/universal/hap/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/universal/hap") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/hap + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/hap + fi + else + echo "Skipping transforms/universal/hap kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi + + test-kfp-v2: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Free up space in github runner + # Free space as indicated here : https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + run: | + df -h + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup + sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true + df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV + - name: Test V2 KFP workflow for transforms/universal/hap + timeout-minutes: 120 + run: | + KFP_BLACK_LIST=$(./scripts/check-workflows.sh -show-kfp-black-list) + if [ -e "transforms/universal/hap/Makefile" -a -e "transforms/universal/hap/kfp_ray/Makefile" ]; then + transform=$(basename "transforms/universal/hap") + if echo ${KFP_BLACK_LIST} | grep -qv ${transform}; then + $PWD/scripts/workflow_helper.sh install-tools + $PWD/scripts/workflow_helper.sh test-workflow transforms/universal/hap + else + $PWD/scripts/workflow_helper.sh build-workflow transforms/universal/hap + fi + else + echo "Skipping transforms/universal/hap kfp test for lack of Makefile and/or kfp_ray/Makefile" + fi \ No newline at end of file diff --git a/scripts/k8s-setup/populate_minio.sh b/scripts/k8s-setup/populate_minio.sh index 3b22b37e7..793a3cf73 100755 --- a/scripts/k8s-setup/populate_minio.sh +++ b/scripts/k8s-setup/populate_minio.sh @@ -42,4 +42,4 @@ mc cp --recursive ${REPOROOT}/transforms/universal/noop/ray/test-data/input/ kfp mc cp --recursive ${REPOROOT}/transforms/universal/tokenization/ray/test-data/ds01/input/ kfp/test/tokenization/ds01/input mc cp --recursive ${REPOROOT}/transforms/universal/profiler/ray/test-data/input/ kfp/test/profiler/input mc cp --recursive ${REPOROOT}/transforms/universal/resize/ray/test-data/input/ kfp/test/resize/input - +mc cp --recursive ${REPOROOT}/transforms/universal/hap/ray/test-data/input/ kfp/test/hap/input diff --git a/transforms/universal/hap/kfp_ray/Makefile b/transforms/universal/hap/kfp_ray/Makefile new file mode 100644 index 000000000..4074b8713 --- /dev/null +++ b/transforms/universal/hap/kfp_ray/Makefile @@ -0,0 +1,59 @@ +REPOROOT=${CURDIR}/../../../../ + +WORKFLOW_VENV_ACTIVATE=${REPOROOT}/transforms/venv/bin/activate +include $(REPOROOT)/transforms/.make.workflows + +# Include the common configuration for this transform +include ../transform.config + +SRC_DIR=${CURDIR}/../ray/ + +PYTHON_WF := $(shell find ./ -name '*_wf.py') +YAML_WF := $(patsubst %.py, %.yaml, ${PYTHON_WF}) + +workflow-venv: .check_python_version ${WORKFLOW_VENV_ACTIVATE} + +.PHONY: clean +clean: + @# Help: Clean up the virtual environment. + rm -rf ${REPOROOT}/transforms/venv + +venv:: + +build:: + +setup:: + +test:: + +test-src:: + +test-image:: + +publish:: + +image:: + +kind-load-image:: + +docker-load-image:: + +docker-save-image:: + +.PHONY: workflow-build +workflow-build: workflow-venv + $(MAKE) $(YAML_WF) + +.PHONY: workflow-test +workflow-test: workflow-build + $(MAKE) .workflows.test-pipeline TRANSFORM_SRC=${SRC_DIR} PIPELINE_FILE=hap_wf.yaml + +.PHONY: workflow-upload +workflow-upload: workflow-build + @for file in $(YAML_WF); do \ + $(MAKE) .workflows.upload-pipeline PIPELINE_FILE=$$file; \ + done + +.PHONY: workflow-generate +workflow-generate: workflow-venv + . ${WORKFLOW_VENV_ACTIVATE} && ../../../../kfp/pipeline_generator/single-pipeline/run.sh -c `pwd`/pipeline_definitions.yaml -od . diff --git a/transforms/universal/hap/kfp_ray/hap_wf.py b/transforms/universal/hap/kfp_ray/hap_wf.py new file mode 100644 index 000000000..3ee65fa52 --- /dev/null +++ b/transforms/universal/hap/kfp_ray/hap_wf.py @@ -0,0 +1,227 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ +import os + +import kfp.compiler as compiler +import kfp.components as comp +import kfp.dsl as dsl + +from workflow_support.compile_utils import ONE_HOUR_SEC, ONE_WEEK_SEC, ComponentUtils + + +task_image = "quay.io/dataprep1/data-prep-kit/hap-ray:latest" + +# the name of the job script +EXEC_SCRIPT_NAME: str = "hap_transform_ray.py" + +# components +base_kfp_image = "quay.io/dataprep1/data-prep-kit/kfp-data-processing:latest" + +# path to kfp component specifications files +component_spec_path = "../../../../kfp/kfp_ray_components/" + +# compute execution parameters. Here different transforms might need different implementations. As +# a result, instead of creating a component we are creating it in place here. +def compute_exec_params_func( + worker_options: dict, + actor_options: dict, + data_s3_config: str, + data_max_files: int, + data_num_samples: int, + runtime_pipeline_id: str, + runtime_job_id: str, + runtime_code_location: dict, + model_name_or_path: str, + annotation_column: str, + doc_text_column: str, + inference_engine: str, + max_length: int, + batch_size: int, +) -> dict: + from runtime_utils import KFPUtils + + return { + "data_s3_config": data_s3_config, + "data_max_files": data_max_files, + "data_num_samples": data_num_samples, + "runtime_num_workers": KFPUtils.default_compute_execution_params(str(worker_options), str(actor_options)), + "runtime_worker_options": str(actor_options), + "runtime_pipeline_id": runtime_pipeline_id, + "runtime_job_id": runtime_job_id, + "runtime_code_location": str(runtime_code_location), + "model_name_or_path": model_name_or_path, + "annotation_column": annotation_column, + "doc_text_column": doc_text_column, + "inference_engine": inference_engine, + "max_length": max_length, + "batch_size": batch_size, + } + + +# KFPv1 and KFP2 uses different methods to create a component from a function. KFPv1 uses the +# `create_component_from_func` function, but it is deprecated by KFPv2 and so has a different import path. +# KFPv2 recommends using the `@dsl.component` decorator, which doesn't exist in KFPv1. Therefore, here we use +# this if/else statement and explicitly call the decorator. +if os.getenv("KFPv2", "0") == "1": + # In KFPv2 dsl.RUN_ID_PLACEHOLDER is deprecated and cannot be used since SDK 2.5.0. On another hand we cannot create + # a unique string in a component (at runtime) and pass it to the `clean_up_task` of `ExitHandler`, due to + # https://github.com/kubeflow/pipelines/issues/10187. Therefore, meantime we use a unique string created at + # compilation time. + import uuid + + compute_exec_params_op = dsl.component_decorator.component( + func=compute_exec_params_func, base_image=base_kfp_image + ) + print( + "WARNING: the ray cluster name can be non-unique at runtime, please do not execute simultaneous Runs of the " + + "same version of the same pipeline !!!" + ) + run_id = uuid.uuid4().hex +else: + compute_exec_params_op = comp.create_component_from_func(func=compute_exec_params_func, base_image=base_kfp_image) + run_id = dsl.RUN_ID_PLACEHOLDER + +# create Ray cluster +create_ray_op = comp.load_component_from_file(component_spec_path + "createRayClusterComponent.yaml") +# execute job +execute_ray_jobs_op = comp.load_component_from_file(component_spec_path + "executeRayJobComponent.yaml") +# clean up Ray +cleanup_ray_op = comp.load_component_from_file(component_spec_path + "deleteRayClusterComponent.yaml") + +# Task name is part of the pipeline name, the ray cluster name and the job name in DMF. +TASK_NAME: str = "hap" + + +@dsl.pipeline( + name=TASK_NAME + "-ray-pipeline", + description="Pipeline for hap task", +) +def hap( + # Ray cluster + ray_name: str = "hap-kfp-ray", # name of Ray cluster + # Add image_pull_secret and image_pull_policy to ray workers if needed + ray_head_options: dict = {"cpu": 1, "memory": 4, "image": task_image}, + ray_worker_options: dict = {"replicas": 2, "max_replicas": 2, "min_replicas": 2, "cpu": 2, "memory": 4, "image": task_image}, + server_url: str = "http://kuberay-apiserver-service.kuberay.svc.cluster.local:8888", + # data access + data_s3_config: str = "{'input_folder': 'test/hap/input/', 'output_folder': 'test/hap/output/'}", + data_s3_access_secret: str = "s3-secret", + data_max_files: int = -1, + data_num_samples: int = -1, + data_checkpointing: bool = False, + # orchestrator + runtime_actor_options: dict = {'num_cpus': 0.8}, + runtime_pipeline_id: str = "pipeline_id", + runtime_code_location: dict = {'github': 'github', 'commit_hash': '12345', 'path': 'path'}, + # hap parameters + model_name_or_path: str = "ibm-granite/granite-guardian-hap-38m", + annotation_column: str = "hap_score", + doc_text_column: str = "contents", + inference_engine: str = "CPU", + max_length: int = 512, + batch_size: int = 128, + # additional parameters + additional_params: str = '{"wait_interval": 2, "wait_cluster_ready_tmout": 400, "wait_cluster_up_tmout": 300, "wait_job_ready_tmout": 400, "wait_print_tmout": 30, "http_retries": 5, "delete_cluster_delay_minutes": 0}', +): + """ + Pipeline to execute hap transform + :param ray_name: name of the Ray cluster + :param ray_head_options: head node options, containing the following: + cpu - number of cpus + memory - memory + image - image to use + image_pull_secret - image pull secret + tolerations - (optional) tolerations for the ray pods + :param ray_worker_options: worker node options (we here are using only 1 worker pool), containing the following: + replicas - number of replicas to create + max_replicas - max number of replicas + min_replicas - min number of replicas + cpu - number of cpus + memory - memory + image - image to use + image_pull_secret - image pull secret + tolerations - (optional) tolerations for the ray pods + :param server_url - server url + :param additional_params: additional (support) parameters, containing the following: + wait_interval - wait interval for API server, sec + wait_cluster_ready_tmout - time to wait for cluster ready, sec + wait_cluster_up_tmout - time to wait for cluster up, sec + wait_job_ready_tmout - time to wait for job ready, sec + wait_print_tmout - time between prints, sec + http_retries - http retries for API server calls + :param data_s3_access_secret - s3 access secret + :param data_s3_config - s3 configuration + :param data_max_files - max files to process + :param data_num_samples - num samples to process + :param runtime_actor_options - actor options + :param runtime_pipeline_id - pipeline id + :param runtime_code_location - code location + :param model_name_or_path - # HAP model path + :param annotation_column - # hap score for each document + :param doc_text_column - # The column name that contains the document text + :param inference_engine - # inference engine used + :param max_length - # inference engine used + :param batch_size - # batch size + :return: None + """ + # create clean_up task + clean_up_task = cleanup_ray_op(ray_name=ray_name, run_id=run_id, server_url=server_url, additional_params=additional_params) + ComponentUtils.add_settings_to_component(clean_up_task, ONE_HOUR_SEC * 2) + # pipeline definition + with dsl.ExitHandler(clean_up_task): + # compute execution params + compute_exec_params = compute_exec_params_op( + worker_options=ray_worker_options, + actor_options=runtime_actor_options, + data_s3_config=data_s3_config, + data_max_files=data_max_files, + data_num_samples=data_num_samples, + runtime_pipeline_id=runtime_pipeline_id, + runtime_job_id=run_id, + runtime_code_location=runtime_code_location, + model_name_or_path=model_name_or_path, + annotation_column=annotation_column, + doc_text_column=doc_text_column, + inference_engine=inference_engine, + max_length=max_length, + batch_size=batch_size, + ) + + ComponentUtils.add_settings_to_component(compute_exec_params, ONE_HOUR_SEC * 2) + # start Ray cluster + ray_cluster = create_ray_op( + ray_name=ray_name, + run_id=run_id, + ray_head_options=ray_head_options, + ray_worker_options=ray_worker_options, + server_url=server_url, + additional_params=additional_params, + ) + ComponentUtils.add_settings_to_component(ray_cluster, ONE_HOUR_SEC * 2) + ray_cluster.after(compute_exec_params) + + # Execute job + execute_job = execute_ray_jobs_op( + ray_name=ray_name, + run_id=run_id, + additional_params=additional_params, + exec_params=compute_exec_params.output, + exec_script_name=EXEC_SCRIPT_NAME, + server_url=server_url, + ) + ComponentUtils.add_settings_to_component(execute_job, ONE_WEEK_SEC) + ComponentUtils.set_s3_env_vars_to_component(execute_job, data_s3_access_secret) + execute_job.after(ray_cluster) + +if __name__ == "__main__": + # Compiling the pipeline + compiler.Compiler().compile(hap, __file__.replace(".py", ".yaml")) \ No newline at end of file diff --git a/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml new file mode 100644 index 000000000..f02d7df0a --- /dev/null +++ b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml @@ -0,0 +1,44 @@ +pipeline_parameters: + name: "hap" + description: "Pipeline for hap task" + script_name: "hap_transform_ray.py" + prefix: "" + multi_s3: False + compute_func_name: "" + compute_func_import: "" + component_spec_path: "" + +pipeline_common_input_parameters_values: + kfp_base_image: "quay.io/dataprep1/data-prep-kit/kfp-data-processing:latest" + transform_image: "quay.io/dataprep1/data-prep-kit/hap-ray:latest" + s3_access_secret: "s3-secret" + image_pull_secret: "prod-all-icr-io" + input_folder: "test/hap/input/" + output_folder: "test/hap/output/" + +pipeline_transform_input_parameters: + pipeline_arguments: + - name: "model_name_or_path" + type: "str" + value: "ibm-granite/granite-guardian-hap-38m" + description: "# HAP model path" + - name: "annotation_column" + type: "str" + value: "hap_score" + description: "# hap score for each document" + - name: "doc_text_column" + type: "str" + value: "contents" + description: "# The column name that contains the document text" + - name: "inference_engine" + type: "str" + value: "CPU" + description: "# inference engine used" + - name: max_length + type: "int" + value: 512 + description: "# inference engine used" + - name: "batch_size" + type: "int" + value: 128 + description: "# batch size" From 6f153442761651d712d2a8a51a7359a9e1fe5526 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Sun, 13 Oct 2024 13:39:55 +0300 Subject: [PATCH 47/70] Run pre-commit. Signed-off-by: Revital Sur --- transforms/universal/hap/kfp_ray/hap_wf.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/transforms/universal/hap/kfp_ray/hap_wf.py b/transforms/universal/hap/kfp_ray/hap_wf.py index 3ee65fa52..66a081531 100644 --- a/transforms/universal/hap/kfp_ray/hap_wf.py +++ b/transforms/universal/hap/kfp_ray/hap_wf.py @@ -14,7 +14,6 @@ import kfp.compiler as compiler import kfp.components as comp import kfp.dsl as dsl - from workflow_support.compile_utils import ONE_HOUR_SEC, ONE_WEEK_SEC, ComponentUtils @@ -110,7 +109,14 @@ def hap( ray_name: str = "hap-kfp-ray", # name of Ray cluster # Add image_pull_secret and image_pull_policy to ray workers if needed ray_head_options: dict = {"cpu": 1, "memory": 4, "image": task_image}, - ray_worker_options: dict = {"replicas": 2, "max_replicas": 2, "min_replicas": 2, "cpu": 2, "memory": 4, "image": task_image}, + ray_worker_options: dict = { + "replicas": 2, + "max_replicas": 2, + "min_replicas": 2, + "cpu": 2, + "memory": 4, + "image": task_image, + }, server_url: str = "http://kuberay-apiserver-service.kuberay.svc.cluster.local:8888", # data access data_s3_config: str = "{'input_folder': 'test/hap/input/', 'output_folder': 'test/hap/output/'}", @@ -119,9 +125,9 @@ def hap( data_num_samples: int = -1, data_checkpointing: bool = False, # orchestrator - runtime_actor_options: dict = {'num_cpus': 0.8}, + runtime_actor_options: dict = {"num_cpus": 0.8}, runtime_pipeline_id: str = "pipeline_id", - runtime_code_location: dict = {'github': 'github', 'commit_hash': '12345', 'path': 'path'}, + runtime_code_location: dict = {"github": "github", "commit_hash": "12345", "path": "path"}, # hap parameters model_name_or_path: str = "ibm-granite/granite-guardian-hap-38m", annotation_column: str = "hap_score", @@ -174,7 +180,9 @@ def hap( :return: None """ # create clean_up task - clean_up_task = cleanup_ray_op(ray_name=ray_name, run_id=run_id, server_url=server_url, additional_params=additional_params) + clean_up_task = cleanup_ray_op( + ray_name=ray_name, run_id=run_id, server_url=server_url, additional_params=additional_params + ) ComponentUtils.add_settings_to_component(clean_up_task, ONE_HOUR_SEC * 2) # pipeline definition with dsl.ExitHandler(clean_up_task): @@ -222,6 +230,7 @@ def hap( ComponentUtils.set_s3_env_vars_to_component(execute_job, data_s3_access_secret) execute_job.after(ray_cluster) + if __name__ == "__main__": # Compiling the pipeline - compiler.Compiler().compile(hap, __file__.replace(".py", ".yaml")) \ No newline at end of file + compiler.Compiler().compile(hap, __file__.replace(".py", ".yaml")) From d21f0f1c0800723285743ff6fbb8cc618f7376dd Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Sun, 13 Oct 2024 14:35:02 +0300 Subject: [PATCH 48/70] Minor fix. Signed-off-by: Revital Sur --- transforms/universal/hap/kfp_ray/pipeline_definitions.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml index f02d7df0a..895d26837 100644 --- a/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml +++ b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml @@ -12,7 +12,6 @@ pipeline_common_input_parameters_values: kfp_base_image: "quay.io/dataprep1/data-prep-kit/kfp-data-processing:latest" transform_image: "quay.io/dataprep1/data-prep-kit/hap-ray:latest" s3_access_secret: "s3-secret" - image_pull_secret: "prod-all-icr-io" input_folder: "test/hap/input/" output_folder: "test/hap/output/" From a1f6059c94a7ca32cedfc98810b910b839b92d41 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Sun, 13 Oct 2024 19:21:57 +0300 Subject: [PATCH 49/70] Add data_checkpointing. Signed-off-by: Revital Sur --- transforms/universal/hap/kfp_ray/hap_wf.py | 3 +++ transforms/universal/hap/kfp_ray/pipeline_definitions.yaml | 1 + 2 files changed, 4 insertions(+) diff --git a/transforms/universal/hap/kfp_ray/hap_wf.py b/transforms/universal/hap/kfp_ray/hap_wf.py index 66a081531..786011d4d 100644 --- a/transforms/universal/hap/kfp_ray/hap_wf.py +++ b/transforms/universal/hap/kfp_ray/hap_wf.py @@ -36,6 +36,7 @@ def compute_exec_params_func( data_s3_config: str, data_max_files: int, data_num_samples: int, + data_checkpointing: bool, runtime_pipeline_id: str, runtime_job_id: str, runtime_code_location: dict, @@ -52,6 +53,7 @@ def compute_exec_params_func( "data_s3_config": data_s3_config, "data_max_files": data_max_files, "data_num_samples": data_num_samples, + "data_checkpointing": data_checkpointing, "runtime_num_workers": KFPUtils.default_compute_execution_params(str(worker_options), str(actor_options)), "runtime_worker_options": str(actor_options), "runtime_pipeline_id": runtime_pipeline_id, @@ -193,6 +195,7 @@ def hap( data_s3_config=data_s3_config, data_max_files=data_max_files, data_num_samples=data_num_samples, + data_checkpointing=data_checkpointing, runtime_pipeline_id=runtime_pipeline_id, runtime_job_id=run_id, runtime_code_location=runtime_code_location, diff --git a/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml index 895d26837..9716bb349 100644 --- a/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml +++ b/transforms/universal/hap/kfp_ray/pipeline_definitions.yaml @@ -12,6 +12,7 @@ pipeline_common_input_parameters_values: kfp_base_image: "quay.io/dataprep1/data-prep-kit/kfp-data-processing:latest" transform_image: "quay.io/dataprep1/data-prep-kit/hap-ray:latest" s3_access_secret: "s3-secret" + image_pull_secret: "" input_folder: "test/hap/input/" output_folder: "test/hap/output/" From e494540b25931f3a988c9887fbc1f9cef804716a Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Mon, 14 Oct 2024 10:02:11 +0300 Subject: [PATCH 50/70] Fix workflow Failures. Signed-off-by: Revital Sur --- scripts/workflow_helper.sh | 4 +++- transforms/.make.workflows | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/workflow_helper.sh b/scripts/workflow_helper.sh index 5f89df770..0246c3d1b 100755 --- a/scripts/workflow_helper.sh +++ b/scripts/workflow_helper.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -euo pipefail + op=$1 install_tools(){ @@ -25,7 +27,7 @@ test_workflow(){ local workflow="$1" echo "Testing $workflow" - DEPLOY_KUBEFLOW=1 make -C $workflow setup + DEPLOY_KUBEFLOW=1 make -C scripts/k8s-setup setup make -C $workflow workflow-test echo "Run workflow completed" } diff --git a/transforms/.make.workflows b/transforms/.make.workflows index cd4c2532a..85a707f08 100644 --- a/transforms/.make.workflows +++ b/transforms/.make.workflows @@ -57,8 +57,9 @@ ${WORKFLOW_VENV_ACTIVATE}: ${REPOROOT}/.make.versions ${REPOROOT}/kfp/kfp_ray_co pip install -e $(REPOROOT)/kfp/kfp_support_lib/shared_workflow_support; \ pip install -e $(REPOROOT)/kfp/kfp_support_lib/$(WORKFLOW_SUPPORT_LIB); \ $(MAKE) -C ${REPOROOT}/kfp/kfp_ray_components set-versions - pip install jinja2 - pip install pyyaml + . ${WORKFLOW_VENV_ACTIVATE}; \ + pip install jinja2; \ + pip install pyyaml; \ pip install pre-commit @# Help: Create the virtual environment common to all workflows From 539857b784a79d5942764748f8d18e0c0471eb01 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Mon, 14 Oct 2024 10:42:55 +0300 Subject: [PATCH 51/70] Minor change. Signed-off-by: Revital Sur --- transforms/universal/noop/kfp_ray/noop_wf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/universal/noop/kfp_ray/noop_wf.py b/transforms/universal/noop/kfp_ray/noop_wf.py index f5961ba36..e8125328b 100644 --- a/transforms/universal/noop/kfp_ray/noop_wf.py +++ b/transforms/universal/noop/kfp_ray/noop_wf.py @@ -98,7 +98,7 @@ def compute_exec_params_func( def noop( # Ray cluster ray_name: str = "noop-kfp-ray", # name of Ray cluster - # Add image_pull_secret and image_pull_policy to ray workers if needed + # Add image_pull_secret, image_pull_policy and tolerations to ray options if needed ray_head_options: dict = {"cpu": 1, "memory": 4, "image": task_image}, ray_worker_options: dict = {"replicas": 2, "max_replicas": 2, "min_replicas": 2, "cpu": 2, "memory": 4, "image": task_image}, server_url: str = "http://kuberay-apiserver-service.kuberay.svc.cluster.local:8888", From 3babebc94deb90917daaae356a9df978481e197c Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Mon, 14 Oct 2024 11:51:22 +0300 Subject: [PATCH 52/70] Address review comments. Signed-off-by: Revital Sur --- scripts/workflow_helper.sh | 4 ++++ transforms/.make.workflows | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/workflow_helper.sh b/scripts/workflow_helper.sh index 0246c3d1b..a92befbc6 100755 --- a/scripts/workflow_helper.sh +++ b/scripts/workflow_helper.sh @@ -1,5 +1,9 @@ #!/bin/bash + +# This ensures that the script exits immediately if any command +# returns a non-zero status, preventing cases where the GitHub +# Action running the script might overlook an error if it occurs. set -euo pipefail op=$1 diff --git a/transforms/.make.workflows b/transforms/.make.workflows index 85a707f08..259971880 100644 --- a/transforms/.make.workflows +++ b/transforms/.make.workflows @@ -56,8 +56,7 @@ ${WORKFLOW_VENV_ACTIVATE}: ${REPOROOT}/.make.versions ${REPOROOT}/kfp/kfp_ray_co . ${WORKFLOW_VENV_ACTIVATE}; \ pip install -e $(REPOROOT)/kfp/kfp_support_lib/shared_workflow_support; \ pip install -e $(REPOROOT)/kfp/kfp_support_lib/$(WORKFLOW_SUPPORT_LIB); \ - $(MAKE) -C ${REPOROOT}/kfp/kfp_ray_components set-versions - . ${WORKFLOW_VENV_ACTIVATE}; \ + $(MAKE) -C ${REPOROOT}/kfp/kfp_ray_components set-versions; \ pip install jinja2; \ pip install pyyaml; \ pip install pre-commit From 05c3ee2d2a72f108eabd1ae07178a5123e73a567 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 09:43:32 -0400 Subject: [PATCH 53/70] change workflows back to using ubuntu-22.04 from latest Signed-off-by: David Wood --- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/deploy-library.yml | 6 +++--- .github/workflows/deploy-transforms.yml | 4 ++-- .github/workflows/test-code-code2parquet-kfp.yml | 6 +++--- .github/workflows/test-code-code2parquet.yml | 6 +++--- .github/workflows/test-code-code_quality-kfp.yml | 6 +++--- .github/workflows/test-code-code_quality.yml | 6 +++--- .github/workflows/test-code-header_cleanser-kfp.yml | 6 +++--- .github/workflows/test-code-header_cleanser.yml | 6 +++--- .github/workflows/test-code-license_select-kfp.yml | 6 +++--- .github/workflows/test-code-license_select.yml | 6 +++--- .github/workflows/test-code-malware-kfp.yml | 6 +++--- .github/workflows/test-code-malware.yml | 6 +++--- .github/workflows/test-code-proglang_select-kfp.yml | 6 +++--- .github/workflows/test-code-proglang_select.yml | 6 +++--- .../workflows/test-code-repo_level_ordering-kfp.yml | 6 +++--- .github/workflows/test-code-repo_level_ordering.yml | 6 +++--- .github/workflows/test-kfp-transform.template | 6 +++--- .github/workflows/test-kfp.yml | 8 ++++---- .github/workflows/test-language-doc_chunk-kfp.yml | 6 +++--- .github/workflows/test-language-doc_chunk.yml | 6 +++--- .github/workflows/test-language-doc_quality-kfp.yml | 6 +++--- .github/workflows/test-language-doc_quality.yml | 6 +++--- .github/workflows/test-language-html2parquet.yml | 6 +++--- .github/workflows/test-language-lang_id-kfp.yml | 6 +++--- .github/workflows/test-language-lang_id.yml | 6 +++--- .github/workflows/test-language-pdf2parquet-kfp.yml | 6 +++--- .github/workflows/test-language-pdf2parquet.yml | 6 +++--- .github/workflows/test-language-pii_redactor-kfp.yml | 6 +++--- .github/workflows/test-language-pii_redactor.yml | 6 +++--- .github/workflows/test-language-text_encoder-kfp.yml | 6 +++--- .github/workflows/test-language-text_encoder.yml | 6 +++--- .github/workflows/test-misc.yml | 4 ++-- .github/workflows/test-packaging-python.yml | 2 +- .github/workflows/test-packaging-ray.yml | 2 +- .github/workflows/test-processing-lib.yml | 10 +++++----- .github/workflows/test-transform.template | 6 +++--- .github/workflows/test-universal-doc_id-kfp.yml | 6 +++--- .github/workflows/test-universal-doc_id.yml | 6 +++--- .github/workflows/test-universal-ededup-kfp.yml | 6 +++--- .github/workflows/test-universal-ededup.yml | 6 +++--- .github/workflows/test-universal-fdedup-kfp.yml | 6 +++--- .github/workflows/test-universal-fdedup.yml | 6 +++--- .github/workflows/test-universal-filter-kfp.yml | 6 +++--- .github/workflows/test-universal-filter.yml | 6 +++--- .github/workflows/test-universal-hap.yml | 6 +++--- .github/workflows/test-universal-noop-kfp.yml | 6 +++--- .github/workflows/test-universal-noop.yml | 6 +++--- .github/workflows/test-universal-profiler-kfp.yml | 6 +++--- .github/workflows/test-universal-profiler.yml | 6 +++--- .github/workflows/test-universal-resize-kfp.yml | 6 +++--- .github/workflows/test-universal-resize.yml | 6 +++--- .github/workflows/test-universal-tokenization-kfp.yml | 6 +++--- .github/workflows/test-universal-tokenization.yml | 6 +++--- .github/workflows/workflow-manual-run.yml | 2 +- 55 files changed, 158 insertions(+), 158 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 09678e937..a2909c55d 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -8,7 +8,7 @@ on: - "releases/**" jobs: deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: REPO_URL: "https://github.com/${{ github.repository }}" REPO_BRANCH: "dev" diff --git a/.github/workflows/deploy-library.yml b/.github/workflows/deploy-library.yml index 0c2473175..8ec97ed9e 100644 --- a/.github/workflows/deploy-library.yml +++ b/.github/workflows/deploy-library.yml @@ -14,7 +14,7 @@ permissions: jobs: build-package: name: Build Ray data processing libraries - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -30,7 +30,7 @@ jobs: name: Publish packages to test.pypi.org # disabled if: false - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-package steps: @@ -47,7 +47,7 @@ jobs: publish-pypi: name: Publish release to pypi.org - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-package # disabled as of now if: false diff --git a/.github/workflows/deploy-transforms.yml b/.github/workflows/deploy-transforms.yml index 0f002187d..7fe5c8b4d 100644 --- a/.github/workflows/deploy-transforms.yml +++ b/.github/workflows/deploy-transforms.yml @@ -9,7 +9,7 @@ on: jobs: build-images: name: Build and check images - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -23,7 +23,7 @@ jobs: name: Publish packages to quay.io # disabled if: false - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: build-images steps: diff --git a/.github/workflows/test-code-code2parquet-kfp.yml b/.github/workflows/test-code-code2parquet-kfp.yml index 4cb943469..710654571 100644 --- a/.github/workflows/test-code-code2parquet-kfp.yml +++ b/.github/workflows/test-code-code2parquet-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/code2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-code2parquet.yml b/.github/workflows/test-code-code2parquet.yml index 3f83e9856..3f2d19159 100644 --- a/.github/workflows/test-code-code2parquet.yml +++ b/.github/workflows/test-code-code2parquet.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-code_quality-kfp.yml b/.github/workflows/test-code-code_quality-kfp.yml index f177d8a37..c07bc1d2d 100644 --- a/.github/workflows/test-code-code_quality-kfp.yml +++ b/.github/workflows/test-code-code_quality-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/code_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-code_quality.yml b/.github/workflows/test-code-code_quality.yml index 5a901edbb..d5f3d3a5b 100644 --- a/.github/workflows/test-code-code_quality.yml +++ b/.github/workflows/test-code-code_quality.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-header_cleanser-kfp.yml b/.github/workflows/test-code-header_cleanser-kfp.yml index 2b5538806..7c419fd14 100644 --- a/.github/workflows/test-code-header_cleanser-kfp.yml +++ b/.github/workflows/test-code-header_cleanser-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/header_cleanser kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-header_cleanser.yml b/.github/workflows/test-code-header_cleanser.yml index 05f09a8c5..7c1d6d74d 100644 --- a/.github/workflows/test-code-header_cleanser.yml +++ b/.github/workflows/test-code-header_cleanser.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-license_select-kfp.yml b/.github/workflows/test-code-license_select-kfp.yml index 89e7deae6..d72a85dd4 100644 --- a/.github/workflows/test-code-license_select-kfp.yml +++ b/.github/workflows/test-code-license_select-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/license_select kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-license_select.yml b/.github/workflows/test-code-license_select.yml index 59592c82f..7db2ad42b 100644 --- a/.github/workflows/test-code-license_select.yml +++ b/.github/workflows/test-code-license_select.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-malware-kfp.yml b/.github/workflows/test-code-malware-kfp.yml index 389a04555..89bf47239 100644 --- a/.github/workflows/test-code-malware-kfp.yml +++ b/.github/workflows/test-code-malware-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/malware kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-malware.yml b/.github/workflows/test-code-malware.yml index 44196c62c..2bf06c407 100644 --- a/.github/workflows/test-code-malware.yml +++ b/.github/workflows/test-code-malware.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-proglang_select-kfp.yml b/.github/workflows/test-code-proglang_select-kfp.yml index 09fd67520..31328f3d5 100644 --- a/.github/workflows/test-code-proglang_select-kfp.yml +++ b/.github/workflows/test-code-proglang_select-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/proglang_select kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-proglang_select.yml b/.github/workflows/test-code-proglang_select.yml index 4723e5d3a..870baa7a9 100644 --- a/.github/workflows/test-code-proglang_select.yml +++ b/.github/workflows/test-code-proglang_select.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-code-repo_level_ordering-kfp.yml b/.github/workflows/test-code-repo_level_ordering-kfp.yml index a3813bc62..4e328f53e 100644 --- a/.github/workflows/test-code-repo_level_ordering-kfp.yml +++ b/.github/workflows/test-code-repo_level_ordering-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/code/repo_level_ordering kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-code-repo_level_ordering.yml b/.github/workflows/test-code-repo_level_ordering.yml index 19ec8daf5..6e9c4719c 100644 --- a/.github/workflows/test-code-repo_level_ordering.yml +++ b/.github/workflows/test-code-repo_level_ordering.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-kfp-transform.template b/.github/workflows/test-kfp-transform.template index f9ace4831..f12511118 100644 --- a/.github/workflows/test-kfp-transform.template +++ b/.github/workflows/test-kfp-transform.template @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping @TARGET_TRANSFORM_DIR@ kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-kfp.yml b/.github/workflows/test-kfp.yml index 89061199c..685898aac 100644 --- a/.github/workflows/test-kfp.yml +++ b/.github/workflows/test-kfp.yml @@ -56,7 +56,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -73,7 +73,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -122,7 +122,7 @@ jobs: echo "Run ${transforms[$index]} completed" test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -172,7 +172,7 @@ jobs: header_text "Run ${transforms[$index]} completed" build-kfp-components: needs: [check_if_push_images] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 30 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-doc_chunk-kfp.yml b/.github/workflows/test-language-doc_chunk-kfp.yml index f689f1d1b..985f79b97 100644 --- a/.github/workflows/test-language-doc_chunk-kfp.yml +++ b/.github/workflows/test-language-doc_chunk-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/doc_chunk kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-doc_chunk.yml b/.github/workflows/test-language-doc_chunk.yml index ec78512e5..32a01d2d9 100644 --- a/.github/workflows/test-language-doc_chunk.yml +++ b/.github/workflows/test-language-doc_chunk.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-doc_quality-kfp.yml b/.github/workflows/test-language-doc_quality-kfp.yml index 3bb709515..6842a1859 100644 --- a/.github/workflows/test-language-doc_quality-kfp.yml +++ b/.github/workflows/test-language-doc_quality-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/doc_quality kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-doc_quality.yml b/.github/workflows/test-language-doc_quality.yml index 443c22152..527e863df 100644 --- a/.github/workflows/test-language-doc_quality.yml +++ b/.github/workflows/test-language-doc_quality.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-html2parquet.yml b/.github/workflows/test-language-html2parquet.yml index e5ef8e510..8f46dde5a 100644 --- a/.github/workflows/test-language-html2parquet.yml +++ b/.github/workflows/test-language-html2parquet.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-lang_id-kfp.yml b/.github/workflows/test-language-lang_id-kfp.yml index da2f3bee3..936ba8e45 100644 --- a/.github/workflows/test-language-lang_id-kfp.yml +++ b/.github/workflows/test-language-lang_id-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/lang_id kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-lang_id.yml b/.github/workflows/test-language-lang_id.yml index 7c318a3a1..3108cca53 100644 --- a/.github/workflows/test-language-lang_id.yml +++ b/.github/workflows/test-language-lang_id.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-pdf2parquet-kfp.yml b/.github/workflows/test-language-pdf2parquet-kfp.yml index 45b99cdc6..f232d78e4 100644 --- a/.github/workflows/test-language-pdf2parquet-kfp.yml +++ b/.github/workflows/test-language-pdf2parquet-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/pdf2parquet kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-pdf2parquet.yml b/.github/workflows/test-language-pdf2parquet.yml index fbdd81b8e..dda8d4273 100644 --- a/.github/workflows/test-language-pdf2parquet.yml +++ b/.github/workflows/test-language-pdf2parquet.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-pii_redactor-kfp.yml b/.github/workflows/test-language-pii_redactor-kfp.yml index 17c42ba2a..451ac7961 100644 --- a/.github/workflows/test-language-pii_redactor-kfp.yml +++ b/.github/workflows/test-language-pii_redactor-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/pii_redactor kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-pii_redactor.yml b/.github/workflows/test-language-pii_redactor.yml index 5ecc80b08..8739e84d2 100644 --- a/.github/workflows/test-language-pii_redactor.yml +++ b/.github/workflows/test-language-pii_redactor.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-language-text_encoder-kfp.yml b/.github/workflows/test-language-text_encoder-kfp.yml index 4129ae677..96b8308d1 100644 --- a/.github/workflows/test-language-text_encoder-kfp.yml +++ b/.github/workflows/test-language-text_encoder-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/language/text_encoder kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-language-text_encoder.yml b/.github/workflows/test-language-text_encoder.yml index d49c1193d..b0a042163 100644 --- a/.github/workflows/test-language-text_encoder.yml +++ b/.github/workflows/test-language-text_encoder.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-misc.yml b/.github/workflows/test-misc.yml index 62c1a187a..2c601bbd5 100644 --- a/.github/workflows/test-misc.yml +++ b/.github/workflows/test-misc.yml @@ -29,7 +29,7 @@ on: jobs: test-make: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -37,7 +37,7 @@ jobs: run: | make -n clean test build publish set-versions check-transform-test-workflows: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test-packaging-python.yml b/.github/workflows/test-packaging-python.yml index e88eeeae2..4ee491c8e 100644 --- a/.github/workflows/test-packaging-python.yml +++ b/.github/workflows/test-packaging-python.yml @@ -27,7 +27,7 @@ on: jobs: test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test-packaging-ray.yml b/.github/workflows/test-packaging-ray.yml index 9dbce3110..4b812540c 100644 --- a/.github/workflows/test-packaging-ray.yml +++ b/.github/workflows/test-packaging-ray.yml @@ -27,7 +27,7 @@ on: jobs: test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test-processing-lib.yml b/.github/workflows/test-processing-lib.yml index 5d76c13f7..ddcdd9565 100644 --- a/.github/workflows/test-processing-lib.yml +++ b/.github/workflows/test-processing-lib.yml @@ -36,7 +36,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -53,7 +53,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-python-lib: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -61,7 +61,7 @@ jobs: run: | make -C data-processing-lib/python DOCKER=docker venv test test-ray-lib: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -69,7 +69,7 @@ jobs: run: | make -C data-processing-lib/ray DOCKER=docker venv test test-spark-lib: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: test-data-processing-lib-images: needs: [check_if_push_images] if: needs.check_if_push_images.outputs.publish_images == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} DOCKER_REGISTRY_KEY: ${{ secrets.DOCKER_REGISTRY_KEY }} diff --git a/.github/workflows/test-transform.template b/.github/workflows/test-transform.template index f3907d56a..4cf165e45 100644 --- a/.github/workflows/test-transform.template +++ b/.github/workflows/test-transform.template @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-doc_id-kfp.yml b/.github/workflows/test-universal-doc_id-kfp.yml index b9d4fa04f..fadcc6403 100644 --- a/.github/workflows/test-universal-doc_id-kfp.yml +++ b/.github/workflows/test-universal-doc_id-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/doc_id kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-doc_id.yml b/.github/workflows/test-universal-doc_id.yml index d314f3b25..fce8faf11 100644 --- a/.github/workflows/test-universal-doc_id.yml +++ b/.github/workflows/test-universal-doc_id.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-ededup-kfp.yml b/.github/workflows/test-universal-ededup-kfp.yml index ef9c4e6d3..225d7539e 100644 --- a/.github/workflows/test-universal-ededup-kfp.yml +++ b/.github/workflows/test-universal-ededup-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/ededup kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-ededup.yml b/.github/workflows/test-universal-ededup.yml index 8b4034570..481410b28 100644 --- a/.github/workflows/test-universal-ededup.yml +++ b/.github/workflows/test-universal-ededup.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-fdedup-kfp.yml b/.github/workflows/test-universal-fdedup-kfp.yml index fd37421e7..b36df964d 100644 --- a/.github/workflows/test-universal-fdedup-kfp.yml +++ b/.github/workflows/test-universal-fdedup-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/fdedup kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-fdedup.yml b/.github/workflows/test-universal-fdedup.yml index 5f68d4799..a8c6f14c0 100644 --- a/.github/workflows/test-universal-fdedup.yml +++ b/.github/workflows/test-universal-fdedup.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-filter-kfp.yml b/.github/workflows/test-universal-filter-kfp.yml index eab5179d6..cab769f11 100644 --- a/.github/workflows/test-universal-filter-kfp.yml +++ b/.github/workflows/test-universal-filter-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/filter kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-filter.yml b/.github/workflows/test-universal-filter.yml index 43e936166..658cc6170 100644 --- a/.github/workflows/test-universal-filter.yml +++ b/.github/workflows/test-universal-filter.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-hap.yml b/.github/workflows/test-universal-hap.yml index c845506c1..d59edcdc1 100644 --- a/.github/workflows/test-universal-hap.yml +++ b/.github/workflows/test-universal-hap.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-noop-kfp.yml b/.github/workflows/test-universal-noop-kfp.yml index 3def4f09c..9322ea9b6 100644 --- a/.github/workflows/test-universal-noop-kfp.yml +++ b/.github/workflows/test-universal-noop-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/noop kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-noop.yml b/.github/workflows/test-universal-noop.yml index 13e066d58..fa5cf3163 100644 --- a/.github/workflows/test-universal-noop.yml +++ b/.github/workflows/test-universal-noop.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-profiler-kfp.yml b/.github/workflows/test-universal-profiler-kfp.yml index a079d43ec..f31543ad3 100644 --- a/.github/workflows/test-universal-profiler-kfp.yml +++ b/.github/workflows/test-universal-profiler-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/profiler kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-profiler.yml b/.github/workflows/test-universal-profiler.yml index e018e0ed3..aff87a4ab 100644 --- a/.github/workflows/test-universal-profiler.yml +++ b/.github/workflows/test-universal-profiler.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-resize-kfp.yml b/.github/workflows/test-universal-resize-kfp.yml index aa554372d..3e099bba5 100644 --- a/.github/workflows/test-universal-resize-kfp.yml +++ b/.github/workflows/test-universal-resize-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/resize kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-resize.yml b/.github/workflows/test-universal-resize.yml index b3399e5ec..335eb0a28 100644 --- a/.github/workflows/test-universal-resize.yml +++ b/.github/workflows/test-universal-resize.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/test-universal-tokenization-kfp.yml b/.github/workflows/test-universal-tokenization-kfp.yml index 952043caf..e4a5b5693 100644 --- a/.github/workflows/test-universal-tokenization-kfp.yml +++ b/.github/workflows/test-universal-tokenization-kfp.yml @@ -43,7 +43,7 @@ concurrency: jobs: test-kfp-v1: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -79,7 +79,7 @@ jobs: fi test-kfp-v2: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -113,4 +113,4 @@ jobs: fi else echo "Skipping transforms/universal/tokenization kfp test for lack of Makefile and/or kfp_ray/Makefile" - fi \ No newline at end of file + fi diff --git a/.github/workflows/test-universal-tokenization.yml b/.github/workflows/test-universal-tokenization.yml index ae547c396..4f6626b1c 100644 --- a/.github/workflows/test-universal-tokenization.yml +++ b/.github/workflows/test-universal-tokenization.yml @@ -51,7 +51,7 @@ jobs: # The images are pushed if it is a merge to dev branch or a new tag is created. # The latter being part of the release process. # The images tag is derived from the value of the DOCKER_IMAGE_VERSION variable set in the .make.versions file. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: publish_images: ${{ steps.version.outputs.publish_images }} steps: @@ -68,7 +68,7 @@ jobs: fi echo "publish_images=$publish_images" >> "$GITHUB_OUTPUT" test-src: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fi test-image: needs: [check_if_push_image] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 120 env: DOCKER_REGISTRY_USER: ${{ secrets.DOCKER_REGISTRY_USER }} diff --git a/.github/workflows/workflow-manual-run.yml b/.github/workflows/workflow-manual-run.yml index f0f7028b6..3c0f37d47 100644 --- a/.github/workflows/workflow-manual-run.yml +++ b/.github/workflows/workflow-manual-run.yml @@ -22,7 +22,7 @@ jobs: KFPv2: ${{ github.event.inputs.kfp_v2 }} WORKFLOW_PATH: ${{ github.event.inputs.workflow-path }} DEBUG_MODE: ${{ github.event.inputs.debug }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 From 5ba03427ebffbc39f76bca6146230c23563f8baa Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 09:47:43 -0400 Subject: [PATCH 54/70] Make test-kfp.yml trigger on .make.* changes Signed-off-by: David Wood --- .github/workflows/test-kfp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-kfp.yml b/.github/workflows/test-kfp.yml index 685898aac..425f8ed45 100644 --- a/.github/workflows/test-kfp.yml +++ b/.github/workflows/test-kfp.yml @@ -9,6 +9,7 @@ on: tags: - "*" paths: + - ".make.*" - "kfp/**" - "!kfp/**.md" - "!kfp/**/test/**" @@ -27,6 +28,7 @@ on: - "dev" - "releases/**" paths: + - ".make.*" - "kfp/**" - "!kfp/**/test/**" - "!kfp/**.md" From 1f76fb86cc0ac67dcc05f22057dc3736af26237d Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 09:48:11 -0400 Subject: [PATCH 55/70] noop change to .make.defualts to trigger builds Signed-off-by: David Wood --- .make.defaults | 1 - 1 file changed, 1 deletion(-) diff --git a/.make.defaults b/.make.defaults index a7cc023ba..652cdd128 100644 --- a/.make.defaults +++ b/.make.defaults @@ -22,7 +22,6 @@ # To augment the the clean rule # clean: .clean # rm -rf other-stuff -# ####################################################################################### SHELL=/bin/bash include $(REPOROOT)/.make.versions From b843cc1bd943fdc8f06d81dc00eaf18e670c9b77 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 09:55:46 -0400 Subject: [PATCH 56/70] add .make.* to triggers for test-misc and test-packaging workflows Signed-off-by: David Wood --- .github/workflows/test-misc.yml | 2 ++ .github/workflows/test-packaging-python.yml | 2 ++ .github/workflows/test-packaging-ray.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/test-misc.yml b/.github/workflows/test-misc.yml index 2c601bbd5..2fa493f03 100644 --- a/.github/workflows/test-misc.yml +++ b/.github/workflows/test-misc.yml @@ -9,6 +9,7 @@ on: tags: - "*" paths-ignore: + - ".make.*" - "**.md" - "examples/**" - "**/doc/**" @@ -20,6 +21,7 @@ on: - "dev" - "releases/**" paths-ignore: + - ".make.*" - "**.md" - "examples/**" - "**/doc/**" diff --git a/.github/workflows/test-packaging-python.yml b/.github/workflows/test-packaging-python.yml index 4ee491c8e..12b8a25c9 100644 --- a/.github/workflows/test-packaging-python.yml +++ b/.github/workflows/test-packaging-python.yml @@ -9,6 +9,7 @@ on: tags: - "*" paths: + - ".make.*" - "transforms/packaging/python/**" - "!**.md" - "!**/doc/**" @@ -19,6 +20,7 @@ on: - "dev" - "releases/**" paths: + - ".make.*" - "transforms/packaging/python/**" - "!**.md" - "!**/doc/**" diff --git a/.github/workflows/test-packaging-ray.yml b/.github/workflows/test-packaging-ray.yml index 4b812540c..3ee62c95b 100644 --- a/.github/workflows/test-packaging-ray.yml +++ b/.github/workflows/test-packaging-ray.yml @@ -9,6 +9,7 @@ on: tags: - "*" paths: + - ".make.*" - "transforms/packaging/ray/**" - "!**.md" - "!**/doc/**" @@ -19,6 +20,7 @@ on: - "dev" - "releases/**" paths: + - ".make.*" - "transforms/packaging/ray/**" - "!**.md" - "!**/doc/**" From 0219dc74af7ece8852f78ead5bb93471c095e79e Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 10:06:28 -0400 Subject: [PATCH 57/70] add .make.* to test-data-processing workflow Signed-off-by: David Wood --- .github/workflows/test-processing-lib.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-processing-lib.yml b/.github/workflows/test-processing-lib.yml index ddcdd9565..f3cd6eb9a 100644 --- a/.github/workflows/test-processing-lib.yml +++ b/.github/workflows/test-processing-lib.yml @@ -10,6 +10,7 @@ on: - "*" paths: # Note: the transform workflows are expected to trigger when data-processing-lib/** changes + - ".make.*" - "data-processing-lib/**" - "!data-processing-lib/**.md" - "!data-processing-lib/**/doc/**" @@ -20,6 +21,7 @@ on: - "releases/**" paths: # Note: the transform workflows are expected to trigger when data-processing-lib/** changes + - ".make.*" - "data-processing-lib/**" - "!data-processing-lib/**.md" - "!data-processing-lib/**/doc/**" From c6709d511fdd473a37f4b4137ed6cbc4e84aed09 Mon Sep 17 00:00:00 2001 From: SHAHROKH DAIJAVAD Date: Mon, 14 Oct 2024 11:52:03 -0700 Subject: [PATCH 58/70] Update README.md Added a checkmark for KFP for HAP to the table --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2b46e2b7..124dabe15 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ The matrix below shows the the combination of modules and supported runtimes. Al | **Data Ingestion** | | | | | | [Code (from zip) to Parquet](transforms/code/code2parquet/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | [PDF to Parquet](transforms/language/pdf2parquet/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| [HTML to Parquet](transforms/language/html2parquet/python/README.md) | :white_check_mark: | :white_check_mark: | | | +| [HTML to Parquet](transforms/language/html2parquet/python/README.md) | :white_check_mark: | :white_check_mark: | | | | **Universal (Code & Language)** | | | | | | [Exact dedup filter](transforms/universal/ededup/ray/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | [Fuzzy dedup filter](transforms/universal/fdedup/ray/README.md) | | :white_check_mark: | | :white_check_mark: | @@ -141,7 +141,7 @@ The matrix below shows the the combination of modules and supported runtimes. Al | [Filter on annotations](transforms/universal/filter/python/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [Profiler](transforms/universal/profiler/ray/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [Resize](transforms/universal/resize/python/README.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [HAP](transforms/universal/hap/python/README.md) | :white_check_mark: | :white_check_mark: | | | +| [HAP](transforms/universal/hap/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | [Tokenizer](transforms/universal/tokenization/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | **Language-only** | | | | | | [Language identification](transforms/language/lang_id/python/README.md) | :white_check_mark: | :white_check_mark: | | :white_check_mark: | From ea2cc23d22e48a97fa07d083ba736ea24b0ee7ef Mon Sep 17 00:00:00 2001 From: blublinsky Date: Thu, 10 Oct 2024 19:05:39 +0100 Subject: [PATCH 59/70] added folder_transform --- .../pure_python/transform_file_processor.py | 15 ++++-- .../pure_python/transform_orchestrator.py | 42 ++++++++++------ .../runtime/transform_file_processor.py | 41 ++++++++------- .../src/data_processing/transform/__init__.py | 2 + .../transform/abstract_transform.py | 16 ++++++ .../transform/binary_transform.py | 5 +- .../transform/folder_transform.py | 50 +++++++++++++++++++ .../runtime/ray/transform_file_processor.py | 1 + .../runtime/ray/transform_orchestrator.py | 19 ++++--- .../runtime/spark/transform_file_processor.py | 5 +- .../runtime/spark/transform_orchestrator.py | 25 +++++++--- 11 files changed, 168 insertions(+), 53 deletions(-) create mode 100644 data-processing-lib/python/src/data_processing/transform/abstract_transform.py create mode 100644 data-processing-lib/python/src/data_processing/transform/folder_transform.py diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py index 143835dd0..fa3e69e4a 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py @@ -14,7 +14,7 @@ from data_processing.data_access import DataAccessFactoryBase from data_processing.runtime import AbstractTransformFileProcessor -from data_processing.transform import AbstractBinaryTransform, TransformStatistics +from data_processing.transform import AbstractTransform, TransformStatistics from data_processing.utils import UnrecoverableException @@ -28,7 +28,8 @@ def __init__( data_access_factory: DataAccessFactoryBase, statistics: TransformStatistics, transform_params: dict[str, Any], - transform_class: type[AbstractBinaryTransform], + transform_class: type[AbstractTransform], + is_folder: bool, ): """ Init method @@ -36,11 +37,13 @@ def __init__( :param statistics - reference to statistics class :param transform_params - transform parameters :param transform_class: transform class + :param is_folder: folder transform flag """ # invoke superclass super().__init__( data_access_factory=data_access_factory, transform_parameters=dict(transform_params), + is_folder=is_folder, ) self.transform_params["statistics"] = statistics # Create local processor @@ -52,7 +55,8 @@ def __init__( # Create statistics self.stats = statistics - def _publish_stats(self, stats: dict[str, Any]) -> None: + +def _publish_stats(self, stats: dict[str, Any]) -> None: self.stats.add_stats(stats) @@ -65,17 +69,20 @@ def __init__( self, data_access_factory: DataAccessFactoryBase, transform_params: dict[str, Any], - transform_class: type[AbstractBinaryTransform], + transform_class: type[AbstractTransform], + is_folder: bool ): """ Init method :param data_access_factory - data access factory :param transform_params - transform parameters :param transform_class: transform class + :param is_folder: folder tranform flag """ super().__init__( data_access_factory=data_access_factory, transform_parameters=dict(transform_params), + is_folder=is_folder, ) # Add data access and statistics to the processor parameters self.transform_params["data_access"] = self.data_access diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py index 8692da29e..153eaaf0a 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py @@ -24,7 +24,7 @@ PythonTransformFileProcessor, PythonTransformRuntimeConfiguration, ) -from data_processing.transform import AbstractBinaryTransform, TransformStatistics +from data_processing.transform import AbstractBinaryTransform, TransformStatistics, AbstractFolderTransform from data_processing.utils import GB, get_logger @@ -48,8 +48,6 @@ def _execution_resources() -> dict[str, Any]: "object_store": 0, } - - def orchestrate( data_access_factory: DataAccessFactoryBase, runtime_config: PythonTransformRuntimeConfiguration, @@ -74,15 +72,21 @@ def orchestrate( return 1 # create additional execution parameters runtime = runtime_config.create_transform_runtime() + is_folder = issubclass(runtime_config.get_transform_class(), AbstractFolderTransform) try: - # Get files to process - files, profile, retries = data_access.get_files_to_process() - if len(files) == 0: - logger.error("No input files to process - exiting") - return 0 - if retries > 0: - statistics.add_stats({"data access retries": retries}) - logger.info(f"Number of files is {len(files)}, source profile {profile}") + if is_folder: + # folder transform + files = AbstractFolderTransform.get_folders(data_access=data_access) + logger.info(f"Number of folders is {len(files)}") + else: + # Get files to process + files, profile, retries = data_access.get_files_to_process() + if len(files) == 0: + logger.error("No input files to process - exiting") + return 0 + if retries > 0: + statistics.add_stats({"data access retries": retries}) + logger.info(f"Number of files is {len(files)}, source profile {profile}") # Print interval print_interval = int(len(files) / 100) if print_interval == 0: @@ -99,6 +103,7 @@ def orchestrate( data_access_factory=data_access_factory, statistics=statistics, files=files ), transform_class=runtime_config.get_transform_class(), + is_folder=is_folder, ) else: # using sequential execution @@ -111,6 +116,7 @@ def orchestrate( data_access_factory=data_access_factory, statistics=statistics, files=files ), transform_class=runtime_config.get_transform_class(), + is_folder=is_folder, ) status = "success" return_code = 0 @@ -157,7 +163,8 @@ def _process_transforms( data_access_factory: DataAccessFactoryBase, statistics: TransformStatistics, transform_params: dict[str, Any], - transform_class: type[AbstractBinaryTransform], + transform_class: type[AbstractTransform], + is_folder: bool, ) -> None: """ Process transforms sequentially @@ -167,9 +174,8 @@ def _process_transforms( :param data_access_factory: data access factory :param transform_params - transform parameters :param transform_class: transform class + :param is_folder: folder transform flag :return: metadata for the execution - - :return: None """ # create executor executor = PythonTransformFileProcessor( @@ -177,6 +183,7 @@ def _process_transforms( statistics=statistics, transform_params=transform_params, transform_class=transform_class, + is_folder=is_folder, ) # process data t_start = time.time() @@ -203,6 +210,7 @@ def _process_transforms_multiprocessor( data_access_factory: DataAccessFactoryBase, transform_params: dict[str, Any], transform_class: type[AbstractBinaryTransform], + is_folder: bool ) -> TransformStatistics: """ Process transforms using multiprocessing pool @@ -212,13 +220,17 @@ def _process_transforms_multiprocessor( :param data_access_factory: data access factory :param transform_params - transform parameters :param transform_class: transform class + :param is_folder: folder transform class :return: metadata for the execution """ # result statistics statistics = TransformStatistics() # create processor processor = PythonPoolTransformFileProcessor( - data_access_factory=data_access_factory, transform_params=transform_params, transform_class=transform_class + data_access_factory=data_access_factory, + transform_params=transform_params, + transform_class=transform_class, + is_folder=is_folder, ) completed = 0 t_start = time.time() diff --git a/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py b/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py index d4ec548d8..1d268875f 100644 --- a/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py +++ b/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py @@ -26,11 +26,13 @@ def __init__( self, data_access_factory: DataAccessFactoryBase, transform_parameters: dict[str, Any], + is_folder: bool = False, ): """ Init method :param data_access_factory: Data Access Factory :param transform_parameters: Transform parameters + :param is_folder: folder transform flag """ self.logger = get_logger(__name__) # validate parameters @@ -46,6 +48,7 @@ def __init__( # Add data access and statistics to the processor parameters self.transform_params = transform_parameters self.transform_params["data_access"] = self.data_access + self.is_folder = is_folder def process_file(self, f_name: str) -> None: """ @@ -58,25 +61,29 @@ def process_file(self, f_name: str) -> None: self.logger.warning("No data_access found. Returning.") return t_start = time.time() - # Read source file - filedata, retries = self.data_access.get_file(path=f_name) - if retries > 0: - self._publish_stats({"data access retries": retries}) - if filedata is None: - self.logger.warning(f"File read resulted in None for {f_name}. Returning.") - self._publish_stats({"failed_reads": 1}) - return - self._publish_stats({"source_files": 1, "source_size": len(filedata)}) + if not self.is_folder: + # Read source file only if we are processing file + filedata, retries = self.data_access.get_file(path=f_name) + if retries > 0: + self._publish_stats({"data access retries": retries}) + if filedata is None: + self.logger.warning(f"File read resulted in None for {f_name}. Returning.") + self._publish_stats({"failed_reads": 1}) + return + self._publish_stats({"source_files": 1, "source_size": len(filedata)}) # Process input file try: - # execute local processing - name_extension = TransformUtils.get_file_extension(f_name) self.logger.debug(f"Begin transforming file {f_name}") - out_files, stats = self.transform.transform_binary(file_name=f_name, byte_array=filedata) + if not self.is_folder: + # execute local processing + out_files, stats = self.transform.transform_binary(file_name=f_name, byte_array=filedata) + name_extension = TransformUtils.get_file_extension(f_name) + self.last_file_name = name_extension[0] + self.last_file_name_next_index = None + self.last_extension = name_extension[1] + else: + out_files, stats = self.transform.transform(folder_name=f_name) self.logger.debug(f"Done transforming file {f_name}, got {len(out_files)} files") - self.last_file_name = name_extension[0] - self.last_file_name_next_index = None - self.last_extension = name_extension[1] # save results self._submit_file(t_start=t_start, out_files=out_files, stats=stats) # Process unrecoverable exceptions @@ -95,10 +102,10 @@ def flush(self) -> None: the hook for them to return back locally stored data and their statistics. :return: None """ - if self.last_file_name is None: + if self.last_file_name is None or self.is_folder: # for some reason a given worker never processed anything. Happens in testing # when the amount of workers is greater than the amount of files - self.logger.debug("skipping flush, no name for file is defined") + self.logger.debug("skipping flush, no name for file is defined or this is a folder transform") return try: t_start = time.time() diff --git a/data-processing-lib/python/src/data_processing/transform/__init__.py b/data-processing-lib/python/src/data_processing/transform/__init__.py index 6af43ad60..20254e47b 100644 --- a/data-processing-lib/python/src/data_processing/transform/__init__.py +++ b/data-processing-lib/python/src/data_processing/transform/__init__.py @@ -1,3 +1,5 @@ +from data_processing.transform.abstract_transform import AbstractTransform +from data_processing.transform.folder_transform import AbstractFolderTransform from data_processing.transform.binary_transform import AbstractBinaryTransform from data_processing.transform.table_transform import AbstractTableTransform from data_processing.transform.transform_statistics import TransformStatistics diff --git a/data-processing-lib/python/src/data_processing/transform/abstract_transform.py b/data-processing-lib/python/src/data_processing/transform/abstract_transform.py new file mode 100644 index 000000000..89db70f42 --- /dev/null +++ b/data-processing-lib/python/src/data_processing/transform/abstract_transform.py @@ -0,0 +1,16 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +class AbstractTransform: + """ + Base class for all transform types + """ \ No newline at end of file diff --git a/data-processing-lib/python/src/data_processing/transform/binary_transform.py b/data-processing-lib/python/src/data_processing/transform/binary_transform.py index 80dff61ea..b313aff2f 100644 --- a/data-processing-lib/python/src/data_processing/transform/binary_transform.py +++ b/data-processing-lib/python/src/data_processing/transform/binary_transform.py @@ -10,10 +10,11 @@ # limitations under the License. ################################################################################ -from typing import Any, TypeVar +from typing import Any +from data_processing.transform import AbstractTransform -class AbstractBinaryTransform: +class AbstractBinaryTransform(AbstractTransform): """ Converts input binary file to output file(s) (binary) Sub-classes must provide the transform() method to provide the conversion of one binary files to 0 or diff --git a/data-processing-lib/python/src/data_processing/transform/folder_transform.py b/data-processing-lib/python/src/data_processing/transform/folder_transform.py new file mode 100644 index 000000000..866e3286f --- /dev/null +++ b/data-processing-lib/python/src/data_processing/transform/folder_transform.py @@ -0,0 +1,50 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +from typing import Any +from data_processing.data_access import data_access +from data_processing.transform import AbstractTransform + + +class AbstractFolderTransform(AbstractTransform): + """ + Converts input folder to output file(s) (binary) + Sub-classes must provide the transform() method to provide the conversion of a folder to 0 or + more new binary files and metadata. + """ + + def __init__(self, config: dict[str, Any]): + """ + Initialize based on the dictionary of configuration information. + This simply stores the given instance in this instance for later use. + """ + self.config = config + + def transform(self, folder_name: str) -> tuple[list[tuple[bytes, str]], dict[str, Any]]: + """ + Converts input folder into o or more output files. + If there is an error, an exception must be raised - exit()ing is not generally allowed. + :param folder_name: the name of the folder containing arbitrary amount of files. + :return: a tuple of a list of 0 or more tuples and a dictionary of statistics that will be propagated + to metadata. Each element of the return list, is a tuple of the transformed bytes and a string + holding the extension to be used when writing out the new bytes. + """ + raise NotImplemented() + + @staticmethod + def get_folders(data_access:data_access) -> list(str): + """ + Compute the list of folders to use. + :param data_access - data access class + :return: + """ + raise NotImplemented() diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_file_processor.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_file_processor.py index e1fabb144..cdad1309f 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_file_processor.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_file_processor.py @@ -35,6 +35,7 @@ def __init__(self, params: dict[str, Any]): super().__init__( data_access_factory=params.get("data_access_factory", None), transform_parameters=dict(params.get("transform_params", {})), + is_folder=params.get("is_folder", False) ) # Create statistics self.stats = params.get("statistics", None) diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py index 42eba47a6..8276eb56c 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py @@ -16,6 +16,7 @@ import ray from data_processing.data_access import DataAccessFactoryBase +from data_processing.transform import AbstractFolderTransform from data_processing_ray.runtime.ray import ( RayTransformExecutionConfiguration, RayTransformFileProcessor, @@ -56,13 +57,18 @@ def orchestrate( # create transformer runtime runtime = runtime_config.create_transform_runtime() resources = RayUtils.get_cluster_resources() + is_folder = issubclass(runtime_config.get_transform_class(), AbstractFolderTransform) try: - # Get files to process - files, profile, retries = data_access.get_files_to_process() - if len(files) == 0: - logger.error("No input files to process - exiting") - return 0 - logger.info(f"Number of files is {len(files)}, source profile {profile}") + if is_folder: + # folder transform + files = AbstractFolderTransform.get_folders(data_access=data_access) + logger.info(f"Number of folders is {len(files)}") # Get files to process + else: + files, profile, retries = data_access.get_files_to_process() + if len(files) == 0: + logger.error("No input files to process - exiting") + return 0 + logger.info(f"Number of files is {len(files)}, source profile {profile}") # Print interval print_interval = int(len(files) / 100) if print_interval == 0: @@ -84,6 +90,7 @@ def orchestrate( data_access_factory=data_access_factory, statistics=statistics, files=files ), "statistics": statistics, + "is_folder": is_folder, } logger.debug("Creating actors") processors = RayUtils.create_actors( diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_file_processor.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_file_processor.py index d63664ac4..a0968ab1d 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_file_processor.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_file_processor.py @@ -29,12 +29,15 @@ def __init__( data_access_factory: DataAccessFactoryBase, runtime_configuration: SparkTransformRuntimeConfiguration, statistics: TransformStatistics, + is_folder: bool, ): """ Init method """ super().__init__( - data_access_factory=data_access_factory, transform_parameters=runtime_configuration.get_transform_params() + data_access_factory=data_access_factory, + transform_parameters=runtime_configuration.get_transform_params(), + is_folder=is_folder, ) # Add data access ant statistics to the processor parameters self.runtime_configuration = runtime_configuration diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py index c279f2b73..c534b685f 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py @@ -18,7 +18,7 @@ import yaml from data_processing.data_access import DataAccessFactoryBase -from data_processing.transform import TransformStatistics +from data_processing.transform import TransformStatistics, AbstractFolderTransform from data_processing.utils import GB, get_logger from data_processing_spark.runtime.spark import ( SparkTransformExecutionConfiguration, @@ -117,7 +117,10 @@ def process_partition(iterator): runtime = runtime_conf.create_transform_runtime() # create file processor file_processor = SparkTransformFileProcessor( - data_access_factory=d_access_factory, runtime_configuration=runtime_conf, statistics=statistics + data_access_factory=d_access_factory, + runtime_configuration=runtime_conf, + statistics=statistics, + is_folder=is_folder, ) first = True for f in iterator: @@ -144,13 +147,19 @@ def process_partition(iterator): return list(statistics.get_execution_stats().items()) num_partitions = 0 + is_folder = issubclass(runtime_config.get_transform_class(), AbstractFolderTransform) try: - # Get files to process - files, profile, retries = data_access.get_files_to_process() - if len(files) == 0: - logger.error("No input files to process - exiting") - return 0 - logger.info(f"Number of files is {len(files)}, source profile {profile}") + if is_folder: + # folder transform + files = AbstractFolderTransform.get_folders(data_access=data_access) + logger.info(f"Number of folders is {len(files)}") # Get files to process + else: + # Get files to process + files, profile, retries = data_access.get_files_to_process() + if len(files) == 0: + logger.error("No input files to process - exiting") + return 0 + logger.info(f"Number of files is {len(files)}, source profile {profile}") # process data logger.debug("Begin processing files") # process files split by partitions From 4d30ac3cc8ade8e94569f52533e92260d60f5af2 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Thu, 10 Oct 2024 19:13:01 +0100 Subject: [PATCH 60/70] added folder_transform --- .../runtime/pure_python/transform_orchestrator.py | 2 +- .../python/src/data_processing/transform/folder_transform.py | 4 ++-- .../data_processing_ray/runtime/ray/transform_orchestrator.py | 2 +- .../runtime/spark/transform_orchestrator.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py index 153eaaf0a..d51f80a8a 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py @@ -76,7 +76,7 @@ def orchestrate( try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(data_access=data_access) + files = AbstractFolderTransform.get_folders(d_access=data_access) logger.info(f"Number of folders is {len(files)}") else: # Get files to process diff --git a/data-processing-lib/python/src/data_processing/transform/folder_transform.py b/data-processing-lib/python/src/data_processing/transform/folder_transform.py index 866e3286f..eca191bbb 100644 --- a/data-processing-lib/python/src/data_processing/transform/folder_transform.py +++ b/data-processing-lib/python/src/data_processing/transform/folder_transform.py @@ -41,10 +41,10 @@ def transform(self, folder_name: str) -> tuple[list[tuple[bytes, str]], dict[str raise NotImplemented() @staticmethod - def get_folders(data_access:data_access) -> list(str): + def get_folders(d_access: data_access) -> list(str): """ Compute the list of folders to use. - :param data_access - data access class + :param d_access - data access class :return: """ raise NotImplemented() diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py index 8276eb56c..a8ff95729 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py @@ -61,7 +61,7 @@ def orchestrate( try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(data_access=data_access) + files = AbstractFolderTransform.get_folders(d_access=data_access) logger.info(f"Number of folders is {len(files)}") # Get files to process else: files, profile, retries = data_access.get_files_to_process() diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py index c534b685f..4a0897952 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py @@ -151,7 +151,7 @@ def process_partition(iterator): try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(data_access=data_access) + files = AbstractFolderTransform.get_folders(d_access=data_access) logger.info(f"Number of folders is {len(files)}") # Get files to process else: # Get files to process From a772d1d876c1a5d70c75ab687a41b6b69610d9f7 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Thu, 10 Oct 2024 21:00:43 +0100 Subject: [PATCH 61/70] added folder_transform --- .../runtime/pure_python/transform_file_processor.py | 3 +-- .../runtime/pure_python/transform_orchestrator.py | 11 ++++++----- .../runtime/pure_python/transform_runtime.py | 10 +++++++++- .../data_processing/transform/folder_transform.py | 12 +----------- .../runtime/ray/transform_orchestrator.py | 2 +- .../runtime/ray/transform_runtime.py | 10 +++++++++- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py index fa3e69e4a..44ccd0ef0 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_file_processor.py @@ -55,8 +55,7 @@ def __init__( # Create statistics self.stats = statistics - -def _publish_stats(self, stats: dict[str, Any]) -> None: + def _publish_stats(self, stats: dict[str, Any]) -> None: self.stats.add_stats(stats) diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py index d51f80a8a..812be8caf 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_orchestrator.py @@ -24,14 +24,13 @@ PythonTransformFileProcessor, PythonTransformRuntimeConfiguration, ) -from data_processing.transform import AbstractBinaryTransform, TransformStatistics, AbstractFolderTransform +from data_processing.transform import AbstractTransform, TransformStatistics, AbstractFolderTransform from data_processing.utils import GB, get_logger logger = get_logger(__name__) -@staticmethod def _execution_resources() -> dict[str, Any]: """ Get Execution resource @@ -48,6 +47,7 @@ def _execution_resources() -> dict[str, Any]: "object_store": 0, } + def orchestrate( data_access_factory: DataAccessFactoryBase, runtime_config: PythonTransformRuntimeConfiguration, @@ -76,7 +76,7 @@ def orchestrate( try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(d_access=data_access) + files = runtime.get_folders(data_access=data_access) logger.info(f"Number of folders is {len(files)}") else: # Get files to process @@ -145,7 +145,8 @@ def orchestrate( "job_input_params": input_params | data_access_factory.get_input_params() | execution_config.get_input_params(), - "execution_stats": _execution_resources() | {"execution time, min": round((time.time() - start_time) / 60.0, 3)}, + "execution_stats": _execution_resources() | + {"execution time, min": round((time.time() - start_time) / 60.0, 3)}, "job_output_stats": stats, } logger.debug(f"Saving job metadata: {metadata}.") @@ -209,7 +210,7 @@ def _process_transforms_multiprocessor( print_interval: int, data_access_factory: DataAccessFactoryBase, transform_params: dict[str, Any], - transform_class: type[AbstractBinaryTransform], + transform_class: type[AbstractTransform], is_folder: bool ) -> TransformStatistics: """ diff --git a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_runtime.py b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_runtime.py index 4173154ae..478d40837 100644 --- a/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_runtime.py +++ b/data-processing-lib/python/src/data_processing/runtime/pure_python/transform_runtime.py @@ -12,7 +12,7 @@ from typing import Any -from data_processing.data_access import DataAccessFactoryBase +from data_processing.data_access import DataAccessFactoryBase, DataAccess from data_processing.transform import TransformStatistics @@ -28,6 +28,14 @@ def __init__(self, params: dict[str, Any]): """ self.params = params + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + raise NotImplemented() + def get_transform_config( self, data_access_factory: DataAccessFactoryBase, statistics: TransformStatistics, files: list[str] ) -> dict[str, Any]: diff --git a/data-processing-lib/python/src/data_processing/transform/folder_transform.py b/data-processing-lib/python/src/data_processing/transform/folder_transform.py index eca191bbb..9a2fb3713 100644 --- a/data-processing-lib/python/src/data_processing/transform/folder_transform.py +++ b/data-processing-lib/python/src/data_processing/transform/folder_transform.py @@ -11,7 +11,6 @@ ################################################################################ from typing import Any -from data_processing.data_access import data_access from data_processing.transform import AbstractTransform @@ -38,13 +37,4 @@ def transform(self, folder_name: str) -> tuple[list[tuple[bytes, str]], dict[str to metadata. Each element of the return list, is a tuple of the transformed bytes and a string holding the extension to be used when writing out the new bytes. """ - raise NotImplemented() - - @staticmethod - def get_folders(d_access: data_access) -> list(str): - """ - Compute the list of folders to use. - :param d_access - data access class - :return: - """ - raise NotImplemented() + raise NotImplemented() \ No newline at end of file diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py index a8ff95729..b29682997 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py @@ -61,7 +61,7 @@ def orchestrate( try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(d_access=data_access) + files = runtime.get_folders(data_access=data_access) logger.info(f"Number of folders is {len(files)}") # Get files to process else: files, profile, retries = data_access.get_files_to_process() diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_runtime.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_runtime.py index 57f071406..64479302c 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_runtime.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_runtime.py @@ -12,7 +12,7 @@ from typing import Any -from data_processing.data_access import DataAccessFactoryBase +from data_processing.data_access import DataAccessFactoryBase, DataAccess from ray.actor import ActorHandle @@ -28,6 +28,14 @@ def __init__(self, params: dict[str, Any]): """ self.params = params + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + raise NotImplemented() + def get_transform_config( self, data_access_factory: DataAccessFactoryBase, statistics: ActorHandle, files: list[str] ) -> dict[str, Any]: From 2f28ab4457cfa1f3c5bbf109cdccc0a85b90cb6c Mon Sep 17 00:00:00 2001 From: blublinsky Date: Fri, 11 Oct 2024 08:48:00 +0100 Subject: [PATCH 62/70] added folder_transform --- .../runtime/spark/transform_orchestrator.py | 3 ++- .../runtime/spark/transform_runtime.py | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py index 4a0897952..096fab272 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_orchestrator.py @@ -151,7 +151,8 @@ def process_partition(iterator): try: if is_folder: # folder transform - files = AbstractFolderTransform.get_folders(d_access=data_access) + runtime = runtime_config.create_transform_runtime() + files = runtime.get_folders(data_access=data_access) logger.info(f"Number of folders is {len(files)}") # Get files to process else: # Get files to process diff --git a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py index 7b968b1e9..7410d09d1 100644 --- a/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py +++ b/data-processing-lib/spark/src/data_processing_spark/runtime/spark/transform_runtime.py @@ -12,7 +12,7 @@ from typing import Any -from data_processing.data_access import DataAccessFactoryBase +from data_processing.data_access import DataAccessFactoryBase, DataAccess from data_processing.transform import TransformStatistics @@ -28,6 +28,14 @@ def __init__(self, params: dict[str, Any]): """ self.params = params + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + raise NotImplemented() + def get_transform_config( self, partition: int, data_access_factory: DataAccessFactoryBase, statistics: TransformStatistics ) -> dict[str, Any]: From b3588efa391c714c93c0a8f19026636b16404e34 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Fri, 11 Oct 2024 15:35:00 +0100 Subject: [PATCH 63/70] added noop testing --- .../runtime/transform_file_processor.py | 44 +++++--- .../test_support/transform/__init__.py | 13 ++- .../transform/noop_folder_transform.py | 105 ++++++++++++++++++ .../test_support/transform/noop_transform.py | 6 +- .../transform/folder_transform.py | 2 +- .../transform/transform_configuration.py | 6 +- .../transform/test_folders_noop.py | 33 ++++++ .../launch/ray/ray_test_noop_launch.py | 6 - .../ededup/ray/src/ededup_transform_ray.py | 9 +- 9 files changed, 187 insertions(+), 37 deletions(-) create mode 100644 data-processing-lib/python/src/data_processing/test_support/transform/noop_folder_transform.py create mode 100644 data-processing-lib/python/test/data_processing_tests/transform/test_folders_noop.py diff --git a/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py b/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py index 1d268875f..4075f40be 100644 --- a/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py +++ b/data-processing-lib/python/src/data_processing/runtime/transform_file_processor.py @@ -83,6 +83,7 @@ def process_file(self, f_name: str) -> None: self.last_extension = name_extension[1] else: out_files, stats = self.transform.transform(folder_name=f_name) + self.last_file_name = f_name self.logger.debug(f"Done transforming file {f_name}, got {len(out_files)} files") # save results self._submit_file(t_start=t_start, out_files=out_files, stats=stats) @@ -148,15 +149,21 @@ def _submit_file(self, t_start: float, out_files: list[tuple[bytes, str]], stats ) case 1: # we have exactly 1 output file - file_ext = out_files[0] - lfn = self.last_file_name - if self.last_file_name_next_index is not None: - lfn = f"{lfn}_{self.last_file_name_next_index}" - output_name = self.data_access.get_output_location(path=f"{lfn}{file_ext[1]}") + if self.is_folder: + # its folder + output_name = out_files[0][1] + dt = out_files[0][0] + else: + file_ext = out_files[0] + lfn = self.last_file_name + if self.last_file_name_next_index is not None: + lfn = f"{lfn}_{self.last_file_name_next_index}" + output_name = self.data_access.get_output_location(path=f"{lfn}{file_ext[1]}") + dt = file_ext[0] self.logger.debug( f"Writing transformed file {self.last_file_name}{self.last_extension} to {output_name}" ) - save_res, retries = self.data_access.save_file(path=output_name, data=file_ext[0]) + save_res, retries = self.data_access.save_file(path=output_name, data=dt) if retries > 0: self._publish_stats({"data access retries": retries}) if save_res is None: @@ -166,7 +173,7 @@ def _submit_file(self, t_start: float, out_files: list[tuple[bytes, str]], stats self._publish_stats( { "result_files": 1, - "result_size": len(file_ext[0]), + "result_size": len(dt), "processing_time": time.time() - t_start, } ) @@ -183,14 +190,21 @@ def _submit_file(self, t_start: float, out_files: list[tuple[bytes, str]], stats start_index = 0 count = len(out_files) for index in range(count): - file_ext = out_files[index] - output_name_indexed = f"{output_file_name}_{start_index + index}{file_ext[1]}" - file_sizes += len(file_ext[0]) - self.logger.debug( - f"Writing transformed file {self.last_file_name}{self.last_extension}, {index + 1} " - f"of {count} to {output_name_indexed}" - ) - save_res, retries = self.data_access.save_file(path=output_name_indexed, data=file_ext[0]) + if self.is_folder: + # its a folder + output_name_indexed = out_files[index][1] + dt = out_files[index][0] + else: + # files + file_ext = out_files[index] + output_name_indexed = f"{output_file_name}_{start_index + index}{file_ext[1]}" + self.logger.debug( + f"Writing transformed file {self.last_file_name}{self.last_extension}, {index + 1} " + f"of {count} to {output_name_indexed}" + ) + dt = file_ext[0] + file_sizes += len(dt) + save_res, retries = self.data_access.save_file(path=output_name_indexed, data=dt) if retries > 0: self._publish_stats({"data access retries": retries}) if save_res is None: diff --git a/data-processing-lib/python/src/data_processing/test_support/transform/__init__.py b/data-processing-lib/python/src/data_processing/test_support/transform/__init__.py index 0e90f7ffd..04d6f3b0f 100644 --- a/data-processing-lib/python/src/data_processing/test_support/transform/__init__.py +++ b/data-processing-lib/python/src/data_processing/test_support/transform/__init__.py @@ -1,6 +1,11 @@ -from .table_transform_test import AbstractTableTransformTest -from .binary_transform_test import AbstractBinaryTransformTest -from .noop_transform import ( +from data_processing.test_support.transform.table_transform_test import AbstractTableTransformTest +from data_processing.test_support.transform.binary_transform_test import AbstractBinaryTransformTest +from data_processing.test_support.transform.noop_transform import ( NOOPTransform, - NOOPPythonTransformConfiguration, + NOOPTransformConfiguration, + NOOPPythonTransformConfiguration ) +from data_processing.test_support.transform.noop_folder_transform import ( + NOOPFolderTransform, + NOOPFolderPythonTransformConfiguration +) \ No newline at end of file diff --git a/data-processing-lib/python/src/data_processing/test_support/transform/noop_folder_transform.py b/data-processing-lib/python/src/data_processing/test_support/transform/noop_folder_transform.py new file mode 100644 index 000000000..5baab7858 --- /dev/null +++ b/data-processing-lib/python/src/data_processing/test_support/transform/noop_folder_transform.py @@ -0,0 +1,105 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +import time +from typing import Any + +from data_processing.data_access import DataAccess +from data_processing.runtime.pure_python import ( + PythonTransformLauncher, + PythonTransformRuntimeConfiguration, + DefaultPythonTransformRuntime) +from data_processing.transform import AbstractFolderTransform +from data_processing.utils import get_logger +from data_processing.test_support.transform import NOOPTransformConfiguration + + +logger = get_logger(__name__) + + +class NOOPFolderTransform(AbstractFolderTransform): + """ + Implements a simple copy of a pyarrow Table. + """ + + def __init__(self, config: dict[str, Any]): + """ + Initialize based on the dictionary of configuration information. + This is generally called with configuration parsed from the CLI arguments defined + by the companion runtime, NOOPTransformRuntime. If running inside the RayMutatingDriver, + these will be provided by that class with help from the RayMutatingDriver. + """ + # Make sure that the param name corresponds to the name used in apply_input_params method + # of NOOPTransformConfiguration class + super().__init__(config) + self.sleep = config.get("sleep_sec", 1) + self.data_access = config.get("data_access") + + def transform(self, folder_name: str) -> tuple[list[tuple[bytes, str]], dict[str, Any]]: + """ + Converts input folder into o or more output files. + If there is an error, an exception must be raised - exit()ing is not generally allowed. + :param folder_name: the name of the folder containing arbitrary amount of files. + :return: a tuple of a list of 0 or more tuples and a dictionary of statistics that will be propagated + to metadata. Each element of the return list, is a tuple of the transformed bytes and a string + holding the file name to use. + """ + logger.debug(f"Transforming one folder {folder_name}") + metadata = {} + # get folder files + files, retries = self.data_access.get_folder_files(path=folder_name) + if retries > 0: + metadata |= {"data access retries": retries} + result = [()] * len(files) + index = 0 + for name, file in files.items(): + result[index] = (file, self.data_access.get_output_location(name)) + if self.sleep is not None: + logger.info(f"Sleep for {self.sleep} seconds") + time.sleep(self.sleep) + logger.info("Sleep completed - continue") + index += 1 + # Add some sample metadata. + metadata |= {"nfiles": len(files)} + return result, metadata + + +class NOOPFolderPythonRuntime(DefaultPythonTransformRuntime): + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + return [data_access.get_input_folder()] + + +class NOOPFolderPythonTransformConfiguration(PythonTransformRuntimeConfiguration): + """ + Implements the PythonTransformConfiguration for NOOP as required by the PythonTransformLauncher. + NOOP does not use a RayRuntime class so the superclass only needs the base + python-only configuration. + """ + + def __init__(self): + """ + Initialization + """ + super().__init__(transform_config=NOOPTransformConfiguration(clazz=NOOPFolderTransform), + runtime_class=NOOPFolderPythonRuntime) + + +if __name__ == "__main__": + # launcher = NOOPRayLauncher() + launcher = PythonTransformLauncher(NOOPFolderPythonTransformConfiguration()) + logger.info("Launching noop transform") + launcher.launch() diff --git a/data-processing-lib/python/src/data_processing/test_support/transform/noop_transform.py b/data-processing-lib/python/src/data_processing/test_support/transform/noop_transform.py index 0dee013a4..2fea35506 100644 --- a/data-processing-lib/python/src/data_processing/test_support/transform/noop_transform.py +++ b/data-processing-lib/python/src/data_processing/test_support/transform/noop_transform.py @@ -19,7 +19,7 @@ from data_processing.runtime.pure_python.runtime_configuration import ( PythonTransformRuntimeConfiguration, ) -from data_processing.transform import AbstractTableTransform, TransformConfiguration +from data_processing.transform import AbstractTableTransform, TransformConfiguration, AbstractTransform from data_processing.utils import CLIArgumentProvider, get_logger @@ -75,10 +75,10 @@ class NOOPTransformConfiguration(TransformConfiguration): configuration with CLI args. """ - def __init__(self): + def __init__(self, clazz: type[AbstractTransform] = NOOPTransform): super().__init__( name=short_name, - transform_class=NOOPTransform, + transform_class=clazz, remove_from_metadata=[pwd_key], ) diff --git a/data-processing-lib/python/src/data_processing/transform/folder_transform.py b/data-processing-lib/python/src/data_processing/transform/folder_transform.py index 9a2fb3713..caa3bfa52 100644 --- a/data-processing-lib/python/src/data_processing/transform/folder_transform.py +++ b/data-processing-lib/python/src/data_processing/transform/folder_transform.py @@ -35,6 +35,6 @@ def transform(self, folder_name: str) -> tuple[list[tuple[bytes, str]], dict[str :param folder_name: the name of the folder containing arbitrary amount of files. :return: a tuple of a list of 0 or more tuples and a dictionary of statistics that will be propagated to metadata. Each element of the return list, is a tuple of the transformed bytes and a string - holding the extension to be used when writing out the new bytes. + holding the file name to use. """ raise NotImplemented() \ No newline at end of file diff --git a/data-processing-lib/python/src/data_processing/transform/transform_configuration.py b/data-processing-lib/python/src/data_processing/transform/transform_configuration.py index 033e92f2a..a5c9ec9ad 100644 --- a/data-processing-lib/python/src/data_processing/transform/transform_configuration.py +++ b/data-processing-lib/python/src/data_processing/transform/transform_configuration.py @@ -13,7 +13,7 @@ from argparse import ArgumentParser from typing import Any -from data_processing.transform import AbstractBinaryTransform +from data_processing.transform import AbstractTransform from data_processing.utils import CLIArgumentProvider @@ -23,7 +23,7 @@ class TransformConfiguration(CLIArgumentProvider): """ def __init__( - self, name: str, transform_class: type[AbstractBinaryTransform], remove_from_metadata: list[str] = [] + self, name: str, transform_class: type[AbstractTransform], remove_from_metadata: list[str] = [] ): """ Initialization @@ -36,7 +36,7 @@ def __init__( self.remove_from_metadata = remove_from_metadata self.params = {} - def get_transform_class(self) -> type[AbstractBinaryTransform]: + def get_transform_class(self) -> type[AbstractTransform]: """ Get the class extending AbstractBinaryTransform which implements a specific transformation. The class will generally be instantiated with a dictionary of configuration produced by diff --git a/data-processing-lib/python/test/data_processing_tests/transform/test_folders_noop.py b/data-processing-lib/python/test/data_processing_tests/transform/test_folders_noop.py new file mode 100644 index 000000000..e0fdd86c8 --- /dev/null +++ b/data-processing-lib/python/test/data_processing_tests/transform/test_folders_noop.py @@ -0,0 +1,33 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +import os + +from data_processing.test_support.launch.transform_test import ( + AbstractTransformLauncherTest, +) +from data_processing.runtime.pure_python import PythonTransformLauncher +from data_processing.test_support.transform import NOOPFolderPythonTransformConfiguration + + +class TestRayNOOPTransform(AbstractTransformLauncherTest): + """ + Extends the super-class to define the test data for the tests defined there. + The name of this class MUST begin with the word Test so that pytest recognizes it as a test class. + """ + + def get_test_transform_fixtures(self) -> list[tuple]: + basedir = "../../../test-data/data_processing/python/noop/" + basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), basedir)) + launcher = PythonTransformLauncher(NOOPFolderPythonTransformConfiguration()) + fixtures = [(launcher, {"noop_sleep_sec": 0}, basedir + "/input", basedir + "/expected")] + return fixtures diff --git a/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_launch.py b/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_launch.py index d4cc874f0..e706a4dfa 100644 --- a/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_launch.py +++ b/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_launch.py @@ -12,7 +12,6 @@ import os -import pyarrow as pa from data_processing.test_support.launch.transform_test import ( AbstractTransformLauncherTest, ) @@ -20,11 +19,6 @@ from data_processing_ray.test_support.transform import NOOPRayTransformConfiguration -table = pa.Table.from_pydict({"name": pa.array(["Tom"]), "age": pa.array([23])}) -expected_table = table # We're a noop after all. -expected_metadata_list = [{"nfiles": 1, "nrows": 1}, {}] # transform() result # flush() result - - class TestRayNOOPTransform(AbstractTransformLauncherTest): """ Extends the super-class to define the test data for the tests defined there. diff --git a/transforms/universal/ededup/ray/src/ededup_transform_ray.py b/transforms/universal/ededup/ray/src/ededup_transform_ray.py index c0823a22e..d90dfa780 100644 --- a/transforms/universal/ededup/ray/src/ededup_transform_ray.py +++ b/transforms/universal/ededup/ray/src/ededup_transform_ray.py @@ -149,13 +149,12 @@ def _load_snapshots(self, data_access_factory: DataAccessFactoryBase, statistics statistics.add_stats.remote({"data access retries": retries}) self.logger.info(f"Found the following snapshot files {files.keys()}") # process snapshot files - for file in files.keys(): - # load the file + for file in files.values(): + # convert the file try: - b_hashes, _ = data_access.get_file(file) - snaps = pickle.loads(b_hashes) + snaps = pickle.loads(file) except Exception as e: - self.logger.warning(f"Failed to load hashes from file {file} with exception {e}") + self.logger.warning(f"Failed to load hashes with exception {e}") raise UnrecoverableException("failed to load hashes") request = [[] for _ in range(len(self.filters))] for h in snaps: From a0a1400069fdf02f7fbec7b029a888fcb8ec4211 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Sun, 13 Oct 2024 08:53:49 +0100 Subject: [PATCH 64/70] added noop Ray testing --- .../runtime/ray/transform_orchestrator.py | 6 +- .../test_support/transform/__init__.py | 1 + .../transform/noop_folder_transform.py | 57 +++++++++++++++++++ .../test_support/transform/noop_transform.py | 4 +- .../launch/ray/ray_test_noop_folder_launch.py | 33 +++++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py create mode 100644 data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_folder_launch.py diff --git a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py index b29682997..da39cbcf7 100644 --- a/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py +++ b/data-processing-lib/ray/src/data_processing_ray/runtime/ray/transform_orchestrator.py @@ -68,6 +68,9 @@ def orchestrate( if len(files) == 0: logger.error("No input files to process - exiting") return 0 + # log retries + if retries > 0: + statistics.add_stats.remote({"data access retries": retries}) logger.info(f"Number of files is {len(files)}, source profile {profile}") # Print interval print_interval = int(len(files) / 100) @@ -79,9 +82,6 @@ def orchestrate( logger.info( f"Number of workers - {preprocessing_params.n_workers} " f"with {preprocessing_params.worker_options} each" ) - # log retries - if retries > 0: - statistics.add_stats.remote({"data access retries": retries}) # create executors processor_params = { "data_access_factory": data_access_factory, diff --git a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/__init__.py b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/__init__.py index a6cd700f7..dd095c961 100644 --- a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/__init__.py +++ b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/__init__.py @@ -1 +1,2 @@ from data_processing_ray.test_support.transform.noop_transform import NOOPRayTransformConfiguration +from data_processing_ray.test_support.transform.noop_folder_transform import NOOPFolderRayTransformConfiguration diff --git a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py new file mode 100644 index 000000000..9919600c4 --- /dev/null +++ b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py @@ -0,0 +1,57 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + + +from data_processing.test_support.transform import NOOPTransformConfiguration +from data_processing.test_support.transform import NOOPFolderTransform +from data_processing.utils import get_logger +from data_processing_ray.runtime.ray import ( + RayTransformLauncher, + RayTransformRuntimeConfiguration, + DefaultRayTransformRuntime +) +from data_processing.data_access import DataAccess + + +logger = get_logger(__name__) + + +class NOOPFolderPythonRuntime(DefaultRayTransformRuntime): + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + return [data_access.get_input_folder()] + + +class NOOPFolderRayTransformConfiguration(RayTransformRuntimeConfiguration): + """ + Implements the RayTransformConfiguration for NOOP as required by the RayTransformLauncher. + NOOP does not use a RayRuntime class so the superclass only needs the base + python-only configuration. + """ + + def __init__(self): + """ + Initialization + """ + super().__init__(transform_config=NOOPTransformConfiguration(clazz=NOOPFolderTransform), + runtime_class=NOOPFolderPythonRuntime) + + +if __name__ == "__main__": + # launcher = NOOPRayLauncher() + launcher = RayTransformLauncher(NOOPFolderRayTransformConfiguration()) + logger.info("Launching noop transform") + launcher.launch() diff --git a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_transform.py b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_transform.py index 67cf20253..a2082c48c 100644 --- a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_transform.py +++ b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_transform.py @@ -11,9 +11,7 @@ ################################################################################ -from data_processing.test_support.transform.noop_transform import ( - NOOPTransformConfiguration, -) +from data_processing.test_support.transform import NOOPTransformConfiguration from data_processing.utils import get_logger from data_processing_ray.runtime.ray import ( RayTransformLauncher, diff --git a/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_folder_launch.py b/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_folder_launch.py new file mode 100644 index 000000000..cd61c6745 --- /dev/null +++ b/data-processing-lib/ray/test/data_processing_ray_tests/launch/ray/ray_test_noop_folder_launch.py @@ -0,0 +1,33 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +import os + +from data_processing.test_support.launch.transform_test import ( + AbstractTransformLauncherTest, +) +from data_processing_ray.runtime.ray import RayTransformLauncher +from data_processing_ray.test_support.transform import NOOPFolderRayTransformConfiguration + + +class TestRayNOOPTransform(AbstractTransformLauncherTest): + """ + Extends the super-class to define the test data for the tests defined there. + The name of this class MUST begin with the word Test so that pytest recognizes it as a test class. + """ + + def get_test_transform_fixtures(self) -> list[tuple]: + basedir = "../../../../test-data/data_processing/ray/noop/" + basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), basedir)) + launcher = RayTransformLauncher(NOOPFolderRayTransformConfiguration()) + fixtures = [(launcher, {"noop_sleep_sec": 0, "run_locally": True}, basedir + "/input", basedir + "/expected")] + return fixtures From c2dd53cc4c901552784979e561c1a72a272343c5 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Sun, 13 Oct 2024 09:07:48 +0100 Subject: [PATCH 65/70] added noop Spark testing --- .../transform/noop_folder_transform.py | 7 ++- .../test_support/transform/__init__.py | 1 + .../transform/noop_folder_transform.py | 53 +++++++++++++++++++ .../launch/spark/test_noop_folder_launch.py | 34 ++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 data-processing-lib/spark/src/data_processing_spark/test_support/transform/noop_folder_transform.py create mode 100644 data-processing-lib/spark/test/data_processing_spark_tests/launch/spark/test_noop_folder_launch.py diff --git a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py index 9919600c4..1d084b58a 100644 --- a/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py +++ b/data-processing-lib/ray/src/data_processing_ray/test_support/transform/noop_folder_transform.py @@ -11,8 +11,7 @@ ################################################################################ -from data_processing.test_support.transform import NOOPTransformConfiguration -from data_processing.test_support.transform import NOOPFolderTransform +from data_processing.test_support.transform import NOOPFolderTransform, NOOPTransformConfiguration from data_processing.utils import get_logger from data_processing_ray.runtime.ray import ( RayTransformLauncher, @@ -25,7 +24,7 @@ logger = get_logger(__name__) -class NOOPFolderPythonRuntime(DefaultRayTransformRuntime): +class NOOPFolderRayRuntime(DefaultRayTransformRuntime): def get_folders(self, data_access: DataAccess) -> list[str]: """ Get folders to process @@ -47,7 +46,7 @@ def __init__(self): Initialization """ super().__init__(transform_config=NOOPTransformConfiguration(clazz=NOOPFolderTransform), - runtime_class=NOOPFolderPythonRuntime) + runtime_class=NOOPFolderRayRuntime) if __name__ == "__main__": diff --git a/data-processing-lib/spark/src/data_processing_spark/test_support/transform/__init__.py b/data-processing-lib/spark/src/data_processing_spark/test_support/transform/__init__.py index 83516f9ae..041cb43d6 100644 --- a/data-processing-lib/spark/src/data_processing_spark/test_support/transform/__init__.py +++ b/data-processing-lib/spark/src/data_processing_spark/test_support/transform/__init__.py @@ -11,3 +11,4 @@ ################################################################################ from data_processing_spark.test_support.transform.noop_transform import NOOPSparkTransformConfiguration +from data_processing_spark.test_support.transform.noop_folder_transform import NOOPFolderSparkTransformConfiguration diff --git a/data-processing-lib/spark/src/data_processing_spark/test_support/transform/noop_folder_transform.py b/data-processing-lib/spark/src/data_processing_spark/test_support/transform/noop_folder_transform.py new file mode 100644 index 000000000..9972e0f79 --- /dev/null +++ b/data-processing-lib/spark/src/data_processing_spark/test_support/transform/noop_folder_transform.py @@ -0,0 +1,53 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the “License”); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an “AS IS” BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +from data_processing.test_support.transform import NOOPFolderTransform, NOOPTransformConfiguration +from data_processing.utils import get_logger +from data_processing_spark.runtime.spark import SparkTransformLauncher +from data_processing_spark.runtime.spark import SparkTransformRuntimeConfiguration, DefaultSparkTransformRuntime +from data_processing.data_access import DataAccess + + +logger = get_logger(__name__) + + +class NOOPFolderSparkRuntime(DefaultSparkTransformRuntime): + def get_folders(self, data_access: DataAccess) -> list[str]: + """ + Get folders to process + :param data_access: data access + :return: list of folders to process + """ + return [data_access.get_input_folder()] + + +class NOOPFolderSparkTransformConfiguration(SparkTransformRuntimeConfiguration): + """ + Implements the SparkTransformConfiguration for NOOP as required by the PythonTransformLauncher. + NOOP does not use a RayRuntime class so the superclass only needs the base + python-only configuration. + """ + + def __init__(self): + """ + Initialization + """ + super().__init__(transform_config=NOOPTransformConfiguration(clazz=NOOPFolderTransform), + runtime_class=NOOPFolderSparkRuntime) + + +if __name__ == "__main__": + # create launcher + launcher = SparkTransformLauncher(runtime_config=NOOPFolderSparkTransformConfiguration()) + logger.info("Launching noop transform") + # Launch the ray actor(s) to process the input + launcher.launch() diff --git a/data-processing-lib/spark/test/data_processing_spark_tests/launch/spark/test_noop_folder_launch.py b/data-processing-lib/spark/test/data_processing_spark_tests/launch/spark/test_noop_folder_launch.py new file mode 100644 index 000000000..c8e3ce40b --- /dev/null +++ b/data-processing-lib/spark/test/data_processing_spark_tests/launch/spark/test_noop_folder_launch.py @@ -0,0 +1,34 @@ +# (C) Copyright IBM Corp. 2024. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +import os + +from data_processing.test_support.launch.transform_test import ( + AbstractTransformLauncherTest, +) +from data_processing_spark.runtime.spark import SparkTransformLauncher +from data_processing_spark.test_support.transform import NOOPFolderSparkTransformConfiguration + + +class TestSparkNOOPTransform(AbstractTransformLauncherTest): + """ + Extends the super-class to define the test data for the tests defined there. + The name of this class MUST begin with the word Test so that pytest recognizes it as a test class. + """ + + def get_test_transform_fixtures(self) -> list[tuple]: + basedir = "../../../../test-data" + basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), basedir)) + fixtures = [] + launcher = SparkTransformLauncher(NOOPFolderSparkTransformConfiguration()) + fixtures.append((launcher, {"noop_sleep_sec": 1}, basedir + "/input", basedir + "/expected")) + return fixtures From 59d57df4f1873767c6d0b79ba7e69010004a5dc0 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Sun, 13 Oct 2024 10:03:21 +0100 Subject: [PATCH 66/70] more data access simplifications --- .../src/data_processing/data_access/data_access.py | 5 ++++- .../data_processing/data_access/data_access_local.py | 11 ----------- .../src/data_processing/data_access/data_access_s3.py | 11 ----------- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/data-processing-lib/python/src/data_processing/data_access/data_access.py b/data-processing-lib/python/src/data_processing/data_access/data_access.py index bba5afd2b..51d7b54b8 100644 --- a/data-processing-lib/python/src/data_processing/data_access/data_access.py +++ b/data-processing-lib/python/src/data_processing/data_access/data_access.py @@ -358,7 +358,10 @@ def get_output_location(self, path: str) -> str: :param path: input file location :return: output file location """ - raise NotImplementedError("Subclasses should implement this!") + if self.get_output_folder() is None: + self.logger.error("Get out put location. S3 configuration is not provided, returning None") + return None + return path.replace(self.get_input_folder(), self.get_output_folder()) def save_table(self, path: str, table: pa.Table) -> tuple[int, dict[str, Any], int]: """ diff --git a/data-processing-lib/python/src/data_processing/data_access/data_access_local.py b/data-processing-lib/python/src/data_processing/data_access/data_access_local.py index 224e30ce8..d37e571a3 100644 --- a/data-processing-lib/python/src/data_processing/data_access/data_access_local.py +++ b/data-processing-lib/python/src/data_processing/data_access/data_access_local.py @@ -130,17 +130,6 @@ def get_table(self, path: str) -> tuple[pa.table, int]: logger.error(f"Error reading table from {path}: {e}") return None, 0 - def get_output_location(self, path: str) -> str: - """ - Get output location based on input - :param path: input file location - :return: output file location - """ - if self.output_folder is None: - logger.error("Get output location. local configuration is not defined, returning None") - return None - return path.replace(self.input_folder, self.output_folder) - def save_table(self, path: str, table: pa.Table) -> tuple[int, dict[str, Any], int]: """ Saves a pyarrow table to a file and returns information about the operation. diff --git a/data-processing-lib/python/src/data_processing/data_access/data_access_s3.py b/data-processing-lib/python/src/data_processing/data_access/data_access_s3.py index 43e13bcb1..8ddc772c5 100644 --- a/data-processing-lib/python/src/data_processing/data_access/data_access_s3.py +++ b/data-processing-lib/python/src/data_processing/data_access/data_access_s3.py @@ -126,17 +126,6 @@ def get_table(self, path: str) -> tuple[pyarrow.table, int]: self.logger.error(f"Exception reading table {path} from S3 - {e}") return None, 0 - def get_output_location(self, path: str) -> str: - """ - Get output location based on input - :param path: input file location - :return: output file location - """ - if self.output_folder is None: - self.logger.error("Get out put location. S3 configuration is not provided, returning None") - return None - return path.replace(self.input_folder, self.output_folder) - def save_table(self, path: str, table: pyarrow.Table) -> tuple[int, dict[str, Any], int]: """ Save table to a given location From 7b7736c5301f84a5635a156dfa2416f066c409f7 Mon Sep 17 00:00:00 2001 From: blublinsky Date: Mon, 14 Oct 2024 20:56:16 +0100 Subject: [PATCH 67/70] documentation update --- data-processing-lib/doc/transforms.md | 34 +++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/data-processing-lib/doc/transforms.md b/data-processing-lib/doc/transforms.md index c8f1b74e8..fc3509ba3 100644 --- a/data-processing-lib/doc/transforms.md +++ b/data-processing-lib/doc/transforms.md @@ -1,26 +1,24 @@ # Transforms -[All transforms](../python/src/data_processing/transform/abstract_transform.py) -are generalized to operate on generically typed `DATA.` -[Ray](ray-runtime.md) and [Python](python-runtime.md) runtimes -currently support `DATA` as both byte arrays -and [pyarrow Tables](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html). -The [Spark runtime](spark-runtime.md) currently supports the native Spark `DataFrame`. +Transform is a basic integration unit of DPK that can be executed in any of the supported by the DPK +runtimes ([Python](python-runtime.md), [Ray](ray-runtime.md) and [Spark](spark-runtime.md)). All transforms +are derived from the +[AbstractTransform class](../python/src/data_processing/transform/abstract_transform.py). Theis class +provides no functionality and is used as just a marker that a given class implements transform. +There are currently two types of transforms defined in DPK: -All transforms convert their input `DATA` to a list of transformed `DATA` objects -and optional metadata about the transformation of the `DATA` instance. -The Transform itself need only be concerned with the conversion -of one `DATA` instance at a time. -Transforms, where possible, should be implemented without regard to the -runtime it will run in or where its configuration originates. +* [AbstractBinaryTransform](../python/src/data_processing/transform/binary_transform.py) which is a base +class for all data transforms. Data transforms convert a file of data producing zero or more data files +and metadata. A specific class of the binary transform is +[AbstractTableTransform](../python/src/data_processing/transform/table_transform.py) that consumes and produces +data files containing [pyarrow tables](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html) +* [AbstractFolderTransform](../python/src/data_processing/transform/folder_transform.py) which is a base +class consuming a folder (that can contain an arbitrary set of files, that need to be processed together) +and proces zero or more data files and metadata. -In the discussion that follows, we'll focus on the transformation of pyarrow Tables -using the `AbstractTableTransform` class (see below), supported by both -the Ray and Python runtimes. -Mapping from this tutorial to a Spark runtime can be done by using -`data-prep-kit-spark`'s [AbstractSparkTransform](../spark/src/data_processing_spark/runtime/spark/spark_transform.py) -which operates on a Spark DataFrame instead of a pyarrow Table. +In the discussion that follows, we'll focus on the transformation of pyarrow Tables +using the `AbstractTableTransform` class (see below), supported by Ray Spark and Python runtimes. #### AbstractTableTransform class [AbstractTableTransform](../python/src/data_processing/transform/table_transform.py) From 04c85d8d06a346d3bd7e73ef99f297f207c23328 Mon Sep 17 00:00:00 2001 From: Revital Sur Date: Mon, 14 Oct 2024 23:15:52 +0300 Subject: [PATCH 68/70] Update test-kfp.yml workflow. (#702) * Update .github/workflows/test-kfp.yml Signed-off-by: Revital Sur * Trigger test. Signed-off-by: Revital Sur * Minor fix. Signed-off-by: Revital Sur * Address review comments. Signed-off-by: Revital Sur * Run noop in test-kfp workflow instead of random transform. Signed-off-by: Revital Sur --------- Signed-off-by: Revital Sur --- .github/workflows/test-kfp.yml | 84 ++++++++------------------------- kfp/kfp_ray_components/Makefile | 2 +- 2 files changed, 20 insertions(+), 66 deletions(-) diff --git a/.github/workflows/test-kfp.yml b/.github/workflows/test-kfp.yml index 425f8ed45..60db612b7 100644 --- a/.github/workflows/test-kfp.yml +++ b/.github/workflows/test-kfp.yml @@ -49,9 +49,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -env: - KFP_BLACK_LIST: "doc_chunk-ray,pdf2parquet-ray,pii_redactor" - jobs: check_if_push_images: # check whether the Docker images should be pushed to the remote repository @@ -88,41 +85,19 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV - name: Test KFP libs (shared and v1) and run a workflow timeout-minutes: 120 run: | - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - make -C $K8S_SETUP_SCRIPTS setup + $PWD/scripts/workflow_helper.sh install-tools make -C kfp/kfp_support_lib test - source $K8S_SETUP_SCRIPTS/common.sh - while : - do - dir=("code" "universal" "language") && index=$(($RANDOM % ${#dir[@]})) && subdirs=${dir[$index]} && transforms=($(find transforms/$subdirs -type d -maxdepth 1 -mindepth 1 )) - set -- "${transforms[@]}" && transforms=("$@") && size=${#transforms[@]} && index=$(($RANDOM % $size)) - transform=$(basename "${transforms[$index]}") - if [ -d ${transforms[$index]}/kfp_ray ] && echo ${KFP_BLACK_LIST} | grep -qv ${transform} ; then - header_text "Running ${transforms[$index]} workflow test" - break - fi - done - make -C ${transforms[$index]} workflow-build - make -C ${transforms[$index]} workflow-test - echo "Run ${transforms[$index]} completed" - + $PWD/scripts/workflow_helper.sh test-workflow "transforms/universal/noop" + echo "Run noop completed" test-kfp-v2: runs-on: ubuntu-22.04 steps: @@ -137,41 +112,20 @@ jobs: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/powershell /usr/share/swift /usr/lib/jvm /usr/local/.ghcup sudo docker rmi $(docker image ls -aq) >/dev/null 2>&1 || true df -h + - name: Import environment variables + run: | + cat scripts/k8s-setup/requirements.env >> $GITHUB_ENV + echo "K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup" >> $GITHUB_ENV + echo "REPOROOT=$PWD" >> $GITHUB_ENV + echo "PATH=$PATH:/tmp" >> $GITHUB_ENV + echo "KFPv2=1" >> $GITHUB_ENV - name: Test KFP libs (shared and v2) and run a workflow timeout-minutes: 120 run: | - export REPOROOT=$PWD - export K8S_SETUP_SCRIPTS=$PWD/scripts/k8s-setup - source $K8S_SETUP_SCRIPTS/requirements.env - export PATH=$PATH:/tmp/ - curl -Lo /tmp/kind https://kind.sigs.k8s.io/dl/v${KIND_VERSION}/kind-linux-amd64 - chmod 777 /tmp/kind - curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 /tmp/get_helm.sh - HELM_INSTALL_DIR=/tmp/ /tmp/get_helm.sh -v v${HELM_VERSION} --no-sudo - chmod 777 /tmp/helm - curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /tmp/kubectl - chmod 777 /tmp/kubectl - curl https://dl.min.io/client/mc/release/linux-amd64/mc --create-dirs -o /tmp/mc - chmod +x /tmp/mc - export DEPLOY_KUBEFLOW=1 - export KFPv2=1 - make -C $K8S_SETUP_SCRIPTS setup + $PWD/scripts/workflow_helper.sh install-tools make -C kfp/kfp_support_lib test - source $K8S_SETUP_SCRIPTS/common.sh - while : - do - dir=("code" "universal" "language") && index=$(($RANDOM % ${#dir[@]})) && subdirs=${dir[$index]} && transforms=($(find transforms/$subdirs -type d -maxdepth 1 -mindepth 1 )) - set -- "${transforms[@]}" && transforms=("$@") && size=${#transforms[@]} && index=$(($RANDOM % $size)) - transform=$(basename "${transforms[$index]}") - if [ -d ${transforms[$index]}/kfp_ray ] && echo ${KFP_BLACK_LIST} | grep -qv ${transform} ; then - header_text "Running ${transforms[$index]} workflow test" - break - fi - done - make -C ${transforms[$index]} workflow-build - make -C ${transforms[$index]} workflow-test - header_text "Run ${transforms[$index]} completed" + $PWD/scripts/workflow_helper.sh test-workflow "transforms/universal/noop" + echo "Run noop completed" build-kfp-components: needs: [check_if_push_images] runs-on: ubuntu-22.04 diff --git a/kfp/kfp_ray_components/Makefile b/kfp/kfp_ray_components/Makefile index 4a4768a10..3ba8bfc00 100644 --- a/kfp/kfp_ray_components/Makefile +++ b/kfp/kfp_ray_components/Makefile @@ -42,7 +42,7 @@ image: Dockerfile requirements.txt .PHONE: set-versions set-versions: - @# Help: Update yaml files to build images tagged as version $(KFP_DOCKER_VERSION) + @# Help: Update yaml files to build images tagged as version $(KFP_DOCKER_VERSION). @$(MAKE) .reconcile-requirements FILE=createRayClusterComponent.yaml @$(MAKE) .reconcile-requirements FILE=deleteRayClusterComponent.yaml @$(MAKE) .reconcile-requirements FILE=executeRayJobComponent.yaml From 5b14137928896d77cae5cac9cd4adef1f8ee6396 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 16:16:49 -0400 Subject: [PATCH 69/70] Change the release script and documentation to bump the minor version instead of micro version. (#698) * minor logging/refinement changes to release script Signed-off-by: David Wood * bump minor instead of micro version in release-branch.sh Signed-off-by: David Wood * change release pr branch to pending-releases/vx.y.z Signed-off-by: David Wood * comments/logging in release-branch.sh Signed-off-by: David Wood * updated RELEASE.md to match recent changes to release-branch.sh Signed-off-by: David Wood * release documentation Signed-off-by: David Wood --------- Signed-off-by: David Wood --- RELEASE.md | 18 ++++++++++------ scripts/release-branch.sh | 43 +++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index ef3f51f29..cef64687b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -16,13 +16,17 @@ allows intermediate publishing from the main branch using version X.Y.Z.dev\ 1. Building and publishing is done manually, or soon via a git action, in the branch created by `scripts/release-branch.sh`. 1. Wheels can only be published once to pypi for a given version. 1. Transform and kfp images may be republished to the docker registry. +1. Releases done via the `release-branch.sh` script will have their micro version number set to 0 (e.g., 1.2.0) +1. Intermediate releases that bump the micro version may be done by individual transforms. This can mean +that version X.Y.Z of a transform is equivalent to the X.Y+1.0 release. The latter created when running +the `release-branch.sh` script. ## Cutting the release Creating the release involves -1. Edit the `release-notes.md` to list major/minor changes +1. Editing the `release-notes.md` to list major/minor changes and commit to the main branch. 1. Creating a release branch and updating the main branch versions (using `release-branch.sh`). -1. Creating a github release and tag from the release branch. +1. Creating a github release and tag from the release branch using the github web UI. 1. Building and publishing pypi library wheels and docker registry image. Each is discussed below. @@ -36,19 +40,21 @@ Commit this to the main branch so it is ready for including in the release branc The `scripts/release-branch.sh` is currently run manually to create the branch and tags as follows: 1. Creates the `releases/vX.Y.Z` from the main branch where `X.Y.Z` are defined in .make.versions -1. Creates the `vX.Y.Z` branch for PR'ing back into the `releases/vX.Y.Z` branch. -1. In the new `vX.Y.Z` branch +1. Creates the `pending-releases/vX.Y.Z` branch for PR'ing back into the `releases/vX.Y.Z` branch. +1. In the new `pending-releases/vX.Y.Z` branch 1. Nulls out the version suffix in the new branch's `.make.version` file. 1. Applies the unsuffixed versions to the artifacts published from the repo using `make set-versions`.. 1. Commits and pushes branch 1. Creates the `pending-version-change/vX.Y.Z` branch for PR'ing back into the main branch. + * Note: this branch is named with the new release version (i.e. vX.Y.Z), however + the version in this branch is actually X.Y+1.0.dev0. 1. In the `pending-version-change/vX.Y.Z` branch 1. Increments the minor version (i.e. Z+1) and resets the suffix to `dev0` in `.make.versions`. 1. Commits and pushes branch To double-check the version that will be published from the release, ``` -git checkout vX.Y.Z +git checkout pending-releases/vX.Y.Z make show-version ``` This will print for example, 1.2.3. @@ -60,7 +66,7 @@ scripts/release-branch.sh ``` After running the script, you should -1. Create a pull request from branch `vX.Y.Z` into the `releases/vX.Y.Z` branch, and merge. +1. Create a pull request from branch `pending-releases/vX.Y.Z` into the `releases/vX.Y.Z` branch, and merge. 2. Use the github web UI to create a git release and tag of the `releases/vX.Y.Z` branch 3. Create a pull request from branch `pending-version-change/vX.Y.Z` into the main branch, and merge. diff --git a/scripts/release-branch.sh b/scripts/release-branch.sh index a4dc521bb..cf45ff0b7 100755 --- a/scripts/release-branch.sh +++ b/scripts/release-branch.sh @@ -24,7 +24,7 @@ else tag=test$version fi release_branch=releases/$tag -release_branch_pr=$tag +release_branch_pr=pending-releases/$tag # Create a new branch for this version and switch to it if [ ! -z "$debug" ]; then @@ -66,46 +66,53 @@ git push # Now create and push a new branch from which we will PR into main to update the version this_version=$(make show-version) -next_version_branch=pending-version-change/$this_version -echo Creating $next_version_branch branch for PR request back to $DEFAULT_BRANCH for version upgrade -git checkout -b $next_version_branch -git push --set-upstream origin $next_version_branch +next_version_branch_pr=pending-version-change/$this_version +echo Creating $next_version_branch_pr branch for PR request back to $DEFAULT_BRANCH for version upgrade +git checkout -b $next_version_branch_pr +git push --set-upstream origin $next_version_branch_pr git commit --no-verify -s -a -m "Initializing branch to PR back into $DEFAULT_BRANCH holding the next development version" git push -# Change to the next development version (bumped minor version with suffix). -micro=$(cat .make.versions | grep '^DPK_MICRO_VERSION=' | sed -e 's/DPK_MICRO_VERSION=\([0-9]*\).*/\1/') -micro=$(($micro + 1)) -cat .make.versions | sed -e "s/^DPK_MICRO_VERSION=.*/DPK_MICRO_VERSION=$micro/" \ +# Change to the next development version (bump minor version with reset suffix). +minor=$(cat .make.versions | grep '^DPK_MINOR_VERSION' | sed -e 's/DPK_MINOR_VERSION[ ]*=[ ]*\([0-9]*\).*/\1/') +minor=$(($minor + 1)) +cat .make.versions | sed -e "s/^DPK_MINOR_VERSION=.*/DPK_MINOR_VERSION=$minor/" \ + -e "s/^DPK_MICRO_VERSION=.*/DPK_MICRO_VERSION=0/" \ -e "s/^DPK_VERSION_SUFFIX=.*/DPK_VERSION_SUFFIX=.dev0/" > tt mv tt .make.versions next_version=$(make show-version) # Apply the version change to all files in the repo -echo Applying updated version $next_version to $next_version_branch branch +echo Applying updated version $next_version to $next_version_branch_pr branch make set-versions > /dev/null # Push the version change back to the origin if [ -z "$debug" ]; then - echo Committing and pushing version $next_version to $next_version_branch branch. - git commit --no-verify -s -a -m "Bump micro version to $next_version" - #git diff origin/$next_version_branch $next_version_branch + echo Committing and pushing version $next_version to $next_version_branch_pr branch. + git commit --no-verify -s -a -m "Bump version to $next_version" + #git diff origin/$next_version_branch_pr $next_version_branch_pr git push else git status - echo In non-debug mode, the above diffs would have been committed to the $next_version_branch branch + echo In non-debug mode, the above diffs would have been committed to the $next_version_branch_pr branch fi + +# Return to the main branch +git checkout $DEFAULT_BRANCH + cat << EOM Summary of changes: - 1. Pushed $release_branch_pr branch holding version $version of the repository. + 1. Pushed temporary $release_branch_pr branch holding version $version of the repository. 2. Pushed $release_branch branch to receive PR from the $release_branch_pr branch. - 3. Pushed $next_version_branch branch to hold updated version $next_version of the repository on the main branch. - No modifications made to the $DEFAULT_BRANCH branch. + 3. Pushed temporary $next_version_branch_pr branch to hold updated version $next_version of the repository on the main branch. + No modifications were made to the $DEFAULT_BRANCH branch. To complete this process, please go to https://github.com/IBM/data-prep-kit and ... - 1. Create a new pull request from the $next_version_branch branch back into $DEFAULT_BRANCH branch. + 1. Create a new pull request from the $next_version_branch_pr branch back into $DEFAULT_BRANCH branch. 2. Create a pull request from $release_branch_pr branch into $release_branch branch. 3. After the PR into $release_branch is merged, create a new tag $tag on $release_branch branch. 4. Create a release from the $tag tag + 5. Once all PRs are merged, you may delete the $release_branch_pr and $next_version_branch_pr branches. EOM +git status From 51d808eb00ac358f7bcfaabf7bce79cc554357b5 Mon Sep 17 00:00:00 2001 From: Maroun Touma Date: Mon, 14 Oct 2024 16:17:34 -0400 Subject: [PATCH 70/70] Fix missing dev1 in pip install Signed-off-by: Maroun Touma --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f2b46e2b7..1aa02399a 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,8 @@ conda install gxx_linux-64 Next, install the data prep toolkit library. This library installs both the python and ray versions of the transforms. For better management of dependencies, it is recommended to install the same tagged version of both the library and the transform. ```bash -pip3 install data-prep-toolkit[ray]==0.2.2 -pip3 install data-prep-toolkit-transforms[ray,all]==0.2.2 +pip3 install 'data-prep-toolkit[ray]==0.2.2.dev1' +pip3 install 'data-prep-toolkit-transforms[ray,all]==0.2.2.dev1' pip3 install jupyterlab ipykernel ipywidgets ## install custom kernel