Skip to content

Commit 0f116b6

Browse files
committed
Serve Python content form a repository version
closes #1324 Assisted by: Claude Sonnet 4
1 parent 2a3c52c commit 0f116b6

4 files changed

Lines changed: 115 additions & 5 deletions

File tree

CHANGES/1324.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for serving Python content form a repository version.

pulp-glue/src/pulp_glue/python/context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ def preprocess_entity(self, body: EntityDefinition, partial: bool = False) -> En
7272
body["publication"] = None
7373
if "repository" not in body and "publication" in body:
7474
body["repository"] = None
75+
76+
version = body.pop("version", None)
77+
if version is not None:
78+
repository_href = body.pop("repository")
79+
body["repository_version"] = f"{repository_href}versions/{version}/"
7580
return body
7681

7782

src/pulpcore/cli/python/distribution.py

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import typing as t
2+
13
import click
24

3-
from pulp_glue.common.context import PluginRequirement
5+
from pulp_glue.common.context import (
6+
EntityDefinition,
7+
EntityFieldDefinition,
8+
PluginRequirement,
9+
PulpEntityContext,
10+
)
411
from pulp_glue.common.i18n import get_translation
512
from pulp_glue.python.context import (
613
PulpPythonDistributionContext,
@@ -20,13 +27,13 @@
2027
label_command,
2128
list_command,
2229
name_option,
30+
pass_entity_context,
2331
pass_pulp_context,
2432
pulp_group,
2533
pulp_labels_option,
2634
pulp_option,
2735
resource_option,
2836
show_command,
29-
update_command,
3037
)
3138

3239
translation = get_translation(__package__)
@@ -40,6 +47,7 @@
4047
context_table={"python:python": PulpPythonRepositoryContext},
4148
help=_(
4249
"Repository to be used for auto-distributing."
50+
" When used with --version, this will create repository_version instead."
4351
" When set, this will unset the 'publication'."
4452
" Specified as '[[<plugin>:]<type>:]<name>' or as href."
4553
),
@@ -75,6 +83,12 @@ def distribution(ctx: click.Context, pulp_ctx: PulpCLIContext, /, distribution_t
7583
),
7684
),
7785
repository_option,
86+
pulp_option(
87+
"--version",
88+
type=int,
89+
help=_("A repository version number, leave blank for latest."),
90+
needs_plugins=[PluginRequirement("python", specifier=">=3.21.0")],
91+
),
7892
content_guard_option,
7993
pulp_option(
8094
"--allow-uploads/--block-uploads",
@@ -96,8 +110,77 @@ def distribution(ctx: click.Context, pulp_ctx: PulpCLIContext, /, distribution_t
96110
distribution.add_command(list_command(decorators=distribution_filter_options))
97111
distribution.add_command(show_command(decorators=lookup_options))
98112
distribution.add_command(create_command(decorators=create_options))
99-
distribution.add_command(
100-
update_command(decorators=lookup_options + update_options + [click.option("--base-path")])
101-
)
102113
distribution.add_command(destroy_command(decorators=lookup_options))
103114
distribution.add_command(label_command(decorators=nested_lookup_options))
115+
116+
117+
def apply_decorators(decorators_list: list[t.Callable[..., t.Any]]) -> t.Callable[..., t.Any]:
118+
def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
119+
for d in decorators_list:
120+
func = d(func)
121+
return func
122+
123+
return decorator
124+
125+
126+
@distribution.command()
127+
@apply_decorators(lookup_options + update_options + [click.option("--base-path")])
128+
@pass_entity_context
129+
def update(
130+
distribution_ctx: PulpEntityContext,
131+
/,
132+
publication: str | None,
133+
repository: EntityFieldDefinition,
134+
version: int | None,
135+
content_guard: EntityFieldDefinition,
136+
allow_uploads: bool | None,
137+
remote: EntityFieldDefinition,
138+
pulp_labels: dict[str, str] | None,
139+
base_path: str | None,
140+
) -> None:
141+
"""
142+
Update a Python distribution.
143+
"""
144+
assert isinstance(distribution_ctx, PulpPythonDistributionContext)
145+
146+
dist_body: EntityDefinition = distribution_ctx.entity
147+
body: EntityDefinition = dict()
148+
149+
if publication is not None:
150+
body["publication"] = publication
151+
if content_guard is not None:
152+
body["content_guard"] = content_guard
153+
if allow_uploads is not None:
154+
body["allow_uploads"] = allow_uploads
155+
if remote is not None:
156+
body["remote"] = remote
157+
if pulp_labels is not None:
158+
body["pulp_labels"] = pulp_labels
159+
if base_path is not None:
160+
body["base_path"] = base_path
161+
162+
if repository is not None and isinstance(repository, PulpPythonRepositoryContext):
163+
repo = repository.entity
164+
if version is not None:
165+
if dist_body.get("repository"):
166+
distribution_ctx.update(body={"repository": ""}, non_blocking=True)
167+
body["repository_version"] = f"{repo['versions_href']}{version}/"
168+
else:
169+
if dist_body.get("repository_version"):
170+
distribution_ctx.update(body={"repository_version": ""}, non_blocking=True)
171+
body["repository"] = repo["pulp_href"]
172+
elif version is not None:
173+
if dist_body.get("repository"):
174+
distribution_ctx.update(body={"repository": ""}, non_blocking=True)
175+
body["repository_version"] = f"{dist_body['repository']}versions/{version}/"
176+
elif dist_body.get("repository_version"):
177+
repository_href = dist_body["repository_version"].partition("versions")[0]
178+
body["repository_version"] = f"{repository_href}versions/{version}/"
179+
else:
180+
raise click.ClickException(
181+
_(
182+
"Distribution doesn't have a repository set, "
183+
"please specify the repository to use with --repository"
184+
)
185+
)
186+
distribution_ctx.update(body=body)

tests/scripts/pulp_python/test_distribution.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ cleanup() {
1010
pulp python repository destroy --name "cli_test_python_distribution_repository" || true
1111
pulp python remote destroy --name "cli_test_python_distribution_remote" || true
1212
pulp python distribution destroy --name "cli_test_python_distro" || true
13+
pulp python distribution destroy --name "cli_test_python_distro_repo_version" || true
1314
}
1415
trap cleanup EXIT
1516

@@ -42,3 +43,23 @@ expect_succ pulp python distribution update \
4243
--remote "cli_test_python_distribution_remote"
4344

4445
expect_succ pulp python distribution destroy --distribution "cli_test_python_distro"
46+
47+
# Test repository_version functionality
48+
expect_succ pulp python distribution create \
49+
--name "cli_test_python_distro_repo_version" \
50+
--base-path "cli_test_python_distro_repo_version" \
51+
--repository "cli_test_python_distribution_repository" \
52+
--version 0
53+
expect_succ pulp python distribution show --distribution "cli_test_python_distro_repo_version"
54+
echo "$OUTPUT" | jq -e '.repository_version | contains("/versions/0/")'
55+
echo "$OUTPUT" | jq -e '.repository == null'
56+
57+
expect_succ pulp python distribution update \
58+
--distribution "cli_test_python_distro_repo_version" \
59+
--repository "cli_test_python_distribution_repository" \
60+
--version 1
61+
expect_succ pulp python distribution show --distribution "cli_test_python_distro_repo_version"
62+
echo "$OUTPUT" | jq -e '.repository_version | contains("/versions/1/")'
63+
echo "$OUTPUT" | jq -e '.repository == null'
64+
65+
expect_succ pulp python distribution destroy --distribution "cli_test_python_distro_repo_version"

0 commit comments

Comments
 (0)