Skip to content

Commit 1ab5688

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

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
@@ -583,6 +583,9 @@ def otel_middleware_hook(settings):
583583
api_root = "/<path:api_root>/"
584584
else:
585585
api_root = settings.API_ROOT
586+
# protocol://host:port/{API_ROOT}{domain}/api/{version}/
587+
# All of the below are DEPRECATED, and should be replaced by calling
588+
# pulpcore.plugin.find_url.find_api_root() (q.v.)
586589
settings.set("V3_API_ROOT", api_root + "api/v3/") # Not user configurable
587590
settings.set("V3_DOMAIN_API_ROOT", api_root + "<slug:pulp_domain>/api/v3/")
588591
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
@@ -27,15 +27,13 @@
2727
OrphansCleanupViewset,
2828
ReclaimSpaceViewSet,
2929
)
30+
from pulpcore.plugin.find_url import find_api_root
3031

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

4038

4139
class ViewSetNode:
@@ -199,7 +197,7 @@ class PulpDefaultRouter(routers.DefaultRouter):
199197
SpectacularRedocView.as_view(
200198
authentication_classes=[],
201199
permission_classes=[],
202-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
200+
url=f"/{PATH_NODOMAIN_NOREWRITE_NOFRONT}docs/api.json?include_html=1&pk_path=1",
203201
)
204202
),
205203
name="schema-redoc",
@@ -210,17 +208,18 @@ class PulpDefaultRouter(routers.DefaultRouter):
210208
SpectacularSwaggerView.as_view(
211209
authentication_classes=[],
212210
permission_classes=[],
213-
url=f"{V3_API_ROOT}docs/api.json?include_html=1&pk_path=1",
211+
url=f"/{PATH_NODOMAIN_NOREWRITE_NOFRONT}docs/api.json?include_html=1&pk_path=1",
214212
)
215213
),
216214
name="schema-swagger",
217215
),
218216
]
219217

220218
urlpatterns = [
221-
path(API_ROOT, include(special_views)),
219+
path(PATH_DOMAIN_REWRITE_NOFRONT, include(special_views)),
222220
path("auth/", include("rest_framework.urls")),
223-
path(settings.V3_API_ROOT_NO_FRONT_SLASH, include(docs_and_status)),
221+
# docs/status aren't "inside" a domain
222+
path(PATH_NODOMAIN_REWRITE_NOFRONT, include(docs_and_status)),
224223
]
225224

226225
if settings.DOMAIN_ENABLED:
@@ -235,7 +234,7 @@ class NoSchema(p.callback.cls):
235234
view = NoSchema.as_view(**p.callback.initkwargs)
236235
name = p.name + "-domains" if p.name else None
237236
docs_and_status_no_schema.append(path(str(p.pattern), view, name=name))
238-
urlpatterns.insert(-1, path(API_ROOT, include(docs_and_status_no_schema)))
237+
urlpatterns.insert(-1, path(PATH_DOMAIN_REWRITE_NOFRONT, include(docs_and_status_no_schema)))
239238

240239
if "social_django" in settings.INSTALLED_APPS:
241240
urlpatterns.append(
@@ -247,7 +246,7 @@ class NoSchema(p.callback.cls):
247246

248247
all_routers = [root_router] + vs_tree.register_with(root_router)
249248
for router in all_routers:
250-
urlpatterns.append(path(API_ROOT, include(router.urls)))
249+
urlpatterns.append(path(PATH_DOMAIN_REWRITE_NOFRONT, include(router.urls)))
251250

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

pulpcore/app/util.py

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

3232
# a little cache so viewset_for_model doesn't have to iterate over every app every time
3333
_model_viewset_cache = {}
34-
STRIPPED_API_ROOT = settings.API_ROOT.strip("/")
3534

3635

3736
def reverse(viewname, args=None, kwargs=None, request=None, relative_url=True, **extra):
@@ -45,7 +44,7 @@ def reverse(viewname, args=None, kwargs=None, request=None, relative_url=True, *
4544
if settings.DOMAIN_ENABLED:
4645
kwargs.setdefault("pulp_domain", get_domain().name)
4746
if settings.API_ROOT_REWRITE_HEADER:
48-
kwargs.setdefault("api_root", getattr(request, "api_root", STRIPPED_API_ROOT))
47+
kwargs.setdefault("api_root", getattr(request, "api_root", settings.API_ROOT.strip("/")))
4948
if relative_url:
5049
request = None
5150
return drf_reverse(viewname, args=args, kwargs=kwargs, request=request, **extra)
@@ -79,7 +78,6 @@ def get_url(model, domain=None, request=None):
7978
kwargs["pk"] = model.pk
8079
else:
8180
view_action = "list"
82-
8381
return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request)
8482

8583

@@ -301,7 +299,6 @@ def get_view_name_for_model(model_obj, view_action):
301299
if isinstance(model_obj, models.MasterModel):
302300
model_obj = model_obj.cast()
303301
viewset = get_viewset_for_model(model_obj)
304-
305302
# return the complete view name, joining the registered viewset base name with
306303
# the requested view method.
307304
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)
@@ -63,7 +68,7 @@ class PulpAutoSchema(AutoSchema):
6368
"patch": "partial_update",
6469
"delete": "delete",
6570
}
66-
V3_API = API_ROOT_NO_FRONT_SLASH.replace("{pulp_domain}/", "")
71+
V3_API = FULL_API_PATH_NOFRONT.replace("{pulp_domain}/", "")
6772

6873
def _tokenize_path(self):
6974
"""
@@ -120,7 +125,7 @@ class MyViewSet(ViewSet):
120125
tokenized_path = self._tokenize_path()
121126

122127
subpath = "/".join(tokenized_path)
123-
operation_keys = subpath.replace(settings.V3_API_ROOT_NO_FRONT_SLASH, "").split("/")
128+
operation_keys = subpath.replace(self.V3_API, "").split("/")
124129
operation_keys = [i.title() for i in operation_keys]
125130
if len(operation_keys) > 2:
126131
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)
@@ -775,18 +776,18 @@ def pulp_content_origin_with_prefix(pulp_settings, bindings_cfg):
775776

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

791792

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

0 commit comments

Comments
 (0)