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.
Files changed (86) hide show
  1. python_template_project-0.2.4/.github/workflows/build-linux.yml +48 -0
  2. {python_template_project-0.2.3 → python_template_project-0.2.4}/HISTORY.md +15 -0
  3. {python_template_project-0.2.3 → python_template_project-0.2.4}/Makefile +13 -6
  4. {python_template_project-0.2.3/src/python_template_project.egg-info → python_template_project-0.2.4}/PKG-INFO +3 -2
  5. {python_template_project-0.2.3 → python_template_project-0.2.4}/README.md +3 -17
  6. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/index.md +1 -1
  7. {python_template_project-0.2.3 → python_template_project-0.2.4}/mkdocs.yml +1 -1
  8. {python_template_project-0.2.3 → python_template_project-0.2.4}/pyproject.toml +1 -0
  9. python_template_project-0.2.4/pytest.ini +3 -0
  10. python_template_project-0.2.4/scripts/show_filelist.py +51 -0
  11. {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/show_tree.py +2 -3
  12. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/_version.py +3 -3
  13. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/cli.py +2 -2
  14. {python_template_project-0.2.3 → python_template_project-0.2.4/src/python_template_project.egg-info}/PKG-INFO +3 -2
  15. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/SOURCES.txt +4 -3
  16. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/requires.txt +1 -0
  17. python_template_project-0.2.4/tests/test_gpx.py +292 -0
  18. {python_template_project-0.2.3 → python_template_project-0.2.4}/uv.lock +54 -0
  19. python_template_project-0.2.3/scripts/show_filelist.ps1 +0 -14
  20. python_template_project-0.2.3/scripts/show_tree.ps1 +0 -58
  21. python_template_project-0.2.3/tests/test_gpx.py +0 -361
  22. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/FUNDING.yml +0 -0
  23. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  25. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  26. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/actions/setup-environment/action.yml +0 -0
  27. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/dependabot.yml +0 -0
  28. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/init.sh +0 -0
  29. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/release_message.sh +0 -0
  30. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/rename_project.sh +0 -0
  31. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/template.yml +0 -0
  32. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/update_funding.py +0 -0
  33. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/build-macos.yml +0 -0
  34. /python_template_project-0.2.3/.github/workflows/build.yml → /python_template_project-0.2.4/.github/workflows/build-windows.yml +0 -0
  35. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/main.yml +0 -0
  36. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/release.yml +0 -0
  37. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/rename_project.yml +0 -0
  38. {python_template_project-0.2.3 → python_template_project-0.2.4}/.github/workflows/update_readme.yml +0 -0
  39. {python_template_project-0.2.3 → python_template_project-0.2.4}/.gitignore +0 -0
  40. {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/config_generate.xml +0 -0
  41. {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/module_cli.xml +0 -0
  42. {python_template_project-0.2.3 → python_template_project-0.2.4}/.idea/runConfigurations/module_gui.xml +0 -0
  43. {python_template_project-0.2.3 → python_template_project-0.2.4}/.pre-commit-config-linux.yaml +0 -0
  44. {python_template_project-0.2.3 → python_template_project-0.2.4}/.pre-commit-config.yaml +0 -0
  45. {python_template_project-0.2.3 → python_template_project-0.2.4}/.readthedocs.yaml +0 -0
  46. {python_template_project-0.2.3 → python_template_project-0.2.4}/LICENSE +0 -0
  47. {python_template_project-0.2.3 → python_template_project-0.2.4}/config.yaml +0 -0
  48. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/.nav.yml +0 -0
  49. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/_static/img/favicon.png +0 -0
  50. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/_static/img/logo.png +0 -0
  51. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/css/custom.css +0 -0
  52. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/contributing.md +0 -0
  53. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/make_windows.md +0 -0
  54. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/naming_convention.md +0 -0
  55. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/develop/pypi_release.md +0 -0
  56. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/funding/funding.md +0 -0
  57. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/getting-started/install.md +0 -0
  58. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/getting-started/virtual-environment.md +0 -0
  59. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/usage/cli.md +0 -0
  60. {python_template_project-0.2.3 → python_template_project-0.2.4}/docs/usage/config.md +0 -0
  61. {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/kuhkopfsteig.gpx +0 -0
  62. {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/rother_lilienstein.gpx +0 -0
  63. {python_template_project-0.2.3 → python_template_project-0.2.4}/examples/teneriffa.gpx +0 -0
  64. {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/generate_config_docs.py +0 -0
  65. {python_template_project-0.2.3 → python_template_project-0.2.4}/scripts/update_readme.py +0 -0
  66. {python_template_project-0.2.3 → python_template_project-0.2.4}/setup.cfg +0 -0
  67. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/__init__.py +0 -0
  68. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/firewall_handler.py +0 -0
  69. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/main.py +0 -0
  70. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/__init__.py +0 -0
  71. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/__main__.py +0 -0
  72. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/__init__.py +0 -0
  73. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/cli/__main__.py +0 -0
  74. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/config/__init__.py +0 -0
  75. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/config/config.py +0 -0
  76. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/__init__.py +0 -0
  77. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/base.py +0 -0
  78. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/core/logging.py +0 -0
  79. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/__init__.py +0 -0
  80. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/__main__.py +0 -0
  81. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project/gui/gui.py +0 -0
  82. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/dependency_links.txt +0 -0
  83. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/entry_points.txt +0 -0
  84. {python_template_project-0.2.3 → python_template_project-0.2.4}/src/python_template_project.egg-info/top_level.txt +0 -0
  85. {python_template_project-0.2.3 → python_template_project-0.2.4}/template.yml.url +0 -0
  86. {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
- @powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/show_tree.ps1
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
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 with auto-generated CLI, GUI and parameterized configuration.
36
+ A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
36
37
 
37
38
  [![Github CI Status](https://github.com/pamagister/python-template-project/actions/workflows/main.yml/badge.svg)](https://github.com/pamagister/python-template-project/actions)
38
39
  [![GitHub release](https://img.shields.io/github/v/release/pamagister/python-template-project)](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 with auto-generated CLI, GUI and parameterized configuration.
5
+ A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
6
6
 
7
7
  [![Github CI Status](https://github.com/pamagister/python-template-project/actions/workflows/main.yml/badge.svg)](https://github.com/pamagister/python-template-project/actions)
8
8
  [![GitHub release](https://img.shields.io/github/v/release/pamagister/python-template-project)](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 with auto-generated CLI, GUI and parameterized configuration.
3
+ A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
4
4
 
5
5
  [![Github CI Status](https://github.com/pamagister/python-template-project/actions/workflows/main.yml/badge.svg)](https://github.com/pamagister/python-template-project/actions)
6
6
  [![GitHub release](https://img.shields.io/github/v/release/pamagister/python-template-project)](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 with auto-generated CLI, GUI and parameterized configuration.
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
 
@@ -11,6 +11,7 @@ dependencies = [
11
11
  "config-cli-gui>=0.2.2",
12
12
  "fastkml>=1.1.0",
13
13
  "gpxpy>=1.6.2",
14
+ "lxml>=6.0.2",
14
15
  "shapely>=2.1.1",
15
16
  "srtm-py>=0.3.7",
16
17
  ]
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ pythonpath = src
3
+ testpaths = tests
@@ -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=True,
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.3'
32
- __version_tuple__ = version_tuple = (0, 2, 3)
31
+ __version__ = version = '0.2.4'
32
+ __version_tuple__ = version_tuple = (0, 2, 4)
33
33
 
34
- __commit_id__ = commit_id = 'g05d198f46'
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("python_template_project.cli")
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("python_template_project.cli")
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
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 with auto-generated CLI, GUI and parameterized configuration.
36
+ A feature-rich Python project template with auto-generated CLI, GUI and parameterized configuration.
36
37
 
37
38
  [![Github CI Status](https://github.com/pamagister/python-template-project/actions/workflows/main.yml/badge.svg)](https://github.com/pamagister/python-template-project/actions)
38
39
  [![GitHub release](https://img.shields.io/github/v/release/pamagister/python-template-project)](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.ps1
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
@@ -1,6 +1,7 @@
1
1
  config-cli-gui>=0.2.2
2
2
  fastkml>=1.1.0
3
3
  gpxpy>=1.6.2
4
+ lxml>=6.0.2
4
5
  shapely>=2.1.1
5
6
  srtm-py>=0.3.7
6
7
 
@@ -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)