osmsg 1.2.1__tar.gz → 1.2.2__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 osmsg might be problematic. Click here for more details.

Files changed (44) hide show
  1. {osmsg-1.2.1 → osmsg-1.2.2}/PKG-INFO +4 -3
  2. {osmsg-1.2.1 → osmsg-1.2.2}/README.md +3 -2
  3. osmsg-1.2.2/osmsg/__version__.py +1 -0
  4. osmsg-1.2.2/osmsg/gui.py +195 -0
  5. {osmsg-1.2.1 → osmsg-1.2.2}/pyproject.toml +4 -1
  6. osmsg-1.2.1/osmsg/__version__.py +0 -1
  7. {osmsg-1.2.1 → osmsg-1.2.2}/LICENSE +0 -0
  8. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/__init__.py +0 -0
  9. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/_http.py +0 -0
  10. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/_tick.py +0 -0
  11. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/auth.py +0 -0
  12. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/boundary.py +0 -0
  13. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/cli.py +0 -0
  14. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/db/__init__.py +0 -0
  15. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/db/duckdb_schema.py +0 -0
  16. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/db/ingest.py +0 -0
  17. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/db/queries.py +0 -0
  18. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/db/schema.py +0 -0
  19. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/exceptions.py +0 -0
  20. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/__init__.py +0 -0
  21. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/csv.py +0 -0
  22. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/json.py +0 -0
  23. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/markdown.py +0 -0
  24. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/parquet.py +0 -0
  25. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/export/psql.py +0 -0
  26. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/fetch.py +0 -0
  27. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/geofabrik.py +0 -0
  28. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/handlers.py +0 -0
  29. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/history.py +0 -0
  30. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/__init__.py +0 -0
  31. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/cli.py +0 -0
  32. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/convert.py +0 -0
  33. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/manifest.py +0 -0
  34. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/month.py +0 -0
  35. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/parquet.py +0 -0
  36. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/maintain/pbf_split.py +0 -0
  37. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/models.py +0 -0
  38. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/pg_schema.py +0 -0
  39. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/pipeline.py +0 -0
  40. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/py.typed +0 -0
  41. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/replication.py +0 -0
  42. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/tm.py +0 -0
  43. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/ui.py +0 -0
  44. {osmsg-1.2.1 → osmsg-1.2.2}/osmsg/workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osmsg
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: OpenStreetMap Stats Generator: Commandline
5
5
  Keywords: osm,stats,commandline,openstreetmap
6
6
  Author: Kshitij Raj Sharma
@@ -78,7 +78,8 @@ brew install osgeonepal/tap/osmsg # macOS / Linux (Homebrew tap)
78
78
  ```
79
79
 
80
80
  On Windows, download `osmsg.exe` from the [latest release](https://github.com/osgeonepal/osmsg/releases)
81
- and run it directly, no Python required.
81
+ and double-click it to open the desktop app. Fill in the dates and options, click Run, and open the
82
+ output folder. The CLI below is for macOS, Linux, and pip/uv users.
82
83
 
83
84
  ## Quick start
84
85
 
@@ -237,7 +238,7 @@ docker-compose `environment:` block all reach the same setting. CLI flag wins ov
237
238
  | `--history` / `--no-history` | `OSMSG_HISTORY` | on | Read covered months from the published dataset. |
238
239
  | `--history-url` | `OSMSG_HISTORY_URL` | `osmsg-history` | Published dataset location. |
239
240
  | `--insert` | (none) | off | Load history into the store and seed resume, then exit. No window loads all of it. |
240
- | `--osh-file` / `--changeset-file` | (none) | unset | Insert from local planet history + changeset files instead of the dataset. |
241
+ | `--osh-file` / `--changeset-file` | (none) | unset | Insert from local planet history + changeset files. |
241
242
  | `--changeset-pad-hours` | `OSMSG_CHANGESET_PAD_HOURS` | `1` | See below. |
242
243
  | (auto-bootstrap on `--update`) | `OSMSG_BOOTSTRAP` | `hour` | `hour`, `day`, or `week`. Used when `--update` runs against an empty DB. |
243
244
  | (auto-bootstrap on `--update`) | `OSMSG_BOOTSTRAP_DAYS` | unset | Integer N; overrides `OSMSG_BOOTSTRAP`. |
@@ -46,7 +46,8 @@ brew install osgeonepal/tap/osmsg # macOS / Linux (Homebrew tap)
46
46
  ```
47
47
 
48
48
  On Windows, download `osmsg.exe` from the [latest release](https://github.com/osgeonepal/osmsg/releases)
49
- and run it directly, no Python required.
49
+ and double-click it to open the desktop app. Fill in the dates and options, click Run, and open the
50
+ output folder. The CLI below is for macOS, Linux, and pip/uv users.
50
51
 
51
52
  ## Quick start
52
53
 
@@ -205,7 +206,7 @@ docker-compose `environment:` block all reach the same setting. CLI flag wins ov
205
206
  | `--history` / `--no-history` | `OSMSG_HISTORY` | on | Read covered months from the published dataset. |
206
207
  | `--history-url` | `OSMSG_HISTORY_URL` | `osmsg-history` | Published dataset location. |
207
208
  | `--insert` | (none) | off | Load history into the store and seed resume, then exit. No window loads all of it. |
208
- | `--osh-file` / `--changeset-file` | (none) | unset | Insert from local planet history + changeset files instead of the dataset. |
209
+ | `--osh-file` / `--changeset-file` | (none) | unset | Insert from local planet history + changeset files. |
209
210
  | `--changeset-pad-hours` | `OSMSG_CHANGESET_PAD_HOURS` | `1` | See below. |
210
211
  | (auto-bootstrap on `--update`) | `OSMSG_BOOTSTRAP` | `hour` | `hour`, `day`, or `week`. Used when `--update` runs against an empty DB. |
211
212
  | (auto-bootstrap on `--update`) | `OSMSG_BOOTSTRAP_DAYS` | unset | Integer N; overrides `OSMSG_BOOTSTRAP`. |
@@ -0,0 +1 @@
1
+ __version__ = "1.2.2"
@@ -0,0 +1,195 @@
1
+ """Minimal tkinter desktop UI for running osmsg and saving the output."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime as dt
6
+ import os
7
+ import queue
8
+ import sys
9
+ import threading
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from .exceptions import NoDataFoundError, OsmsgError
14
+ from .pipeline import RunConfig, run
15
+
16
+ UTC = dt.UTC
17
+ FORMATS = ["parquet", "csv", "json", "markdown"]
18
+
19
+
20
+ def _parse_date(value: str) -> dt.datetime | None:
21
+ value = value.strip()
22
+ if not value:
23
+ return None
24
+ for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d"):
25
+ try:
26
+ return dt.datetime.strptime(value, fmt).replace(tzinfo=UTC)
27
+ except ValueError:
28
+ continue
29
+ raise OsmsgError(f"Unrecognized date: {value!r}. Use YYYY-MM-DD.")
30
+
31
+
32
+ def _split(value: str | None) -> list[str] | None:
33
+ items: list[str] = [part.strip() for part in (value or "").split(",") if part.strip()]
34
+ return items if items else None
35
+
36
+
37
+ def build_config(form: dict[str, object], output_dir: str) -> RunConfig:
38
+ """Map the form fields to a RunConfig, raising OsmsgError on invalid input."""
39
+ formats = [name for name in FORMATS if form.get(name)]
40
+ if not formats:
41
+ raise OsmsgError("Pick at least one output format.")
42
+ start = _parse_date(str(form.get("start", "")))
43
+ if start is None:
44
+ raise OsmsgError("Start date is required (YYYY-MM-DD).")
45
+ return RunConfig(
46
+ name=str(form.get("name") or "stats"),
47
+ start_date=start,
48
+ end_date=_parse_date(str(form.get("end", ""))),
49
+ hashtags=_split(str(form.get("hashtags") or "")),
50
+ additional_tags=_split(str(form.get("tags") or "")),
51
+ tag_mode="all" if form.get("all_tags") else "none",
52
+ summary=bool(form.get("summary")),
53
+ formats=formats,
54
+ output_dir=Path(output_dir or "."),
55
+ )
56
+
57
+
58
+ def _open_folder(path: Path) -> None:
59
+ if sys.platform == "win32":
60
+ os.startfile(path) # noqa: S606
61
+ elif sys.platform == "darwin":
62
+ import subprocess
63
+
64
+ subprocess.run(["open", str(path)], check=False)
65
+ else:
66
+ import subprocess
67
+
68
+ subprocess.run(["xdg-open", str(path)], check=False)
69
+
70
+
71
+ class _Redirector:
72
+ def __init__(self, sink: queue.Queue) -> None:
73
+ self.sink = sink
74
+
75
+ def write(self, text: str) -> None:
76
+ if text:
77
+ self.sink.put(("log", text))
78
+
79
+ def flush(self) -> None:
80
+ pass
81
+
82
+ def isatty(self) -> bool:
83
+ return False
84
+
85
+
86
+ class App:
87
+ def __init__(self) -> None:
88
+ import tkinter as tk
89
+ from tkinter import filedialog, scrolledtext, ttk
90
+
91
+ self._tk = tk
92
+ self._filedialog = filedialog
93
+ self.events: queue.Queue = queue.Queue()
94
+ self.out_dir = str(Path.home() / "osmsg")
95
+
96
+ self.root = tk.Tk()
97
+ self.root.title("osmsg")
98
+ self.vars: dict[str, Any] = {}
99
+ frame = ttk.Frame(self.root, padding=12)
100
+ frame.grid(sticky="nsew")
101
+
102
+ rows = [
103
+ ("Name", "name", "stats"),
104
+ ("Start (YYYY-MM-DD)", "start", ""),
105
+ ("End (blank = now)", "end", ""),
106
+ ("Hashtags (comma-sep)", "hashtags", ""),
107
+ ("Tags (comma-sep)", "tags", ""),
108
+ ]
109
+ for i, (label, key, default) in enumerate(rows):
110
+ ttk.Label(frame, text=label).grid(row=i, column=0, sticky="w", pady=2)
111
+ var = tk.StringVar(value=default)
112
+ ttk.Entry(frame, textvariable=var, width=40).grid(row=i, column=1, columnspan=3, sticky="we", pady=2)
113
+ self.vars[key] = var
114
+
115
+ self.vars["all_tags"] = tk.BooleanVar()
116
+ self.vars["summary"] = tk.BooleanVar()
117
+ ttk.Checkbutton(frame, text="All tags", variable=self.vars["all_tags"]).grid(row=5, column=0, sticky="w")
118
+ ttk.Checkbutton(frame, text="Daily summary", variable=self.vars["summary"]).grid(row=5, column=1, sticky="w")
119
+
120
+ fmt_frame = ttk.LabelFrame(frame, text="Formats", padding=6)
121
+ fmt_frame.grid(row=6, column=0, columnspan=4, sticky="we", pady=6)
122
+ for i, name in enumerate(FORMATS):
123
+ var = tk.BooleanVar(value=name in ("parquet", "csv"))
124
+ ttk.Checkbutton(fmt_frame, text=name, variable=var).grid(row=0, column=i, padx=4)
125
+ self.vars[name] = var
126
+
127
+ self.out_label = ttk.Label(frame, text=f"Output: {self.out_dir}")
128
+ self.out_label.grid(row=7, column=0, columnspan=3, sticky="w")
129
+ ttk.Button(frame, text="Choose folder", command=self._choose_folder).grid(row=7, column=3, sticky="e")
130
+
131
+ self.run_btn = ttk.Button(frame, text="Run", command=self._on_run)
132
+ self.run_btn.grid(row=8, column=0, pady=8, sticky="w")
133
+ self.open_btn = ttk.Button(frame, text="Open output folder", command=lambda: _open_folder(Path(self.out_dir)))
134
+ self.open_btn.grid(row=8, column=1, pady=8, sticky="w")
135
+
136
+ self.log = scrolledtext.ScrolledText(frame, width=70, height=14, state="disabled")
137
+ self.log.grid(row=9, column=0, columnspan=4, sticky="nsew")
138
+ self.root.after(120, self._drain)
139
+
140
+ def _choose_folder(self) -> None:
141
+ chosen = self._filedialog.askdirectory(initialdir=self.out_dir)
142
+ if chosen:
143
+ self.out_dir = chosen
144
+ self.out_label.config(text=f"Output: {self.out_dir}")
145
+
146
+ def _append(self, text: str) -> None:
147
+ self.log.config(state="normal")
148
+ self.log.insert("end", text)
149
+ self.log.see("end")
150
+ self.log.config(state="disabled")
151
+
152
+ def _on_run(self) -> None:
153
+ try:
154
+ cfg = build_config({k: v.get() for k, v in self.vars.items()}, self.out_dir)
155
+ except OsmsgError as exc:
156
+ self._append(f"\n{exc}\n")
157
+ return
158
+ self.run_btn.config(state="disabled")
159
+ self._append(f"\nRunning into {self.out_dir} ...\n")
160
+ threading.Thread(target=self._worker, args=(cfg,), daemon=True).start()
161
+
162
+ def _worker(self, cfg: RunConfig) -> None:
163
+ saved = sys.stdout, sys.stderr
164
+ sys.stdout = sys.stderr = _Redirector(self.events) # type: ignore[assignment]
165
+ try:
166
+ result = run(cfg)
167
+ self.events.put(("done", f"Done. {result['rows']} rows. Files in {self.out_dir}"))
168
+ except NoDataFoundError:
169
+ self.events.put(("done", "No data found for that range."))
170
+ except OsmsgError as exc:
171
+ self.events.put(("done", f"Error: {exc}"))
172
+ except Exception as exc:
173
+ self.events.put(("done", f"Unexpected error: {type(exc).__name__}: {exc}"))
174
+ finally:
175
+ sys.stdout, sys.stderr = saved
176
+
177
+ def _drain(self) -> None:
178
+ try:
179
+ while True:
180
+ kind, payload = self.events.get_nowait()
181
+ if kind == "log":
182
+ self._append(payload)
183
+ else:
184
+ self._append(f"\n{payload}\n")
185
+ self.run_btn.config(state="normal")
186
+ except queue.Empty:
187
+ pass
188
+ self.root.after(120, self._drain)
189
+
190
+ def run(self) -> None:
191
+ self.root.mainloop()
192
+
193
+
194
+ def launch() -> None:
195
+ App().run()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "osmsg"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  description = "OpenStreetMap Stats Generator: Commandline"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -41,6 +41,9 @@ repository = "https://github.com/osgeonepal/osmsg"
41
41
  [project.scripts]
42
42
  osmsg = "osmsg.cli:app"
43
43
 
44
+ [project.gui-scripts]
45
+ osmsg-gui = "osmsg.gui:launch"
46
+
44
47
  [build-system]
45
48
  requires = ["uv_build>=0.5.15,<0.9"]
46
49
  build-backend = "uv_build"
@@ -1 +0,0 @@
1
- __version__ = "1.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes