Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Use query parameters:
| Parameter | Description |
| ----------- | ----------- |
| **`url`** | Required. Address of the JSON document (`http` or `https`). Must be URL-encoded in the query string. |
| **`query`** | Required. [JSONPath](https://goessner.net/articles/JsonPath/) (e.g. `$.items[0].metrics.pct`) or dot form from the root (e.g. `items.0.metrics.pct`). |
| **`query`** | Required. [JSONPath](https://goessner.net/articles/JsonPath/) (e.g. `$.items[0].metrics.pct`) or dot form from the root (e.g. `items.0.metrics.pct`). Filter expressions are supported, so you can look up a value by a sibling field instead of a hardcoded index β€” e.g. `$.progress[?(@.data.language.name=='Spanish')].data.translationProgress`. |
| **`cache`** | Optional. `Cache-Control` max-age in seconds. Default: none (header not set unless provided and `> 0`). |

Any other parameters from the table above (`title`, `scale`, `style`, …) apply the same way as on `/{number}/`.
Expand Down
15 changes: 11 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from urllib.request import HTTPRedirectHandler, Request, build_opener

from flask import Flask, make_response, redirect, render_template, request
from jsonpath_ng import parse
from jsonpath_ng.exceptions import JsonPathParserError
from jsonpath_ng.ext import parse

app = Flask(__name__)

Expand Down Expand Up @@ -100,7 +100,13 @@ def normalize_jsonpath_query(selector):
"""
Normalize a path selector into jsonpath-ng syntax or dot form from the root.

Examples: ``$.items[0].metrics.pct`` or ``items.0.metrics.pct``.
Accepts full JSONPath (including filter expressions) when prefixed with ``$``,
or a simple dot/index form from the root.

Examples:
- ``$.items[0].metrics.pct``
- ``items.0.metrics.pct``
- ``$.progress[?(@.data.language.name=='Spanish')].data.translationProgress``
"""
q = (selector or "").strip()
if not q:
Expand Down Expand Up @@ -439,8 +445,9 @@ def get_progress_svg_dynamic_json():
"""
Load progress from a remote JSON `url` and a `query` into that document.

`query` is a jsonpath-ng expression (e.g. $.items[0].metrics.pct) or dot form
from the root (e.g. items.0.metrics.pct).
`query` is a jsonpath-ng expression (e.g. $.items[0].metrics.pct), a dot form
from the root (e.g. items.0.metrics.pct), or a filter expression
(e.g. $.progress[?(@.data.language.name=='Spanish')].data.translationProgress).
Optional `cache` sets Cache-Control max-age in seconds.
Other params match /N/ (title, scale, width, style, …).
"""
Expand Down
36 changes: 36 additions & 0 deletions test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,42 @@ def test_dynamic_json_jsonpath_dollar(mock_fetch, client):
assert b"42" in response.data


@patch("app.fetch_json_document")
def test_dynamic_json_filter_expression_by_name(mock_fetch, client):
mock_fetch.return_value = {
"progress": [
{"data": {"language": {"name": "Arabic"}, "translationProgress": 5}},
{"data": {"language": {"name": "Spanish"}, "translationProgress": 87}},
],
}
response = client.get(
"/dynamic/json/",
query_string={
"url": "https://example.com/stats.json",
"query": "$.progress[?(@.data.language.name=='Spanish')].data.translationProgress",
},
)
assert response.status_code == 200
assert b"87" in response.data


@patch("app.fetch_json_document")
def test_dynamic_json_filter_expression_no_match_returns_422(mock_fetch, client):
mock_fetch.return_value = {
"progress": [
{"data": {"language": {"name": "Arabic"}, "translationProgress": 5}},
],
}
response = client.get(
"/dynamic/json/",
query_string={
"url": "https://example.com/stats.json",
"query": "$.progress[?(@.data.language.name=='Klingon')].data.translationProgress",
},
)
assert response.status_code == 422


@patch("app.fetch_json_document")
def test_dynamic_json_percent_suffix_string(mock_fetch, client):
mock_fetch.return_value = {"approvalProgress": "73%"}
Expand Down