Skip to content

Commit 0e10ec5

Browse files
mdellwegdralley
authored andcommitted
Create a parallel v4 API
closes #6462
1 parent 210994a commit 0e10ec5

File tree

11 files changed

+80
-42
lines changed

11 files changed

+80
-42
lines changed

CHANGES/6462.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a /pulp/api/v4/ namespace in parallel with the existing /pulp/api/v3/ namespace. This is disabled by default (`settings.ENABLE_V4_API`) and should be used only for development & experimentation.

pulpcore/app/management/commands/analyze-publication.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def handle(self, *args, **options):
4848
published_artifacts = publication.published_artifact.select_related(
4949
"content_artifact__artifact"
5050
).order_by("relative_path")
51-
artifact_href_prefix = reverse(get_view_name_for_model(Artifact, "list"))
51+
artifact_href_prefix = reverse(get_view_name_for_model(Artifact, "list")) # todo: reverse() + namespacing issues, print PRN instead?
5252

5353
if options["tabular"]:
5454
table = PrettyTable()

pulpcore/app/models/repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ def get_content_href(self, request=None):
13611361
ctype_model = ctypes[self.content_type]
13621362
ctype_view = get_view_name_for_model(ctype_model, "list")
13631363
try:
1364-
ctype_url = reverse(ctype_view, request=request)
1364+
ctype_url = reverse(ctype_view, request=request) # TODO: reverse() + namespacing issues
13651365
except django.urls.exceptions.NoReverseMatch:
13661366
# We've hit a content type for which there is no viewset.
13671367
# There's nothing we can do here, except to skip it.

pulpcore/app/response.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def __init__(self, task, request):
2323
request (rest_framework.request.Request): Request used to generate the pulp_href urls
2424
"""
2525
kwargs = {"pk": task.pk}
26-
resp = {"task": reverse("tasks-detail", kwargs=kwargs, request=request)}
26+
resp = {"task": reverse("tasks-detail", kwargs=kwargs, request=request)} # reverse() + namespacing issues
2727
super().__init__(data=resp, status=202)
2828

2929

@@ -47,5 +47,5 @@ def __init__(self, task_group, request):
4747
request (rest_framework.request.Request): Request used to generate the pulp_href urls
4848
"""
4949
kwargs = {"pk": task_group.pk}
50-
resp = {"task_group": reverse("task-groups-detail", kwargs=kwargs, request=request)}
50+
resp = {"task_group": reverse("task-groups-detail", kwargs=kwargs, request=request)} # reverse() + namespacing issues
5151
super().__init__(data=resp, status=202)

pulpcore/app/serializers/base.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class HrefPrnFieldMixin:
6868

6969
def get_url(self, obj, view_name, request, *args, **kwargs):
7070
# Use the Pulp reverse method to display relative hrefs.
71-
self.reverse = _reverse(obj)
71+
self.reverse = _reverse(obj) # TODO: reverse() + namespacing issues
7272
return super().get_url(obj, view_name, request, *args, **kwargs)
7373

7474
def to_internal_value(self, data):
@@ -456,6 +456,15 @@ class Meta:
456456
read_only=True,
457457
)
458458

459+
def to_representation(self, instance):
460+
"""Overridden to drop the pulp_href field from responses"""
461+
representation = super().to_representation(instance)
462+
if self.context.get("request").version == "v4":
463+
# TODO: this feels hacky, but apparently this code is being used on serializers w/o pulp_href
464+
if "pulp_href" in representation:
465+
representation.pop("pulp_href")
466+
return representation
467+
459468
def _validate_relative_path(self, path):
460469
"""
461470
Validate a relative path (eg from a url) to ensure it forms a valid url and does not begin

pulpcore/app/serializers/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def to_representation(self, value):
229229
if content_artifact.artifact_id:
230230
kwargs["pk"] = content_artifact.artifact_id
231231
request = self.context.get("request")
232-
url = reverse("artifacts-detail", kwargs=kwargs, request=request)
232+
url = reverse("artifacts-detail", kwargs=kwargs, request=request) # TODO: reverse() + namespacing issues
233233
else:
234234
url = None
235235
ret[content_artifact.relative_path] = url

pulpcore/app/serializers/task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def get_created_by(self, obj):
9494
if user_id := task_user_map.get(str(obj.pk)):
9595
kwargs = {"pk": user_id}
9696
request = self.context.get("request")
97-
return reverse("users-detail", kwargs=kwargs, request=request)
97+
return reverse("users-detail", kwargs=kwargs, request=request) # TODO: reverse() + namespacing issues
9898
return None
9999

100100
class Meta:

pulpcore/app/settings.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@
119119
API_ROOT = "/pulp/"
120120
API_ROOT_REWRITE_HEADER = None
121121

122+
# Enable Pulp v4 API namespace
123+
ENABLE_V4_API = False
124+
122125
# Application definition
123126

124127
INSTALLED_APPS = [
@@ -211,7 +214,9 @@
211214
"rest_framework.authentication.SessionAuthentication",
212215
),
213216
"UPLOADED_FILES_USE_URL": False,
214-
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
217+
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
218+
"DEFAULT_VERSION": "v3",
219+
"ALLOWED_VERSIONS": ["v3", "v4"],
215220
"DEFAULT_SCHEMA_CLASS": "pulpcore.openapi.PulpAutoSchema",
216221
}
217222

@@ -639,3 +644,8 @@ def otel_middleware_hook(settings):
639644
settings.set("V3_DOMAIN_API_ROOT", api_root + "<slug:pulp_domain>/api/v3/")
640645
settings.set("V3_API_ROOT_NO_FRONT_SLASH", settings.V3_API_ROOT.lstrip("/"))
641646
settings.set("V3_DOMAIN_API_ROOT_NO_FRONT_SLASH", settings.V3_DOMAIN_API_ROOT.lstrip("/"))
647+
648+
settings.set("V4_API_ROOT", api_root + "api/v4/") # Not user configurable
649+
settings.set("V4_DOMAIN_API_ROOT", api_root + "<slug:pulp_domain>/api/v4/")
650+
settings.set("V4_API_ROOT_NO_FRONT_SLASH", settings.V4_API_ROOT.lstrip("/"))
651+
settings.set("V4_DOMAIN_API_ROOT_NO_FRONT_SLASH", settings.V4_DOMAIN_API_ROOT.lstrip("/"))

pulpcore/app/urls.py

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
API_ROOT = settings.V3_API_ROOT_NO_FRONT_SLASH
3535
if settings.API_ROOT_REWRITE_HEADER:
3636
V3_API_ROOT = settings.V3_API_ROOT.replace("/<path:api_root>/", settings.API_ROOT)
37+
V4_API_ROOT = settings.V4_API_ROOT.replace("/<path:api_root>/", settings.API_ROOT)
3738
else:
3839
V3_API_ROOT = settings.V3_API_ROOT
40+
V4_API_ROOT = settings.V4_API_ROOT
3941

4042

4143
class ViewSetNode:
@@ -174,45 +176,57 @@ class PulpDefaultRouter(routers.DefaultRouter):
174176
),
175177
]
176178

177-
docs_and_status = [
178-
path("livez/", LivezView.as_view()),
179-
path("status/", StatusView.as_view()),
180-
path(
181-
"docs/api.json",
182-
SpectacularJSONAPIView.as_view(authentication_classes=[], permission_classes=[]),
183-
name="schema",
184-
),
185-
path(
186-
"docs/api.yaml",
187-
SpectacularYAMLAPIView.as_view(authentication_classes=[], permission_classes=[]),
188-
name="schema-yaml",
189-
),
190-
path(
191-
"docs/",
192-
SpectacularRedocView.as_view(
193-
authentication_classes=[],
194-
permission_classes=[],
195-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
179+
def _docs_and_status(_api_root):
180+
paths = [
181+
path(
182+
"docs/api.json",
183+
SpectacularJSONAPIView.as_view(authentication_classes=[], permission_classes=[]),
184+
name="schema",
196185
),
197-
name="schema-redoc",
198-
),
199-
path(
200-
"swagger/",
201-
SpectacularSwaggerView.as_view(
202-
authentication_classes=[],
203-
permission_classes=[],
204-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
186+
path(
187+
"docs/api.yaml",
188+
SpectacularYAMLAPIView.as_view(authentication_classes=[], permission_classes=[]),
189+
name="schema-yaml",
205190
),
206-
name="schema-swagger",
207-
),
208-
]
191+
path(
192+
"docs/",
193+
SpectacularRedocView.as_view(
194+
authentication_classes=[],
195+
permission_classes=[],
196+
url=f"{_api_root}docs/api.json?include_html=1&pk_path=1",
197+
),
198+
name="schema-redoc",
199+
),
200+
path(
201+
"swagger/",
202+
SpectacularSwaggerView.as_view(
203+
authentication_classes=[],
204+
permission_classes=[],
205+
url=f"{_api_root}docs/api.json?include_html=1&pk_path=1",
206+
),
207+
name="schema-swagger",
208+
),
209+
path("livez/", LivezView.as_view(), name="livez"),
210+
path("status/", StatusView.as_view(), name="status"),
211+
]
212+
213+
return paths
214+
215+
v3_docs_and_status = _docs_and_status(V3_API_ROOT)
216+
v4_docs_and_status = _docs_and_status(V4_API_ROOT)
209217

210218
urlpatterns = [
211-
path(API_ROOT, include(special_views)),
212219
path("auth/", include("rest_framework.urls")),
213-
path(settings.V3_API_ROOT_NO_FRONT_SLASH, include(docs_and_status)),
220+
path(API_ROOT, include(special_views)),
221+
path(settings.V3_API_ROOT_NO_FRONT_SLASH, include(v3_docs_and_status)),
214222
]
215223

224+
if settings.ENABLE_V4_API:
225+
urlpatterns.extend([
226+
path(V4_API_ROOT, include((special_views, "core"), namespace="v4")),
227+
path(settings.V4_API_ROOT_NO_FRONT_SLASH, include((v4_docs_and_status, "core"), namespace="v4")),
228+
])
229+
216230
if settings.DOMAIN_ENABLED:
217231
# Ensure Docs and Status endpoints are available within domains, but are not shown in API schema
218232
docs_and_status_no_schema = []
@@ -239,6 +253,10 @@ class NoSchema(p.callback.cls):
239253
for router in all_routers:
240254
urlpatterns.append(path(API_ROOT, include(router.urls)))
241255

256+
if settings.ENABLE_V4_API:
257+
for router in all_routers:
258+
urlpatterns.append(path(V4_API_ROOT, include((router.urls, "core"), namespace="v4")))
259+
242260
# If plugins define a urls.py, include them into the root namespace.
243261
for plugin_pattern in plugin_patterns:
244262
urlpatterns.append(path("", include(plugin_pattern)))

pulpcore/app/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def get_url(model, domain=None, request=None):
7979
view_action = "detail"
8080
kwargs["pk"] = model.pk
8181

82-
return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request)
82+
return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request) # TODO: reverse() + namespacing issues
8383

8484

8585
def get_prn(instance=None, uri=None):

0 commit comments

Comments
 (0)