Skip to content

Commit 0af11b5

Browse files
GitHKAndrei Neagu
and
Andrei Neagu
authored
monkey_patches an issue with Pydantic causing PageLinks validation to fail (#2298)
* refactoring test * added missing refactor * adding breaking test * added monkeypatch to fix issues with urls * extended test suite to work with new type of urls * fix pylint * removed unused * moved to take effect inside webserver * using more reliable version parsing * trying to see if CI is broken because of this patch * if pyndatic is missing will skip patching * adding message to disable patching * skip module import if not required and print a message for later usage * this pattern should avoid locking the CI Co-authored-by: Andrei Neagu <[email protected]>
1 parent e72a398 commit 0af11b5

File tree

3 files changed

+127
-15
lines changed

3 files changed

+127
-15
lines changed

packages/service-library/src/servicelib/rest_pagination_utils.py

+42
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,48 @@
1313
from yarl import URL
1414

1515

16+
def monkey_patch_pydantic_url_regex() -> None:
17+
# waiting for PR https://github.com/samuelcolvin/pydantic/pull/2512 to be released into
18+
# pydantic main codebase
19+
import pydantic
20+
21+
if pydantic.VERSION > "1.8.1":
22+
raise RuntimeError(
23+
(
24+
"Please check that PR https://github.com/samuelcolvin/pydantic/pull/2512 "
25+
"was merged. If already present in this version, remove this monkey_patch"
26+
)
27+
)
28+
29+
from typing import Pattern
30+
from pydantic import networks
31+
import re
32+
33+
def url_regex() -> Pattern[str]:
34+
_url_regex_cache = networks._url_regex_cache # pylint: disable=protected-access
35+
if _url_regex_cache is None:
36+
_url_regex_cache = re.compile(
37+
r"(?:(?P<scheme>[a-z][a-z0-9+\-.]+)://)?" # scheme https://tools.ietf.org/html/rfc3986#appendix-A
38+
r"(?:(?P<user>[^\s:/]*)(?::(?P<password>[^\s/]*))?@)?" # user info
39+
r"(?:"
40+
r"(?P<ipv4>(?:\d{1,3}\.){3}\d{1,3})(?=$|[/:#?])|" # ipv4
41+
r"(?P<ipv6>\[[A-F0-9]*:[A-F0-9:]+\])(?=$|[/:#?])|" # ipv6
42+
r"(?P<domain>[^\s/:?#]+)" # domain, validation occurs later
43+
r")?"
44+
r"(?::(?P<port>\d+))?" # port
45+
r"(?P<path>/[^\s?#]*)?" # path
46+
r"(?:\?(?P<query>[^\s#]+))?" # query
47+
r"(?:#(?P<fragment>\S+))?", # fragment
48+
re.IGNORECASE,
49+
)
50+
return _url_regex_cache
51+
52+
networks.url_regex = url_regex
53+
54+
55+
monkey_patch_pydantic_url_regex()
56+
57+
1658
class PageMetaInfoLimitOffset(BaseModel):
1759
limit: PositiveInt
1860
total: NonNegativeInt

packages/service-library/tests/test_rest_pagination_utils.py

+82-15
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,28 @@ def test_empty_data_is_converted_to_list():
5757
assert model_instance.data == []
5858

5959

60-
def test_paginating_data():
60+
@pytest.mark.parametrize(
61+
"base_url",
62+
[
63+
"http://site.com",
64+
"http://site.com/",
65+
"http://some/random/url.com",
66+
"http://some/random/url.com/",
67+
"http://s.s.s.s.subsite.site.com",
68+
"http://s.s.s.s.subsite.site.com/",
69+
"http://10.0.0.1.nip.io/",
70+
"http://10.0.0.1.nip.io:8091/",
71+
"http://10.0.0.1.nip.io",
72+
"http://10.0.0.1.nip.io:8091",
73+
],
74+
)
75+
def test_paginating_data(base_url):
6176
# create random data
6277
total_number_of_data = 29
6378
limit = 9
6479
offset = 0
6580
partial_data = [range(9)]
66-
request_url = URL("http://some/random/url.com?some=1&random=4&query=true")
81+
request_url = URL(f"{base_url}?some=1&random=4&query=true")
6782

6883
# first "call"
6984
model_instance: PageResponseLimitOffset = PageResponseLimitOffset.paginate_data(
@@ -75,11 +90,27 @@ def test_paginating_data():
7590
total=total_number_of_data, count=len(partial_data), limit=limit, offset=offset
7691
)
7792
assert model_instance.links == PageLinks(
78-
self=f"http://some/random/url.com?some=1&random=4&query=true&offset={offset}&limit={limit}",
79-
first=f"http://some/random/url.com?some=1&random=4&query=true&offset=0&limit={limit}",
93+
self=str(
94+
URL(base_url).with_query(
95+
f"some=1&random=4&query=true&offset={offset}&limit={limit}"
96+
)
97+
),
98+
first=str(
99+
URL(base_url).with_query(
100+
f"some=1&random=4&query=true&offset=0&limit={limit}"
101+
)
102+
),
80103
prev=None,
81-
next=f"http://some/random/url.com?some=1&random=4&query=true&offset=9&limit={limit}",
82-
last=f"http://some/random/url.com?some=1&random=4&query=true&offset=27&limit={limit}",
104+
next=str(
105+
URL(base_url).with_query(
106+
f"some=1&random=4&query=true&offset=9&limit={limit}"
107+
)
108+
),
109+
last=str(
110+
URL(base_url).with_query(
111+
f"some=1&random=4&query=true&offset=27&limit={limit}"
112+
)
113+
),
83114
)
84115

85116
# next "call"s
@@ -100,11 +131,31 @@ def test_paginating_data():
100131
offset=offset + i * limit,
101132
)
102133
assert model_instance.links == PageLinks(
103-
self=f"http://some/random/url.com?some=1&random=4&query=true&offset={offset + i*limit}&limit={limit}",
104-
first=f"http://some/random/url.com?some=1&random=4&query=true&offset=0&limit={limit}",
105-
prev=f"http://some/random/url.com?some=1&random=4&query=true&offset={offset + i*limit-limit}&limit={limit}",
106-
next=f"http://some/random/url.com?some=1&random=4&query=true&offset={offset + i*limit+limit}&limit={limit}",
107-
last=f"http://some/random/url.com?some=1&random=4&query=true&offset=27&limit={limit}",
134+
self=str(
135+
URL(base_url).with_query(
136+
f"some=1&random=4&query=true&offset={offset + i*limit}&limit={limit}"
137+
)
138+
),
139+
first=str(
140+
URL(base_url).with_query(
141+
f"some=1&random=4&query=true&offset=0&limit={limit}"
142+
)
143+
),
144+
prev=str(
145+
URL(base_url).with_query(
146+
f"some=1&random=4&query=true&offset={offset + i*limit-limit}&limit={limit}"
147+
)
148+
),
149+
next=str(
150+
URL(base_url).with_query(
151+
f"some=1&random=4&query=true&offset={offset + i*limit+limit}&limit={limit}"
152+
)
153+
),
154+
last=str(
155+
URL(base_url).with_query(
156+
f"some=1&random=4&query=true&offset=27&limit={limit}"
157+
)
158+
),
108159
)
109160

110161
# last "call"
@@ -124,9 +175,25 @@ def test_paginating_data():
124175
offset=offset + 3 * limit,
125176
)
126177
assert model_instance.links == PageLinks(
127-
self=f"http://some/random/url.com?some=1&random=4&query=true&offset={offset+3*limit}&limit={limit}",
128-
first=f"http://some/random/url.com?some=1&random=4&query=true&offset=0&limit={limit}",
129-
prev=f"http://some/random/url.com?some=1&random=4&query=true&offset=18&limit={limit}",
178+
self=str(
179+
URL(base_url).with_query(
180+
f"some=1&random=4&query=true&offset={offset+3*limit}&limit={limit}"
181+
)
182+
),
183+
first=str(
184+
URL(base_url).with_query(
185+
f"some=1&random=4&query=true&offset=0&limit={limit}"
186+
)
187+
),
188+
prev=str(
189+
URL(base_url).with_query(
190+
f"some=1&random=4&query=true&offset=18&limit={limit}"
191+
)
192+
),
130193
next=None,
131-
last=f"http://some/random/url.com?some=1&random=4&query=true&offset=27&limit={limit}",
194+
last=str(
195+
URL(base_url).with_query(
196+
f"some=1&random=4&query=true&offset=27&limit={limit}"
197+
)
198+
),
132199
)

services/web/server/src/simcore_service_webserver/application.py

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
from aiohttp import web
99
from servicelib.application import create_safe_application
10+
from servicelib.rest_pagination_utils import monkey_patch_pydantic_url_regex
11+
12+
monkey_patch_pydantic_url_regex()
1013

1114
from ._meta import WELCOME_MSG
1215
from .activity import setup_activity

0 commit comments

Comments
 (0)