Skip to content

Commit eec80b9

Browse files
committed
Add limit and max_age params to RSS feeds
1 parent c332062 commit eec80b9

File tree

2 files changed

+155
-6
lines changed

2 files changed

+155
-6
lines changed

tests/unit/rss/test_views.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
import datetime
1414

1515
import pretend
16+
import pytest
17+
from pyramid.httpexceptions import HTTPBadRequest
1618

1719
from warehouse.rss import views as rss
20+
from warehouse.utils import now
1821

1922
from ...common.db.packaging import ProjectFactory, ReleaseFactory
2023

@@ -44,6 +47,76 @@ def test_rss_updates(db_request):
4447
assert db_request.response.content_type == "text/xml"
4548

4649

50+
def test_rss_updates_limit(db_request):
51+
db_request.params = {"limit": 2}
52+
53+
db_request.find_service = pretend.call_recorder(
54+
lambda *args, **kwargs: pretend.stub(
55+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
56+
)
57+
)
58+
59+
db_request.session = pretend.stub()
60+
61+
project1 = ProjectFactory.create()
62+
project2 = ProjectFactory.create()
63+
64+
release1 = ReleaseFactory.create(project=project1)
65+
release1.created = datetime.date(2011, 1, 1)
66+
release2 = ReleaseFactory.create(project=project2)
67+
release2.created = datetime.date(2012, 1, 1)
68+
release3 = ReleaseFactory.create(project=project1)
69+
release3.created = datetime.date(2013, 1, 1)
70+
71+
assert rss.rss_updates(db_request) == {
72+
"latest_releases": [release3, release2]
73+
}
74+
assert db_request.response.content_type == "text/xml"
75+
76+
77+
def test_rss_updates_max_age(db_request):
78+
db_request.params = {"max_age": 150}
79+
80+
db_request.find_service = pretend.call_recorder(
81+
lambda *args, **kwargs: pretend.stub(
82+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
83+
)
84+
)
85+
86+
db_request.session = pretend.stub()
87+
88+
project1 = ProjectFactory.create()
89+
90+
release1 = ReleaseFactory.create(project=project1)
91+
release1.created = now() - datetime.timedelta(seconds=100)
92+
release2 = ReleaseFactory.create(project=project1)
93+
release2.created = now() - datetime.timedelta(seconds=200)
94+
95+
assert rss.rss_updates(db_request) == {
96+
"latest_releases": [release1]
97+
}
98+
assert db_request.response.content_type == "text/xml"
99+
100+
101+
def test_rss_updates_max_age_invalid(db_request):
102+
db_request.params = {"max_age": "foo"}
103+
104+
db_request.find_service = pretend.call_recorder(
105+
lambda *args, **kwargs: pretend.stub(
106+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
107+
)
108+
)
109+
110+
db_request.session = pretend.stub()
111+
112+
with pytest.raises(HTTPBadRequest) as excinfo:
113+
rss.rss_updates(db_request)
114+
115+
resp = excinfo.value
116+
117+
assert resp.status_code == 400
118+
119+
47120
def test_rss_packages(db_request):
48121
db_request.find_service = pretend.call_recorder(
49122
lambda *args, **kwargs: pretend.stub(
@@ -66,3 +139,49 @@ def test_rss_packages(db_request):
66139

67140
assert rss.rss_packages(db_request) == {"newest_projects": [project3, project1]}
68141
assert db_request.response.content_type == "text/xml"
142+
143+
144+
def test_rss_packages_limit(db_request):
145+
db_request.params = {"limit": 1}
146+
147+
db_request.find_service = pretend.call_recorder(
148+
lambda *args, **kwargs: pretend.stub(
149+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
150+
)
151+
)
152+
153+
db_request.session = pretend.stub()
154+
155+
project1 = ProjectFactory.create()
156+
project1.created = datetime.date(2011, 1, 1)
157+
ReleaseFactory.create(project=project1)
158+
159+
project2 = ProjectFactory.create()
160+
project2.created = datetime.date(2012, 1, 1)
161+
ReleaseFactory.create(project=project2)
162+
163+
assert rss.rss_packages(db_request) == {"newest_projects": [project2]}
164+
assert db_request.response.content_type == "text/xml"
165+
166+
167+
def test_rss_packages_max_age(db_request):
168+
db_request.params = {"max_age": 150}
169+
170+
db_request.find_service = pretend.call_recorder(
171+
lambda *args, **kwargs: pretend.stub(
172+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
173+
)
174+
)
175+
176+
db_request.session = pretend.stub()
177+
178+
project1 = ProjectFactory.create()
179+
project1.created = now() - datetime.timedelta(seconds=100)
180+
ReleaseFactory.create(project=project1)
181+
182+
project2 = ProjectFactory.create()
183+
project2.created = now() - datetime.timedelta(seconds=200)
184+
ReleaseFactory.create(project=project2)
185+
186+
assert rss.rss_packages(db_request) == {"newest_projects": [project1]}
187+
assert db_request.response.content_type == "text/xml"

warehouse/rss/views.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,31 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13+
from datetime import timedelta
14+
15+
from pyramid.httpexceptions import HTTPBadRequest
1316
from pyramid.view import view_config
1417
from sqlalchemy.orm import joinedload
1518

1619
from warehouse.cache.origin import origin_cache
1720
from warehouse.packaging.models import Project, Release
21+
from warehouse.utils import now
1822
from warehouse.xml import XML_CSP
1923

24+
DEFAULT_RESULTS = 40
25+
MAX_RESULTS = 200
26+
27+
28+
def _get_int_query_param(request, param, default=None):
29+
value = request.params.get(param)
30+
if not value:
31+
# Return default if 'param' is absent or has an empty value.
32+
return default
33+
try:
34+
return int(value)
35+
except ValueError:
36+
raise HTTPBadRequest(f"'{param}' must be an integer.") from None
37+
2038

2139
@view_config(
2240
route_name="rss.updates",
@@ -39,11 +57,17 @@ def rss_updates(request):
3957
request.db.query(Release)
4058
.options(joinedload(Release.project))
4159
.order_by(Release.created.desc())
42-
.limit(40)
43-
.all()
4460
)
4561

46-
return {"latest_releases": latest_releases}
62+
max_age = _get_int_query_param(request, "max_age")
63+
if max_age is not None:
64+
created_since = now() - timedelta(seconds=max_age)
65+
latest_releases = latest_releases.filter(Release.created > created_since)
66+
67+
limit = min(_get_int_query_param(request, "limit", DEFAULT_RESULTS), MAX_RESULTS)
68+
latest_releases = latest_releases.limit(limit)
69+
70+
return {"latest_releases": latest_releases.all()}
4771

4872

4973
@view_config(
@@ -67,8 +91,14 @@ def rss_packages(request):
6791
request.db.query(Project)
6892
.options(joinedload(Project.releases, innerjoin=True))
6993
.order_by(Project.created.desc())
70-
.limit(40)
71-
.all()
7294
)
7395

74-
return {"newest_projects": newest_projects}
96+
max_age = _get_int_query_param(request, "max_age")
97+
if max_age is not None:
98+
created_since = now() - timedelta(seconds=max_age)
99+
newest_projects = newest_projects.filter(Project.created > created_since)
100+
101+
limit = min(_get_int_query_param(request, "limit", DEFAULT_RESULTS), MAX_RESULTS)
102+
newest_projects = newest_projects.limit(limit)
103+
104+
return {"newest_projects": newest_projects.all()}

0 commit comments

Comments
 (0)