python-template-project 0.1.20__py3-none-any.whl → 0.1.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_template_project/_version.py +2 -2
- python_template_project/core/base.py +182 -71
- python_template_project/gui/gui.py +226 -47
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/METADATA +3 -1
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/RECORD +9 -9
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/WHEEL +0 -0
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/entry_points.txt +0 -0
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,17 @@ except ImportError:
|
|
|
16
16
|
SRTM_AVAILABLE = False
|
|
17
17
|
srtm = None
|
|
18
18
|
|
|
19
|
+
# Optional fastkml import for KML reading
|
|
20
|
+
try:
|
|
21
|
+
from fastkml import kml, styles
|
|
22
|
+
from fastkml.features import Document, Folder, Placemark
|
|
23
|
+
from shapely.geometry import LineString, Point
|
|
24
|
+
|
|
25
|
+
KML_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
KML_AVAILABLE = False
|
|
28
|
+
kml = styles = Folder = Placemark = Document = Point = LineString = None
|
|
29
|
+
|
|
19
30
|
NAME = "python_template_project"
|
|
20
31
|
|
|
21
32
|
|
|
@@ -114,7 +125,9 @@ class BaseGPXProcessor:
|
|
|
114
125
|
if self.output:
|
|
115
126
|
output_path = Path(self.output)
|
|
116
127
|
else:
|
|
117
|
-
timestamp = datetime.now().strftime(
|
|
128
|
+
timestamp = datetime.now().strftime(
|
|
129
|
+
f"{self.date_format}_%H%M%S"
|
|
130
|
+
) # Added seconds for uniqueness
|
|
118
131
|
output_path = Path.cwd() / f"gpx_processed_{timestamp}"
|
|
119
132
|
|
|
120
133
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -228,54 +241,56 @@ class BaseGPXProcessor:
|
|
|
228
241
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
229
242
|
return track_points # Return original points if optimization fails
|
|
230
243
|
|
|
231
|
-
def
|
|
232
|
-
"""Get all GPX files from input (file, folder, or zip)."""
|
|
233
|
-
|
|
234
|
-
for
|
|
244
|
+
def _get_input_files(self) -> list[Path]:
|
|
245
|
+
"""Get all GPX/KML files from input (file, folder, or zip)."""
|
|
246
|
+
input_files = []
|
|
247
|
+
for input_path_str in self.input:
|
|
235
248
|
try:
|
|
236
|
-
|
|
237
|
-
input_path = Path(input_path)
|
|
249
|
+
input_path = Path(input_path_str)
|
|
238
250
|
self.logger.debug(f"Input path: {input_path.absolute()}")
|
|
239
251
|
|
|
240
252
|
if input_path.is_file():
|
|
241
253
|
if input_path.suffix.lower() == ".gpx":
|
|
242
|
-
|
|
254
|
+
input_files.append(input_path)
|
|
255
|
+
elif input_path.suffix.lower() == ".kml":
|
|
256
|
+
input_files.append(input_path)
|
|
243
257
|
elif input_path.suffix.lower() == ".zip":
|
|
244
|
-
|
|
258
|
+
input_files.extend(self._extract_gpx_kml_from_zip(input_path))
|
|
245
259
|
elif input_path.is_dir():
|
|
246
|
-
# Get all GPX files in directory
|
|
247
|
-
|
|
260
|
+
# Get all GPX/KML files in directory
|
|
261
|
+
input_files.extend(input_path.glob("*.gpx"))
|
|
262
|
+
input_files.extend(input_path.glob("*.kml"))
|
|
248
263
|
|
|
249
|
-
# Get GPX files from ZIP files in directory
|
|
264
|
+
# Get GPX/KML files from ZIP files in directory
|
|
250
265
|
for zip_file in input_path.glob("*.zip"):
|
|
251
|
-
|
|
266
|
+
input_files.extend(self._extract_gpx_kml_from_zip(zip_file))
|
|
252
267
|
except Exception as e:
|
|
253
268
|
self.logger.error(f"Error processing input path {input_path}: {e}")
|
|
254
269
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
255
270
|
continue
|
|
256
271
|
|
|
257
|
-
return
|
|
272
|
+
return input_files
|
|
258
273
|
|
|
259
|
-
def
|
|
260
|
-
"""Extract GPX files from ZIP archive to temporary location."""
|
|
261
|
-
|
|
262
|
-
temp_dir = Path.cwd() / "
|
|
274
|
+
def _extract_gpx_kml_from_zip(self, zip_path: Path) -> list[Path]:
|
|
275
|
+
"""Extract GPX/KML files from ZIP archive to temporary location."""
|
|
276
|
+
extracted_files = []
|
|
277
|
+
temp_dir = Path.cwd() / "temp_extracted_files"
|
|
263
278
|
|
|
264
279
|
try:
|
|
265
280
|
temp_dir.mkdir(exist_ok=True)
|
|
266
281
|
|
|
267
282
|
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
268
283
|
for file_info in zip_ref.infolist():
|
|
269
|
-
if file_info.filename.lower().endswith(".gpx"):
|
|
284
|
+
if file_info.filename.lower().endswith((".gpx", ".kml")):
|
|
270
285
|
extracted_path = temp_dir / Path(file_info.filename).name
|
|
271
286
|
with open(extracted_path, "wb") as f:
|
|
272
287
|
f.write(zip_ref.read(file_info.filename))
|
|
273
|
-
|
|
288
|
+
extracted_files.append(extracted_path)
|
|
274
289
|
except Exception as e:
|
|
275
290
|
self.logger.error(f"Error extracting ZIP file {zip_path}: {e}")
|
|
276
291
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
277
292
|
|
|
278
|
-
return
|
|
293
|
+
return extracted_files
|
|
279
294
|
|
|
280
295
|
def _load_gpx_file(self, gpx_path: Path) -> GPX | None:
|
|
281
296
|
"""Load and parse GPX file."""
|
|
@@ -290,35 +305,104 @@ class BaseGPXProcessor:
|
|
|
290
305
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
291
306
|
return None
|
|
292
307
|
|
|
293
|
-
def
|
|
294
|
-
"""
|
|
308
|
+
def _load_kml_file(self, kml_path: Path) -> GPX | None:
|
|
309
|
+
"""Load and parse KML file, converting it to a GPX object."""
|
|
310
|
+
if not KML_AVAILABLE:
|
|
311
|
+
self.logger.error("fastkml library is not available to process KML files.")
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
with open(kml_path, "r", encoding="utf-8") as f:
|
|
316
|
+
doc = f.read()
|
|
317
|
+
|
|
318
|
+
k = kml.KML()
|
|
319
|
+
k.from_string(doc)
|
|
320
|
+
|
|
321
|
+
gpx = gpxpy.gpx.GPX()
|
|
322
|
+
|
|
323
|
+
# Iterate through KML features and convert to GPX tracks/waypoints
|
|
324
|
+
for feature in k.features():
|
|
325
|
+
if isinstance(feature, Document) or isinstance(feature, Folder):
|
|
326
|
+
for sub_feature in feature.features():
|
|
327
|
+
self._process_kml_feature(sub_feature, gpx)
|
|
328
|
+
else:
|
|
329
|
+
self._process_kml_feature(feature, gpx)
|
|
330
|
+
|
|
331
|
+
self.logger.info(f"Successfully loaded and converted KML file {kml_path} to GPX.")
|
|
332
|
+
return gpx
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self.logger.error(f"Error loading or converting KML file {kml_path}: {e}")
|
|
336
|
+
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
def _process_kml_feature(self, feature, gpx: GPX):
|
|
340
|
+
"""Recursively process KML features to extract points and add to GPX."""
|
|
341
|
+
if isinstance(feature, Placemark):
|
|
342
|
+
if feature.geometry is not None:
|
|
343
|
+
if isinstance(feature.geometry, Point):
|
|
344
|
+
waypoint = gpxpy.gpx.GPXWaypoint(
|
|
345
|
+
latitude=feature.geometry.y,
|
|
346
|
+
longitude=feature.geometry.x,
|
|
347
|
+
elevation=feature.geometry.z if feature.geometry.has_z else None,
|
|
348
|
+
name=feature.name,
|
|
349
|
+
description=feature.description,
|
|
350
|
+
)
|
|
351
|
+
gpx.waypoints.append(waypoint)
|
|
352
|
+
elif isinstance(feature.geometry, LineString):
|
|
353
|
+
gpx_track = gpxpy.gpx.GPXTrack()
|
|
354
|
+
gpx_track.name = feature.name
|
|
355
|
+
gpx_segment = gpxpy.gpx.GPXTrackSegment()
|
|
356
|
+
for coord in feature.geometry.coords:
|
|
357
|
+
point = gpxpy.gpx.GPXTrackPoint(
|
|
358
|
+
latitude=coord[1],
|
|
359
|
+
longitude=coord[0],
|
|
360
|
+
elevation=coord[2] if len(coord) > 2 else None,
|
|
361
|
+
)
|
|
362
|
+
gpx_segment.points.append(point)
|
|
363
|
+
if gpx_segment.points:
|
|
364
|
+
gpx_track.segments.append(gpx_segment)
|
|
365
|
+
if gpx_track.segments:
|
|
366
|
+
gpx.tracks.append(gpx_track)
|
|
367
|
+
elif isinstance(feature, Document) or isinstance(feature, Folder):
|
|
368
|
+
for sub_feature in feature.features():
|
|
369
|
+
self._process_kml_feature(sub_feature, gpx)
|
|
370
|
+
|
|
371
|
+
def _save_gpx_file(
|
|
372
|
+
self, gpx: GPX, output_path: Path, original_file: Path | None = None
|
|
373
|
+
) -> Path:
|
|
374
|
+
"""Save GPX object to file and return the path."""
|
|
295
375
|
try:
|
|
296
376
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
297
377
|
f.write(gpx.to_xml())
|
|
298
378
|
if original_file and original_file.exists():
|
|
299
379
|
self.logger.info(
|
|
300
|
-
f"Original
|
|
301
|
-
f"{Path(original_file).stat().st_size / 1024:.2f} KB"
|
|
380
|
+
f"Original file size: {Path(original_file).stat().st_size / 1024:.2f} KB"
|
|
302
381
|
)
|
|
303
|
-
self.logger.info(
|
|
304
|
-
|
|
305
|
-
)
|
|
306
|
-
|
|
382
|
+
self.logger.info(f"Processed file size: {output_path.stat().st_size / 1024:.2f} KB")
|
|
383
|
+
return output_path
|
|
307
384
|
except Exception as e:
|
|
308
385
|
self.logger.error(f"Error saving GPX file {output_path}: {e}")
|
|
309
386
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
387
|
+
return None
|
|
310
388
|
|
|
311
|
-
def compress_files(self):
|
|
312
|
-
"""Shrink the size of all given gpx files in self.input."""
|
|
389
|
+
def compress_files(self) -> list[str]:
|
|
390
|
+
"""Shrink the size of all given gpx/kml files in self.input."""
|
|
391
|
+
generated_files = []
|
|
313
392
|
try:
|
|
314
|
-
|
|
393
|
+
input_files = self._get_input_files()
|
|
315
394
|
output_folder = self._get_output_folder()
|
|
316
395
|
|
|
317
|
-
self.logger.info(f"Processing {len(
|
|
396
|
+
self.logger.info(f"Processing {len(input_files)} GPX/KML files for compression...")
|
|
318
397
|
|
|
319
|
-
for
|
|
398
|
+
for input_file in input_files:
|
|
320
399
|
try:
|
|
321
|
-
gpx =
|
|
400
|
+
gpx = None
|
|
401
|
+
if input_file.suffix.lower() == ".gpx":
|
|
402
|
+
gpx = self._load_gpx_file(input_file)
|
|
403
|
+
elif input_file.suffix.lower() == ".kml":
|
|
404
|
+
gpx = self._load_kml_file(input_file)
|
|
405
|
+
|
|
322
406
|
if gpx is None:
|
|
323
407
|
continue
|
|
324
408
|
|
|
@@ -340,14 +424,17 @@ class BaseGPXProcessor:
|
|
|
340
424
|
gpx.extensions = None
|
|
341
425
|
|
|
342
426
|
# Save compressed file
|
|
343
|
-
output_path = output_folder / f"compressed_{
|
|
344
|
-
self._save_gpx_file(gpx, output_path,
|
|
345
|
-
|
|
427
|
+
output_path = output_folder / f"compressed_{input_file.stem}.gpx"
|
|
428
|
+
saved_path = self._save_gpx_file(gpx, output_path, input_file)
|
|
429
|
+
if saved_path:
|
|
430
|
+
generated_files.append(str(saved_path))
|
|
431
|
+
self.logger.info(f"Compressed: {input_file.name} -> {output_path}")
|
|
346
432
|
|
|
347
433
|
except Exception as e:
|
|
348
|
-
self.logger.error(f"Error processing file {
|
|
434
|
+
self.logger.error(f"Error processing file {input_file}: {e}")
|
|
349
435
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
350
436
|
continue
|
|
437
|
+
return generated_files
|
|
351
438
|
|
|
352
439
|
except Exception as e:
|
|
353
440
|
self.logger.error(f"Error in compress_files: {e}")
|
|
@@ -383,41 +470,49 @@ class BaseGPXProcessor:
|
|
|
383
470
|
self.logger.warning(f"Error optimizing waypoint: {e}")
|
|
384
471
|
return waypoint
|
|
385
472
|
|
|
386
|
-
def merge_files(self):
|
|
473
|
+
def merge_files(self) -> list[str]:
|
|
387
474
|
"""Merge all files of self.input into one gpx file with reduced resolution."""
|
|
475
|
+
generated_files = []
|
|
388
476
|
try:
|
|
389
|
-
|
|
477
|
+
input_files = self._get_input_files()
|
|
390
478
|
output_folder = self._get_output_folder()
|
|
391
479
|
|
|
392
|
-
if not
|
|
393
|
-
self.logger.error("No GPX files found to merge.")
|
|
394
|
-
return
|
|
480
|
+
if not input_files:
|
|
481
|
+
self.logger.error("No GPX/KML files found to merge.")
|
|
482
|
+
return []
|
|
395
483
|
|
|
396
|
-
self.logger.info(f"Merging {len(
|
|
484
|
+
self.logger.info(f"Merging {len(input_files)} GPX/KML files...")
|
|
397
485
|
|
|
398
486
|
# Create new GPX object
|
|
399
487
|
merged_gpx = gpxpy.gpx.GPX()
|
|
400
488
|
merged_gpx.name = "Merged GPX Tracks"
|
|
401
|
-
merged_gpx.description = f"Merged from {len(
|
|
489
|
+
merged_gpx.description = f"Merged from {len(input_files)} GPX/KML files."
|
|
402
490
|
|
|
403
491
|
track_counter = 1
|
|
404
492
|
|
|
405
|
-
for
|
|
493
|
+
for input_file in input_files:
|
|
406
494
|
try:
|
|
407
|
-
gpx =
|
|
495
|
+
gpx = None
|
|
496
|
+
if input_file.suffix.lower() == ".gpx":
|
|
497
|
+
gpx = self._load_gpx_file(input_file)
|
|
498
|
+
elif input_file.suffix.lower() == ".kml":
|
|
499
|
+
gpx = self._load_kml_file(input_file)
|
|
500
|
+
|
|
408
501
|
if gpx is None:
|
|
409
502
|
continue
|
|
410
503
|
|
|
411
504
|
# Add all waypoints from this file
|
|
412
505
|
for waypoint in gpx.waypoints:
|
|
413
506
|
waypoint = self._optimize_waypoint(waypoint)
|
|
414
|
-
waypoint.name = f"{waypoint.name}_{track_counter}"
|
|
507
|
+
waypoint.name = f"{waypoint.name or 'Waypoint'}_{track_counter}"
|
|
415
508
|
merged_gpx.waypoints.append(waypoint)
|
|
416
509
|
|
|
417
510
|
# Add all tracks from this file
|
|
418
511
|
for track in gpx.tracks:
|
|
419
512
|
new_track = gpxpy.gpx.GPXTrack()
|
|
420
|
-
new_track.name =
|
|
513
|
+
new_track.name = (
|
|
514
|
+
f"{track.name or 'Track'}_{input_file.stem}_{track_counter}"
|
|
515
|
+
)
|
|
421
516
|
|
|
422
517
|
for segment in track.segments:
|
|
423
518
|
optimized_points = self._optimize_track_points(segment.points)
|
|
@@ -433,7 +528,9 @@ class BaseGPXProcessor:
|
|
|
433
528
|
# Add all routes from this file
|
|
434
529
|
for route in gpx.routes:
|
|
435
530
|
new_route = gpxpy.gpx.GPXRoute()
|
|
436
|
-
new_route.name =
|
|
531
|
+
new_route.name = (
|
|
532
|
+
f"{route.name or 'Route'}_{input_file.stem}_{track_counter}"
|
|
533
|
+
)
|
|
437
534
|
new_route.points = self._optimize_track_points(route.points)
|
|
438
535
|
|
|
439
536
|
if new_route.points:
|
|
@@ -441,43 +538,52 @@ class BaseGPXProcessor:
|
|
|
441
538
|
track_counter += 1
|
|
442
539
|
|
|
443
540
|
except Exception as e:
|
|
444
|
-
self.logger.error(f"Error processing file {
|
|
541
|
+
self.logger.error(f"Error processing file {input_file} during merge: {e}")
|
|
445
542
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
446
543
|
continue
|
|
447
544
|
|
|
448
545
|
# Save merged file
|
|
449
546
|
output_path = output_folder / "merged_tracks.gpx"
|
|
450
|
-
self._save_gpx_file(merged_gpx, output_path)
|
|
451
|
-
|
|
547
|
+
saved_path = self._save_gpx_file(merged_gpx, output_path)
|
|
548
|
+
if saved_path:
|
|
549
|
+
generated_files.append(str(saved_path))
|
|
550
|
+
self.logger.info(f"Merged file saved: {output_path}")
|
|
551
|
+
return generated_files
|
|
452
552
|
|
|
453
553
|
except Exception as e:
|
|
454
554
|
self.logger.error(f"Error in merge_files: {e}")
|
|
455
555
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
456
556
|
raise
|
|
457
557
|
|
|
458
|
-
def extract_pois(self):
|
|
558
|
+
def extract_pois(self) -> list[str]:
|
|
459
559
|
"""Merge every starting point of each track in all files
|
|
460
560
|
into one gpx file with many pois."""
|
|
561
|
+
generated_files = []
|
|
461
562
|
try:
|
|
462
|
-
|
|
563
|
+
input_files = self._get_input_files()
|
|
463
564
|
output_folder = self._get_output_folder()
|
|
464
565
|
|
|
465
|
-
if not
|
|
466
|
-
self.logger.error("No GPX files found to extract POIs from.")
|
|
467
|
-
return
|
|
566
|
+
if not input_files:
|
|
567
|
+
self.logger.error("No GPX/KML files found to extract POIs from.")
|
|
568
|
+
return []
|
|
468
569
|
|
|
469
|
-
self.logger.info(f"Extracting POIs from {len(
|
|
570
|
+
self.logger.info(f"Extracting POIs from {len(input_files)} GPX/KML files...")
|
|
470
571
|
|
|
471
572
|
# Create new GPX object for waypoints
|
|
472
573
|
poi_gpx = gpxpy.gpx.GPX()
|
|
473
574
|
poi_gpx.name = "Extracted Track Starting Points"
|
|
474
|
-
poi_gpx.description = f"Starting points extracted from {len(
|
|
575
|
+
poi_gpx.description = f"Starting points extracted from {len(input_files)} GPX/KML files"
|
|
475
576
|
|
|
476
577
|
poi_counter = 1
|
|
477
578
|
|
|
478
|
-
for
|
|
579
|
+
for input_file in input_files:
|
|
479
580
|
try:
|
|
480
|
-
gpx =
|
|
581
|
+
gpx = None
|
|
582
|
+
if input_file.suffix.lower() == ".gpx":
|
|
583
|
+
gpx = self._load_gpx_file(input_file)
|
|
584
|
+
elif input_file.suffix.lower() == ".kml":
|
|
585
|
+
gpx = self._load_kml_file(input_file)
|
|
586
|
+
|
|
481
587
|
if gpx is None:
|
|
482
588
|
continue
|
|
483
589
|
|
|
@@ -496,7 +602,9 @@ class BaseGPXProcessor:
|
|
|
496
602
|
|
|
497
603
|
track_name = track.name or f"Track_{track_idx + 1}"
|
|
498
604
|
waypoint.name = f"POI_{poi_counter:03d}"
|
|
499
|
-
waypoint.description =
|
|
605
|
+
waypoint.description = (
|
|
606
|
+
f"Start of {track_name} from {input_file.name}"
|
|
607
|
+
)
|
|
500
608
|
waypoint.type = "Track Start"
|
|
501
609
|
|
|
502
610
|
poi_gpx.waypoints.append(waypoint)
|
|
@@ -516,7 +624,7 @@ class BaseGPXProcessor:
|
|
|
516
624
|
|
|
517
625
|
route_name = route.name or f"Route_{route_idx + 1}"
|
|
518
626
|
waypoint.name = f"POI_{poi_counter:03d}"
|
|
519
|
-
waypoint.description = f"Start of {route_name} from {
|
|
627
|
+
waypoint.description = f"Start of {route_name} from {input_file.name}"
|
|
520
628
|
waypoint.type = "Route Start"
|
|
521
629
|
|
|
522
630
|
poi_gpx.waypoints.append(waypoint)
|
|
@@ -524,24 +632,27 @@ class BaseGPXProcessor:
|
|
|
524
632
|
|
|
525
633
|
except Exception as e:
|
|
526
634
|
self.logger.error(
|
|
527
|
-
f"Error processing file {
|
|
635
|
+
f"Error processing file {input_file} during POI extraction: {e}"
|
|
528
636
|
)
|
|
529
637
|
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
530
638
|
continue
|
|
531
639
|
|
|
532
640
|
# Save POI file
|
|
533
641
|
output_path = output_folder / "extracted_pois.gpx"
|
|
534
|
-
self._save_gpx_file(poi_gpx, output_path)
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
642
|
+
saved_path = self._save_gpx_file(poi_gpx, output_path)
|
|
643
|
+
if saved_path:
|
|
644
|
+
generated_files.append(str(saved_path))
|
|
645
|
+
self.logger.info(
|
|
646
|
+
f"POI file saved with {len(poi_gpx.waypoints)} waypoints: {output_path}"
|
|
647
|
+
)
|
|
538
648
|
|
|
539
649
|
# Clean up temporary files
|
|
540
|
-
temp_dir = Path.cwd() / "
|
|
650
|
+
temp_dir = Path.cwd() / "temp_extracted_files" # Changed folder name
|
|
541
651
|
if temp_dir.exists():
|
|
542
652
|
import shutil
|
|
543
653
|
|
|
544
654
|
shutil.rmtree(temp_dir)
|
|
655
|
+
return generated_files
|
|
545
656
|
|
|
546
657
|
except Exception as e:
|
|
547
658
|
self.logger.error(f"Error in extract_pois: {e}")
|
|
@@ -7,12 +7,14 @@ run gui: python -m python_template_project.gui
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
+
import subprocess
|
|
10
11
|
import sys
|
|
11
12
|
import threading
|
|
12
13
|
import tkinter as tk
|
|
13
14
|
import traceback
|
|
14
15
|
import webbrowser
|
|
15
16
|
from functools import partial
|
|
17
|
+
from pathlib import Path
|
|
16
18
|
from tkinter import filedialog, messagebox, ttk
|
|
17
19
|
|
|
18
20
|
from python_template_project.config.config import ConfigParameter, ConfigParameterManager
|
|
@@ -77,6 +79,7 @@ class GuiLogWriter:
|
|
|
77
79
|
def __init__(self, text_widget):
|
|
78
80
|
self.text_widget = text_widget
|
|
79
81
|
self.root = text_widget.winfo_toplevel()
|
|
82
|
+
self.hyperlink_tags = {} # To store clickable links
|
|
80
83
|
|
|
81
84
|
def write(self, text):
|
|
82
85
|
"""Write text to the widget in a thread-safe manner."""
|
|
@@ -86,13 +89,57 @@ class GuiLogWriter:
|
|
|
86
89
|
def _update_text(self, text):
|
|
87
90
|
"""Update the text widget (must be called from main thread)."""
|
|
88
91
|
try:
|
|
92
|
+
current_end = self.text_widget.index(tk.END)
|
|
89
93
|
self.text_widget.insert(tk.END, text)
|
|
94
|
+
|
|
95
|
+
# Check for a directory path (simplified regex for common path formats)
|
|
96
|
+
# This regex looks for paths that start with a drive letter (C:\), a forward slash (/)
|
|
97
|
+
# or a backslash (\) followed by word characters, and ends with a word character.
|
|
98
|
+
# This is a basic approach; more robust path detection might be needed for edge cases.
|
|
99
|
+
import re
|
|
100
|
+
|
|
101
|
+
path_match = re.search(
|
|
102
|
+
r"([A-Za-z]:[\\/][\S ]*|[\\][\\/][\S ]*|[\w/.-]+[/][\S ]*)\b", text
|
|
103
|
+
)
|
|
104
|
+
if path_match:
|
|
105
|
+
path = path_match.group(0).strip()
|
|
106
|
+
# Ensure the path exists and is a directory to make it clickable
|
|
107
|
+
if Path(path).is_dir():
|
|
108
|
+
start_index = self.text_widget.search(path, current_end, tk.END)
|
|
109
|
+
if start_index:
|
|
110
|
+
end_index = f"{start_index}+{len(path)}c"
|
|
111
|
+
tag_name = f"link_{len(self.hyperlink_tags)}"
|
|
112
|
+
self.text_widget.tag_config(tag_name, foreground="blue", underline=True)
|
|
113
|
+
self.text_widget.tag_bind(
|
|
114
|
+
tag_name, "<Button-1>", lambda e, p=path: self._open_path_in_explorer(p)
|
|
115
|
+
)
|
|
116
|
+
self.text_widget.tag_bind(
|
|
117
|
+
tag_name, "<Enter>", lambda e: self.text_widget.config(cursor="hand2")
|
|
118
|
+
)
|
|
119
|
+
self.text_widget.tag_bind(
|
|
120
|
+
tag_name, "<Leave>", lambda e: self.text_widget.config(cursor="")
|
|
121
|
+
)
|
|
122
|
+
self.text_widget.tag_add(tag_name, start_index, end_index)
|
|
123
|
+
self.hyperlink_tags[tag_name] = path
|
|
124
|
+
|
|
90
125
|
self.text_widget.see(tk.END)
|
|
91
126
|
self.text_widget.update_idletasks()
|
|
92
127
|
except tk.TclError:
|
|
93
128
|
# Widget might be destroyed
|
|
94
129
|
pass
|
|
95
130
|
|
|
131
|
+
def _open_path_in_explorer(self, path):
|
|
132
|
+
"""Opens the given path in the file explorer."""
|
|
133
|
+
try:
|
|
134
|
+
if sys.platform == "win32":
|
|
135
|
+
os.startfile(path)
|
|
136
|
+
elif sys.platform == "darwin":
|
|
137
|
+
subprocess.Popen(["open", path])
|
|
138
|
+
else:
|
|
139
|
+
subprocess.Popen(["xdg-open", path])
|
|
140
|
+
except Exception as e:
|
|
141
|
+
get_logger("gui.main").error(f"Failed to open path {path}: {e}")
|
|
142
|
+
|
|
96
143
|
def flush(self):
|
|
97
144
|
"""Flush method for compatibility."""
|
|
98
145
|
pass
|
|
@@ -320,7 +367,7 @@ class MainGui:
|
|
|
320
367
|
def __init__(self, root):
|
|
321
368
|
self.root = root
|
|
322
369
|
self.root.title("python-template-project")
|
|
323
|
-
self.root.geometry("
|
|
370
|
+
self.root.geometry("1200x600") # Increased width for new layout
|
|
324
371
|
|
|
325
372
|
# Initialize configuration
|
|
326
373
|
self.config_manager = ConfigParameterManager()
|
|
@@ -329,8 +376,9 @@ class MainGui:
|
|
|
329
376
|
self.logger_manager = initialize_logging(self.config_manager)
|
|
330
377
|
self.logger = get_logger("gui.main")
|
|
331
378
|
|
|
332
|
-
# File
|
|
333
|
-
self.
|
|
379
|
+
# File lists
|
|
380
|
+
self.input_files = []
|
|
381
|
+
self.output_files = []
|
|
334
382
|
|
|
335
383
|
self._build_widgets()
|
|
336
384
|
self._create_menu()
|
|
@@ -348,35 +396,55 @@ class MainGui:
|
|
|
348
396
|
"""Build the main GUI widgets."""
|
|
349
397
|
# Main container
|
|
350
398
|
main_frame = ttk.Frame(self.root)
|
|
351
|
-
main_frame.pack(fill=tk.BOTH, expand=True, padx=
|
|
399
|
+
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
|
|
352
400
|
|
|
353
|
-
# Top frame for file
|
|
401
|
+
# Top frame for file lists and buttons
|
|
354
402
|
top_frame = ttk.Frame(main_frame)
|
|
355
403
|
top_frame.pack(fill=tk.BOTH, expand=True)
|
|
356
404
|
|
|
357
|
-
# Left side - File list
|
|
358
|
-
|
|
359
|
-
|
|
405
|
+
# Left side - Input File list
|
|
406
|
+
input_file_frame = ttk.LabelFrame(top_frame, text="Input Files")
|
|
407
|
+
input_file_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
|
360
408
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
409
|
+
self.input_file_listbox = tk.Listbox(input_file_frame, selectmode=tk.EXTENDED)
|
|
410
|
+
input_file_scrollbar = ttk.Scrollbar(
|
|
411
|
+
input_file_frame, orient="vertical", command=self.input_file_listbox.yview
|
|
412
|
+
)
|
|
413
|
+
self.input_file_listbox.configure(yscrollcommand=input_file_scrollbar.set)
|
|
364
414
|
|
|
365
|
-
self.
|
|
366
|
-
|
|
367
|
-
|
|
415
|
+
self.input_file_listbox.pack(side="left", fill="both", expand=True, padx=5, pady=5)
|
|
416
|
+
input_file_scrollbar.pack(side="right", fill="y", pady=5)
|
|
417
|
+
self.input_file_listbox.bind(
|
|
418
|
+
"<Double-Button-1>", lambda event: self._open_selected_file(event, self.input_files)
|
|
368
419
|
)
|
|
369
|
-
self.file_listbox.configure(yscrollcommand=file_scrollbar.set)
|
|
370
420
|
|
|
371
|
-
|
|
372
|
-
|
|
421
|
+
# Middle - Output File list
|
|
422
|
+
output_file_frame = ttk.LabelFrame(top_frame, text="Generated Files")
|
|
423
|
+
output_file_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 5))
|
|
373
424
|
|
|
374
|
-
|
|
425
|
+
self.output_file_listbox = tk.Listbox(output_file_frame)
|
|
426
|
+
output_file_scrollbar = ttk.Scrollbar(
|
|
427
|
+
output_file_frame, orient="vertical", command=self.output_file_listbox.yview
|
|
428
|
+
)
|
|
429
|
+
self.output_file_listbox.configure(yscrollcommand=output_file_scrollbar.set)
|
|
430
|
+
|
|
431
|
+
self.output_file_listbox.pack(side="left", fill="both", expand=True, padx=5, pady=5)
|
|
432
|
+
output_file_scrollbar.pack(side="right", fill="y", pady=5)
|
|
433
|
+
self.output_file_listbox.bind(
|
|
434
|
+
"<Double-Button-1>", lambda event: self._open_selected_file(event, self.output_files)
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Right side - Buttons
|
|
375
438
|
button_frame = ttk.Frame(top_frame)
|
|
376
439
|
button_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(5, 0))
|
|
377
440
|
|
|
378
441
|
open_button = ttk.Button(button_frame, text="Open Files", command=self._open_files)
|
|
379
|
-
open_button.pack(pady=8)
|
|
442
|
+
open_button.pack(pady=8, fill=tk.X)
|
|
443
|
+
|
|
444
|
+
remove_selected_button = ttk.Button(
|
|
445
|
+
button_frame, text="Remove Selected", command=self._remove_selected_input_files
|
|
446
|
+
)
|
|
447
|
+
remove_selected_button.pack(pady=1, fill=tk.X)
|
|
380
448
|
|
|
381
449
|
# Create buttons dynamically
|
|
382
450
|
self.run_buttons = {}
|
|
@@ -384,13 +452,20 @@ class MainGui:
|
|
|
384
452
|
button = ttk.Button(
|
|
385
453
|
button_frame, text=label, command=partial(self._run_processing, mode=mode)
|
|
386
454
|
)
|
|
387
|
-
button.pack(pady=1)
|
|
455
|
+
button.pack(pady=1, fill=tk.X)
|
|
388
456
|
# Save buttons in dictionary for later access
|
|
389
457
|
self.run_buttons[mode] = button
|
|
390
458
|
|
|
391
459
|
# Clear files button
|
|
392
|
-
self.
|
|
393
|
-
|
|
460
|
+
self.clear_input_button = ttk.Button(
|
|
461
|
+
button_frame, text="Clear Input Files", command=self._clear_input_files
|
|
462
|
+
)
|
|
463
|
+
self.clear_input_button.pack(pady=8, fill=tk.X)
|
|
464
|
+
|
|
465
|
+
self.clear_output_button = ttk.Button(
|
|
466
|
+
button_frame, text="Clear Generated Files", command=self._clear_output_files
|
|
467
|
+
)
|
|
468
|
+
self.clear_output_button.pack(pady=1, fill=tk.X)
|
|
394
469
|
|
|
395
470
|
# Progress bar
|
|
396
471
|
self.progress = ttk.Progressbar(button_frame, mode="indeterminate")
|
|
@@ -481,57 +556,159 @@ class MainGui:
|
|
|
481
556
|
self.log_text.delete(1.0, tk.END)
|
|
482
557
|
self.logger.debug("Log display cleared")
|
|
483
558
|
|
|
484
|
-
def
|
|
485
|
-
"""Clear the file list."""
|
|
486
|
-
self.
|
|
487
|
-
self.
|
|
488
|
-
self.logger.info("
|
|
559
|
+
def _clear_input_files(self):
|
|
560
|
+
"""Clear the input file list."""
|
|
561
|
+
self.input_files.clear()
|
|
562
|
+
self.input_file_listbox.delete(0, tk.END)
|
|
563
|
+
self.logger.info("Input file list cleared")
|
|
564
|
+
|
|
565
|
+
def _clear_output_files(self):
|
|
566
|
+
"""Clear the output file list."""
|
|
567
|
+
self.output_files.clear()
|
|
568
|
+
self.output_file_listbox.delete(0, tk.END)
|
|
569
|
+
self.logger.info("Generated file list cleared")
|
|
570
|
+
|
|
571
|
+
def _remove_selected_input_files(self):
|
|
572
|
+
"""Remove selected files from the input file list."""
|
|
573
|
+
selected_indices = self.input_file_listbox.curselection()
|
|
574
|
+
if not selected_indices:
|
|
575
|
+
messagebox.showwarning("Warning", "No files selected to remove!")
|
|
576
|
+
return
|
|
577
|
+
|
|
578
|
+
# Delete from listbox from end to start to avoid index issues
|
|
579
|
+
for i in reversed(selected_indices):
|
|
580
|
+
self.input_file_listbox.delete(i)
|
|
581
|
+
del self.input_files[i]
|
|
582
|
+
self.logger.info(f"Removed {len(selected_indices)} selected input files.")
|
|
583
|
+
|
|
584
|
+
def _open_selected_file(self, event, file_list_source):
|
|
585
|
+
"""Opens the selected file in the system's default application or explorer."""
|
|
586
|
+
selection_index = event.widget.nearest(event.y)
|
|
587
|
+
if selection_index == -1: # No item clicked
|
|
588
|
+
return
|
|
589
|
+
|
|
590
|
+
file_path_str = file_list_source[selection_index]["path"]
|
|
591
|
+
file_path = Path(file_path_str)
|
|
592
|
+
|
|
593
|
+
if not file_path.exists():
|
|
594
|
+
self.logger.error(f"File not found: {file_path}")
|
|
595
|
+
messagebox.showerror("Error", f"File not found: {file_path}")
|
|
596
|
+
return
|
|
597
|
+
|
|
598
|
+
try:
|
|
599
|
+
if sys.platform == "win32":
|
|
600
|
+
os.startfile(file_path)
|
|
601
|
+
elif sys.platform == "darwin":
|
|
602
|
+
subprocess.Popen(["open", file_path])
|
|
603
|
+
else:
|
|
604
|
+
subprocess.Popen(["xdg-open", file_path])
|
|
605
|
+
self.logger.info(f"Opened file: {file_path}")
|
|
606
|
+
except Exception as e:
|
|
607
|
+
self.logger.error(f"Could not open file {file_path}: {e}")
|
|
608
|
+
messagebox.showerror("Error", f"Could not open file {file_path}: {e}")
|
|
489
609
|
|
|
490
610
|
def _open_files(self):
|
|
491
611
|
"""Open file dialog and add files to list."""
|
|
492
612
|
files = filedialog.askopenfilenames(
|
|
493
|
-
title="Select input files",
|
|
613
|
+
title="Select input files",
|
|
614
|
+
filetypes=[
|
|
615
|
+
("GPX/KML files", "*.gpx *.kml *.zip"), # Added KML support
|
|
616
|
+
("GPX files", "*.gpx"),
|
|
617
|
+
("KML files", "*.kml"),
|
|
618
|
+
("ZIP files", "*.zip"),
|
|
619
|
+
("All files", "*.*"),
|
|
620
|
+
],
|
|
494
621
|
)
|
|
495
622
|
|
|
496
623
|
new_files = 0
|
|
497
|
-
for
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
624
|
+
for file_path_str in files:
|
|
625
|
+
file_path = Path(file_path_str)
|
|
626
|
+
if file_path_str not in [f["path"] for f in self.input_files]:
|
|
627
|
+
try:
|
|
628
|
+
file_size_kb = file_path.stat().st_size / 1024
|
|
629
|
+
self.input_files.append({"path": file_path_str, "size": file_size_kb})
|
|
630
|
+
self.input_file_listbox.insert(
|
|
631
|
+
tk.END, f"{file_path.name} ({file_size_kb:.2f} KB)"
|
|
632
|
+
)
|
|
633
|
+
new_files += 1
|
|
634
|
+
except Exception as e:
|
|
635
|
+
self.logger.warning(f"Could not get size for {file_path_str}: {e}")
|
|
636
|
+
self.input_files.append({"path": file_path_str, "size": 0})
|
|
637
|
+
self.input_file_listbox.insert(tk.END, f"{file_path.name} (N/A KB)")
|
|
502
638
|
|
|
503
639
|
if new_files > 0:
|
|
504
640
|
self.logger.info(f"Added {new_files} new files to processing list")
|
|
505
641
|
else:
|
|
506
642
|
self.logger.debug("No new files selected")
|
|
507
643
|
|
|
644
|
+
def _update_output_listbox(self, generated_files_info):
|
|
645
|
+
"""Updates the output file listbox with newly generated files."""
|
|
646
|
+
self.output_file_listbox.delete(0, tk.END) # Clear current list
|
|
647
|
+
self.output_files.clear() # Clear internal list
|
|
648
|
+
for file_path_str in generated_files_info:
|
|
649
|
+
file_path = Path(file_path_str)
|
|
650
|
+
try:
|
|
651
|
+
file_size_kb = file_path.stat().st_size / 1024
|
|
652
|
+
self.output_files.append({"path": file_path_str, "size": file_size_kb})
|
|
653
|
+
self.output_file_listbox.insert(tk.END, f"{file_path.name} ({file_size_kb:.2f} KB)")
|
|
654
|
+
except Exception as e:
|
|
655
|
+
self.logger.warning(f"Could not get size for generated file {file_path_str}: {e}")
|
|
656
|
+
self.output_files.append({"path": file_path_str, "size": 0})
|
|
657
|
+
self.output_file_listbox.insert(tk.END, f"{file_path.name} (N/A KB)")
|
|
658
|
+
|
|
659
|
+
if generated_files_info:
|
|
660
|
+
output_dir = Path(generated_files_info[0]).parent
|
|
661
|
+
self.logger.info(f"Generated files saved in: {output_dir}") # Log directory
|
|
662
|
+
|
|
508
663
|
def _run_processing(self, mode="compress_files"):
|
|
509
664
|
"""Run the processing in a separate thread."""
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
665
|
+
selected_indices = self.input_file_listbox.curselection()
|
|
666
|
+
files_to_process = []
|
|
667
|
+
|
|
668
|
+
if selected_indices:
|
|
669
|
+
for i in selected_indices:
|
|
670
|
+
files_to_process.append(self.input_files[i]["path"])
|
|
671
|
+
else:
|
|
672
|
+
files_to_process = [f["path"] for f in self.input_files]
|
|
673
|
+
|
|
674
|
+
if not files_to_process:
|
|
675
|
+
self.logger.warning("No input files selected or all are deselected.")
|
|
676
|
+
messagebox.showwarning("Warning", "No input files selected or all are deselected!")
|
|
513
677
|
return
|
|
514
678
|
|
|
515
|
-
self.logger.info(f"Starting processing of {len(
|
|
679
|
+
self.logger.info(f"Starting processing of {len(files_to_process)} files in mode: {mode}")
|
|
516
680
|
|
|
517
|
-
#
|
|
681
|
+
# Disable all buttons during processing
|
|
518
682
|
for button in self.run_buttons.values():
|
|
519
683
|
button.config(state="disabled")
|
|
684
|
+
self.clear_input_button.config(state="disabled")
|
|
685
|
+
self.clear_output_button.config(state="disabled")
|
|
520
686
|
self.progress.start()
|
|
521
687
|
|
|
522
688
|
# Run in separate thread to avoid blocking GUI
|
|
523
|
-
thread = threading.Thread(
|
|
689
|
+
thread = threading.Thread(
|
|
690
|
+
target=self._process_files,
|
|
691
|
+
args=(
|
|
692
|
+
mode,
|
|
693
|
+
files_to_process,
|
|
694
|
+
),
|
|
695
|
+
daemon=True,
|
|
696
|
+
)
|
|
524
697
|
thread.start()
|
|
525
698
|
|
|
526
|
-
def _process_files(self, mode="compress_files"):
|
|
699
|
+
def _process_files(self, mode="compress_files", files_to_process=None):
|
|
527
700
|
"""Process the selected files."""
|
|
701
|
+
generated_files_paths = []
|
|
528
702
|
try:
|
|
529
703
|
self.logger.info("=== Processing Started ===")
|
|
530
704
|
self.logger.info("Processing files...")
|
|
531
705
|
|
|
706
|
+
if files_to_process is None:
|
|
707
|
+
files_to_process = [] # Should not happen with the check in _run_processing
|
|
708
|
+
|
|
532
709
|
# Create and run project
|
|
533
710
|
project = BaseGPXProcessor(
|
|
534
|
-
|
|
711
|
+
files_to_process, # Pass selected files
|
|
535
712
|
self.config_manager.cli.output.default,
|
|
536
713
|
self.config_manager.cli.min_dist.default,
|
|
537
714
|
self.config_manager.app.date_format.default,
|
|
@@ -540,18 +717,19 @@ class MainGui:
|
|
|
540
717
|
)
|
|
541
718
|
# implement switch case for different processing modes
|
|
542
719
|
if mode == "compress_files":
|
|
543
|
-
project.compress_files()
|
|
720
|
+
generated_files_paths = project.compress_files()
|
|
544
721
|
elif mode == "merge_files":
|
|
545
|
-
project.merge_files()
|
|
722
|
+
generated_files_paths = project.merge_files()
|
|
546
723
|
elif mode == "extract_pois":
|
|
547
|
-
project.extract_pois()
|
|
724
|
+
generated_files_paths = project.extract_pois()
|
|
548
725
|
else:
|
|
549
726
|
self.logger.warning(f"Unknown mode: {mode}")
|
|
550
727
|
|
|
551
|
-
self.logger.info(f"Completed: {len(
|
|
552
|
-
|
|
728
|
+
self.logger.info(f"Completed: {len(files_to_process)} files processed")
|
|
553
729
|
self.logger.info("=== All files processed successfully! ===")
|
|
554
730
|
|
|
731
|
+
self.root.after(0, self._update_output_listbox, generated_files_paths)
|
|
732
|
+
|
|
555
733
|
except Exception as err:
|
|
556
734
|
self.logger.error(f"Processing failed: {err}", exc_info=True)
|
|
557
735
|
# Show error dialog in main thread
|
|
@@ -567,7 +745,8 @@ class MainGui:
|
|
|
567
745
|
"""Re-enable controls after processing is finished."""
|
|
568
746
|
for button in self.run_buttons.values():
|
|
569
747
|
button.config(state="normal")
|
|
570
|
-
self.
|
|
748
|
+
self.clear_input_button.config(state="normal")
|
|
749
|
+
self.clear_output_button.config(state="normal")
|
|
571
750
|
self.progress.stop()
|
|
572
751
|
|
|
573
752
|
def _open_settings(self):
|
{python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/METADATA
RENAMED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-template-project
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
+
Requires-Dist: fastkml>=1.1.0
|
|
9
10
|
Requires-Dist: gpxpy>=1.6.2
|
|
10
11
|
Requires-Dist: pydantic>=2.11.7
|
|
11
12
|
Requires-Dist: pyyaml>=6.0.2
|
|
13
|
+
Requires-Dist: shapely>=2.1.1
|
|
12
14
|
Requires-Dist: srtm-py>=0.3.7
|
|
13
15
|
Provides-Extra: dev
|
|
14
16
|
Requires-Dist: pytest>=8.4.0; extra == "dev"
|
{python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/RECORD
RENAMED
|
@@ -3,21 +3,21 @@ firewall_handler.py,sha256=qT0dInkeKHulpCQD2x3HH7_lMxz4lwdkIPxEvBSl3No,7336
|
|
|
3
3
|
main.py,sha256=d6ZpnMxcPBf860gQmuYwPVJg1di6r2CR6gSvFLkAZkk,4448
|
|
4
4
|
python_template_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
python_template_project/__main__.py,sha256=JS28LiHoU9Xo4c_RvyLBTDKHqdZRRytJLj6qyrg26mQ,195
|
|
6
|
-
python_template_project/_version.py,sha256=
|
|
6
|
+
python_template_project/_version.py,sha256=b9rzYHmy3i_G_wlsa53qvXrAz8O-mxRK23uXogBfZws,513
|
|
7
7
|
python_template_project/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
python_template_project/cli/__main__.py,sha256=qnGkoNNbhnvLTA24X8YTEDxRqDbNEAzgjp9Ki_JYIhs,184
|
|
9
9
|
python_template_project/cli/cli.py,sha256=Eumz1XfcBnpI4Yt1tn-TNG_VHLCvrDFREdx-6nI-sNY,6974
|
|
10
10
|
python_template_project/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
python_template_project/config/config.py,sha256=naSOpbmMXZQfAoOrZuEPYhv1_9TKJtpxjRKBVzCKUlE,17924
|
|
12
12
|
python_template_project/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
python_template_project/core/base.py,sha256=
|
|
13
|
+
python_template_project/core/base.py,sha256=Cl4fgT7ME_HZ8T_MVv5Rki9_xyG7kTsC-aJP8HJ3OMc,28017
|
|
14
14
|
python_template_project/core/logging.py,sha256=D99ZAXY9KZvmV7oVlAy3S3fuDf9G7vHvIk277xB7068,6944
|
|
15
15
|
python_template_project/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
python_template_project/gui/__main__.py,sha256=5AaiBxNCAKWcuSQFcQ_lsEQfEsXm6BA5-i5MScdf30c,184
|
|
17
|
-
python_template_project/gui/gui.py,sha256=
|
|
18
|
-
python_template_project-0.1.
|
|
19
|
-
python_template_project-0.1.
|
|
20
|
-
python_template_project-0.1.
|
|
21
|
-
python_template_project-0.1.
|
|
22
|
-
python_template_project-0.1.
|
|
23
|
-
python_template_project-0.1.
|
|
17
|
+
python_template_project/gui/gui.py,sha256=0dNsWHvLu0l8lP-PNIHGUDjM0UM-aW_iN6uCJfo9fNw,30404
|
|
18
|
+
python_template_project-0.1.21.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
19
|
+
python_template_project-0.1.21.dist-info/METADATA,sha256=CtgqqcU_vyydu5fI-hWmbZyE6kLJXKM19CQNwhL5jRM,5119
|
|
20
|
+
python_template_project-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
python_template_project-0.1.21.dist-info/entry_points.txt,sha256=LmgMiqxIVRdD7gkktVBBR0CR4AhkugqlgifuglkPhtI,148
|
|
22
|
+
python_template_project-0.1.21.dist-info/top_level.txt,sha256=O-Yg0tzEwgH58MhdcYWpJsgZSUPg6SBbOUwlmVHZo3A,55
|
|
23
|
+
python_template_project-0.1.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_template_project-0.1.20.dist-info → python_template_project-0.1.21.dist-info}/top_level.txt
RENAMED
|
File without changes
|