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:
parent
e8020e5867
commit
b678f5471b
1 changed files with 127 additions and 135 deletions
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue