Skip to content

Commit 50421eb

Browse files
committed
Update architecture docs
Update the examples to more a modern way of doing things and bring some names in line with other parts of the docs.
1 parent 2728f61 commit 50421eb

File tree

3 files changed

+78
-28
lines changed

3 files changed

+78
-28
lines changed

docs/dev/learn/architecture.md

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ def mount(main: click.Group, **kwargs: Any) -> None:
5252
## Contexts
5353

5454
In `click`, every subcommand is accompanied by a `click.Context`, and objects can be attached to them.
55-
In this CLI we attach a [`PulpCLIContext`][pulp_cli.generic.PulpCLIContext] to the main command, which inherits from `pulp-glue`'s [`PulpContext`][pulp_glue.common.context.PulpContext].
55+
In this CLI we attach a [`PulpCLIContext`][pulp_cli.generic.PulpCLIContext] to the main command,
56+
which inherits from `pulp-glue`'s [`PulpContext`][pulp_glue.common.context.PulpContext].
5657
This context handles the communication to the pulp server through its `api` property.
5758

58-
Further we encourage the handling of communication with certain endpoints by subclassing the [`PulpEntityContext`][pulp_glue.common.context.PulpEntityContext] or some of the resource-specific children, such as [PulpRepositoryContext][pulp_glue.common.context.PulpRepositoryContext].
59+
Further we encourage the handling of communication with certain endpoints by subclassing the [`PulpEntityContext`][pulp_glue.common.context.PulpEntityContext]
60+
or some of the resource-specific children, such as [`PulpRepositoryContext`][pulp_glue.common.context.PulpRepositoryContext].
5961
Some examples of this can be found under `pulp_glue/{plugin-name}/context.py`.
6062

6163
By attaching them to the contexts of certain command groups, they are accessible to commands via the `pass_entity_context` decorator.
@@ -65,41 +67,58 @@ Those entity contexts should provide a common interface to the layer of `click`
6567
@pulp_group()
6668
@pass_pulp_context
6769
@click.pass_context
68-
def my_command(ctx, pulp_ctx):
69-
ctx.obj = MyEntityContext(pulp_ctx)
70+
def my_resource(ctx, pulp_ctx: PulpContext) -> None:
71+
ctx.obj = PulpMyResourceContext(pulp_ctx)
7072

7173

72-
@my_command.command()
74+
@my_resource.command()
7375
@pass_entity_context
74-
def my_sub_command(entity_ctx):
75-
entity_ctx.entity = {"name": "myentity")
76-
entity_ctx.destroy()
76+
def frobnicate(entity_ctx: PulpMyResourceContext) -> None:
77+
entity_ctx.entity = {"name": "myentity"}
78+
entity_ctx.frobnicate()
7779
```
7880

7981
## Generics
8082

8183
For certain often repeated patterns like listing all entities of a particular kind,
8284
we provide generic commands that use the underlying context objects.
8385
The following example shows the use of the [`show_command`][pulp_cli.generic.show_command] generic.
86+
It uses a generated lookup option that will populate the closest matching `entity_ctx` in the command hierarchy.
8487

8588
```python
86-
from pulp_cli.generic import name_option, show_command,
89+
from pulp_cli.generic import resource_lookup_option, show_command,
8790

88-
lookup_params = [name_option]
89-
my_command.add_command(show_command(decorators=lookup_params))
91+
92+
my_resource_lookup_option = resource_lookup_option(
93+
"--my-resource",
94+
context_class=PulpMyResourceContext,
95+
)
96+
lookup_params = [my_resource_lookup_option]
97+
my_resource.add_command(show_command(decorators=lookup_params))
98+
99+
# The example from above could use the lookup option too.
100+
101+
@my_resource.command()
102+
@my_resource_lookup_option
103+
@pass_entity_context
104+
def frobnicate(entity_ctx: PulpMyResourceContext) -> None:
105+
# At this point, the lookup option has already selected an entity.
106+
entity_ctx.frobnicate()
90107
```
91108

92109
To add options to these subcommands, pass a list of [`PulpOption`][pulp_cli.generic.PulpOption] objects to the `decorators` argument.
93110
Preferably these are created using the [`pulp_option`][pulp_cli.generic.pulp_option] factory.
111+
More specific factories for resource handling are [`resource_option`][pulp_cli.generic.resource_option] and [`resource_lookup_option`][pulp_cli.generic.resource_lookup_option].
94112

113+
Another example is the `list-command` that allows filtering (based on the server's capabilities) by adding named options:
95114
```python
96115
from pulp_cli.generic import list_command,
97116

98117
filter_params = [
99118
pulp_option("--name"),
100119
pulp_option("--name-contains", "name__contains"),
101120
]
102-
my_command.add_command(list_command(decorators=filter_params))
121+
my_resource.add_command(list_command(decorators=filter_params))
103122
```
104123

105124
## Version dependent code paths
@@ -116,22 +135,23 @@ It will raise an error, once the first access to the server is attempted.
116135

117136
```python
118137
# In pulp_glue_my_plugin
119-
class MyEntityContext(PulpEntityContext):
120-
def show(self, href):
121-
if self.pulp_ctx.has_plugin(PluginRequirement("my_content", specifier=">=1.2.3", inverted=True)):
138+
class PulpMyResourceContext(PulpEntityContext):
139+
NEEDS_PLUGINS = [PluginRequirement("my_plugin", specifier=">=1.0.0")]
140+
141+
def show(self) -> t.Dict[str, t.Any]:
142+
if self.pulp_ctx.has_plugin(PluginRequirement("my_plugin", specifier=">=1.2.3", inverted=True)):
122143
# Versioned workaroud
123144
# see bug-tracker/12345678
124-
return lookup_my_content_legacy(href)
125-
return super().show(href)
145+
return lookup_my_content_legacy(self.pulp_href)
146+
return super().show()
126147

127148

128149
# In pulp_cli_my_plugin
129150
@main.command()
130151
@pass_pulp_context
131-
@click.pass_context
132-
def my_command(ctx, pulp_ctx):
133-
pulp_ctx.needs_plugin(PluginRequirement("my_content", specifier=">=1.0.0"))
134-
ctx.obj = MyEntityContext(pulp_ctx)
152+
def my_command(pulp_ctx:PulpContext) -> None:
153+
pulp_ctx.needs_plugin(PluginRequirement("my_plugin", specifier=">=1.1.0"))
154+
# From here on we can assume `my_plugin>=1.1.0`.
135155
```
136156

137157
To declare version restrictions on *options*, the [`preprocess_entity`][pulp_glue.common.context.PulpEntityContext.preprocess_entity] method can be used to check if a given option is present in the request body and conditionally apply the requirements to the context.

pulp_cli/generic.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,10 @@ def pulp_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
756756

757757

758758
def resource_lookup_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
759-
lookup_key: str = kwargs.pop("lookup_key", "name")
759+
"""
760+
A factory to create a lookup option that will pass the lookup to the closest matching Context.
761+
"""
762+
lookup_key: t.Optional[str] = kwargs.pop("lookup_key", "name")
760763
context_class: t.Type[PulpEntityContext] = kwargs.pop("context_class")
761764

762765
def _option_callback(
@@ -788,9 +791,15 @@ def _option_callback(
788791
value=value, option_name=param.name
789792
)
790793
)
791-
else:
794+
elif lookup_key is not None:
792795
# The named identity of a resource was passed
793796
entity_ctx.entity = {lookup_key: value}
797+
else:
798+
raise click.ClickException(
799+
_("'{value}' is not recognised by {option_name}.").format(
800+
value=value, option_name=param.name
801+
)
802+
)
794803

795804
return entity_ctx
796805

@@ -801,14 +810,25 @@ def _option_callback(
801810
kwargs["expose_value"] = False
802811

803812
if "help" not in kwargs:
804-
kwargs["help"] = _(
805-
"A resource to look for identified by <{lookup_key}> or by <href>."
806-
).format(lookup_key=lookup_key)
813+
kwargs["help"] = (
814+
_("A resource to look for identified by <href>.")
815+
if lookup_key is None
816+
else _("A resource to look for identified by <{lookup_key}> or by <href>.").format(
817+
lookup_key=lookup_key
818+
)
819+
)
807820

808821
return click.option(*args, **kwargs)
809822

810823

811824
def resource_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
825+
"""
826+
Factory for an option that passes a preloaded PulpEntityContext to the command.
827+
828+
The resulting option will accept the entity being described in multiple ways, including its
829+
href.
830+
"""
831+
812832
default_plugin: t.Optional[str] = kwargs.pop("default_plugin", None)
813833
default_type: t.Optional[str] = kwargs.pop("default_type", None)
814834
lookup_key: str = kwargs.pop("lookup_key", "name")
@@ -924,11 +944,12 @@ def _multi_option_callback(
924944

925945
if "help" not in kwargs:
926946
kwargs["help"] = _(
927-
"Referenced resource, in the form {plugin_form}{type_form}<name> or by href. "
947+
"Referenced resource, in the form {plugin_form}{type_form}<{lookup_key}> or by href. "
928948
"{plugin_default}{type_default}{multiple_note}"
929949
).format(
930950
plugin_form=_("[<plugin>:]") if default_plugin else _("<plugin>:"),
931951
type_form=_("[<resource_type>:]") if default_type else _("<resource_type>:"),
952+
lookup_key=lookup_key,
932953
plugin_default=(
933954
_("'<plugin>' defaults to {plugin}. ").format(plugin=default_plugin)
934955
if default_plugin
@@ -946,6 +967,9 @@ def _multi_option_callback(
946967

947968

948969
def type_option(*args: t.Any, **kwargs: t.Any) -> t.Callable[[FC], FC]:
970+
"""
971+
A factory for `--type` options to allow selecting the class of the `PulpEntityContext` to use.
972+
"""
949973
choices: t.Dict[str, t.Type[PulpEntityContext]] = kwargs.pop("choices")
950974
assert choices and isinstance(choices, dict)
951975
type_names = list(choices.keys())

pulpcore/cli/core/upload.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
list_command,
1010
pass_pulp_context,
1111
pulp_group,
12+
resource_lookup_option,
1213
show_command,
1314
)
1415

@@ -23,7 +24,12 @@ def upload(ctx: click.Context, pulp_ctx: PulpCLIContext, /) -> None:
2324
ctx.obj = PulpUploadContext(pulp_ctx)
2425

2526

26-
lookup_options = [href_option]
27+
upload_lookup_option = resource_lookup_option(
28+
"--upload",
29+
lookup_key=None,
30+
context_class=PulpUploadContext,
31+
)
32+
lookup_options = [href_option, upload_lookup_option]
2733

2834
upload.add_command(list_command())
2935
upload.add_command(show_command(decorators=lookup_options))

0 commit comments

Comments
 (0)