1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-03-22 21:29:19 +00:00

uv_python module: format code using black

This commit is contained in:
Mariam Ahhttouche 2026-02-24 12:17:23 +01:00
parent e8020e5867
commit b678f5471b

View file

@ -116,6 +116,7 @@ LIB_IMP_ERR = None
HAS_LIB = False
try:
from packaging.version import Version, InvalidVersion
HAS_LIB = True
except:
LIB_IMP_ERR = traceback.format_exc()
@ -126,38 +127,38 @@ MINIMUM_UV_VERSION = "0.8.0"
class UV:
"""
Module for managing Python versions and installations using "uv python" command
Module for managing Python versions and installations using "uv python" command
"""
def __init__(self, module):
self.module = module
self._ensure_min_uv_version()
python_version = module.params["version"]
try:
self.python_version = Version(python_version)
self.python_version_str = self.python_version.__str__()
except InvalidVersion:
self.module.fail_json(
msg="Unsupported version format. Only canonical Python versions (e.g. 3, 3.12, 3.12.3, 3.15.0a5) are supported in this release."
)
self.module = module
self._ensure_min_uv_version()
python_version = module.params["version"]
try:
self.python_version = Version(python_version)
self.python_version_str = self.python_version.__str__()
except InvalidVersion:
self.module.fail_json(
msg="Unsupported version format. Only canonical Python versions (e.g. 3, 3.12, 3.12.3, 3.15.0a5) are supported in this release."
)
def _ensure_min_uv_version(self):
cmd = [self.module.get_bin_path("uv", required=True), "--version", "--color", "never"]
_, out, _ = self.module.run_command(cmd, check_rc=True)
detected = out.strip().split()[-1]
if LooseVersion(detected) < LooseVersion(MINIMUM_UV_VERSION):
self.module.fail_json(
msg=f"uv_python module requires uv >= {MINIMUM_UV_VERSION}",
detected_version=detected,
required_version=MINIMUM_UV_VERSION,
)
cmd = [self.module.get_bin_path("uv", required=True), "--version", "--color", "never"]
_, out, _ = self.module.run_command(cmd, check_rc=True)
detected = out.strip().split()[-1]
if LooseVersion(detected) < LooseVersion(MINIMUM_UV_VERSION):
self.module.fail_json(
msg=f"uv_python module requires uv >= {MINIMUM_UV_VERSION}",
detected_version=detected,
required_version=MINIMUM_UV_VERSION,
)
def install_python(self):
"""
"""
Runs command 'uv python install X.Y.Z' which installs specified python version.
If patch version is not specified uv installs latest available patch version.
Returns:
tuple [bool, str, str, int, list, list]
Returns:
tuple [bool, str, str, int, list, list]
- boolean to indicate if method changed state
- command's stdout
- command's stderr
@ -168,27 +169,27 @@ class UV:
AnsibleModuleFailJson:
If the install command exits with a non-zero return code.
If specified version is not available for download.
"""
find_rc, existing_version, _ = self._find_python("--show-version")
if find_rc == 0:
_, version_path, _ = self._find_python()
return False, "", "", 0, [existing_version], [version_path]
if self.module.check_mode:
latest_version, _ = self._get_latest_patch_release("--managed-python")
# when uv does not find any available patch version the install command will fail
if not latest_version:
self.module.fail_json(msg=(f"Version {self.python_version_str} is not available."))
return True, "", "", 0, [latest_version], [""]
rc, out, err = self._exec(self.python_version_str, "install", check_rc=True)
latest_version, path = self._get_latest_patch_release("--only-installed", "--managed-python")
return True, out, err, rc, [latest_version], [path]
"""
find_rc, existing_version, _ = self._find_python("--show-version")
if find_rc == 0:
_, version_path, _ = self._find_python()
return False, "", "", 0, [existing_version], [version_path]
if self.module.check_mode:
latest_version, _ = self._get_latest_patch_release("--managed-python")
# when uv does not find any available patch version the install command will fail
if not latest_version:
self.module.fail_json(msg=(f"Version {self.python_version_str} is not available."))
return True, "", "", 0, [latest_version], [""]
rc, out, err = self._exec(self.python_version_str, "install", check_rc=True)
latest_version, path = self._get_latest_patch_release("--only-installed", "--managed-python")
return True, out, err, rc, [latest_version], [path]
def uninstall_python(self):
"""
"""
Runs command 'uv python uninstall X.Y.Z' which removes specified python version from environment.
If patch version is not specified all correspending installed patch versions are removed.
Returns:
tuple [bool, str, str, int, list, list]
Returns:
tuple [bool, str, str, int, list, list]
- boolean to indicate if method changed state
- command's stdout
- command's stderr
@ -198,20 +199,20 @@ class UV:
Raises:
AnsibleModuleFailJson:
If the uninstall command exits with a non-zero return code.
"""
installed_versions, install_paths = self._get_installed_versions("--managed-python")
if not installed_versions:
return False, "", "", 0, [], []
if self.module.check_mode:
return True, "", "", 0, installed_versions, install_paths
rc, out, err = self._exec(self.python_version_str, "uninstall", check_rc=True)
return True, out, err, rc, installed_versions, install_paths
"""
installed_versions, install_paths = self._get_installed_versions("--managed-python")
if not installed_versions:
return False, "", "", 0, [], []
if self.module.check_mode:
return True, "", "", 0, installed_versions, install_paths
rc, out, err = self._exec(self.python_version_str, "uninstall", check_rc=True)
return True, out, err, rc, installed_versions, install_paths
def upgrade_python(self):
"""
"""
Runs command 'uv python install X.Y.Z' with latest patch version available.
Returns:
tuple [bool, str, str, int, list, list]
Returns:
tuple [bool, str, str, int, list, list]
- boolean to indicate if method changed state
- command's stdout
- command's stderr
@ -222,25 +223,25 @@ class UV:
AnsibleModuleFailJson:
If the install command exits with a non-zero return code.
If resolved patch version is not available for download.
"""
rc, installed_version_str, _ = self._find_python("--show-version")
installed_version = self._parse_version(installed_version_str)
latest_version_str, _ = self._get_latest_patch_release("--managed-python")
if not latest_version_str:
self.module.fail_json(msg=f"Version {self.python_version_str} is not available.")
if rc == 0 and installed_version >= Version(latest_version_str):
_, install_path, _ = self._find_python()
return False, "", "", rc, [installed_version.__str__()], [install_path]
if self.module.check_mode:
return True, "", "", 0, [latest_version_str], []
# it's possible to have latest version already installed but not used as default
# so in this case 'uv python install' will set latest version as default
rc, out, err = self._exec(latest_version_str, "install", check_rc=True)
latest_version_str, latest_path = self._get_latest_patch_release("--only-installed", "--managed-python")
return True, out, err, rc, [latest_version_str], [latest_path]
"""
rc, installed_version_str, _ = self._find_python("--show-version")
installed_version = self._parse_version(installed_version_str)
latest_version_str, _ = self._get_latest_patch_release("--managed-python")
if not latest_version_str:
self.module.fail_json(msg=f"Version {self.python_version_str} is not available.")
if rc == 0 and installed_version >= Version(latest_version_str):
_, install_path, _ = self._find_python()
return False, "", "", rc, [installed_version.__str__()], [install_path]
if self.module.check_mode:
return True, "", "", 0, [latest_version_str], []
# it's possible to have latest version already installed but not used as default
# so in this case 'uv python install' will set latest version as default
rc, out, err = self._exec(latest_version_str, "install", check_rc=True)
latest_version_str, latest_path = self._get_latest_patch_release("--only-installed", "--managed-python")
return True, out, err, rc, [latest_version_str], [latest_path]
def _exec(self, python_version, command, *args, check_rc=False):
"""
"""
Execute a uv python subcommand.
Args:
python_version (str): Python version specifier (e.g. "3.12", "3.12.3").
@ -253,15 +254,15 @@ class UV:
Raises:
AnsibleModuleFailJson:
If check_rc is True and the command exits with a non-zero return code.
"""
cmd = [self.module.get_bin_path("uv", required=True), "python", command, python_version, "--color", "never", *args]
rc, out, err = self.module.run_command(cmd, check_rc=check_rc)
return rc, out, err
"""
cmd = [self.module.get_bin_path("uv", required=True), "python", command, python_version, "--color", "never", *args]
rc, out, err = self.module.run_command(cmd, check_rc=check_rc)
return rc, out, err
def _find_python(self, *args, check_rc=False):
"""
"""
Runs command 'uv python find' which returns path of installed patch releases for a given python version.
If multiple patch versions are installed, "uv python find" returns the one used by default
If multiple patch versions are installed, "uv python find" returns the one used by default
if inside a virtualenv otherwise it returns latest installed patch version.
Args:
*args: Additional positional arguments passed to _exec.
@ -272,14 +273,14 @@ class UV:
Raises:
AnsibleModuleFailJson:
If check_rc is True and the command exits with a non-zero return code.
"""
rc, out, err = self._exec(self.python_version_str, "find", *args, check_rc=check_rc)
if rc == 0:
out = out.strip()
return rc, out, err
"""
rc, out, err = self._exec(self.python_version_str, "find", *args, check_rc=check_rc)
if rc == 0:
out = out.strip()
return rc, out, err
def _list_python(self, *args, check_rc=False):
"""
"""
Runs command 'uv python list' (which returns list of installed patch releases for a given python version).
Official documentation https://docs.astral.sh/uv/reference/cli/#uv-python-list
Args:
@ -291,17 +292,17 @@ class UV:
Raises:
AnsibleModuleFailJson:
If check_rc is True and the command exits with a non-zero return code.
"""
rc, out, err = self._exec(self.python_version_str, "list", "--output-format", "json", *args, check_rc=check_rc)
try:
out = json.loads(out)
except json.decoder.JSONDecodeError:
# This happens when no version is found
pass
return rc, out, err
"""
rc, out, err = self._exec(self.python_version_str, "list", "--output-format", "json", *args, check_rc=check_rc)
try:
out = json.loads(out)
except json.decoder.JSONDecodeError:
# This happens when no version is found
pass
return rc, out, err
def _get_latest_patch_release(self, *args):
"""
"""
Returns latest available patch release for a given python version.
Args:
*args: Additional positional arguments passed to _list_python.
@ -309,18 +310,18 @@ class UV:
tuple[str, str]:
- latest found patch version in format X.Y.Z
- installation path of latest patch version if version exists
"""
latest_version = path = ""
_, results, _ = self._list_python(*args) # uv returns versions in descending order but we sort them just in case future uv behavior changes
valid_results = self._parse_versions(results)
if valid_results:
version = max(valid_results, key=lambda result: result["parsed_version"])
latest_version = version.get("version", "")
path = version.get("path", "")
return latest_version, path
"""
latest_version = path = ""
_, results, _ = self._list_python(*args) # uv returns versions in descending order but we sort them just in case future uv behavior changes
valid_results = self._parse_versions(results)
if valid_results:
version = max(valid_results, key=lambda result: result["parsed_version"])
latest_version = version.get("version", "")
path = version.get("path", "")
return latest_version, path
def _get_installed_versions(self, *args):
"""
"""
Returns installed patch releases for a given python version.
Args:
*args: Additional positional arguments passed to _list_python.
@ -328,65 +329,56 @@ class UV:
tuple[list, list]:
- list of latest found patch versions
- list of installation paths of installed versions
"""
_, results, _ = self._list_python("--only-installed", *args)
if results:
return [result["version"] for result in results], [result["path"] for result in results]
return [], []
"""
_, results, _ = self._list_python("--only-installed", *args)
if results:
return [result["version"] for result in results], [result["path"] for result in results]
return [], []
@staticmethod
def _parse_versions(results):
valid_results =[]
for result in results:
try:
result["parsed_version"] = Version(result.get("version", ""))
valid_results.append(result)
except InvalidVersion:
continue
return valid_results
valid_results = []
for result in results:
try:
result["parsed_version"] = Version(result.get("version", ""))
valid_results.append(result)
except InvalidVersion:
continue
return valid_results
@staticmethod
def _parse_version(version_str):
try:
return Version(version_str)
except InvalidVersion:
return Version("0")
try:
return Version(version_str)
except InvalidVersion:
return Version("0")
def main():
module = AnsibleModule(
argument_spec=dict(
version=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent', 'latest']),
version=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent", "latest"]),
),
supports_check_mode=True
supports_check_mode=True,
)
if not HAS_LIB:
module.fail_json(msg=missing_required_lib("packaging"),
exception=LIB_IMP_ERR)
result = dict(
changed=False,
stdout="",
stderr="",
rc=0,
python_versions=[],
python_paths=[],
failed=False
)
module.fail_json(msg=missing_required_lib("packaging"), exception=LIB_IMP_ERR)
result = dict(changed=False, stdout="", stderr="", rc=0, python_versions=[], python_paths=[], failed=False)
state = module.params["state"]
uv = UV(module)
if state == "present":
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.install_python()
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.install_python()
elif state == "absent":
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.uninstall_python()
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.uninstall_python()
elif state == "latest":
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.upgrade_python()
result["changed"], result["stdout"], result["stderr"], result["rc"], result["python_versions"], result["python_paths"] = uv.upgrade_python()
module.exit_json(**result)
if __name__ == "__main__":
main()
main()