whatdidyoudo 0.1.0__py3-none-any.whl

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.

File without changes
whatdidyoudo/app.py ADDED
@@ -0,0 +1,88 @@
1
+ """A Flask app that shows OSM tasks done by a user on a specific day."""
2
+ import datetime
3
+ import xml.etree.ElementTree as ET
4
+ from collections import defaultdict
5
+ import requests
6
+
7
+ from flask import Flask, render_template
8
+ from flask_caching import Cache
9
+ from flask_limiter import Limiter
10
+ from flask_limiter.util import get_remote_address
11
+
12
+
13
+ app = Flask(__name__)
14
+ cache = Cache(app, config={"CACHE_TYPE": "SimpleCache",
15
+ "CACHE_DEFAULT_TIMEOUT": 60 * 60 * 24 * 7})
16
+ limiter = Limiter(app=app, key_func=get_remote_address)
17
+
18
+
19
+ def get_etree_from_url(url: str) -> ET.Element:
20
+ """Fetches XML content from a URL and returns the root Element."""
21
+ response = requests.get(url, timeout=120)
22
+ response.raise_for_status() # Raise an error for bad responses
23
+ return ET.fromstring(response.content)
24
+
25
+
26
+ def get_changes(user: str, date: str):
27
+ """Return a {app: num of changes} dictionary and the changesets amount."""
28
+ changes: defaultdict[str, int] = defaultdict(int)
29
+ changesets = 0
30
+ datetime_date = datetime.date.fromisoformat(date)
31
+ start_time = f"{datetime_date}T00:00:00Z"
32
+ end_time = f"{datetime_date + datetime.timedelta(days=1)}T00:00:00Z"
33
+
34
+ changeset_url = ("https://api.openstreetmap.org/api/0.6/changesets?"
35
+ f"display_name={user}&time={start_time},{end_time}")
36
+ root = get_etree_from_url(url=changeset_url)
37
+
38
+ for cs in root.findall("changeset"):
39
+ cs_id = cs.attrib["id"]
40
+
41
+ tags = {tag.attrib["k"]: tag.attrib["v"] for tag in cs.findall("tag")}
42
+ editor = tags.get("created_by", "")
43
+ changesets += 1
44
+
45
+ diff_url = ("https://api.openstreetmap.org/api/0.6/changeset/"
46
+ f"{cs_id}/download")
47
+ try:
48
+ root = get_etree_from_url(url=diff_url)
49
+ except requests.HTTPError:
50
+ continue
51
+
52
+ for action in root:
53
+ changes[editor] += len(action)
54
+ return changes, changesets
55
+
56
+
57
+ @app.route('/')
58
+ @app.route('/<user>')
59
+ @app.route('/<user>/<date>')
60
+ def whatdidyoudo(user: str | None = None, date: str | None = None) -> str:
61
+ """shows OSM tasks done by a user on a specific day."""
62
+ changes: defaultdict[str, int] = defaultdict(int)
63
+ changesets = 0
64
+ error = ""
65
+ if user and date:
66
+ try:
67
+ today = datetime.date.today().isoformat()
68
+ if date != today:
69
+ cache_key = f"changes_{user}_{date}"
70
+ cached = cache.get(cache_key) # type: ignore
71
+ if cached:
72
+ changes, changesets = cached
73
+ else:
74
+ with limiter.limit("10 per minute"):
75
+ changes, changesets = get_changes(user, date)
76
+ cache.set(cache_key, (changes, changesets)) # type: ignore
77
+ else:
78
+ with limiter.limit("10 per minute"):
79
+ changes, changesets = get_changes(user, date)
80
+ except requests.HTTPError:
81
+ error = f"Can't determine changes for user {user} on {date}."
82
+
83
+ return render_template('form.html', user=user, date=date, changes=changes,
84
+ changesets=changesets, error=error)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ app.run(debug=True)
@@ -0,0 +1,103 @@
1
+ body {
2
+ font-family: 'Segoe UI', Arial, sans-serif;
3
+ background: linear-gradient(120deg, #232526 0%, #414345 100%);
4
+ color: #f3f3f3;
5
+ min-height: 100vh;
6
+ margin: 0;
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: center;
11
+ }
12
+
13
+ .container {
14
+ background: rgba(30, 30, 30, 0.92);
15
+ border-radius: 16px;
16
+ box-shadow: 0 4px 24px rgba(0,0,0,0.3);
17
+ padding: 2em 2.5em 1.5em 2.5em;
18
+ margin-top: 2em;
19
+ max-width: 600px;
20
+ width: 100%;
21
+ }
22
+
23
+ .error {
24
+ background: #ff4d4f;
25
+ color: #fff;
26
+ padding: 0.7em 1em;
27
+ border-radius: 8px;
28
+ margin-bottom: 1em;
29
+ font-weight: 600;
30
+ }
31
+
32
+ form {
33
+ display: flex;
34
+ flex-direction: row;
35
+ align-items: flex-end;
36
+ gap: 1em;
37
+ width: 100%;
38
+ }
39
+
40
+ label {
41
+ display: none;
42
+ }
43
+
44
+ input[type="text"] {
45
+ flex: 2 1 200px;
46
+ padding: 0.7em 1em;
47
+ border-radius: 8px;
48
+ border: none;
49
+ font-size: 1.1em;
50
+ background: #2c2f34;
51
+ color: #f3f3f3;
52
+ outline: none;
53
+ }
54
+
55
+ input[type="date"] {
56
+ flex: 1 1 120px;
57
+ padding: 0.7em 0.5em;
58
+ border-radius: 8px;
59
+ border: none;
60
+ font-size: 1.1em;
61
+ background: #2c2f34;
62
+ color: #f3f3f3;
63
+ outline: none;
64
+ min-width: 120px;
65
+ max-width: 160px;
66
+ }
67
+
68
+ button {
69
+ padding: 0.7em 1.5em;
70
+ border-radius: 8px;
71
+ border: none;
72
+ background: #4f8cff;
73
+ color: #fff;
74
+ font-size: 1.1em;
75
+ font-weight: 600;
76
+ cursor: pointer;
77
+ transition: background 0.2s;
78
+ }
79
+ button:hover {
80
+ background: #2563eb;
81
+ }
82
+
83
+ h2 a {
84
+ color: #f3f3f3;
85
+ text-decoration: none;
86
+ font-weight: 700;
87
+ letter-spacing: 1px;
88
+ }
89
+
90
+ @media (max-width: 600px) {
91
+ .container {
92
+ padding: 1em 0.5em;
93
+ }
94
+ form {
95
+ flex-direction: column;
96
+ gap: 0.7em;
97
+ }
98
+ input[type="date"] {
99
+ width: 100%;
100
+ min-width: unset;
101
+ max-width: unset;
102
+ }
103
+ }
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>What Did You Do?</title>
6
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
7
+ <script>
8
+ window.onload = function() {
9
+ var dateInput = document.getElementById('date');
10
+ if (!dateInput.value) {
11
+ var today = new Date();
12
+ var yyyy = today.getFullYear();
13
+ var mm = String(today.getMonth() + 1).padStart(2, '0');
14
+ var dd = String(today.getDate()).padStart(2, '0');
15
+ dateInput.value = yyyy + '-' + mm + '-' + dd;
16
+ }
17
+ };
18
+ </script>
19
+ </head>
20
+ <body>
21
+ <div class="container">
22
+ <h2><a href="/">What Did You Do?</a></h2>
23
+ <form method="get" action="" onsubmit="event.preventDefault(); window.location.href='/' + encodeURIComponent(document.getElementById('user').value) + '/' + encodeURIComponent(document.getElementById('date').value);">
24
+ <label for="user">User:</label>
25
+ <input type="text" id="user" name="user" value="{{ user or '' }}" placeholder="insert OSM username" required>
26
+
27
+ <label for="date">Date:</label>
28
+ <input type="date" id="date" name="date" value="{{ date or '' }}" required>
29
+
30
+ <button type="submit">Show</button>
31
+ </form>
32
+ </div>
33
+ {% if user and date %}
34
+ {% if error %}
35
+ <p class=error>{{ error }}</p>
36
+ {% elif changes %}
37
+ <p>{{ user }} did some changes on {{ date }}!</p>
38
+ {% for app, count in changes.items() %}
39
+ <ul>
40
+ <li>{{ count }} changes in {{ app }}</li>
41
+ </ul>
42
+ {% endfor %}
43
+ That's a total of {{ changesets }} changesets.
44
+ {% else %}
45
+ <p>{{ user }} did not do anything on {{ date }}.</p>
46
+ {% endif %}
47
+ {% endif %}
48
+ </body>
49
+ </html>
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: whatdidyoudo
3
+ Version: 0.1.0
4
+ Summary: A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
5
+ Author-email: Ulf Rompe <whatdidyoudo.rompe.org@rompe.org>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: flask-caching==2.3.1
9
+ Requires-Dist: flask>=3.0.3
10
+ Requires-Dist: requests>=2.32.4
11
+ Provides-Extra: dev
12
+ Requires-Dist: hatchling>=1.27.0; extra == 'dev'
13
+ Requires-Dist: ruff>=0.13.2; extra == 'dev'
14
+ Requires-Dist: twine>=6.2.0; extra == 'dev'
15
+ Requires-Dist: types-requests>=2.32.4; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # whatdidyoudo
19
+
20
+ A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
21
+
22
+ ## Background
23
+
24
+ I often ask myself after contributing many changes to OpenStreetMap, either by walking around
25
+ while extensively using StreetComplete, MapComplete or Vespucci, or by doing some tasks in iD or
26
+ jOSM: **How many changes did I contribute to the map today?**
27
+
28
+ I'm not the only one. I heard questions like this quite a few times:
29
+ **Where can I see how much I did on yesterday's mapwalk?**
30
+
31
+ Because I think that simple questions deserve simple answers, I made this tool to give exactly
32
+ this information and nothing else.
33
+
34
+ You don't need to self-host it, it is available for anyone at
35
+ [whatdidyoudo.rompe.org](https://whatdidyoudo.rompe.org).
36
+
37
+ ## Setup
38
+
39
+ Fun fact: of course you don't really need *uv* for this. I'm just using this project to
40
+ get used to it as I think it has a lot of potential.
41
+
42
+ ### Install [uv](https://github.com/astral-sh/uv) if needed
43
+
44
+ ```sh
45
+ pip install uv
46
+ ```
47
+
48
+ ### Install dependencies using *uv*
49
+
50
+ ```sh
51
+ uv pip install -r pyproject.toml
52
+ ```
53
+
54
+ If you want to develop:
55
+
56
+ ```sh
57
+ uv pip install -r pyproject.toml --extra dev
58
+ ```
59
+
60
+ ### Run tests
61
+
62
+ ```sh
63
+ pytest
64
+ ```
65
+
66
+ ### Run the app in test mode
67
+
68
+ ```sh
69
+ python whatdidyoudo/app.py
70
+ ```
71
+
72
+ Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your browser to see "hello world".
73
+
74
+ ### Build a package and upload it to Pypi
75
+
76
+ ```sh
77
+ uvx hatchling build
78
+ uvx twine upload dist/*
79
+ ```
80
+
81
+ ## License
82
+
83
+ This project is licensed under the MIT License. See the `pyproject.toml` for details.
@@ -0,0 +1,7 @@
1
+ whatdidyoudo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ whatdidyoudo/app.py,sha256=u6pvalY0SYe5LBlXA0U81akN09MgmK5gPi_HacuWNy4,3302
3
+ whatdidyoudo/static/style.css,sha256=AtT4pHBBrDSMxzForapiP-F-l648lQSISxgg6n93-98,1958
4
+ whatdidyoudo/templates/form.html,sha256=VLWdk1jdrpvKCtekSF088UXk9vXWLwJ4w0pkscpyy30,1904
5
+ whatdidyoudo-0.1.0.dist-info/METADATA,sha256=5PMd0RYYUOB7i_TldzKHPSIjLlIeEDJZbsQqIQPT3Ew,2152
6
+ whatdidyoudo-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ whatdidyoudo-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any