Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/1315.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed warnings about parameters being used multiple times.
107 changes: 96 additions & 11 deletions src/pulp_cli/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ def __init__(
):
self.allowed_with_contexts = allowed_with_contexts
self.needs_plugins = needs_plugins
self.option_processors: list[t.Callable[[click.Context], None]] = []
super().__init__(*args, **kwargs)

def invoke(self, ctx: click.Context) -> t.Any:
Expand All @@ -408,6 +409,8 @@ def invoke(self, ctx: click.Context) -> t.Any:
assert pulp_ctx is not None
for plugin_requirement in self.needs_plugins:
pulp_ctx.needs_plugin(plugin_requirement)
for processor in self.option_processors:
processor(ctx)
return super().invoke(ctx)
except PulpException as e:
raise click.ClickException(str(e))
Expand Down Expand Up @@ -471,7 +474,9 @@ def list_commands(self, ctx: click.Context) -> list[str]:


def pulp_command(
name: str | None = None, **kwargs: t.Any
name: str | None = None,
cls: t.Type[PulpCommand] = PulpCommand,
**kwargs: t.Any,
) -> t.Callable[[_AnyCallable], PulpCommand]:
"""
Pulp command factory.
Expand All @@ -480,17 +485,21 @@ def pulp_command(
`allowed_with_contexts`. It allows rendering the docstring with the values of `ENTITY` and
`ENTITIES` from the closest entity context.
"""
return click.command(name=name, cls=PulpCommand, **kwargs)
return click.command(name=name, cls=cls, **kwargs)


def pulp_group(name: str | None = None, **kwargs: t.Any) -> t.Callable[[_AnyCallable], PulpGroup]:
def pulp_group(
name: str | None = None,
cls: t.Type[PulpGroup] = PulpGroup,
**kwargs: t.Any,
) -> t.Callable[[_AnyCallable], PulpGroup]:
"""
Pulp command group factory.

Creates a click compatible group command that selects subcommands based on
`allowed_with_contexts` and creates `PulpCommand` subcommands by default.
"""
return click.group(name=name, cls=PulpGroup, **kwargs)
return click.group(name=name, cls=cls, **kwargs)


class PulpOption(click.Option):
Expand Down Expand Up @@ -631,7 +640,9 @@ def _version_callback(ctx: click.Context, param: click.Parameter, value: int | N
return value


def load_file_wrapper(handler: t.Callable[[click.Context, click.Parameter, str], t.Any]) -> t.Any:
def load_file_wrapper(
handler: t.Callable[[click.Context, click.Parameter, str], t.Any],
) -> t.Any:
"""
A wrapper that is used for chaining or decorating callbacks that manipulate input data.

Expand Down Expand Up @@ -799,7 +810,11 @@ def remote_header_callback(
# Decorator common options


def pulp_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
def pulp_option(
*args: t.Any,
cls: t.Type[PulpOption] = PulpOption,
**kwargs: t.Any,
) -> t.Callable[[FC], FC]:
"""
Factory of [`PulpOption`][pulp_cli.generic.PulpOption] objects.

Expand All @@ -820,8 +835,72 @@ def pulp_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
)
```
"""
kwargs["cls"] = PulpOption
return click.option(*args, **kwargs)
return click.option(*args, cls=cls, **kwargs)


def option_processor(
*, callback: t.Callable[[click.Context], None]
) -> t.Callable[[PulpCommand], PulpCommand]:
"""
Add a callback i.e. for option processing at a rather generic place before calling the command.
"""

def _inner(cmd: PulpCommand) -> PulpCommand:
cmd.option_processors.append(callback)
return cmd

return _inner


def option_group(
name: str,
options: t.List[str],
/,
callback: t.Callable[[click.Context, t.Dict[str, t.Any]], t.Any] | None = None,
require_all: bool = True,
expose_value: bool = True,
) -> t.Callable[[PulpCommand], PulpCommand]:
def _group_callback(ctx: click.Context) -> None:
"""
Group a list of options into a group represented as a dictionary.
This allows to add a `callback` function for further processing.
`expose_value` allows to hide the value from the command callback.
"""
value = {k: v for k, v in ((k, ctx.params.pop(k, None)) for k in options) if v is not None}
if value:
if require_all and (missing_options := set(options) - set(value.keys())):
raise click.UsageError(
_(
"Illegal usage, please specify all options in the group: {option_list}\n"
"missing: {missing_options}"
).format(
option_list=", ".join(options),
missing_options=", ".join(missing_options),
)
)

if callback is not None:
value = callback(ctx, value)
if expose_value:
ctx.params[name] = value

def _inner(cmd: PulpCommand) -> PulpCommand:
for param in cmd.params:
if param.name in options:
other_options = [o for o in options if o != param.name]
if other_options:
Comment on lines +888 to +891
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on the order the options get decorated to the command, correct? If you place the option_group before the group's options then it would do nothing for modifying the help message.

help_text = (
(getattr(param, "help") or "")
+ "\n"
+ _("Option is grouped with {options}.").format(
options=", ".join(other_options)
)
).strip()
setattr(param, "help", help_text)
cmd.option_processors.append(_group_callback)
return cmd

return _inner


def resource_lookup_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
Expand Down Expand Up @@ -1013,7 +1092,11 @@ def _option_callback(
_(
"The type '{plugin}:{resource_type}' "
"does not support the '{capability}' capability."
).format(plugin=plugin, resource_type=resource_type, capability=capability)
).format(
plugin=plugin,
resource_type=resource_type,
capability=capability,
)
)

if parent_resource_lookup:
Expand Down Expand Up @@ -1044,7 +1127,7 @@ def _multi_option_callback(
"{plugin_default}{type_default}{multiple_note}"
).format(
plugin_form=_("[<plugin>:]") if default_plugin else _("<plugin>:"),
type_form=_("[<resource_type>:]") if default_type else _("<resource_type>:"),
type_form=(_("[<resource_type>:]") if default_type else _("<resource_type>:")),
lookup_key=lookup_key,
plugin_default=(
_("'<plugin>' defaults to {plugin}. ").format(plugin=default_plugin)
Expand Down Expand Up @@ -1376,7 +1459,9 @@ def _type_callback(ctx: click.Context, param: click.Parameter, value: str | None
help=_("total number of simultaneous connections"),
),
click.option(
"--rate-limit", type=int_or_empty, help=_("limit download rate in requests per second")
"--rate-limit",
type=int_or_empty,
help=_("limit download rate in requests per second"),
),
click.option("--sock-connect-timeout", type=float_or_empty),
click.option("--sock-read-timeout", type=float_or_empty),
Expand Down
77 changes: 47 additions & 30 deletions src/pulpcore/cli/ansible/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@
PulpAnsibleRepositoryContext,
PulpAnsibleRoleContext,
)
from pulp_glue.common.context import PluginRequirement, PulpContentContext, PulpRepositoryContext
from pulp_glue.common.context import (
PluginRequirement,
PulpContentContext,
PulpRepositoryContext,
)
from pulp_glue.common.i18n import get_translation
from pulp_glue.core.context import PulpArtifactContext

from pulp_cli.generic import (
GroupOption,
PulpCLIContext,
chunk_size_callback,
content_filter_options,
href_option,
label_command,
list_command,
option_group,
pass_content_context,
pass_pulp_context,
pulp_group,
Expand All @@ -38,7 +42,7 @@
signature_context = (PulpAnsibleCollectionVersionSignatureContext,)


def _content_callback(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any:
def _content_callback(ctx: click.Context, value: t.Any) -> t.Any:
if value:
entity_ctx = ctx.find_object(PulpContentContext)
assert entity_ctx is not None
Expand Down Expand Up @@ -85,9 +89,15 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
list_options = [
pulp_option("--name", help=_("Name of {entity}"), allowed_with_contexts=content_context),
pulp_option(
"--namespace", help=_("Namespace of {entity}"), allowed_with_contexts=content_context
"--namespace",
help=_("Namespace of {entity}"),
allowed_with_contexts=content_context,
),
pulp_option(
"--version",
help=_("Version of {entity}"),
allowed_with_contexts=content_context,
),
pulp_option("--version", help=_("Version of {entity}"), allowed_with_contexts=content_context),
pulp_option(
"--latest",
"is_highest",
Expand Down Expand Up @@ -128,48 +138,52 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
]

lookup_options = [
click.option(
pulp_option(
"--name",
help=_("Name of {entity}"),
group=["namespace", "version"],
expose_value=False,
allowed_with_contexts=(PulpAnsibleRoleContext, PulpAnsibleCollectionVersionContext),
cls=GroupOption,
callback=_content_callback,
allowed_with_contexts=(
PulpAnsibleRoleContext,
PulpAnsibleCollectionVersionContext,
),
),
click.option(
pulp_option(
"--namespace",
help=_("Namespace of {entity}"),
group=["name", "version"],
expose_value=False,
allowed_with_contexts=(PulpAnsibleRoleContext, PulpAnsibleCollectionVersionContext),
cls=GroupOption,
allowed_with_contexts=(
PulpAnsibleRoleContext,
PulpAnsibleCollectionVersionContext,
),
),
click.option(
pulp_option(
"--version",
help=_("Version of {entity}"),
group=["namespace", "name"],
allowed_with_contexts=(
PulpAnsibleRoleContext,
PulpAnsibleCollectionVersionContext,
),
),
option_group(
"content",
["namespace", "name", "version"],
expose_value=False,
allowed_with_contexts=(PulpAnsibleRoleContext, PulpAnsibleCollectionVersionContext),
cls=GroupOption,
callback=_content_callback,
),
click.option(
pulp_option(
"--pubkey-fingerprint",
help=_("Public key fingerprint of the {entity}"),
group=["collection"],
expose_value=False,
allowed_with_contexts=signature_context,
callback=_content_callback,
cls=GroupOption,
),
click.option(
pulp_option(
"--collection",
"signed_collection",
help=_("Collection of {entity}"),
group=["pubkey_fingerprint"],
expose_value=False,
allowed_with_contexts=signature_context,
cls=GroupOption,
),
option_group(
"signature_content",
["signed_collection", "pubkey_fingerprint"],
expose_value=False,
callback=_content_callback,
),
href_option,
]
Expand Down Expand Up @@ -197,7 +211,10 @@ def content(ctx: click.Context, pulp_ctx: PulpCLIContext, /, content_type: str)
allowed_with_contexts=content_context,
)
@pulp_option(
"--name", help=_("Name of {entity}"), allowed_with_contexts=role_context, required=True
"--name",
help=_("Name of {entity}"),
allowed_with_contexts=role_context,
required=True,
)
@pulp_option(
"--namespace",
Expand Down
7 changes: 2 additions & 5 deletions src/pulpcore/cli/ansible/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ def remote(ctx: click.Context, pulp_ctx: PulpCLIContext, /, remote_type: str) ->
nested_lookup_options = [remote_lookup_option]
remote_options = [
click.option("--policy", help=_("policy to use when downloading")),
]
collection_options = [
pulp_option(
"--requirements-file",
callback=yaml_callback,
Expand Down Expand Up @@ -96,9 +94,8 @@ def remote(ctx: click.Context, pulp_ctx: PulpCLIContext, /, remote_type: str) ->
allowed_with_contexts=collection_context,
),
]
ansible_remote_options = remote_options + collection_options
create_options = common_remote_create_options + remote_options + ansible_remote_options
update_options = common_remote_update_options + remote_options + ansible_remote_options
create_options = common_remote_create_options + remote_options
update_options = common_remote_update_options + remote_options

remote.add_command(list_command(decorators=remote_filter_options))
remote.add_command(show_command(decorators=lookup_options))
Expand Down
Loading
Loading