mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-02 14:40:19 -07:00
pipx: accept python version specs in parameter name
(#10031)
* pipx: accept python version specs in parameter "name" * pipx_info: adjustment for backward compatibility * remove unnecessary comprehension * remove f-str * no shebang for module utils * remove f-str * fix syntax error * fix pipx_info * rollback adjustments in existing tests * docs & test update * add debugging tasks to int test * integration test checks for version of packaging * move assertion to block * fix idempotency when using version specifier * add changelog frag * fix docs * dial down the version of tox used in tests To accommodate old Pythons * Update plugins/modules/pipx.py * Apply suggestions from code review * refactor/rename package requirements code * fix filename in BOTMETA * Update plugins/modules/pipx.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/pipx.py Co-authored-by: Felix Fontein <felix@fontein.de> * pipx mod utils: create make_process_dict and deprecate make_process_list * pkg_req: make method private * make_process_dict is simpler and more specialized * ensure version specifiers are honored when state=install * fix insanity * pipx: reformat yaml blocks * pipx: doc wordsmithing --------- Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
626ee3115d
commit
2b4cb6dabc
8 changed files with 302 additions and 64 deletions
|
@ -54,11 +54,17 @@ options:
|
|||
name:
|
||||
type: str
|
||||
description:
|
||||
- The name of the application. In C(pipx) documentation it is also referred to as the name of the virtual environment
|
||||
where the application will be installed.
|
||||
- The name of the application and also the name of the Python package being installed.
|
||||
- In C(pipx) documentation it is also referred to as the name of the virtual environment where the application is installed.
|
||||
- If O(name) is a simple package name without version specifiers, then that name is used as the Python package name
|
||||
to be installed.
|
||||
- Use O(source) for passing package specifications or installing from URLs or directories.
|
||||
- Starting in community.general 10.7.0, you can use package specifiers when O(state=present) or O(state=install). For
|
||||
example, O(name=tox<4.0.0) or O(name=tox>3.0.27).
|
||||
- Please note that when you use O(state=present) and O(name) with version specifiers, contrary to the behavior of C(pipx),
|
||||
this module honors the version specifier and installs a version of the application that satisfies it. If you want
|
||||
to ensure the reinstallation of the application even when the version specifier is met, then you must use O(force=true),
|
||||
or perhaps use O(state=upgrade) instead.
|
||||
- Use O(source) for installing from URLs or directories.
|
||||
source:
|
||||
type: str
|
||||
description:
|
||||
|
@ -69,6 +75,7 @@ options:
|
|||
- The value of this option is passed as-is to C(pipx).
|
||||
- O(name) is still required when using O(source) to establish the application name without fetching the package from
|
||||
a remote source.
|
||||
- The module is not idempotent when using O(source).
|
||||
install_apps:
|
||||
description:
|
||||
- Add apps from the injected packages.
|
||||
|
@ -92,6 +99,7 @@ options:
|
|||
description:
|
||||
- Force modification of the application's virtual environment. See C(pipx) for details.
|
||||
- Only used when O(state=install), O(state=upgrade), O(state=upgrade_all), O(state=latest), or O(state=inject).
|
||||
- The module is not idempotent when O(force=true).
|
||||
type: bool
|
||||
default: false
|
||||
include_injected:
|
||||
|
@ -144,10 +152,10 @@ options:
|
|||
with O(community.general.pipx_info#module:include_raw=true) and obtaining the content from the RV(community.general.pipx_info#module:raw_output).
|
||||
type: path
|
||||
version_added: 9.4.0
|
||||
notes:
|
||||
- This first implementation does not verify whether a specified version constraint has been installed or not. Hence, when
|
||||
using version operators, C(pipx) module will always try to execute the operation, even when the application was previously
|
||||
installed. This feature will be added in the future.
|
||||
requirements:
|
||||
- When using O(name) with version specifiers, the Python package C(packaging) is required.
|
||||
- If the package C(packaging) is at a version lesser than C(22.0.0), it will fail silently when processing invalid specifiers,
|
||||
like C(tox<<<<4.0).
|
||||
author:
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
"""
|
||||
|
@ -201,7 +209,8 @@ version:
|
|||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_list
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_dict
|
||||
from ansible_collections.community.general.plugins.module_utils.pkg_req import PackageRequirement
|
||||
|
||||
from ansible.module_utils.facts.compat import ansible_facts
|
||||
|
||||
|
@ -258,18 +267,13 @@ class PipX(StateModuleHelper):
|
|||
use_old_vardict = False
|
||||
|
||||
def _retrieve_installed(self):
|
||||
name = _make_name(self.vars.name, self.vars.suffix)
|
||||
output_process = make_process_list(self, include_injected=True, name=name)
|
||||
installed = self.runner('_list global', output_process=output_process).run()
|
||||
output_process = make_process_dict(include_injected=True)
|
||||
installed, dummy = self.runner('_list global', output_process=output_process).run()
|
||||
|
||||
if name is not None:
|
||||
app_list = [app for app in installed if app['name'] == name]
|
||||
if app_list:
|
||||
return {name: app_list[0]}
|
||||
else:
|
||||
return {}
|
||||
if self.app_name is None:
|
||||
return installed
|
||||
|
||||
return installed
|
||||
return {k: v for k, v in installed.items() if k == self.app_name}
|
||||
|
||||
def __init_module__(self):
|
||||
if self.vars.executable:
|
||||
|
@ -279,6 +283,11 @@ class PipX(StateModuleHelper):
|
|||
self.command = [facts['python']['executable'], '-m', 'pipx']
|
||||
self.runner = pipx_runner(self.module, self.command)
|
||||
|
||||
pkg_req = PackageRequirement(self.module, self.vars.name)
|
||||
self.parsed_name = pkg_req.parsed_name
|
||||
self.parsed_req = pkg_req.requirement
|
||||
self.app_name = _make_name(self.parsed_name, self.vars.suffix)
|
||||
|
||||
self.vars.set('application', self._retrieve_installed(), change=True, diff=True)
|
||||
|
||||
with self.runner("version") as ctx:
|
||||
|
@ -295,12 +304,27 @@ class PipX(StateModuleHelper):
|
|||
self.vars.set('run_info', ctx.run_info, verbosity=4)
|
||||
|
||||
def state_install(self):
|
||||
if not self.vars.application or self.vars.force:
|
||||
self.changed = True
|
||||
args_order = 'state global index_url install_deps force python system_site_packages editable pip_args suffix name_source'
|
||||
with self.runner(args_order, check_mode_skip=True) as ctx:
|
||||
ctx.run(name_source=[self.vars.name, self.vars.source])
|
||||
self._capture_results(ctx)
|
||||
# If we have a version spec and no source, use the version spec as source
|
||||
if self.parsed_req and not self.vars.source:
|
||||
self.vars.source = self.vars.name
|
||||
|
||||
if self.vars.application.get(self.app_name):
|
||||
is_installed = True
|
||||
version_match = self.vars.application[self.app_name]['version'] in self.parsed_req.specifier if self.parsed_req else True
|
||||
force = self.vars.force or (not version_match)
|
||||
else:
|
||||
is_installed = False
|
||||
version_match = False
|
||||
force = self.vars.force
|
||||
|
||||
if is_installed and version_match and not force:
|
||||
return
|
||||
|
||||
self.changed = True
|
||||
args_order = 'state global index_url install_deps force python system_site_packages editable pip_args suffix name_source'
|
||||
with self.runner(args_order, check_mode_skip=True) as ctx:
|
||||
ctx.run(name_source=[self.parsed_name, self.vars.source], force=force)
|
||||
self._capture_results(ctx)
|
||||
|
||||
state_present = state_install
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ version:
|
|||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_list
|
||||
from ansible_collections.community.general.plugins.module_utils.pipx import pipx_runner, pipx_common_argspec, make_process_dict
|
||||
|
||||
from ansible.module_utils.facts.compat import ansible_facts
|
||||
|
||||
|
@ -158,9 +158,20 @@ class PipXInfo(ModuleHelper):
|
|||
self.vars.version = out.strip()
|
||||
|
||||
def __run__(self):
|
||||
output_process = make_process_list(self, **self.vars.as_dict())
|
||||
output_process = make_process_dict(self.vars.include_injected, self.vars.include_deps)
|
||||
with self.runner('_list global', output_process=output_process) as ctx:
|
||||
self.vars.application = ctx.run()
|
||||
applications, raw_data = ctx.run()
|
||||
if self.vars.include_raw:
|
||||
self.vars.raw_output = raw_data
|
||||
|
||||
if self.vars.name:
|
||||
self.vars.application = [
|
||||
v
|
||||
for k, v in applications.items()
|
||||
if k == self.vars.name
|
||||
]
|
||||
else:
|
||||
self.vars.application = list(applications.values())
|
||||
self._capture_results(ctx)
|
||||
|
||||
def _capture_results(self, ctx):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue