whatdidyoudo 0.1.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.

@@ -0,0 +1,63 @@
1
+ <!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
2
+ - [x] Verify that the copilot-instructions.md file in the .github directory is created.
3
+
4
+ - [ ] Clarify Project Requirements
5
+ - [ ] Scaffold the Project
6
+ - [ ] Customize the Project
7
+ - [ ] Install Required Extensions
8
+ - [ ] Compile the Project
9
+ - [ ] Create and Run Task
10
+ - [ ] Launch the Project
11
+ - [ ] Ensure Documentation is Complete
12
+
13
+ <!--
14
+ ## Execution Guidelines
15
+ PROGRESS TRACKING:
16
+ - If any tools are available to manage the above todo list, use it to track progress through this checklist.
17
+ - After completing each step, mark it complete and add a summary.
18
+ - Read current todo list status before starting each new step.
19
+
20
+ COMMUNICATION RULES:
21
+ - Avoid verbose explanations or printing full command outputs.
22
+ - If a step is skipped, state that briefly (e.g. "No extensions needed").
23
+ - Do not explain project structure unless asked.
24
+ - Keep explanations concise and focused.
25
+
26
+ DEVELOPMENT RULES:
27
+ - Use '.' as the working directory unless user specifies otherwise.
28
+ - Avoid adding media or external links unless explicitly requested.
29
+ - Use placeholders only with a note that they should be replaced.
30
+ - Use VS Code API tool only for VS Code extension projects.
31
+ - Once the project is created, it is already opened in Visual Studio Code—do not suggest commands to open this project in Visual Studio again.
32
+ - If the project setup information has additional rules, follow them strictly.
33
+
34
+ FOLDER CREATION RULES:
35
+ - Always use the current directory as the project root.
36
+ - If you are running any terminal commands, use the '.' argument to ensure that the current working directory is used ALWAYS.
37
+ - Do not create a new folder unless the user explicitly requests it besides a .vscode folder for a tasks.json file.
38
+ - If any of the scaffolding commands mention that the folder name is not correct, let the user know to create a new folder with the correct name and then reopen it again in vscode.
39
+
40
+ EXTENSION INSTALLATION RULES:
41
+ - Only install extension specified by the get_project_setup_info tool. DO NOT INSTALL any other extensions.
42
+
43
+ PROJECT CONTENT RULES:
44
+ - If the user has not specified project details, assume they want a "Hello World" project as a starting point.
45
+ - Avoid adding links of any type (URLs, files, folders, etc.) or integrations that are not explicitly required.
46
+ - Avoid generating images, videos, or any other media files unless explicitly requested.
47
+ - If you need to use any media assets as placeholders, let the user know that these are placeholders and should be replaced with the actual assets later.
48
+ - Ensure all generated components serve a clear purpose within the user's requested workflow.
49
+ - If a feature is assumed but not confirmed, prompt the user for clarification before including it.
50
+ - If you are working on a VS Code extension, use the VS Code API tool with a query to find relevant VS Code API references and samples related to that query.
51
+
52
+ TASK COMPLETION RULES:
53
+ - Your task is complete when:
54
+ - Project is successfully scaffolded and compiled without errors
55
+ - copilot-instructions.md file in the .github directory exists in the project
56
+ - README.md file exists and is up to date
57
+ - User is provided with clear instructions to debug/launch the project
58
+
59
+ Before starting a new task in the above plan, update progress in the plan.
60
+ -->
61
+ - Work through each checklist item systematically.
62
+ - Keep communication concise and focused.
63
+ - Follow development best practices.
@@ -0,0 +1,134 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+ MANIFEST
26
+
27
+ # PyInstaller
28
+ # Usually these files are written by a python script from a template
29
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ debug.log
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+
51
+ # Translations
52
+ *.mo
53
+ *.pot
54
+
55
+ # Django stuff:
56
+ *.log
57
+ local_settings.py
58
+ db.sqlite3
59
+ db.sqlite3-journal
60
+
61
+ # Flask stuff:
62
+ instance/
63
+ .webassets-cache
64
+
65
+ # Scrapy stuff:
66
+ .scrapy
67
+
68
+ # Sphinx documentation
69
+ docs/_build/
70
+
71
+ # PyBuilder
72
+ .target/
73
+
74
+ # Jupyter Notebook
75
+ .ipynb_checkpoints
76
+
77
+ # IPython
78
+ profile_default/
79
+ ipython_config.py
80
+
81
+ # pyenv
82
+ .python-version
83
+
84
+ # pipenv
85
+ Pipfile.lock
86
+
87
+ # poetry
88
+ poetry.lock
89
+
90
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
91
+ __pypackages__/
92
+
93
+ # Celery stuff
94
+ celerybeat-schedule
95
+ celerybeat.pid
96
+
97
+ # SageMath parsed files
98
+ *.sage.py
99
+
100
+ # Environments
101
+ .env
102
+ .venv
103
+ env/
104
+ venv/
105
+ ENV/
106
+ env.bak/
107
+ venv.bak/
108
+
109
+ # Spyder project settings
110
+ .spyderproject
111
+ .spyproject
112
+
113
+ # Rope project settings
114
+ .ropeproject
115
+
116
+ # mkdocs documentation
117
+ /site
118
+
119
+ # mypy
120
+ .mypy_cache/
121
+ .dmypy.json
122
+ .dmypy-working/
123
+
124
+ # Pyre type checker
125
+ .pyre/
126
+
127
+ # pytype static type analyzer
128
+ .pytype/
129
+
130
+ # Cython debug symbols
131
+ cython_debug/
132
+
133
+ # VS Code
134
+ .vscode/
@@ -0,0 +1,77 @@
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
+ 1. Install [uv](https://github.com/astral-sh/uv) if needed:
43
+
44
+ ```sh
45
+ pip install uv
46
+ ```
47
+
48
+ 2. 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
+ 3. Run the app in test mode:
61
+
62
+ ```sh
63
+ python run_test.py
64
+ ```
65
+
66
+ Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your browser to see "hello world".
67
+
68
+ 4. Build a package and upload it to Pypi
69
+
70
+ ```sh
71
+ uv hatchling build
72
+ uv twine upload dist/*
73
+ ```
74
+
75
+ ## License
76
+
77
+ This project is licensed under the MIT License. See the `pyproject.toml` for details.
@@ -0,0 +1,60 @@
1
+ # whatdidyoudo
2
+
3
+ A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
4
+
5
+ ## Background
6
+
7
+ I often ask myself after contributing many changes to OpenStreetMap, either by walking around
8
+ while extensively using StreetComplete, MapComplete or Vespucci, or by doing some tasks in iD or
9
+ jOSM: **How many changes did I contribute to the map today?**
10
+
11
+ I'm not the only one. I heard questions like this quite a few times:
12
+ **Where can I see how much I did on yesterday's mapwalk?**
13
+
14
+ Because I think that simple questions deserve simple answers, I made this tool to give exactly
15
+ this information and nothing else.
16
+
17
+ You don't need to self-host it, it is available for anyone at
18
+ [whatdidyoudo.rompe.org](https://whatdidyoudo.rompe.org).
19
+
20
+ ## Setup
21
+
22
+ Fun fact: of course you don't really need *uv* for this. I'm just using this project to
23
+ get used to it as I think it has a lot of potential.
24
+
25
+ 1. Install [uv](https://github.com/astral-sh/uv) if needed:
26
+
27
+ ```sh
28
+ pip install uv
29
+ ```
30
+
31
+ 2. Install dependencies using *uv*:
32
+
33
+ ```sh
34
+ uv pip install -r pyproject.toml
35
+ ```
36
+
37
+ If you want to develop:
38
+
39
+ ```sh
40
+ uv pip install -r pyproject.toml --extra dev
41
+ ```
42
+
43
+ 3. Run the app in test mode:
44
+
45
+ ```sh
46
+ python run_test.py
47
+ ```
48
+
49
+ Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your browser to see "hello world".
50
+
51
+ 4. Build a package and upload it to Pypi
52
+
53
+ ```sh
54
+ uv hatchling build
55
+ uv twine upload dist/*
56
+ ```
57
+
58
+ ## License
59
+
60
+ This project is licensed under the MIT License. See the `pyproject.toml` for details.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "whatdidyoudo"
7
+ version = "0.1.0"
8
+ description = "A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day."
9
+ authors = [
10
+ { name = "Ulf Rompe", email = "whatdidyoudo.rompe.org@rompe.org" }
11
+ ]
12
+ license = { text = "MIT" }
13
+ readme = "README.md"
14
+ requires-python = ">=3.11"
15
+ dependencies = [
16
+ "Flask>=3.0.3",
17
+ "flask-caching==2.3.1",
18
+ "requests>=2.32.4",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "Ruff>=0.13.2",
24
+ "types-requests>=2.32.4",
25
+ "hatchling>=1.27.0",
26
+ "twine>=6.2.0",
27
+ ]
28
+
29
+ [tool.uv]
30
+ # uv-specific settings go here
31
+
32
+ [tool.ruff]
33
+ line-length = 100
34
+ target-version = "py311"
@@ -0,0 +1,8 @@
1
+ import os
2
+ from whatdidyoudo.app import app
3
+
4
+ def run():
5
+ app.run(debug=True)
6
+
7
+ if __name__ == "__main__":
8
+ run()
@@ -0,0 +1,84 @@
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)
@@ -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>