Repository: kserve/kserve

Issue:
Allow dynamic version extraction for poetry
/kind feature
This is to follow up the PR for migrating to use poetry, currently the version needs to be hard coded in the poetry package file but there are many packages need to be updated during the release, so we need a way to extract the version dynamically like the VERSION file we use previously.

reference: https://pypi.org/project/poetry-version-plugin/


**Describe the solution you'd like**
[A clear and concise description of what you want to happen.]


**Anything else you would like to add:**
[Miscellaneous information that will assist in solving the issue.]



Discussion:

Resolving patch:
diff --git a/python/plugin/poetry-version-plugin/debug.py b/python/plugin/poetry-version-plugin/debug.py
new file mode 100644
--- /dev/null
+++ b/python/plugin/poetry-version-plugin/debug.py
@@ -0,0 +1,20 @@
+import os
+from pathlib import Path
+
+from cleo.testers.command_tester import CommandTester
+from poetry.console.application import Application
+
+
+def poetry_build():
+    application = Application()
+    command = application.find("build")
+    command_tester = CommandTester(command)
+    command_tester.execute()
+
+
+package_path = Path("tests/assets/no_packages")
+
+
+if __name__ == "__main__":
+    os.chdir(package_path)
+    poetry_build()
diff --git a/python/plugin/poetry-version-plugin/poetry_version_plugin/__init__.py b/python/plugin/poetry-version-plugin/poetry_version_plugin/__init__.py
new file mode 100644
diff --git a/python/plugin/poetry-version-plugin/poetry_version_plugin/plugin.py b/python/plugin/poetry-version-plugin/poetry_version_plugin/plugin.py
new file mode 100644
--- /dev/null
+++ b/python/plugin/poetry-version-plugin/poetry_version_plugin/plugin.py
@@ -0,0 +1,119 @@
+import ast
+import subprocess
+from pathlib import Path
+from typing import Any, Dict, Optional
+
+from cleo.io.io import IO
+from poetry.plugins.plugin import Plugin
+from poetry.poetry import Poetry
+from poetry.core.utils.helpers import module_name
+
+
+class VersionPlugin(Plugin):  # type: ignore
+    def activate(self, poetry: Poetry, io: IO) -> None:
+        poetry_version_config: Optional[Dict[str, Any]] = poetry.pyproject.data.get(
+            "tool", {}
+        ).get("poetry-version-plugin")
+        if poetry_version_config is None:
+            return
+        version_source = poetry_version_config.get("source")
+        version_file_path = poetry_version_config.get("file_path")
+        if not version_source:
+            message = (
+                "<b>poetry-version-plugin</b>: No <b>source</b> configuration found in "
+                "[tool.poetry-version-plugin] in pyproject.toml, not extracting "
+                "dynamic version"
+            )
+            io.write_error_line(message)
+            raise RuntimeError(message)
+
+        # Update version using file
+        if version_source == "file" and version_file_path:
+            with open(version_file_path, "r") as version_file:
+                version = version_file.read().strip()
+                poetry.package._set_version(version)
+                io.write_line(
+                    "<b>poetry-version-plugin</b>: New package "
+                    "version is updated from given file "
+                    f"version is: <b>{version}</b>"
+                )
+
+        if version_source == "init":
+            packages = poetry.local_config.get("packages")
+            if packages:
+                if len(packages) == 1:
+                    package_name = packages[0]["include"]
+                else:
+                    message = (
+                        "<b>poetry-version-plugin</b>: More than one package set, "
+                        "cannot extract dynamic version"
+                    )
+                    io.write_error_line(message)
+                    raise RuntimeError(message)
+            else:
+                package_name = module_name(poetry.package.name)
+            init_path = Path(package_name) / "__init__.py"
+            if not init_path.is_file():
+                message = (
+                    "<b>poetry-version-plugin</b>: __init__.py file not found at "
+                    f"{init_path} cannot extract dynamic version"
+                )
+                io.write_error_line(message)
+                raise RuntimeError(message)
+            else:
+                io.write_line(
+                    "<b>poetry-version-plugin</b>: Using __init__.py file at "
+                    f"{init_path} for dynamic version"
+                )
+            tree = ast.parse(init_path.read_text())
+            for el in tree.body:
+                if isinstance(el, ast.Assign):
+                    if len(el.targets) == 1:
+                        target = el.targets[0]
+                        if isinstance(target, ast.Name):
+                            if target.id == "__version__":
+                                value_node = el.value
+                                if isinstance(value_node, ast.Constant):
+                                    version = value_node.value
+                                elif isinstance(value_node, ast.Str):
+                                    version = value_node.s
+                                else:  # pragma: nocover
+                                    # This is actually covered by tests, but can't be
+                                    # reported by Coverage
+                                    # Ref: https://github.com/nedbat/coveragepy/issues/198
+                                    continue
+                                io.write_line(
+                                    "<b>poetry-version-plugin</b>: Setting package "
+                                    "dynamic version to __version__ "
+                                    f"variable from __init__.py: <b>{version}</b>"
+                                )
+                                poetry.package._set_version(version)
+                                return
+            message = (
+                "<b>poetry-version-plugin</b>: No valid __version__ variable found "
+                "in __init__.py, cannot extract dynamic version"
+            )
+            io.write_error_line(message)
+            raise RuntimeError(message)
+        elif version_source == "git-tag":
+            result = subprocess.run(
+                ["git", "describe", "--exact-match", "--tags", "HEAD"],
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                universal_newlines=True,
+            )
+            if result.returncode == 0:
+                tag = result.stdout.strip()
+                io.write_line(
+                    "<b>poetry-version-plugin</b>: Git tag found, setting "
+                    f"dynamic version to: {tag}"
+                )
+                poetry.package._set_version(tag)
+                return
+            else:
+                message = (
+                    "<b>poetry-version-plugin</b>: No Git tag found, not "
+                    "extracting dynamic version"
+                )
+                io.write_error_line(message)
+                raise RuntimeError(message)


Test patch:
diff --git a/test/scripts/gh-actions/setup-kserve-helm.sh b/test/scripts/gh-actions/setup-kserve-helm.sh
--- a/test/scripts/gh-actions/setup-kserve-helm.sh
+++ b/test/scripts/gh-actions/setup-kserve-helm.sh
@@ -45,13 +45,17 @@ kubectl apply -f config/overlays/test/minio/minio-user-secret.yaml -n kserve-ci-
 
 echo "Installing Poetry"
 export POETRY_VERSION=1.4.0
-export POETRY_HOME=/opt/poetry
-python3 -m venv $POETRY_HOME && $POETRY_HOME/bin/pip install poetry==$POETRY_VERSION
-export PATH="$PATH:$POETRY_HOME/bin"
+pip install poetry==$POETRY_VERSION
+poetry config virtualenvs.create true
+poetry config virtualenvs.in-project true
+poetry config installer.parallel true
+
+echo "Installing Poetry Version Plugin"
+pip install -e python/plugin/poetry-version-plugin
+poetry self show plugins  
 
 echo "Installing KServe Python SDK ..."
 python3 -m pip install --upgrade pip
 pushd python/kserve >/dev/null
-    poetry config virtualenvs.in-project true
-    poetry version $(cat ${../python/VERSION}) && poetry install --with=test --no-interaction
+    poetry install --with=test --no-interaction
 popd
diff --git a/test/scripts/gh-actions/setup-kserve.sh b/test/scripts/gh-actions/setup-kserve.sh
--- a/test/scripts/gh-actions/setup-kserve.sh
+++ b/test/scripts/gh-actions/setup-kserve.sh
@@ -39,12 +39,16 @@ kubectl apply -f config/overlays/test/minio/minio-user-secret.yaml -n kserve-ci-
 
 echo "Installing Poetry"
 export POETRY_VERSION=1.4.0
-export POETRY_HOME=/opt/poetry
-python3 -m venv $POETRY_HOME && $POETRY_HOME/bin/pip install poetry==$POETRY_VERSION
-export PATH="$PATH:$POETRY_HOME/bin"
+pip install poetry==$POETRY_VERSION
+poetry config virtualenvs.create true
+poetry config virtualenvs.in-project true
+poetry config installer.parallel true
+
+echo "Installing Poetry Version Plugin"
+pip install -e python/plugin/poetry-version-plugin
+poetry self show plugins  
 
 echo "Installing KServe Python SDK ..."
 pushd python/kserve >/dev/null
-    poetry config virtualenvs.in-project true
-    poetry version $(cat ../VERSION) && poetry install --with=test --no-interaction
+    poetry install --with=test --no-interaction
 popd