Skip to content

Commit d6ea269

Browse files
scopdi
andcommitted
Add item authors to RSS feeds (#7012)
* Add item authors to RSS feeds * Add docstring to warehouse.rss.views._format_author * Nit: single character variable names Co-authored-by: Dustin Ingram <[email protected]>
1 parent 6dbbd92 commit d6ea269

File tree

4 files changed

+80
-6
lines changed

4 files changed

+80
-6
lines changed

tests/unit/rss/test_views.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ def test_rss_updates(db_request):
3535
release1.created = datetime.date(2011, 1, 1)
3636
release2 = ReleaseFactory.create(project=project2)
3737
release2.created = datetime.date(2012, 1, 1)
38+
release2.author_email = "[email protected]"
3839
release3 = ReleaseFactory.create(project=project1)
3940
release3.created = datetime.date(2013, 1, 1)
4041

4142
assert rss.rss_updates(db_request) == {
42-
"latest_releases": [release3, release2, release1]
43+
"latest_releases": tuple(
44+
zip((release3, release2, release1), (None, "[email protected]", None))
45+
)
4346
}
4447
assert db_request.response.content_type == "text/xml"
4548

@@ -64,5 +67,42 @@ def test_rss_packages(db_request):
6467
project3.created = datetime.date(2013, 1, 1)
6568
ReleaseFactory.create(project=project3)
6669

67-
assert rss.rss_packages(db_request) == {"newest_projects": [project3, project1]}
70+
assert rss.rss_packages(db_request) == {
71+
"newest_projects": tuple(zip((project3, project1), (None, None)))
72+
}
6873
assert db_request.response.content_type == "text/xml"
74+
75+
76+
def test_format_author(db_request):
77+
db_request.find_service = pretend.call_recorder(
78+
lambda *args, **kwargs: pretend.stub(
79+
enabled=False, csp_policy=pretend.stub(), merge=lambda _: None
80+
)
81+
)
82+
83+
db_request.session = pretend.stub()
84+
85+
project = ProjectFactory.create()
86+
release = ReleaseFactory.create(project=project)
87+
88+
release.author_email = "[email protected]"
89+
assert rss._format_author(release) == release.author_email
90+
91+
release.author_email = "No Reply <[email protected]>"
92+
assert rss._format_author(release) == "[email protected]"
93+
94+
for invalid in (None, "", "UNKNOWN", "[email protected], UNKNOWN"):
95+
release.author_email = invalid
96+
assert rss._format_author(release) is None
97+
98+
release.author_email = (
99+
# simple, no spaces
100+
101+
# space after
102+
103+
# space before, incl realname
104+
" No Reply <[email protected]>,"
105+
# two spaces before, angle brackets
106+
107+
)
108+
assert rss._format_author(release) == ", ".join(["[email protected]"] * 4)

warehouse/rss/views.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13+
from email.utils import getaddresses
14+
1315
from pyramid.view import view_config
1416
from sqlalchemy.orm import joinedload
1517

@@ -18,6 +20,32 @@
1820
from warehouse.xml import XML_CSP
1921

2022

23+
def _format_author(release):
24+
"""
25+
Format release author suitably for inclusion in an RSS feed.
26+
27+
Release author names and emails are hard to match robustly, mainly
28+
because there may be multiple in both, and especially the names may
29+
contain pretty much anything. So stick with just the emails with some
30+
rudimentary sanity checks.
31+
32+
Even though the spec says "the email address" and thus assumes a single
33+
author, we let multiple pass, comma separated.
34+
35+
http://www.rssboard.org/rss-specification#ltauthorgtSubelementOfLtitemgt
36+
"""
37+
author_emails = []
38+
for _, author_email in getaddresses([release.author_email or ""]):
39+
if "@" not in author_email:
40+
# Require all valid looking
41+
return None
42+
author_emails.append(author_email)
43+
44+
if not author_emails:
45+
return None
46+
return ", ".join(author_emails)
47+
48+
2149
@view_config(
2250
route_name="rss.updates",
2351
renderer="rss/updates.xml",
@@ -42,8 +70,9 @@ def rss_updates(request):
4270
.limit(40)
4371
.all()
4472
)
73+
release_authors = [_format_author(release) for release in latest_releases]
4574

46-
return {"latest_releases": latest_releases}
75+
return {"latest_releases": tuple(zip(latest_releases, release_authors))}
4776

4877

4978
@view_config(
@@ -70,5 +99,8 @@ def rss_packages(request):
7099
.limit(40)
71100
.all()
72101
)
102+
project_authors = [
103+
_format_author(project.releases[0]) for project in newest_projects
104+
]
73105

74-
return {"newest_projects": newest_projects}
106+
return {"newest_projects": tuple(zip(newest_projects, project_authors))}

warehouse/templates/rss/packages.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
{% block title %}PyPI newest packages{% endblock %}
33
{% block description %}Newest packages registered at the Python Package Index{% endblock %}
44
{% block items -%}
5-
{% for project in newest_projects %}
5+
{% for project, author in newest_projects %}
66
<item>
77
<title>{{ project.name }} added to PyPI</title>
88
<link>{{ request.route_url('packaging.project', name=project.normalized_name) }}</link>
99
<guid>{{ request.route_url('packaging.project', name=project.normalized_name) }}</guid>
1010
<description>{{ project.releases[0].summary }}</description>
11+
{% if author %}<author>{{ author }}</author>{% endif %}
1112
<pubDate>{{ project.created|format_rfc822_datetime() }}</pubDate>
1213
</item>
1314
{%- endfor %}

warehouse/templates/rss/updates.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
{% block title %}PyPI recent updates{% endblock %}
33
{% block description %}Recent updates to the Python Package Index{% endblock %}
44
{% block items -%}
5-
{% for release in latest_releases %}
5+
{% for release, author in latest_releases %}
66
<item>
77
<title>{{ release.project.name }} {{ release.version }}</title>
88
<link>{{ request.route_url('packaging.release', name=release.project.normalized_name, version=release.version) }}</link>
99
<description>{{ release.summary }}</description>
10+
{% if author %}<author>{{ author }}</author>{% endif %}
1011
<pubDate>{{ release.created|format_rfc822_datetime() }}</pubDate>
1112
</item>
1213
{%- endfor %}

0 commit comments

Comments
 (0)