python-template-project 0.2.3__tar.gz → 0.2.4__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.
- python_template_project-0.2.4/.github/workflows/build-linux.yml +48 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/HISTORY.md +15 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/Makefile +13 -6
- {python_template_project-0.2.3/src/python_template_project.egg-info → python_template_project-0.2.4}/PKG-INFO +3 -2
- {python_template_project-0.2.3 → python_template_project-0.2.4}/README.md +3 -17
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/index.md +1 -1
- {python_template_project-0.2.3 → python_template_project-0.2.4}/mkdocs.yml +1 -1
- {python_template_project-0.2.3 → python_template_project-0.2.4}/pyproject.toml +1 -0
- python_template_project-0.2.4/pytest.ini +3 -0
- python_template_project-0.2.4/scripts/show_filelist.py +51 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/show_tree.py +2 -3
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/_version.py +3 -3
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/cli.py +2 -2
- {python_template_project-0.2.3 → python_template_project-0.2.4/src/python_template_project.egg-info}/PKG-INFO +3 -2
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/SOURCES.txt +4 -3
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/requires.txt +1 -0
- python_template_project-0.2.4/tests/test_gpx.py +292 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/uv.lock +54 -0
- python_template_project-0.2.3/scripts/show_filelist.ps1 +0 -14
- python_template_project-0.2.3/scripts/show_tree.ps1 +0 -58
- python_template_project-0.2.3/tests/test_gpx.py +0 -361
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/FUNDING.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/actions/setup-environment/action.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/dependabot.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/init.sh +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/release_message.sh +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/rename_project.sh +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/template.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/update_funding.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/build-macos.yml +0 -0
- /python_template_project-0.2.3/.github/workflows/build.yml → /python_template_project-0.2.4/.github/workflows/build-windows.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/main.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/release.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/rename_project.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/update_readme.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.gitignore +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/config_generate.xml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/module_cli.xml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/module_gui.xml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.pre-commit-config-linux.yaml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.pre-commit-config.yaml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/.readthedocs.yaml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/LICENSE +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/config.yaml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/.nav.yml +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/_static/img/favicon.png +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/_static/img/logo.png +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/css/custom.css +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/contributing.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/make_windows.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/naming_convention.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/pypi_release.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/funding/funding.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/getting-started/install.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/getting-started/virtual-environment.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/usage/cli.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/usage/config.md +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/kuhkopfsteig.gpx +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/rother_lilienstein.gpx +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/teneriffa.gpx +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/generate_config_docs.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/update_readme.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/setup.cfg +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/firewall_handler.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/main.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/__main__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/__main__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/config/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/config/config.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/base.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/logging.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/__init__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/__main__.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/gui.py +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/dependency_links.txt +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/entry_points.txt +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/top_level.txt +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/template.yml.url +0 -0
- {python_template_project-0.2.3 → python_template_project-0.2.4}/tests/__init__.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Build Linux App
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- '*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: [ 3.11 ]
|
|
15
|
+
os: [ ubuntu-latest ]
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Setup Environment
|
|
28
|
+
uses: ./.github/actions/setup-environment
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Build Unified Linux Executable with PyInstaller
|
|
33
|
+
run: |
|
|
34
|
+
make build-linux
|
|
35
|
+
|
|
36
|
+
- name: Prepare ZIP file for release
|
|
37
|
+
run: |
|
|
38
|
+
echo "Create the ZIP archive from the contents of the release folder"
|
|
39
|
+
cd release
|
|
40
|
+
zip -r ../package-linux.zip .
|
|
41
|
+
|
|
42
|
+
- name: Upload ZIP to GitHub Release
|
|
43
|
+
uses: softprops/action-gh-release@v2
|
|
44
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
45
|
+
with:
|
|
46
|
+
files: package-linux.zip
|
|
47
|
+
env:
|
|
48
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -4,6 +4,21 @@ Changelog
|
|
|
4
4
|
|
|
5
5
|
(unreleased)
|
|
6
6
|
------------
|
|
7
|
+
- Add linux build workflow. [Paul Magister]
|
|
8
|
+
- Avoid warning by adding lxml package. [Paul Magister]
|
|
9
|
+
- Fix format. [Paul Magister]
|
|
10
|
+
- Only use on py file to show tree with or without python classes. [Paul
|
|
11
|
+
Magister]
|
|
12
|
+
- Switch to python scripts that print the project structure. [Paul
|
|
13
|
+
Magister]
|
|
14
|
+
- Switch to pytest. [Paul Magister]
|
|
15
|
+
- Update README.md from docs/index.md. [github-actions]
|
|
16
|
+
- Typo with with. [Paul Magister]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
0.2.3 (2025-11-14)
|
|
20
|
+
------------------
|
|
21
|
+
- Docs: Update HISTORY.md for release 0.2.3. [Paul Magister]
|
|
7
22
|
- Run precommit before building release for proper formatting. [Paul
|
|
8
23
|
Magister]
|
|
9
24
|
- Valid app_name for cli configuration. [Paul Magister]
|
|
@@ -65,6 +65,17 @@ build-win: ## Build the Windows executable.
|
|
|
65
65
|
cp config.yaml release
|
|
66
66
|
cp README.md release
|
|
67
67
|
|
|
68
|
+
.PHONY: build-linux
|
|
69
|
+
build-linux: ## Build the Linux executable.
|
|
70
|
+
## Build the Linux executable.
|
|
71
|
+
uv run pyinstaller --onefile src/main.py --name python-template-project --add-data "config.yaml;." --hidden-import python_template_project.cli.cli --hidden-import python_template_project.gui.gui
|
|
72
|
+
|
|
73
|
+
rm -rf release
|
|
74
|
+
mkdir release
|
|
75
|
+
cp dist/python-template-project release
|
|
76
|
+
cp config.yaml release
|
|
77
|
+
|
|
78
|
+
|
|
68
79
|
.PHONY: build-macos
|
|
69
80
|
build-macos: ## Build the macOS executable.
|
|
70
81
|
echo "Building unified CLI/GUI application as executable"
|
|
@@ -157,17 +168,13 @@ docs: ## Build and sync the documentation.
|
|
|
157
168
|
@uv run mkdocs build
|
|
158
169
|
@uv run mkdocs serve
|
|
159
170
|
|
|
160
|
-
.PHONY: list
|
|
161
|
-
list: ## Show project file list (excluding ignored folders)
|
|
162
|
-
@powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show_filelist.ps1
|
|
163
|
-
|
|
164
171
|
.PHONY: tree
|
|
165
172
|
tree: ## Show project tree (excluding ignored folders)
|
|
166
|
-
@
|
|
173
|
+
@uv run ./scripts/show_tree.py
|
|
167
174
|
|
|
168
175
|
.PHONY: pytree
|
|
169
176
|
pytree: ## Show project tree (excluding ignored folders)
|
|
170
|
-
@uv run ./scripts/show_tree.py
|
|
177
|
+
@uv run ./scripts/show_tree.py --show-code
|
|
171
178
|
|
|
172
179
|
.PHONY: init
|
|
173
180
|
init: ## Initialize the project based on an application template.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-template-project
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Feature-rich Python project template designed for robustness and ease of use.
|
|
5
5
|
Author: pamagister
|
|
6
6
|
Requires-Python: <3.12,>=3.10
|
|
@@ -9,6 +9,7 @@ License-File: LICENSE
|
|
|
9
9
|
Requires-Dist: config-cli-gui>=0.2.2
|
|
10
10
|
Requires-Dist: fastkml>=1.1.0
|
|
11
11
|
Requires-Dist: gpxpy>=1.6.2
|
|
12
|
+
Requires-Dist: lxml>=6.0.2
|
|
12
13
|
Requires-Dist: shapely>=2.1.1
|
|
13
14
|
Requires-Dist: srtm-py>=0.3.7
|
|
14
15
|
Provides-Extra: dev
|
|
@@ -32,7 +33,7 @@ Dynamic: license-file
|
|
|
32
33
|
|
|
33
34
|
# Welcome to python-template-project
|
|
34
35
|
|
|
35
|
-
A feature-rich Python project template with
|
|
36
|
+
A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
|
|
36
37
|
|
|
37
38
|
[](https://github.com/pamagister/python-template-project/actions)
|
|
38
39
|
[](https://github.com/pamagister/python-template-project/releases)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Welcome to python-template-project
|
|
4
4
|
|
|
5
|
-
A feature-rich Python project template with
|
|
5
|
+
A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
|
|
6
6
|
|
|
7
7
|
[](https://github.com/pamagister/python-template-project/actions)
|
|
8
8
|
[](https://github.com/pamagister/python-template-project/releases)
|
|
@@ -51,22 +51,7 @@ Getting started on developing your own project based on this template
|
|
|
51
51
|
7. **Open the project in your IDE**
|
|
52
52
|
|
|
53
53
|
8. **Install dependencies and create virtual environment**
|
|
54
|
-
|
|
55
|
-
Install uv first:
|
|
56
|
-
```bash
|
|
57
|
-
pip install uv
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
On Linux, install pipx first:
|
|
61
|
-
```bash
|
|
62
|
-
sudo apt install pipx
|
|
63
|
-
pipx ensurepath
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
then, intall uv
|
|
67
|
-
```bash
|
|
68
|
-
pipx install uv
|
|
69
|
-
```
|
|
54
|
+
Run:
|
|
70
55
|
|
|
71
56
|
```bash
|
|
72
57
|
make install
|
|
@@ -108,6 +93,7 @@ Getting started on developing your own project based on this template
|
|
|
108
93
|
* Generate a **PyPI API token** from your PyPI account.
|
|
109
94
|
* Go to **GitHub → Settings → Secrets and variables → Actions**.
|
|
110
95
|
* Add the secret as `PYPI_API_TOKEN`.
|
|
96
|
+
* The token probably starts with "pypi-..."
|
|
111
97
|
|
|
112
98
|
16. **Release your first version**
|
|
113
99
|
Run:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Welcome to python-template-project
|
|
2
2
|
|
|
3
|
-
A feature-rich Python project template with
|
|
3
|
+
A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/pamagister/python-template-project/actions)
|
|
6
6
|
[](https://github.com/pamagister/python-template-project/releases)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
site_name: python-template-project
|
|
2
2
|
site_url: !ENV READTHEDOCS_CANONICAL_URL
|
|
3
|
-
site_description: A feature-rich Python project template with
|
|
3
|
+
site_description: A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
|
|
4
4
|
repo_url: https://github.com/pamagister/python-template-project
|
|
5
5
|
repo_name: GitHub
|
|
6
6
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
EXCLUDE_PATTERN = re.compile(
|
|
7
|
+
r"""
|
|
8
|
+
(\.pytest_cache)|
|
|
9
|
+
(\.git)|
|
|
10
|
+
(\.ruff)|
|
|
11
|
+
(\.venv)|
|
|
12
|
+
(\.github)|
|
|
13
|
+
([/\\]build)|
|
|
14
|
+
([/\\]dist)|
|
|
15
|
+
(\.idea)|
|
|
16
|
+
([/\\]htmlcov)|
|
|
17
|
+
([/\\]__pycache__)|
|
|
18
|
+
([/\\]__main__)|
|
|
19
|
+
([/\\]__init__)|
|
|
20
|
+
([/\\]site)|
|
|
21
|
+
(\.pyc$)
|
|
22
|
+
""",
|
|
23
|
+
re.VERBOSE,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def should_exclude(path: Path) -> bool:
|
|
27
|
+
return bool(EXCLUDE_PATTERN.search(str(path)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
base_path = Path.cwd()
|
|
32
|
+
|
|
33
|
+
for root, dirs, files in os.walk(base_path):
|
|
34
|
+
all_entries = dirs + files
|
|
35
|
+
|
|
36
|
+
for name in all_entries:
|
|
37
|
+
full_path = Path(root) / name
|
|
38
|
+
|
|
39
|
+
# apply exclusion filter
|
|
40
|
+
if should_exclude(full_path):
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# compute relative path
|
|
44
|
+
rel = str(full_path.relative_to(base_path))
|
|
45
|
+
|
|
46
|
+
# match PowerShell behavior: always start with "\"
|
|
47
|
+
print("\\" + rel.replace("/", "\\"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -130,7 +130,6 @@ def show_tree(path, prefix="", show_code=False):
|
|
|
130
130
|
|
|
131
131
|
print(f"{prefix}{connector}{symbol} {item}")
|
|
132
132
|
next_prefix = prefix + (INDENT if is_last else VERTICAL_LINE)
|
|
133
|
-
|
|
134
133
|
if os.path.isdir(full_path):
|
|
135
134
|
show_tree(full_path, next_prefix, show_code)
|
|
136
135
|
elif show_code and item.endswith(".py"):
|
|
@@ -159,7 +158,7 @@ if __name__ == "__main__":
|
|
|
159
158
|
parser.add_argument(
|
|
160
159
|
"--show-code",
|
|
161
160
|
action="store_true",
|
|
162
|
-
default=
|
|
161
|
+
default=False,
|
|
163
162
|
help="Shows functions, classes and attributes in .py files",
|
|
164
163
|
)
|
|
165
164
|
args = parser.parse_args()
|
|
@@ -172,4 +171,4 @@ if __name__ == "__main__":
|
|
|
172
171
|
print(f"{CLASS_SYMBOL} - Class")
|
|
173
172
|
print(f"{FUNC_SYMBOL} - Function/Method")
|
|
174
173
|
print(f"{ATTR_SYMBOL} - Attribute")
|
|
175
|
-
|
|
174
|
+
print(args)
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.2.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
31
|
+
__version__ = version = '0.2.4'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 4)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gff1b8a26d'
|
|
@@ -24,7 +24,7 @@ def validate_config(config: ConfigParameterManager) -> bool:
|
|
|
24
24
|
"""
|
|
25
25
|
# Initialize logging system
|
|
26
26
|
logger_manager = initialize_logging(config)
|
|
27
|
-
logger = logger_manager.get_logger("
|
|
27
|
+
logger = logger_manager.get_logger("cli")
|
|
28
28
|
|
|
29
29
|
# Get CLI category and check required parameters
|
|
30
30
|
cli_category = config.cli
|
|
@@ -59,7 +59,7 @@ def run_main_processing(config: ConfigParameterManager) -> int:
|
|
|
59
59
|
"""
|
|
60
60
|
# Initialize logging system
|
|
61
61
|
logger_manager = initialize_logging(config)
|
|
62
|
-
logger = logger_manager.get_logger("
|
|
62
|
+
logger = logger_manager.get_logger("cli")
|
|
63
63
|
|
|
64
64
|
try:
|
|
65
65
|
# Log startup information
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-template-project
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Feature-rich Python project template designed for robustness and ease of use.
|
|
5
5
|
Author: pamagister
|
|
6
6
|
Requires-Python: <3.12,>=3.10
|
|
@@ -9,6 +9,7 @@ License-File: LICENSE
|
|
|
9
9
|
Requires-Dist: config-cli-gui>=0.2.2
|
|
10
10
|
Requires-Dist: fastkml>=1.1.0
|
|
11
11
|
Requires-Dist: gpxpy>=1.6.2
|
|
12
|
+
Requires-Dist: lxml>=6.0.2
|
|
12
13
|
Requires-Dist: shapely>=2.1.1
|
|
13
14
|
Requires-Dist: srtm-py>=0.3.7
|
|
14
15
|
Provides-Extra: dev
|
|
@@ -32,7 +33,7 @@ Dynamic: license-file
|
|
|
32
33
|
|
|
33
34
|
# Welcome to python-template-project
|
|
34
35
|
|
|
35
|
-
A feature-rich Python project template with
|
|
36
|
+
A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
|
|
36
37
|
|
|
37
38
|
[](https://github.com/pamagister/python-template-project/actions)
|
|
38
39
|
[](https://github.com/pamagister/python-template-project/releases)
|
|
@@ -9,6 +9,7 @@ README.md
|
|
|
9
9
|
config.yaml
|
|
10
10
|
mkdocs.yml
|
|
11
11
|
pyproject.toml
|
|
12
|
+
pytest.ini
|
|
12
13
|
template.yml.url
|
|
13
14
|
uv.lock
|
|
14
15
|
.github/FUNDING.yml
|
|
@@ -22,8 +23,9 @@ uv.lock
|
|
|
22
23
|
.github/ISSUE_TEMPLATE/bug_report.md
|
|
23
24
|
.github/ISSUE_TEMPLATE/feature_request.md
|
|
24
25
|
.github/actions/setup-environment/action.yml
|
|
26
|
+
.github/workflows/build-linux.yml
|
|
25
27
|
.github/workflows/build-macos.yml
|
|
26
|
-
.github/workflows/build.yml
|
|
28
|
+
.github/workflows/build-windows.yml
|
|
27
29
|
.github/workflows/main.yml
|
|
28
30
|
.github/workflows/release.yml
|
|
29
31
|
.github/workflows/rename_project.yml
|
|
@@ -49,8 +51,7 @@ examples/kuhkopfsteig.gpx
|
|
|
49
51
|
examples/rother_lilienstein.gpx
|
|
50
52
|
examples/teneriffa.gpx
|
|
51
53
|
scripts/generate_config_docs.py
|
|
52
|
-
scripts/show_filelist.
|
|
53
|
-
scripts/show_tree.ps1
|
|
54
|
+
scripts/show_filelist.py
|
|
54
55
|
scripts/show_tree.py
|
|
55
56
|
scripts/update_readme.py
|
|
56
57
|
src/__init__.py
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import zipfile
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import gpxpy
|
|
7
|
+
import pytest
|
|
8
|
+
from gpxpy.gpx import GPXTrackPoint
|
|
9
|
+
|
|
10
|
+
from python_template_project.config.config import ConfigParameterManager
|
|
11
|
+
from python_template_project.core.logging import initialize_logging
|
|
12
|
+
from src.python_template_project.core.base import BaseGPXProcessor
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Fixtures
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def test_paths(tmp_path):
|
|
21
|
+
"""Provide common paths and a temp output directory."""
|
|
22
|
+
current_test_file_dir = Path(__file__).parent
|
|
23
|
+
test_gpx_file = current_test_file_dir.parent / "examples" / "kuhkopfsteig.gpx"
|
|
24
|
+
|
|
25
|
+
output_dir = tmp_path / "output"
|
|
26
|
+
output_dir.mkdir(exist_ok=True)
|
|
27
|
+
|
|
28
|
+
logger = initialize_logging(ConfigParameterManager()).get_logger("TestGPXProcessor")
|
|
29
|
+
|
|
30
|
+
processor = BaseGPXProcessor(
|
|
31
|
+
input_=str(test_gpx_file),
|
|
32
|
+
output=str(output_dir),
|
|
33
|
+
logger=logger,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"test_gpx_file": test_gpx_file,
|
|
38
|
+
"output_dir": output_dir,
|
|
39
|
+
"logger": logger,
|
|
40
|
+
"processor": processor,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Tests
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_get_output_folder(test_paths):
|
|
50
|
+
logger = test_paths["logger"]
|
|
51
|
+
test_gpx_file = test_paths["test_gpx_file"]
|
|
52
|
+
|
|
53
|
+
processor_with_output = BaseGPXProcessor(
|
|
54
|
+
input_=str(test_gpx_file),
|
|
55
|
+
output="custom_output",
|
|
56
|
+
logger=logger,
|
|
57
|
+
)
|
|
58
|
+
output_path = processor_with_output._get_output_folder()
|
|
59
|
+
assert output_path == Path("custom_output")
|
|
60
|
+
assert output_path.exists()
|
|
61
|
+
shutil.rmtree(output_path)
|
|
62
|
+
|
|
63
|
+
processor_no_output = BaseGPXProcessor(input_=str(test_gpx_file), logger=logger)
|
|
64
|
+
output_path_default = processor_no_output._get_output_folder()
|
|
65
|
+
assert str(output_path_default).startswith(str(Path.cwd() / "gpx_processed_"))
|
|
66
|
+
assert output_path_default.exists()
|
|
67
|
+
shutil.rmtree(output_path_default)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_get_adjusted_elevation(test_paths):
|
|
71
|
+
processor = test_paths["processor"]
|
|
72
|
+
|
|
73
|
+
test_point = GPXTrackPoint(latitude=50.91605, longitude=14.07259, elevation=100.0)
|
|
74
|
+
adjusted = processor._get_adjusted_elevation(test_point)
|
|
75
|
+
|
|
76
|
+
assert isinstance(adjusted, int)
|
|
77
|
+
assert 124 < adjusted < 400
|
|
78
|
+
assert adjusted != 100
|
|
79
|
+
|
|
80
|
+
test_point_no_elev = GPXTrackPoint(latitude=50.91605, longitude=14.07259)
|
|
81
|
+
adjusted2 = processor._get_adjusted_elevation(test_point_no_elev)
|
|
82
|
+
|
|
83
|
+
assert isinstance(adjusted2, int)
|
|
84
|
+
assert 124 < adjusted2 < 400
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_calculate_distance(test_paths):
|
|
88
|
+
processor = test_paths["processor"]
|
|
89
|
+
|
|
90
|
+
p1 = GPXTrackPoint(latitude=50.91605, longitude=14.07259)
|
|
91
|
+
p2 = GPXTrackPoint(latitude=50.91695, longitude=14.07360)
|
|
92
|
+
|
|
93
|
+
distance = processor._calculate_distance(p1, p2)
|
|
94
|
+
assert abs(distance - 122.59003627960) < 1
|
|
95
|
+
|
|
96
|
+
# same point → distance = 0
|
|
97
|
+
p_same = GPXTrackPoint(latitude=50.0, longitude=10.0)
|
|
98
|
+
assert processor._calculate_distance(p_same, p_same) == 0.0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_optimize_track_points(test_paths):
|
|
102
|
+
processor = test_paths["processor"]
|
|
103
|
+
|
|
104
|
+
points = [
|
|
105
|
+
GPXTrackPoint(50.0, 10.0, 100.0, datetime.now()),
|
|
106
|
+
GPXTrackPoint(50.00001, 10.00001, 101.0, datetime.now()),
|
|
107
|
+
GPXTrackPoint(50.00002, 10.00002, 102.0, datetime.now()),
|
|
108
|
+
GPXTrackPoint(50.00010, 10.00010, 103.0, datetime.now()),
|
|
109
|
+
GPXTrackPoint(50.00020, 10.00020, 104.0, datetime.now()),
|
|
110
|
+
GPXTrackPoint(50.00030, 10.00030, 105.0, datetime.now()),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
original_min = processor.min_dist
|
|
114
|
+
processor.min_dist = 20.0
|
|
115
|
+
|
|
116
|
+
optimized = processor._optimize_track_points(points)
|
|
117
|
+
assert len(optimized) == 3
|
|
118
|
+
|
|
119
|
+
for p in optimized:
|
|
120
|
+
assert p.time is None
|
|
121
|
+
assert p.latitude == round(float(p.latitude), 5)
|
|
122
|
+
assert p.longitude == round(float(p.longitude), 5)
|
|
123
|
+
assert isinstance(p.elevation, int)
|
|
124
|
+
|
|
125
|
+
processor.min_dist = original_min
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_get_input_files(test_paths):
|
|
129
|
+
processor = test_paths["processor"]
|
|
130
|
+
test_gpx_file = test_paths["test_gpx_file"]
|
|
131
|
+
output_dir = test_paths["output_dir"]
|
|
132
|
+
logger = test_paths["logger"]
|
|
133
|
+
|
|
134
|
+
# single file
|
|
135
|
+
files_single = processor._get_input_files()
|
|
136
|
+
assert len(files_single) == 1
|
|
137
|
+
assert files_single[0] == test_gpx_file
|
|
138
|
+
|
|
139
|
+
# directory containing GPX files
|
|
140
|
+
temp_dir = output_dir / "temp_gpx_dir"
|
|
141
|
+
temp_dir.mkdir()
|
|
142
|
+
(temp_dir / "test1.gpx").touch()
|
|
143
|
+
(temp_dir / "test2.gpx").touch()
|
|
144
|
+
(temp_dir / "not_gpx.txt").touch()
|
|
145
|
+
|
|
146
|
+
proc_dir = BaseGPXProcessor(input_=str(temp_dir), logger=logger)
|
|
147
|
+
files_dir = proc_dir._get_input_files()
|
|
148
|
+
assert len(files_dir) == 2
|
|
149
|
+
assert any(f.name == "test1.gpx" for f in files_dir)
|
|
150
|
+
assert any(f.name == "test2.gpx" for f in files_dir)
|
|
151
|
+
|
|
152
|
+
# ZIP file case
|
|
153
|
+
temp_zip = output_dir / "test.zip"
|
|
154
|
+
with zipfile.ZipFile(temp_zip, "w") as zf:
|
|
155
|
+
zf.writestr("zip1.gpx", "<gpx></gpx>")
|
|
156
|
+
zf.writestr("zip2.gpx", "<gpx></gpx>")
|
|
157
|
+
zf.writestr("not_gpx.txt", "bad")
|
|
158
|
+
|
|
159
|
+
proc_zip = BaseGPXProcessor(input_=str(temp_zip), logger=logger)
|
|
160
|
+
files_zip = proc_zip._get_input_files()
|
|
161
|
+
assert len(files_zip) == 2
|
|
162
|
+
assert any(f.name == "zip1.gpx" for f in files_zip)
|
|
163
|
+
assert any(f.name == "zip2.gpx" for f in files_zip)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_load_gpx_file(test_paths):
|
|
167
|
+
processor = test_paths["processor"]
|
|
168
|
+
output_dir = test_paths["output_dir"]
|
|
169
|
+
test_gpx_file = test_paths["test_gpx_file"]
|
|
170
|
+
|
|
171
|
+
gpx_obj = processor._load_gpx_file(test_gpx_file)
|
|
172
|
+
assert gpx_obj is not None
|
|
173
|
+
assert isinstance(gpx_obj, gpxpy.gpx.GPX)
|
|
174
|
+
assert len(gpx_obj.tracks) > 0
|
|
175
|
+
|
|
176
|
+
gpx_none = processor._load_gpx_file(Path("nonexistent.gpx"))
|
|
177
|
+
assert gpx_none is None
|
|
178
|
+
|
|
179
|
+
malformed = output_dir / "malformed.gpx"
|
|
180
|
+
malformed.write_text("<gpx><track><segment><point></point></segment></track>")
|
|
181
|
+
assert processor._load_gpx_file(malformed) is None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_save_gpx_file(test_paths):
|
|
185
|
+
processor = test_paths["processor"]
|
|
186
|
+
output_dir = test_paths["output_dir"]
|
|
187
|
+
|
|
188
|
+
gpx_obj = gpxpy.gpx.GPX()
|
|
189
|
+
gpx_obj.name = "Test Save"
|
|
190
|
+
|
|
191
|
+
out_file = output_dir / "saved_test.gpx"
|
|
192
|
+
processor._save_gpx_file(gpx_obj, out_file)
|
|
193
|
+
|
|
194
|
+
assert out_file.exists()
|
|
195
|
+
|
|
196
|
+
reloaded = gpxpy.parse(open(out_file, "r", encoding="utf-8"))
|
|
197
|
+
assert reloaded.name == "Test Save"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_compress_files(test_paths):
|
|
201
|
+
test_gpx_file = test_paths["test_gpx_file"]
|
|
202
|
+
output_dir = test_paths["output_dir"]
|
|
203
|
+
processor = test_paths["processor"]
|
|
204
|
+
|
|
205
|
+
shutil.rmtree(output_dir)
|
|
206
|
+
output_dir.mkdir()
|
|
207
|
+
|
|
208
|
+
compressed_path = output_dir / f"compressed_{test_gpx_file.name}"
|
|
209
|
+
|
|
210
|
+
processor.compress_files()
|
|
211
|
+
assert compressed_path.exists()
|
|
212
|
+
|
|
213
|
+
compressed_gpx = gpxpy.parse(open(compressed_path, "r", encoding="utf-8"))
|
|
214
|
+
original_gpx = gpxpy.parse(open(test_gpx_file, "r", encoding="utf-8"))
|
|
215
|
+
|
|
216
|
+
orig_pts = sum(len(s.points) for t in original_gpx.tracks for s in t.segments)
|
|
217
|
+
cmp_pts = sum(len(s.points) for t in compressed_gpx.tracks for s in t.segments)
|
|
218
|
+
|
|
219
|
+
assert cmp_pts < orig_pts
|
|
220
|
+
assert cmp_pts < orig_pts * 0.5
|
|
221
|
+
assert compressed_gpx.time is None
|
|
222
|
+
assert not bool(compressed_gpx.extensions)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_merge_files(test_paths):
|
|
226
|
+
output_dir = test_paths["output_dir"]
|
|
227
|
+
logger = test_paths["logger"]
|
|
228
|
+
test_gpx_file = test_paths["test_gpx_file"]
|
|
229
|
+
|
|
230
|
+
# create dummy gpx
|
|
231
|
+
dummy_path = output_dir / "dummy.gpx"
|
|
232
|
+
dummy_gpx = gpxpy.gpx.GPX()
|
|
233
|
+
track = gpxpy.gpx.GPXTrack()
|
|
234
|
+
segment = gpxpy.gpx.GPXTrackSegment()
|
|
235
|
+
segment.points.append(GPXTrackPoint(51.0, 14.0, 100))
|
|
236
|
+
segment.points.append(GPXTrackPoint(51.0001, 14.0001, 101))
|
|
237
|
+
track.segments.append(segment)
|
|
238
|
+
dummy_gpx.tracks.append(track)
|
|
239
|
+
dummy_path.write_text(dummy_gpx.to_xml())
|
|
240
|
+
|
|
241
|
+
merge_dir = output_dir / "merge_input"
|
|
242
|
+
merge_dir.mkdir()
|
|
243
|
+
shutil.copy(test_gpx_file, merge_dir)
|
|
244
|
+
shutil.copy(dummy_path, merge_dir)
|
|
245
|
+
|
|
246
|
+
processor = BaseGPXProcessor(
|
|
247
|
+
input_=str(merge_dir),
|
|
248
|
+
output=str(output_dir),
|
|
249
|
+
logger=logger,
|
|
250
|
+
)
|
|
251
|
+
processor.merge_files()
|
|
252
|
+
|
|
253
|
+
merged_path = output_dir / "merged_tracks.gpx"
|
|
254
|
+
assert merged_path.exists()
|
|
255
|
+
|
|
256
|
+
merged = gpxpy.parse(open(merged_path, "r", encoding="utf-8"))
|
|
257
|
+
|
|
258
|
+
assert len(merged.tracks) == 2
|
|
259
|
+
|
|
260
|
+
names = [t.name for t in merged.tracks]
|
|
261
|
+
assert any("kuhkopfsteig" in (n or "").lower() for n in names)
|
|
262
|
+
assert any("dummy" in (n or "").lower() for n in names)
|
|
263
|
+
|
|
264
|
+
merged_cnt = sum(len(s.points) for t in merged.tracks for s in t.segments)
|
|
265
|
+
orig_kuh = gpxpy.parse(open(test_gpx_file, "r", encoding="utf-8"))
|
|
266
|
+
orig_kuh_cnt = sum(len(s.points) for t in orig_kuh.tracks for s in t.segments)
|
|
267
|
+
orig_dummy_cnt = sum(len(s.points) for t in dummy_gpx.tracks for s in t.segments)
|
|
268
|
+
|
|
269
|
+
assert merged_cnt < orig_kuh_cnt + orig_dummy_cnt
|
|
270
|
+
assert merged_cnt > 0
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_extract_pois(test_paths):
|
|
274
|
+
processor = test_paths["processor"]
|
|
275
|
+
output_dir = test_paths["output_dir"]
|
|
276
|
+
|
|
277
|
+
processor.extract_pois()
|
|
278
|
+
|
|
279
|
+
poi_file = output_dir / "extracted_pois.gpx"
|
|
280
|
+
assert poi_file.exists()
|
|
281
|
+
|
|
282
|
+
poi_gpx = gpxpy.parse(open(poi_file, "r", encoding="utf-8"))
|
|
283
|
+
|
|
284
|
+
assert len(poi_gpx.waypoints) == 1
|
|
285
|
+
|
|
286
|
+
poi = poi_gpx.waypoints[0]
|
|
287
|
+
assert poi.name.startswith("POI_")
|
|
288
|
+
assert "Start of" in poi.description
|
|
289
|
+
assert poi.type == "Track Start"
|
|
290
|
+
assert isinstance(poi.latitude, float)
|
|
291
|
+
assert isinstance(poi.longitude, float)
|
|
292
|
+
assert isinstance(poi.elevation, float)
|