diff --git a/plugins/modules/uv_python.py b/plugins/modules/uv_python.py index 76d9fbce09..2856bc8115 100644 --- a/plugins/modules/uv_python.py +++ b/plugins/modules/uv_python.py @@ -56,6 +56,14 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.compat.version import StrictVersion +def max_version(versions_list): + max_version = "" + if versions_list: + versions = [StrictVersion(result["version"]) for result in versions_list] + max_version = max(versions).__str__() + return max_version + + class UV: """ Module for "uv python" command @@ -85,22 +93,27 @@ class UV: - boolean to indicate if method changed state - installed version """ - rc, out, _ = self._find_python("--show-version") - if rc == 0: - return False, out.strip(), "", rc + find_rc, find_out, _ = self._find_python("--show-version") + if find_rc == 0: + existing_version = find_out.split()[0] + return False, "", "", 0, existing_version if self.module.check_mode: - _, versions_available, _ = self._list_python() + _, out_list, _ = self._list_python() + versions_available = json.loads(out_list) + version = max_version(versions_available) # when uv does not find any available patch version the install command will fail try: - if not json.loads(versions_available): + if not versions_available: self.module.fail_json(msg=(f"Version {self.python_version_str} is not available.")) except json.decoder.JSONDecodeError as e: self.module.fail_json(msg=f"Failed to parse 'uv python list' output with error {str(e)}") - return True, self.python_version_str, "", 0 + return True, self.python_version_str, "", 0, version cmd = [self.module.get_bin_path("uv", required=True), self.subcommand, "install", self.python_version_str] - rc, _, err = self.module.run_command(cmd, check_rc=True) - return True, self.python_version_str, err, rc + rc, out, err = self.module.run_command(cmd, check_rc=True, expand_user_and_vars=False) + _, find_out, _ = self._find_python("--show-version") + installed_version = find_out.split()[0] + return True, out, err, rc, installed_version def uninstall_python(self): """ @@ -153,7 +166,7 @@ class UV: - stderr of command """ cmd = [self.module.get_bin_path("uv", required=True), self.subcommand, "find", self.python_version_str, *args] - rc, out, err = self.module.run_command(cmd) + rc, out, err = self.module.run_command(cmd, expand_user_and_vars=False) return rc, out, err def _list_python(self): @@ -175,13 +188,8 @@ class UV: Fails when no available release exists for the specified version. """ _, out, _ = self._list_python() # uv returns versions in descending order but we sort them just in case future uv behavior changes - latest_version = "" - try: - results = json.loads(out) - versions = [StrictVersion(result["version"]) for result in results] - latest_version = max(versions).__str__() - except ValueError: - pass + results = json.loads(out) + latest_version = max_version(results) return latest_version @@ -199,13 +207,14 @@ def main(): stdout="", stderr="", rc=0, + python_version="", failed=False ) state = module.params["state"] uv = UV(module) if state == "present": - result["changed"], result["stdout"], result["stderr"], result["rc"] = uv.install_python() + result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_version"] = uv.install_python() elif state == "absent": result["changed"], result["stdout"], result["stderr"], result["rc"] = uv.uninstall_python() elif state == "latest": diff --git a/tests/integration/targets/uv_python/tasks/main.yaml b/tests/integration/targets/uv_python/tasks/main.yaml index 9d2574dfbd..08b135b34b 100644 --- a/tests/integration/targets/uv_python/tasks/main.yaml +++ b/tests/integration/targets/uv_python/tasks/main.yaml @@ -39,7 +39,7 @@ that: - install_check_mode.changed is true - install_check_mode.failed is false - - '"3.14" in install_check_mode.stdout' + - '"3.14" in install_check_mode.python_version' - name: Install python 3.14 uv_python: @@ -52,7 +52,7 @@ that: - install_python.changed is true - install_python.failed is false - - '"3.14" in install_python.stdout' + - '"3.14" in install_python.python_version' - name: Re-install python 3.14 uv_python: @@ -65,7 +65,7 @@ that: - reinstall_python.changed is false - reinstall_python.failed is false - - '"3.14" in reinstall_python.stdout' + - '"3.14" in reinstall_python.python_version' - name: Install latest python 3.15 # installs latest patch version for 3.15 uv_python: @@ -91,7 +91,7 @@ that: - install_python.changed is true - install_python.failed is false - - '"3.13.5" in install_python.stdout' + - '"3.13.5" in install_python.python_version' - name: Re-install python 3.13.5 uv_python: @@ -104,7 +104,7 @@ that: - reinstall_python.changed is false - reinstall_python.failed is false - - '"3.13.5" in reinstall_python.stdout' + - '"3.13.5" in reinstall_python.python_version' - name: Remove unexisting python 3.10 # removes latest patch version for 3.10 if exists uv_python: @@ -192,7 +192,21 @@ check_mode: yes register: upgrade_python -- name: Verify python 3.13 deletion in check mode +- name: Verify python 3.13 upgrade in check mode + assert: + that: + - upgrade_python.changed is false + - upgrade_python.failed is false + - '"3.13" in upgrade_python.stdout' + +- name: Upgrade python 3.13 again + uv_python: + version: 3.13 + state: latest + check_mode: yes + register: upgrade_python + +- name: Verify python 3.13 upgrade again assert: that: - upgrade_python.changed is false