Skip to content

Commit a3f8ab2

Browse files
ggaineymdellweg
authored andcommitted
Added find_api_root() to standardize URL building-blocks.
(cherry picked from commit ff7a190)
1 parent d071811 commit a3f8ab2

12 files changed

Lines changed: 137 additions & 85 deletions

File tree

CHANGES/+api_root_changes.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `pulpcore.app.find_url.find_api_root()` to standardize Pulp url-creation.

pulp_file/tests/unit/test_serializers.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22
from django.core.files.uploadedfile import SimpleUploadedFile
33
from django.test import TestCase
44

5+
from pulpcore.plugin.find_url import find_api_root
56
from pulpcore.plugin.models import Artifact
67

78
from pulp_file.app.models import FileContent
89
from pulp_file.app.serializers import FileContentSerializer
910

10-
V3_API_ROOT = (
11-
settings.V3_API_ROOT
12-
if not settings.DOMAIN_ENABLED
13-
else settings.V3_DOMAIN_API_ROOT.replace("<slug:pulp_domain>", "default")
14-
)
15-
11+
_, V3_API_ROOT = find_api_root(domain="default")
1612

1713
CHECKSUM_LEN = {
1814
"md5": 32,

pulpcore/app/find_url.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from django.conf import settings
2+
3+
DOMAIN_SLUG = "<slug:pulp_domain>"
4+
REWRITE_SLUG = "/<path:api_root>/"
5+
6+
7+
# We isolate this utility-function to avoid pulling in random "Django App must be configured"
8+
# code segments from the rest of the .util methods/imports
9+
def find_api_root(version="v3", set_domain=True, domain=None, lstrip=False, rewrite_header=True):
10+
"""
11+
Returns the tuple (api-root, <root>/api/<version>)
12+
13+
Args:
14+
version (str): API-version desired
15+
set_domain (bool): Should the domain-slug be included if DOMAIN_ENABLED?
16+
domain (str): Domain-name to replace DOMAIN_SLUG
17+
lstrip (bool): Should the full version have it's leading-/ stripped
18+
rewrite_header (bool): Should API_ROOT_REWRITE_HEADER be honored or not
19+
20+
Examples:
21+
find_api_root() : ("/pulp/", "/pulp/api/v3/")
22+
find_api_root(), DOMAIN_ENABLED: ("/pulp/", "/pulp/<slug:pulp_domain>/api/v3/")
23+
find_api_root(domain="default"), DOMAIN_ENABLED : ("/pulp/", "/pulp/default/api/v3/")
24+
find_api_root(lstrip=True) : ("pulp/", "pulp/api/v3/")
25+
find_api_root(), API_ROOT_REWRITE_HEADER: ("/<path:api_root>/", "/<path:api_root>/api/v3/")
26+
find_api_root(version="v4", domain="foo", lstrip=True), API_ROOT_REWRITE_HEADER :
27+
("<path:api_root>/", "<path:api_root>/default/api/v4/")
28+
Returns:
29+
(str, str) : (API_ROOT (possibly re-written), API_ROOT/api/<version>/
30+
(with <domain> if enabled))
31+
"""
32+
# Some current path-building wants to ignore REWRITE - make that possible
33+
if rewrite_header and settings.API_ROOT_REWRITE_HEADER:
34+
api_root = REWRITE_SLUG
35+
else:
36+
api_root = settings.API_ROOT
37+
38+
# Some current path-building wants to ignore DOMAIN - make that possible
39+
if set_domain and settings.DOMAIN_ENABLED:
40+
if domain:
41+
path = f"{api_root}{domain}/api/{version}/"
42+
else:
43+
path = f"{api_root}{DOMAIN_SLUG}/api/{version}/"
44+
else:
45+
path = f"{api_root}api/{version}/"
46+
if lstrip:
47+
return api_root.lstrip("/"), path.lstrip("/")
48+
else:
49+
return api_root, path

pulpcore/app/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ def otel_middleware_hook(settings):
559559
api_root = "/<path:api_root>/"
560560
else:
561561
api_root = settings.API_ROOT
562+
# protocol://host:port/{API_ROOT}{domain}/api/{version}/
563+
# All of the below are DEPRECATED, and should be replaced by calling
564+
# pulpcore.plugin.find_url.find_api_root() (q.v.)
562565
settings.set("V3_API_ROOT", api_root + "api/v3/") # Not user configurable
563566
settings.set("V3_DOMAIN_API_ROOT", api_root + "<slug:pulp_domain>/api/v3/")
564567
settings.set("V3_API_ROOT_NO_FRONT_SLASH", settings.V3_API_ROOT.lstrip("/"))
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from django.conf import settings
21
from django.template.defaultfilters import register, stringfilter
32
from django.template.defaultfilters import urlize as orig_urlize
43
from django.utils.safestring import SafeData, mark_safe
54

5+
from pulpcore.app.find_url import find_api_root
6+
67

78
@register.filter(needs_autoescape=True)
89
@stringfilter
@@ -12,13 +13,19 @@ def urlize(text, autoescape=True):
1213
1314
This filter overwrites the django default implementation to also handle pulp api hrefs.
1415
"""
16+
# urlize() will turn strings into links of they're of the form protocol://site/path.
17+
# We force it to recognize Pulp-api-links by replacing strings in the incoming text of
18+
# the form "API_ROOT/api/v3/whatever", forcing them to look like
19+
# "http://SENTINEL.org/(string)", urlize()ing the result, then undoing the replace to
20+
# lose the http://SENTINEL.org's.
21+
_, current_path = find_api_root(set_domain=False, rewrite_header=False)
1522
safe_input = isinstance(text, SafeData)
16-
text = text.replace(settings.V3_API_ROOT, "http://SENTINEL.org" + settings.V3_API_ROOT)
23+
text = text.replace(current_path, "http://SENTINEL.org" + current_path)
1724
if safe_input:
1825
text = mark_safe(text)
1926
text = orig_urlize(text, autoescape=autoescape)
2027
safe_input = isinstance(text, SafeData)
21-
text = text.replace("http://SENTINEL.org" + settings.V3_API_ROOT, settings.V3_API_ROOT)
28+
text = text.replace("http://SENTINEL.org" + current_path, current_path)
2229
if safe_input:
2330
text = mark_safe(text)
2431
return text

pulpcore/app/urls.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@
2626
OrphansCleanupViewset,
2727
ReclaimSpaceViewSet,
2828
)
29+
from pulpcore.plugin.find_url import find_api_root
2930

30-
if settings.DOMAIN_ENABLED:
31-
API_ROOT = settings.V3_DOMAIN_API_ROOT_NO_FRONT_SLASH
32-
else:
33-
API_ROOT = settings.V3_API_ROOT_NO_FRONT_SLASH
34-
if settings.API_ROOT_REWRITE_HEADER:
35-
V3_API_ROOT = settings.V3_API_ROOT.replace("/<path:api_root>/", settings.API_ROOT)
36-
else:
37-
V3_API_ROOT = settings.V3_API_ROOT
31+
_, PATH_DOMAIN_REWRITE_NOFRONT = find_api_root(lstrip=True, set_domain=True, rewrite_header=True)
32+
_, PATH_NODOMAIN_NOREWRITE_NOFRONT = find_api_root(
33+
lstrip=True, set_domain=False, rewrite_header=False
34+
)
35+
_, PATH_NODOMAIN_REWRITE_NOFRONT = find_api_root(lstrip=True, set_domain=False, rewrite_header=True)
3836

3937

4038
class ViewSetNode:
@@ -191,7 +189,7 @@ class PulpDefaultRouter(routers.DefaultRouter):
191189
SpectacularRedocView.as_view(
192190
authentication_classes=[],
193191
permission_classes=[],
194-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
192+
url=f"/{PATH_NODOMAIN_NOREWRITE_NOFRONT}docs/api.json?include_html=1&pk_path=1",
195193
),
196194
name="schema-redoc",
197195
),
@@ -200,16 +198,17 @@ class PulpDefaultRouter(routers.DefaultRouter):
200198
SpectacularSwaggerView.as_view(
201199
authentication_classes=[],
202200
permission_classes=[],
203-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
201+
url=f"/{PATH_NODOMAIN_NOREWRITE_NOFRONT}docs/api.json?include_html=1&pk_path=1",
204202
),
205203
name="schema-swagger",
206204
),
207205
]
208206

209207
urlpatterns = [
210-
path(API_ROOT, include(special_views)),
208+
path(PATH_DOMAIN_REWRITE_NOFRONT, include(special_views)),
211209
path("auth/", include("rest_framework.urls")),
212-
path(settings.V3_API_ROOT_NO_FRONT_SLASH, include(docs_and_status)),
210+
# docs/status aren't "inside" a domain
211+
path(PATH_NODOMAIN_REWRITE_NOFRONT, include(docs_and_status)),
213212
]
214213

215214
if settings.DOMAIN_ENABLED:
@@ -224,7 +223,7 @@ class NoSchema(p.callback.cls):
224223
view = NoSchema.as_view(**p.callback.initkwargs)
225224
name = p.name + "-domains" if p.name else None
226225
docs_and_status_no_schema.append(path(str(p.pattern), view, name=name))
227-
urlpatterns.insert(-1, path(API_ROOT, include(docs_and_status_no_schema)))
226+
urlpatterns.insert(-1, path(PATH_DOMAIN_REWRITE_NOFRONT, include(docs_and_status_no_schema)))
228227

229228
if "social_django" in settings.INSTALLED_APPS:
230229
urlpatterns.append(
@@ -236,7 +235,7 @@ class NoSchema(p.callback.cls):
236235

237236
all_routers = [root_router] + vs_tree.register_with(root_router)
238237
for router in all_routers:
239-
urlpatterns.append(path(API_ROOT, include(router.urls)))
238+
urlpatterns.append(path(PATH_DOMAIN_REWRITE_NOFRONT, include(router.urls)))
240239

241240
# If plugins define a urls.py, include them into the root namespace.
242241
for plugin_pattern in plugin_patterns:

pulpcore/app/util.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
# a little cache so viewset_for_model doesn't have to iterate over every app every time
3030
_model_viewset_cache = {}
31-
STRIPPED_API_ROOT = settings.API_ROOT.strip("/")
3231

3332

3433
def reverse(viewname, args=None, kwargs=None, request=None, relative_url=True, **extra):
@@ -42,7 +41,7 @@ def reverse(viewname, args=None, kwargs=None, request=None, relative_url=True, *
4241
if settings.DOMAIN_ENABLED:
4342
kwargs.setdefault("pulp_domain", get_domain().name)
4443
if settings.API_ROOT_REWRITE_HEADER:
45-
kwargs.setdefault("api_root", getattr(request, "api_root", STRIPPED_API_ROOT))
44+
kwargs.setdefault("api_root", getattr(request, "api_root", settings.API_ROOT.strip("/")))
4645
if relative_url:
4746
request = None
4847
return drf_reverse(viewname, args=args, kwargs=kwargs, request=request, **extra)
@@ -76,7 +75,6 @@ def get_url(model, domain=None, request=None):
7675
kwargs["pk"] = model.pk
7776
else:
7877
view_action = "list"
79-
8078
return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request)
8179

8280

@@ -298,7 +296,6 @@ def get_view_name_for_model(model_obj, view_action):
298296
if isinstance(model_obj, models.MasterModel):
299297
model_obj = model_obj.cast()
300298
viewset = get_viewset_for_model(model_obj)
301-
302299
# return the complete view name, joining the registered viewset base name with
303300
# the requested view method.
304301
for router in all_routers:

pulpcore/openapi/__init__.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,21 @@
3434

3535
from pulpcore.app.apps import pulp_plugin_configs
3636
from pulpcore.app.loggers import deprecation_logger
37+
from pulpcore.plugin.find_url import find_api_root
3738

39+
# Get the API-ROOT for this installation
40+
_unused, FULL_API_PATH_NOFRONT = find_api_root(lstrip=True)
41+
42+
# Massage some api-affecting vars to "genericize" them for the spec
3843
if settings.DOMAIN_ENABLED:
39-
API_ROOT_NO_FRONT_SLASH = settings.V3_DOMAIN_API_ROOT_NO_FRONT_SLASH.replace("slug:", "")
40-
else:
41-
API_ROOT_NO_FRONT_SLASH = settings.V3_API_ROOT_NO_FRONT_SLASH
44+
FULL_API_PATH_NOFRONT = FULL_API_PATH_NOFRONT.replace("slug:", "")
4245
if settings.API_ROOT_REWRITE_HEADER:
43-
API_ROOT_NO_FRONT_SLASH = API_ROOT_NO_FRONT_SLASH.replace(
46+
FULL_API_PATH_NOFRONT = FULL_API_PATH_NOFRONT.replace(
4447
"<path:api_root>", settings.API_ROOT.strip("/")
4548
)
46-
API_ROOT_NO_FRONT_SLASH = API_ROOT_NO_FRONT_SLASH.replace("<", "{").replace(">", "}")
49+
50+
# Final massage to make api-root "openapi compatible"
51+
FULL_API_PATH_NOFRONT = FULL_API_PATH_NOFRONT.replace("<", "{").replace(">", "}")
4752

4853
# Python does not distinguish integer sizes. The safest assumption is that they are large.
4954
extend_schema_field(OpenApiTypes.INT64)(serializers.IntegerField)
@@ -59,7 +64,7 @@ class PulpAutoSchema(AutoSchema):
5964
"patch": "partial_update",
6065
"delete": "delete",
6166
}
62-
V3_API = API_ROOT_NO_FRONT_SLASH.replace("{pulp_domain}/", "")
67+
V3_API = FULL_API_PATH_NOFRONT.replace("{pulp_domain}/", "")
6368

6469
def _tokenize_path(self):
6570
"""
@@ -116,7 +121,7 @@ class MyViewSet(ViewSet):
116121
tokenized_path = self._tokenize_path()
117122

118123
subpath = "/".join(tokenized_path)
119-
operation_keys = subpath.replace(settings.V3_API_ROOT_NO_FRONT_SLASH, "").split("/")
124+
operation_keys = subpath.replace(self.V3_API, "").split("/")
120125
operation_keys = [i.title() for i in operation_keys]
121126
if len(operation_keys) > 2:
122127
del operation_keys[1]

pulpcore/plugin/find_url.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from pulpcore.app.find_url import find_api_root
2+
3+
__all__ = ["find_api_root"]

pulpcore/pytest_plugin.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from packaging.version import parse as parse_version
2222
from yarl import URL
2323

24+
from pulpcore.plugin.find_url import find_api_root
2425
from pulpcore.tests.functional.utils import (
2526
SLEEP_TIME,
2627
TASK_TIMEOUT,
@@ -617,7 +618,7 @@ def _settings_factory(storage_class=None, storage_settings=None):
617618
]
618619

619620
def get_installation_storage_option(key, backend):
620-
value = pulp_settings.STORAGES["default"]["OPTIONS"].get(key)
621+
value = pulp_settings.STORAGES["default"].get("OPTIONS", {}).get(key)
621622
# Some FileSystem backend options may be defined in the top settings module
622623
if backend == "pulpcore.app.models.storage.FileSystem" and not value:
623624
value = getattr(pulp_settings, key, None)
@@ -772,18 +773,18 @@ def pulp_content_origin_with_prefix(pulp_settings):
772773

773774
@pytest.fixture(scope="session")
774775
def pulp_api_v3_path(pulp_settings, pulp_domain_enabled):
776+
root, path = find_api_root(set_domain=True, rewrite_header=False, domain="default")
777+
778+
# Check that find_api_root() is doing what the tests expect
775779
if pulp_domain_enabled:
776-
v3_api_root = pulp_settings.V3_DOMAIN_API_ROOT
777-
v3_api_root = v3_api_root.replace("<slug:pulp_domain>", "default")
780+
v3_api_root = f"{pulp_settings.API_ROOT}default/api/v3/"
778781
else:
779-
v3_api_root = pulp_settings.V3_API_ROOT
780-
if v3_api_root is None:
781-
raise RuntimeError(
782-
"This fixture requires the server to have the `V3_API_ROOT` setting set."
783-
)
784-
if pulp_settings.API_ROOT_REWRITE_HEADER:
785-
v3_api_root = v3_api_root.replace("<path:api_root>", pulp_settings.API_ROOT.strip("/"))
786-
return v3_api_root
782+
v3_api_root = f"{pulp_settings.API_ROOT}api/v3/"
783+
assert path == v3_api_root
784+
785+
if path is None:
786+
raise RuntimeError("This fixture requires the server to have the `API_ROOT` setting set.")
787+
return path
787788

788789

789790
@pytest.fixture(scope="session")

0 commit comments

Comments
 (0)