Skip to content

Commit

Permalink
Use canned query title, produce valid Atom
Browse files Browse the repository at this point in the history
Closes #12, closes #6
  • Loading branch information
simonw committed May 28, 2020
1 parent df98a6c commit 64de6b0
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 13 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,33 @@ limit
```

You can try this query by [pasting it in here](https://www.niche-museums.com/browse) - then click the `.atom` link to see it as an Atom feed.

## Using a canned query

Datasette's [canned query mechanism](https://datasette.readthedocs.io/en/stable/sql_queries.html#canned-queries) is a useful way to configure feeds. If a canned query definition has a `title` that will be used as the title of the Atom feed.

Here's an example, defined using a `metadata.yaml` file:

```yaml
databases:
browse:
queries:
feed:
title: Niche Museums
sql: |-
select
'tag:niche-museums.com,' || substr(created, 0, 11) || ':' || id as atom_id,
name as atom_title,
created as atom_updated,
'https://www.niche-museums.com/browse/museums/' || id as atom_link,
coalesce(
'<img src="' || photo_url || '?w=800&amp;h=400&amp;fit=crop&amp;auto=compress">',
''
) || '<p>' || description || '</p>' as atom_content_html
from
museums
order by
created desc
limit
15
```
29 changes: 19 additions & 10 deletions datasette_atom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ def register_output_renderer():
return {"extension": "atom", "render": render_atom, "can_render": can_render_atom}


def render_atom(args, data, view_name):
def render_atom(
datasette, request, sql, columns, rows, database, table, query_name, view_name, data
):
from datasette.views.base import DatasetteError

columns = set(data["columns"])
if not REQUIRED_COLUMNS.issubset(columns):
raise DatasetteError(
"SQL query must return columns {}".format(", ".join(REQUIRED_COLUMNS)),
Expand All @@ -27,17 +28,25 @@ def render_atom(args, data, view_name):
version=__version__,
uri="https://github.com/simonw/datasette",
)
sql = data["query"]["sql"]
fg.id(data["database"] + "/" + hashlib.sha256(sql.encode("utf8")).hexdigest())
fg.updated(max(row["atom_updated"] for row in data["rows"]))
title = args.get("_feed_title", sql)
if data.get("table"):
title += "/" + data["table"]
fg.id(request.url)
fg.link(href=request.url, rel="self")
fg.updated(max(row["atom_updated"] for row in rows))
title = request.args.get("_feed_title", sql)
if table:
title += "/" + table
if data.get("human_description_en"):
title += ": " + data["human_description_en"]
# If this is a canned query the configured title for that over-rides all others
if query_name:
try:
title = datasette.metadata(database=database)["queries"][query_name][
"title"
]
except (KeyError, TypeError):
pass
fg.title(title)
# And the rows
for row in reversed(data["rows"]):
for row in reversed(rows):
entry = fg.add_entry()
entry.id(str(row["atom_id"]))
if "atom_content_html" in columns:
Expand Down Expand Up @@ -67,7 +76,7 @@ def render_atom(args, data, view_name):


def can_render_atom(columns):
return {"atom_id", "atom_title", "atom_updated"}.issubset(columns)
return REQUIRED_COLUMNS.issubset(columns)


def clean(html):
Expand Down
37 changes: 34 additions & 3 deletions tests/test_atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
EXPECTED_ATOM = """
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>:memory:/d765251b024da6156f346354d5ca573b2c8717bc71d82d2e1a2fdd55b9a44215</id>
<id>http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++1+as+atom_id%2C%0A++++++++123+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27blah+%3Cb%3EBold%3C%2Fb%3E%27+as+atom_content%2C%0A++++++++%27Author%27+as+atom_author_name%2C%0A++++++++%27https%3A%2F%2Fwww.example.com%2F%27+as+atom_author_uri%0A++++union+select%0A++++++++%27atom-id-2%27+as+atom_id%2C%0A++++++++%27title+2%27+as+atom_title%2C%0A++++++++%272019-09-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27blah%27+as+atom_content%2C%0A++++++++null+as+atom_author_name%2C%0A++++++++null+as+atom_author_uri%3B%0A++++</id>
<title>
select
1 as atom_id,
Expand All @@ -25,6 +25,7 @@
null as atom_author_uri;
</title>
<updated>2019-10-23T21:32:12-07:00</updated>
<link href="http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++1+as+atom_id%2C%0A++++++++123+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27blah+%3Cb%3EBold%3C%2Fb%3E%27+as+atom_content%2C%0A++++++++%27Author%27+as+atom_author_name%2C%0A++++++++%27https%3A%2F%2Fwww.example.com%2F%27+as+atom_author_uri%0A++++union+select%0A++++++++%27atom-id-2%27+as+atom_id%2C%0A++++++++%27title+2%27+as+atom_title%2C%0A++++++++%272019-09-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27blah%27+as+atom_content%2C%0A++++++++null+as+atom_author_name%2C%0A++++++++null+as+atom_author_uri%3B%0A++++" rel="self"/>
<generator uri="https://github.com/simonw/datasette" version="{version}">Datasette</generator>
<entry>
<id>1</id>
Expand All @@ -48,7 +49,7 @@
EXPECTED_ATOM_WITH_LINK = """
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>:memory:/652f6714d6b9efa3657b50fe0ae8cbac13ccefb2ecbdbafe527c6f6fe97556da</id>
<id>http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++%27atom-id%27+as+atom_id%2C%0A++++++++%27title%27+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27https%3A%2F%2Fwww.niche-museums.com%2F%27+as+atom_link%2C%0A++++++++%27blah%27+as+atom_content%3B%0A++++</id>
<title>
select
'atom-id' as atom_id,
Expand All @@ -58,6 +59,7 @@
'blah' as atom_content;
</title>
<updated>2019-10-23T21:32:12-07:00</updated>
<link href="http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++%27atom-id%27+as+atom_id%2C%0A++++++++%27title%27+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27https%3A%2F%2Fwww.niche-museums.com%2F%27+as+atom_link%2C%0A++++++++%27blah%27+as+atom_content%3B%0A++++" rel="self"/>
<generator uri="https://github.com/simonw/datasette" version="{version}">Datasette</generator>
<entry>
<id>atom-id</id>
Expand All @@ -72,7 +74,7 @@
EXPECTED_ATOM_WITH_HTML = """
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>:memory:/beb5a312c0daa591d04d7dfb5a79eb8bbbcd6f84ebe90cf0345ed8c24bb2ff22</id>
<id>http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++%27atom-id%27+as+atom_id%2C%0A++++++++%27title%27+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27https%3A%2F%2Fwww.niche-museums.com%2F%27+as+atom_link%2C%0A++++++++%27%3Ch2%3Eblah%3C%2Fh2%3E%3Cscript%3Ealert%28%22bad%22%29%3C%2Fscript%3E%27+as+atom_content_html%3B%0A++++</id>
<title>
select
'atom-id' as atom_id,
Expand All @@ -82,6 +84,7 @@
'&lt;h2&gt;blah&lt;/h2&gt;&lt;script&gt;alert("bad")&lt;/script&gt;' as atom_content_html;
</title>
<updated>2019-10-23T21:32:12-07:00</updated>
<link href="http://localhost/:memory:.atom?sql=%0A++++select%0A++++++++%27atom-id%27+as+atom_id%2C%0A++++++++%27title%27+as+atom_title%2C%0A++++++++%272019-10-23T21%3A32%3A12-07%3A00%27+as+atom_updated%2C%0A++++++++%27https%3A%2F%2Fwww.niche-museums.com%2F%27+as+atom_link%2C%0A++++++++%27%3Ch2%3Eblah%3C%2Fh2%3E%3Cscript%3Ealert%28%22bad%22%29%3C%2Fscript%3E%27+as+atom_content_html%3B%0A++++" rel="self"/>
<generator uri="https://github.com/simonw/datasette" version="{version}">Datasette</generator>
<entry>
<id>atom-id</id>
Expand Down Expand Up @@ -208,3 +211,31 @@ async def test_atom_link_only_shown_for_correct_queries():
)
assert b'<a href="/:memory:.json' in response.content
assert b'<a href="/:memory:.atom' not in response.content


@pytest.mark.asyncio
async def test_atom_from_titled_canned_query():
sql = """
select
'atom-id' as atom_id,
'title' as atom_title,
'2019-10-23T21:32:12-07:00' as atom_updated,
'https://www.niche-museums.com/' as atom_link,
'blah' as atom_content;
"""
app = Datasette(
[],
immutables=[],
memory=True,
metadata={
"databases": {
":memory:": {"queries": {"feed": {"sql": sql, "title": "My atom feed"}}}
}
},
).app()
async with httpx.AsyncClient(app=app) as client:
response = await client.get("http://localhost/:memory:/feed.atom")
assert 200 == response.status_code
assert "application/xml; charset=utf-8" == response.headers["content-type"]
xml = response.content.decode("utf-8")
assert "<title>My atom feed</title>" in xml

0 comments on commit 64de6b0

Please sign in to comment.