whatdidyoudo 0.1.12__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.12 → whatdidyoudo-0.2.0}/PKG-INFO +1 -1
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/whatdidyoudo/app.py +20 -12
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/whatdidyoudo/templates/form.html +54 -6
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/.gitignore +0 -0
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/README.md +0 -0
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/pyproject.toml +0 -0
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/whatdidyoudo/__init__.py +0 -0
- {whatdidyoudo-0.1.12 → whatdidyoudo-0.2.0}/whatdidyoudo/static/favicon.svg +0 -0
- {whatdidyoudo-0.1.12 → 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():
|
|
@@ -64,23 +64,71 @@
|
|
|
64
64
|
<h4>Tips</h4>
|
|
65
65
|
<ul>
|
|
66
66
|
<li>Do you want to have a bookmark that always shows your numbers for the current day?
|
|
67
|
-
Just use <code>/YOUR_USERNAME</code> as URL and omit the date
|
|
67
|
+
Just use <code>/YOUR_USERNAME</code> as URL and omit the date!
|
|
68
|
+
</li>
|
|
68
69
|
<li>You can enter multiple usernames separated by commas to see their contributions at once.
|
|
69
|
-
Try this after a mapwalk with friends
|
|
70
|
+
Try this after a mapwalk with friends!
|
|
71
|
+
</li>
|
|
70
72
|
</ul>
|
|
71
73
|
</div>
|
|
72
74
|
<div class="disclaimer">
|
|
73
75
|
<h4>Disclaimers</h4>
|
|
74
76
|
<ul>
|
|
75
|
-
<li>This is a very simple service. It does exactly one job and tries to do it good.</li>
|
|
76
77
|
<li>Quality beats quantity. While it's nice to see big numbers here, please remember
|
|
77
|
-
that just one high-effort change will help more that any amount of mediocre changes
|
|
78
|
+
that just one high-effort change will help more that any amount of mediocre changes.
|
|
79
|
+
</li>
|
|
78
80
|
<li>This service is rate-limited to 10 OSM API requests per minute and caches results
|
|
79
|
-
|
|
81
|
+
for 1 hour (except for the current day) to reduce load on the API.
|
|
82
|
+
Too restrictive? Tell me!
|
|
83
|
+
</li>
|
|
84
|
+
<li>This is a very simple service. It does exactly one job and tries to do it good.
|
|
85
|
+
The data displayed here is shown by many other services, but I didn't find one
|
|
86
|
+
that answers my question in this way with just a simple bookmark.
|
|
87
|
+
Suggestions are welcome! Get in touch via the issue tracker on the project page.
|
|
88
|
+
</li>
|
|
80
89
|
</ul>
|
|
81
90
|
</div>
|
|
82
91
|
{% endif %}
|
|
83
|
-
|
|
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>
|
|
84
132
|
</div>
|
|
85
133
|
</body>
|
|
86
134
|
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|