whatdidyoudo 0.1.13__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of whatdidyoudo might be problematic. Click here for more details.
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/PKG-INFO +1 -1
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/whatdidyoudo/app.py +20 -12
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/whatdidyoudo/templates/form.html +40 -1
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/.gitignore +0 -0
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/README.md +0 -0
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/pyproject.toml +0 -0
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/whatdidyoudo/__init__.py +0 -0
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/whatdidyoudo/static/favicon.svg +0 -0
- {whatdidyoudo-0.1.13 → whatdidyoudo-0.2.0}/whatdidyoudo/static/style.css +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whatdidyoudo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rompe/whatdidyoudo
|
|
6
6
|
Project-URL: Issues, https://github.com/rompe/whatdidyoudo/issues
|
|
@@ -7,10 +7,10 @@ import requests
|
|
|
7
7
|
|
|
8
8
|
from flask import Flask, render_template
|
|
9
9
|
from flask_caching import Cache
|
|
10
|
-
from flask_limiter import Limiter
|
|
10
|
+
from flask_limiter import Limiter, RateLimitExceeded
|
|
11
11
|
from flask_limiter.util import get_remote_address
|
|
12
12
|
|
|
13
|
-
__version__ = "0.
|
|
13
|
+
__version__ = "0.2.0"
|
|
14
14
|
|
|
15
15
|
app = Flask(__name__)
|
|
16
16
|
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache",
|
|
@@ -32,10 +32,10 @@ def get_etree_from_url(url: str) -> ET.Element:
|
|
|
32
32
|
return ET.fromstring(response.content)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def get_changes(user: str, date: str)
|
|
36
|
-
"""Return a {app: Changes} dictionary."""
|
|
37
|
-
# {user: {app: Changes}}
|
|
35
|
+
def get_changes(user: str, date: str):
|
|
36
|
+
"""Return a ({app: Changes} dictionary, [changeset_ids]) tuple."""
|
|
38
37
|
changes: defaultdict[str, Changes] = defaultdict(Changes)
|
|
38
|
+
changeset_ids: list[str] = []
|
|
39
39
|
datetime_date = datetime.date.fromisoformat(date)
|
|
40
40
|
start_time = f"{datetime_date}T00:00:00Z"
|
|
41
41
|
end_time = f"{datetime_date + datetime.timedelta(days=1)}T00:00:00Z"
|
|
@@ -46,6 +46,7 @@ def get_changes(user: str, date: str) -> defaultdict[str, Changes]:
|
|
|
46
46
|
|
|
47
47
|
for cs in root.findall("changeset"):
|
|
48
48
|
cs_id = cs.attrib["id"]
|
|
49
|
+
changeset_ids.append(cs_id)
|
|
49
50
|
|
|
50
51
|
tags = {tag.attrib["k"]: tag.attrib["v"] for tag in cs.findall("tag")}
|
|
51
52
|
editor = tags.get("created_by", "")
|
|
@@ -60,7 +61,7 @@ def get_changes(user: str, date: str) -> defaultdict[str, Changes]:
|
|
|
60
61
|
|
|
61
62
|
for action in root:
|
|
62
63
|
changes[editor].changes += len(action)
|
|
63
|
-
return changes
|
|
64
|
+
return changes, changeset_ids
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
@app.route('/')
|
|
@@ -76,25 +77,32 @@ def whatdidyoudo(user: str | None = None, date: str | None = None) -> str:
|
|
|
76
77
|
today = datetime.date.today().isoformat()
|
|
77
78
|
if not date:
|
|
78
79
|
date = today
|
|
80
|
+
|
|
81
|
+
changeset_ids: list[str] = []
|
|
79
82
|
for name in [item.strip() for item in (user or "").split(",")
|
|
80
83
|
if item.strip()]:
|
|
81
84
|
cache_key = f"changes_{name}_{date}"
|
|
82
|
-
|
|
83
|
-
if not
|
|
85
|
+
cur_data = cache.get(cache_key) # type: ignore
|
|
86
|
+
if not cur_data:
|
|
84
87
|
try:
|
|
85
88
|
with limiter.limit("10 per minute"):
|
|
86
|
-
|
|
89
|
+
cur_data = get_changes(name, date)
|
|
87
90
|
if date != today:
|
|
88
|
-
cache.set(cache_key,
|
|
91
|
+
cache.set(cache_key, cur_data) # type: ignore
|
|
89
92
|
except requests.HTTPError:
|
|
90
93
|
errors.append(
|
|
91
94
|
f"Can't determine changes for user {name} on {date}.")
|
|
92
|
-
|
|
95
|
+
except RateLimitExceeded as msg:
|
|
96
|
+
errors.append("Rate limit exceeded while processing user "
|
|
97
|
+
f"{name}: {msg}")
|
|
98
|
+
if cur_data:
|
|
99
|
+
cur_changes, cur_ids = cur_data
|
|
93
100
|
changes[name] = cur_changes
|
|
101
|
+
changeset_ids.extend(cur_ids)
|
|
94
102
|
|
|
95
103
|
return render_template('form.html', user=user, date=date, changes=changes,
|
|
96
104
|
changesets=changesets, errors=errors,
|
|
97
|
-
version=__version__)
|
|
105
|
+
version=__version__, changeset_ids=changeset_ids)
|
|
98
106
|
|
|
99
107
|
|
|
100
108
|
def main():
|
|
@@ -89,7 +89,46 @@
|
|
|
89
89
|
</ul>
|
|
90
90
|
</div>
|
|
91
91
|
{% endif %}
|
|
92
|
-
|
|
92
|
+
{% if changeset_ids and changeset_ids|length > 0 %}
|
|
93
|
+
<div id="map" style="height: 300px; margin-top: 2em; border-radius: 12px; overflow: hidden;"></div>
|
|
94
|
+
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
|
95
|
+
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
|
96
|
+
<script>
|
|
97
|
+
const changesetIds = {{ changeset_ids|tojson }};
|
|
98
|
+
if (changesetIds.length > 0) {
|
|
99
|
+
const bounds = [];
|
|
100
|
+
Promise.all(
|
|
101
|
+
changesetIds.map(id =>
|
|
102
|
+
fetch(`https://api.openstreetmap.org/api/0.6/changeset/${id}`)
|
|
103
|
+
.then(r => r.text())
|
|
104
|
+
.then(xml => {
|
|
105
|
+
const parser = new DOMParser();
|
|
106
|
+
const doc = parser.parseFromString(xml, 'application/xml');
|
|
107
|
+
const cs = doc.querySelector('changeset');
|
|
108
|
+
if (cs) {
|
|
109
|
+
bounds.push([
|
|
110
|
+
[parseFloat(cs.getAttribute('min_lat')), parseFloat(cs.getAttribute('min_lon'))],
|
|
111
|
+
[parseFloat(cs.getAttribute('max_lat')), parseFloat(cs.getAttribute('max_lon'))]
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
).then(() => {
|
|
117
|
+
if (bounds.length > 0) {
|
|
118
|
+
const map = L.map('map').setView([0, 0], 2);
|
|
119
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
120
|
+
maxZoom: 19,
|
|
121
|
+
attribution: '© OpenStreetMap contributors'
|
|
122
|
+
}).addTo(map);
|
|
123
|
+
const allBounds = L.latLngBounds(bounds.flat());
|
|
124
|
+
map.fitBounds(allBounds);
|
|
125
|
+
bounds.forEach(b => L.rectangle(b, {color: '#4f8cff', weight: 2}).addTo(map));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
</script>
|
|
130
|
+
{% endif %}
|
|
131
|
+
<p class="footer">Powered by <a href="https://github.com/rompe/whatdidyoudo">whatdidyoudo {{ version }}</a></p>
|
|
93
132
|
</div>
|
|
94
133
|
</body>
|
|
95
134
|
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|