Skip to content

Commit edac6c3

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

File tree

2 files changed

+151
-6
lines changed

2 files changed

+151
-6
lines changed

tests/unit/rss/test_views.py

Lines changed: 115 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,72 @@ 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) == {"latest_releases": [release3, release2]}
72+
assert db_request.response.content_type == "text/xml"
73+
74+
75+
def test_rss_updates_max_age(db_request):
76+
db_request.params = {"max_age": 150}
77+
78+
db_request.find_service = pretend.call_recorder(
79+
lambda *args, **kwargs: pretend.stub(
80+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
81+
)
82+
)
83+
84+
db_request.session = pretend.stub()
85+
86+
project1 = ProjectFactory.create()
87+
88+
release1 = ReleaseFactory.create(project=project1)
89+
release1.created = now() - datetime.timedelta(seconds=100)
90+
release2 = ReleaseFactory.create(project=project1)
91+
release2.created = now() - datetime.timedelta(seconds=200)
92+
93+
assert rss.rss_updates(db_request) == {"latest_releases": [release1]}
94+
assert db_request.response.content_type == "text/xml"
95+
96+
97+
def test_rss_updates_max_age_invalid(db_request):
98+
db_request.params = {"max_age": "foo"}
99+
100+
db_request.find_service = pretend.call_recorder(
101+
lambda *args, **kwargs: pretend.stub(
102+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
103+
)
104+
)
105+
106+
db_request.session = pretend.stub()
107+
108+
with pytest.raises(HTTPBadRequest) as excinfo:
109+
rss.rss_updates(db_request)
110+
111+
resp = excinfo.value
112+
113+
assert resp.status_code == 400
114+
115+
47116
def test_rss_packages(db_request):
48117
db_request.find_service = pretend.call_recorder(
49118
lambda *args, **kwargs: pretend.stub(
@@ -66,3 +135,49 @@ def test_rss_packages(db_request):
66135

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