geo-activity-playground 0.38.2__tar.gz → 0.39.1__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 (162) hide show
  1. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/PKG-INFO +5 -1
  2. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/__main__.py +5 -47
  3. geo_activity_playground-0.39.1/geo_activity_playground/alembic/README +1 -0
  4. geo_activity_playground-0.39.1/geo_activity_playground/alembic/env.py +76 -0
  5. geo_activity_playground-0.39.1/geo_activity_playground/alembic/script.py.mako +26 -0
  6. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/451e7836b53d_add_square_planner_bookmark.py +33 -0
  7. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/63d3b7f6f93c_initial_version.py +73 -0
  8. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/ab83b9d23127_add_upstream_id.py +28 -0
  9. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/b03491c593f6_add_crop_indices.py +30 -0
  10. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/e02e27876deb_add_square_planner_bookmark_name.py +28 -0
  11. geo_activity_playground-0.39.1/geo_activity_playground/alembic/versions/script.py.mako +28 -0
  12. geo_activity_playground-0.39.1/geo_activity_playground/core/activities.py +157 -0
  13. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/config.py +3 -3
  14. geo_activity_playground-0.39.1/geo_activity_playground/core/datamodel.py +257 -0
  15. geo_activity_playground-0.39.1/geo_activity_playground/core/enrichment.py +201 -0
  16. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/heart_rate.py +1 -2
  17. geo_activity_playground-0.39.1/geo_activity_playground/core/parametric_plot.py +101 -0
  18. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/paths.py +6 -7
  19. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/raster_map.py +43 -4
  20. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/similarity.py +1 -2
  21. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/tasks.py +2 -2
  22. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/test_meta_search.py +3 -3
  23. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/test_summary_stats.py +1 -1
  24. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/explorer/grid_file.py +2 -2
  25. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/explorer/tile_visits.py +8 -10
  26. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/heatmap_video.py +7 -8
  27. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/activity_parsers.py +2 -2
  28. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/directory.py +9 -10
  29. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/strava_api.py +9 -9
  30. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/strava_checkout.py +12 -13
  31. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/test_csv_parser.py +3 -3
  32. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/test_directory.py +1 -1
  33. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/test_strava_api.py +1 -1
  34. geo_activity_playground-0.39.1/geo_activity_playground/webui/app.py +189 -0
  35. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/authenticator.py +1 -1
  36. geo_activity_playground-0.38.2/geo_activity_playground/webui/activity/controller.py → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/activity_blueprint.py +246 -108
  37. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/auth_blueprint.py +1 -1
  38. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +61 -0
  39. geo_activity_playground-0.38.2/geo_activity_playground/webui/calendar/controller.py → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/calendar_blueprint.py +19 -19
  40. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/eddington_blueprint.py +5 -5
  41. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/entry_views.py +68 -0
  42. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/equipment_blueprint.py +37 -4
  43. geo_activity_playground-0.38.2/geo_activity_playground/webui/explorer/controller.py → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/explorer_blueprint.py +88 -54
  44. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/heatmap_blueprint.py +233 -0
  45. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +43 -0
  46. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/search_blueprint.py +7 -11
  47. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/settings_blueprint.py +446 -0
  48. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/square_planner_blueprint.py +31 -6
  49. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/summary_blueprint.py +11 -23
  50. geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints/tile_blueprint.py +27 -0
  51. {geo_activity_playground-0.38.2/geo_activity_playground/webui → geo_activity_playground-0.39.1/geo_activity_playground/webui/blueprints}/upload_blueprint.py +13 -18
  52. geo_activity_playground-0.39.1/geo_activity_playground/webui/flasher.py +26 -0
  53. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/plot_util.py +1 -1
  54. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/search_util.py +4 -6
  55. geo_activity_playground-0.39.1/geo_activity_playground/webui/static/images/layers-2x.png +0 -0
  56. geo_activity_playground-0.39.1/geo_activity_playground/webui/static/images/layers.png +0 -0
  57. geo_activity_playground-0.39.1/geo_activity_playground/webui/static/images/marker-icon-2x.png +0 -0
  58. geo_activity_playground-0.39.1/geo_activity_playground/webui/static/images/marker-icon.png +0 -0
  59. geo_activity_playground-0.39.1/geo_activity_playground/webui/static/images/marker-shadow.png +0 -0
  60. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/activity/day.html.j2 +81 -0
  61. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/activity/edit.html.j2 +38 -0
  62. {geo_activity_playground-0.38.2/geo_activity_playground/webui/activity → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/activity/name.html.j2 +29 -27
  63. {geo_activity_playground-0.38.2/geo_activity_playground/webui/activity → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/activity/show.html.j2 +57 -33
  64. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/activity/trim.html.j2 +68 -0
  65. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +26 -0
  66. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/calendar/index.html.j2 +48 -0
  67. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/calendar/month.html.j2 +57 -0
  68. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/equipment/index.html.j2 +7 -0
  69. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/home.html.j2 +6 -6
  70. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/page.html.j2 +2 -1
  71. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/plot_builder/index.html.j2 +44 -0
  72. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/index.html.j2 +9 -20
  73. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/settings/manage-equipments.html.j2 +49 -0
  74. geo_activity_playground-0.39.1/geo_activity_playground/webui/templates/settings/manage-kinds.html.j2 +48 -0
  75. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/privacy-zones.html.j2 +2 -0
  76. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/strava.html.j2 +2 -0
  77. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/square_planner/index.html.j2 +63 -13
  78. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/pyproject.toml +5 -1
  79. geo_activity_playground-0.38.2/geo_activity_playground/__init__.py +0 -0
  80. geo_activity_playground-0.38.2/geo_activity_playground/core/__init__.py +0 -0
  81. geo_activity_playground-0.38.2/geo_activity_playground/core/activities.py +0 -240
  82. geo_activity_playground-0.38.2/geo_activity_playground/core/enrichment.py +0 -203
  83. geo_activity_playground-0.38.2/geo_activity_playground/explorer/__init__.py +0 -0
  84. geo_activity_playground-0.38.2/geo_activity_playground/importers/__init__.py +0 -0
  85. geo_activity_playground-0.38.2/geo_activity_playground/webui/__init__.py +0 -0
  86. geo_activity_playground-0.38.2/geo_activity_playground/webui/activity/__init__.py +0 -0
  87. geo_activity_playground-0.38.2/geo_activity_playground/webui/activity/blueprint.py +0 -109
  88. geo_activity_playground-0.38.2/geo_activity_playground/webui/activity/templates/activity/day.html.j2 +0 -80
  89. geo_activity_playground-0.38.2/geo_activity_playground/webui/activity/templates/activity/edit.html.j2 +0 -42
  90. geo_activity_playground-0.38.2/geo_activity_playground/webui/app.py +0 -179
  91. geo_activity_playground-0.38.2/geo_activity_playground/webui/calendar/__init__.py +0 -0
  92. geo_activity_playground-0.38.2/geo_activity_playground/webui/calendar/blueprint.py +0 -23
  93. geo_activity_playground-0.38.2/geo_activity_playground/webui/calendar/templates/calendar/index.html.j2 +0 -46
  94. geo_activity_playground-0.38.2/geo_activity_playground/webui/calendar/templates/calendar/month.html.j2 +0 -55
  95. geo_activity_playground-0.38.2/geo_activity_playground/webui/entry_controller.py +0 -63
  96. geo_activity_playground-0.38.2/geo_activity_playground/webui/explorer/__init__.py +0 -0
  97. geo_activity_playground-0.38.2/geo_activity_playground/webui/explorer/blueprint.py +0 -62
  98. geo_activity_playground-0.38.2/geo_activity_playground/webui/heatmap/__init__.py +0 -0
  99. geo_activity_playground-0.38.2/geo_activity_playground/webui/heatmap/blueprint.py +0 -51
  100. geo_activity_playground-0.38.2/geo_activity_playground/webui/heatmap/heatmap_controller.py +0 -216
  101. geo_activity_playground-0.38.2/geo_activity_playground/webui/settings/blueprint.py +0 -262
  102. geo_activity_playground-0.38.2/geo_activity_playground/webui/settings/controller.py +0 -272
  103. geo_activity_playground-0.38.2/geo_activity_playground/webui/settings/templates/settings/equipment-offsets.html.j2 +0 -44
  104. geo_activity_playground-0.38.2/geo_activity_playground/webui/settings/templates/settings/kind-renames.html.j2 +0 -25
  105. geo_activity_playground-0.38.2/geo_activity_playground/webui/settings/templates/settings/kinds-without-achievements.html.j2 +0 -30
  106. geo_activity_playground-0.38.2/geo_activity_playground/webui/tile_blueprint.py +0 -42
  107. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/LICENSE +0 -0
  108. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/coordinates.py +0 -0
  109. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/meta_search.py +0 -0
  110. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/privacy_zones.py +0 -0
  111. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/summary_stats.py +0 -0
  112. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/test_tiles.py +0 -0
  113. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/test_time_conversion.py +0 -0
  114. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/tiles.py +0 -0
  115. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/core/time_conversion.py +0 -0
  116. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/explorer/video.py +0 -0
  117. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/importers/csv_parser.py +0 -0
  118. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +0 -0
  119. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/MarkerCluster.Default.css +0 -0
  120. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/MarkerCluster.css +0 -0
  121. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/android-chrome-192x192.png +0 -0
  122. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/android-chrome-512x512.png +0 -0
  123. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/apple-touch-icon.png +0 -0
  124. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/bootstrap-dark-mode.js +0 -0
  125. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/bootstrap.bundle.min.js +0 -0
  126. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/bootstrap.min.css +0 -0
  127. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/browserconfig.xml +0 -0
  128. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/favicon-16x16.png +0 -0
  129. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/favicon-32x32.png +0 -0
  130. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/favicon-48x48.png +0 -0
  131. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/favicon.ico +0 -0
  132. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/favicon.svg +0 -0
  133. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/fullscreen.png +0 -0
  134. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
  135. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/leaflet.css +0 -0
  136. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/leaflet.fullscreen.css +0 -0
  137. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/leaflet.js +0 -0
  138. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/leaflet.markercluster.js +0 -0
  139. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/mstile-150x150.png +0 -0
  140. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/site.webmanifest +0 -0
  141. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/table-sort.min.js +0 -0
  142. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/vega-embed@6 +0 -0
  143. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/vega-lite@4 +0 -0
  144. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/vega@5 +0 -0
  145. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/web-app-manifest-192x192.png +0 -0
  146. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/static/web-app-manifest-512x512.png +0 -0
  147. {geo_activity_playground-0.38.2/geo_activity_playground/webui/activity → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/activity/lines.html.j2 +0 -0
  148. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/auth/index.html.j2 +0 -0
  149. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/eddington/index.html.j2 +0 -0
  150. {geo_activity_playground-0.38.2/geo_activity_playground/webui/explorer → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/explorer/index.html.j2 +0 -0
  151. {geo_activity_playground-0.38.2/geo_activity_playground/webui/heatmap → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/heatmap/index.html.j2 +0 -0
  152. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/search/index.html.j2 +0 -0
  153. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/search_form.html.j2 +0 -0
  154. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/admin-password.html.j2 +0 -0
  155. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/color-schemes.html.j2 +0 -0
  156. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/heart-rate.html.j2 +0 -0
  157. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/metadata-extraction.html.j2 +0 -0
  158. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/segmentation.html.j2 +0 -0
  159. {geo_activity_playground-0.38.2/geo_activity_playground/webui/settings → geo_activity_playground-0.39.1/geo_activity_playground/webui}/templates/settings/sharepic.html.j2 +0 -0
  160. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/summary/index.html.j2 +0 -0
  161. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/upload/index.html.j2 +0 -0
  162. {geo_activity_playground-0.38.2 → geo_activity_playground-0.39.1}/geo_activity_playground/webui/templates/upload/reload.html.j2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geo-activity-playground
3
- Version: 0.38.2
3
+ Version: 0.39.1
4
4
  Summary: Analysis of geo data activities like rides, runs or hikes.
5
5
  License: MIT
6
6
  Author: Martin Ueding
@@ -12,12 +12,15 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: Pillow (>=11.0.0,<12.0.0)
15
+ Requires-Dist: alembic (>=1.15.2,<2.0.0)
15
16
  Requires-Dist: altair (>=5.5.0,<6.0.0)
16
17
  Requires-Dist: appdirs (>=1.4.4,<2.0.0)
17
18
  Requires-Dist: charset-normalizer (>=3.3.2,<4.0.0)
18
19
  Requires-Dist: coloredlogs (>=15.0.1,<16.0.0)
19
20
  Requires-Dist: fitdecode (>=0.10.0,<0.11.0)
20
21
  Requires-Dist: flask (>=3.0.0,<4.0.0)
22
+ Requires-Dist: flask-alembic (>=3.1.1,<4.0.0)
23
+ Requires-Dist: flask-sqlalchemy (>=3.1.1,<4.0.0)
21
24
  Requires-Dist: geojson (>=3.0.1,<4.0.0)
22
25
  Requires-Dist: gpxpy (>=1.5.0,<2.0.0)
23
26
  Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
@@ -28,6 +31,7 @@ Requires-Dist: pyarrow (>=19.0.1,<20.0.0)
28
31
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
29
32
  Requires-Dist: requests (>=2.28.1,<3.0.0)
30
33
  Requires-Dist: shapely (>=2.0.5,<3.0.0)
34
+ Requires-Dist: sqlalchemy (>=2.0.40,<3.0.0)
31
35
  Requires-Dist: stravalib (>=2.0,<3.0)
32
36
  Requires-Dist: tcxreader (>=0.4.5,<0.5.0)
33
37
  Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
@@ -1,20 +1,13 @@
1
1
  import argparse
2
2
  import logging
3
- import os
4
3
  import pathlib
5
4
 
6
5
  import coloredlogs
7
6
 
7
+ from .explorer.video import explorer_video_main
8
+ from .heatmap_video import main_heatmap_video
8
9
  from .importers.strava_checkout import convert_strava_checkout
9
- from geo_activity_playground.core.activities import ActivityRepository
10
- from geo_activity_playground.core.config import ConfigAccessor
11
- from geo_activity_playground.core.config import import_old_config
12
- from geo_activity_playground.core.config import import_old_strava_config
13
- from geo_activity_playground.explorer.tile_visits import TileVisitAccessor
14
- from geo_activity_playground.explorer.video import explorer_video_main
15
- from geo_activity_playground.heatmap_video import main_heatmap_video
16
- from geo_activity_playground.webui.app import web_ui_main
17
- from geo_activity_playground.webui.upload_blueprint import scan_for_activities
10
+ from .webui.app import web_ui_main
18
11
 
19
12
  logger = logging.getLogger(__name__)
20
13
 
@@ -35,16 +28,6 @@ def main() -> None:
35
28
  description="The tools are organized in subcommands.", metavar="Command"
36
29
  )
37
30
 
38
- # subparser = subparsers.add_parser(
39
- # "explorer",
40
- # help="Generate GeoJSON/GPX files with explored and missing explorer tiles.",
41
- # )
42
- # subparser.set_defaults(
43
- # func=lambda options: main_explorer(
44
- # make_time_series_source(options.basedir)
45
- # )
46
- # )
47
-
48
31
  subparser = subparsers.add_parser(
49
32
  "explorer-video", help="Generate video with explorer timeline."
50
33
  )
@@ -65,7 +48,8 @@ def main() -> None:
65
48
  subparser = subparsers.add_parser("serve", help="Launch webserver")
66
49
  subparser.set_defaults(
67
50
  func=lambda options: web_ui_main(
68
- *make_activity_repository(options.basedir, options.skip_reload),
51
+ options.basedir,
52
+ options.skip_reload,
69
53
  host=options.host,
70
54
  port=options.port,
71
55
  )
@@ -78,9 +62,6 @@ def main() -> None:
78
62
  )
79
63
  subparser.add_argument("--skip-reload", action=argparse.BooleanOptionalAction)
80
64
 
81
- subparser = subparsers.add_parser("cache", help="Cache stuff")
82
- subparser.set_defaults(func=lambda options: main_cache(options.basedir))
83
-
84
65
  subparser = subparsers.add_parser(
85
66
  "heatmap-video", help="Create a video with the evolution of the heatmap"
86
67
  )
@@ -103,28 +84,5 @@ def main() -> None:
103
84
  options.func(options)
104
85
 
105
86
 
106
- def make_activity_repository(
107
- basedir: pathlib.Path, skip_reload: bool
108
- ) -> tuple[ActivityRepository, TileVisitAccessor, ConfigAccessor]:
109
- os.chdir(basedir)
110
-
111
- repository = ActivityRepository()
112
- tile_visit_accessor = TileVisitAccessor()
113
- config_accessor = ConfigAccessor()
114
- import_old_config(config_accessor)
115
- import_old_strava_config(config_accessor)
116
-
117
- if not skip_reload:
118
- scan_for_activities(repository, tile_visit_accessor, config_accessor())
119
-
120
- return repository, tile_visit_accessor, config_accessor
121
-
122
-
123
- def main_cache(basedir: pathlib.Path) -> None:
124
- (repository, tile_visit_accessor, config_accessor) = make_activity_repository(
125
- basedir, False
126
- )
127
-
128
-
129
87
  if __name__ == "__main__":
130
88
  main()
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,76 @@
1
+ from logging.config import fileConfig
2
+
3
+ from alembic import context
4
+ from sqlalchemy import engine_from_config
5
+ from sqlalchemy import pool
6
+
7
+ from geo_activity_playground.core.datamodel import Base
8
+
9
+ # this is the Alembic Config object, which provides
10
+ # access to the values within the .ini file in use.
11
+ config = context.config
12
+
13
+ # Interpret the config file for Python logging.
14
+ # This line sets up loggers basically.
15
+ if config.config_file_name is not None:
16
+ fileConfig(config.config_file_name)
17
+
18
+ target_metadata = Base.metadata
19
+
20
+ # other values from the config, defined by the needs of env.py,
21
+ # can be acquired:
22
+ # my_important_option = config.get_main_option("my_important_option")
23
+ # ... etc.
24
+
25
+
26
+ def run_migrations_offline() -> None:
27
+ """Run migrations in 'offline' mode.
28
+
29
+ This configures the context with just a URL
30
+ and not an Engine, though an Engine is acceptable
31
+ here as well. By skipping the Engine creation
32
+ we don't even need a DBAPI to be available.
33
+
34
+ Calls to context.execute() here emit the given string to the
35
+ script output.
36
+
37
+ """
38
+ url = config.get_main_option("sqlalchemy.url")
39
+ context.configure(
40
+ url=url,
41
+ target_metadata=target_metadata,
42
+ literal_binds=True,
43
+ dialect_opts={"paramstyle": "named"},
44
+ render_as_batch=True,
45
+ )
46
+
47
+ with context.begin_transaction():
48
+ context.run_migrations()
49
+
50
+
51
+ def run_migrations_online() -> None:
52
+ """Run migrations in 'online' mode.
53
+
54
+ In this scenario we need to create an Engine
55
+ and associate a connection with the context.
56
+
57
+ """
58
+ connectable = engine_from_config(
59
+ config.get_section(config.config_ini_section, {}),
60
+ prefix="sqlalchemy.",
61
+ poolclass=pool.NullPool,
62
+ )
63
+
64
+ with connectable.connect() as connection:
65
+ context.configure(
66
+ connection=connection, target_metadata=target_metadata, render_as_batch=True
67
+ )
68
+
69
+ with context.begin_transaction():
70
+ context.run_migrations()
71
+
72
+
73
+ if context.is_offline_mode():
74
+ run_migrations_offline()
75
+ else:
76
+ run_migrations_online()
@@ -0,0 +1,26 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,33 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "451e7836b53d"
10
+ down_revision: Union[str, None] = "ab83b9d23127"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ op.create_table(
18
+ "square_planner_bookmarks",
19
+ sa.Column("id", sa.Integer(), nullable=False),
20
+ sa.Column("zoom", sa.Integer(), nullable=False),
21
+ sa.Column("x", sa.Integer(), nullable=False),
22
+ sa.Column("y", sa.Integer(), nullable=False),
23
+ sa.Column("size", sa.Integer(), nullable=False),
24
+ sa.PrimaryKeyConstraint("id"),
25
+ sa.UniqueConstraint("zoom", "x", "y", "size", name="kinds_name"),
26
+ )
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ op.drop_table("square_planner_bookmarks")
33
+ # ### end Alembic commands ###
@@ -0,0 +1,73 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "63d3b7f6f93c"
10
+ down_revision: Union[str, None] = None
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ op.create_table(
18
+ "equipments",
19
+ sa.Column("id", sa.Integer(), nullable=False),
20
+ sa.Column("name", sa.String(), nullable=False),
21
+ sa.Column("offset_km", sa.Integer(), nullable=False),
22
+ sa.PrimaryKeyConstraint("id"),
23
+ sa.UniqueConstraint("name", name="equipments_name"),
24
+ )
25
+ op.create_table(
26
+ "kinds",
27
+ sa.Column("id", sa.Integer(), nullable=False),
28
+ sa.Column("name", sa.String(), nullable=False),
29
+ sa.Column("consider_for_achievements", sa.Boolean(), nullable=False),
30
+ sa.Column("default_equipment_id", sa.Integer(), nullable=True),
31
+ sa.ForeignKeyConstraint(
32
+ ["default_equipment_id"], ["equipments.id"], name="default_equipment_id"
33
+ ),
34
+ sa.PrimaryKeyConstraint("id"),
35
+ sa.UniqueConstraint("name", name="kinds_name"),
36
+ )
37
+ op.create_table(
38
+ "activities",
39
+ sa.Column("id", sa.Integer(), nullable=False),
40
+ sa.Column("name", sa.String(), nullable=False),
41
+ sa.Column("path", sa.String(), nullable=True),
42
+ sa.Column("distance_km", sa.Float(), nullable=False),
43
+ sa.Column("start", sa.DateTime(), nullable=True),
44
+ sa.Column("elapsed_time", sa.Interval(), nullable=True),
45
+ sa.Column("moving_time", sa.Interval(), nullable=True),
46
+ sa.Column("start_latitude", sa.Float(), nullable=True),
47
+ sa.Column("start_longitude", sa.Float(), nullable=True),
48
+ sa.Column("end_latitude", sa.Float(), nullable=True),
49
+ sa.Column("end_longitude", sa.Float(), nullable=True),
50
+ sa.Column("elevation_gain", sa.Float(), nullable=True),
51
+ sa.Column("start_elevation", sa.Float(), nullable=True),
52
+ sa.Column("end_elevation", sa.Float(), nullable=True),
53
+ sa.Column("calories", sa.Integer(), nullable=True),
54
+ sa.Column("steps", sa.Integer(), nullable=True),
55
+ sa.Column("num_new_tiles_14", sa.Integer(), nullable=True),
56
+ sa.Column("num_new_tiles_17", sa.Integer(), nullable=True),
57
+ sa.Column("equipment_id", sa.Integer(), nullable=True),
58
+ sa.Column("kind_id", sa.Integer(), nullable=True),
59
+ sa.ForeignKeyConstraint(
60
+ ["equipment_id"], ["equipments.id"], name="equipment_id"
61
+ ),
62
+ sa.ForeignKeyConstraint(["kind_id"], ["kinds.id"], name="kind_id"),
63
+ sa.PrimaryKeyConstraint("id"),
64
+ )
65
+ # ### end Alembic commands ###
66
+
67
+
68
+ def downgrade() -> None:
69
+ # ### commands auto generated by Alembic - please adjust! ###
70
+ op.drop_table("activities")
71
+ op.drop_table("kinds")
72
+ op.drop_table("equipments")
73
+ # ### end Alembic commands ###
@@ -0,0 +1,28 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "ab83b9d23127"
10
+ down_revision: Union[str, None] = "b03491c593f6"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ with op.batch_alter_table("activities", schema=None) as batch_op:
18
+ batch_op.add_column(sa.Column("upstream_id", sa.String(), nullable=True))
19
+
20
+ # ### end Alembic commands ###
21
+
22
+
23
+ def downgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("activities", schema=None) as batch_op:
26
+ batch_op.drop_column("upstream_id")
27
+
28
+ # ### end Alembic commands ###
@@ -0,0 +1,30 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "b03491c593f6"
10
+ down_revision: Union[str, None] = "63d3b7f6f93c"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ with op.batch_alter_table("activities", schema=None) as batch_op:
18
+ batch_op.add_column(sa.Column("index_begin", sa.Integer(), nullable=True))
19
+ batch_op.add_column(sa.Column("index_end", sa.Integer(), nullable=True))
20
+
21
+ # ### end Alembic commands ###
22
+
23
+
24
+ def downgrade() -> None:
25
+ # ### commands auto generated by Alembic - please adjust! ###
26
+ with op.batch_alter_table("activities", schema=None) as batch_op:
27
+ batch_op.drop_column("index_end")
28
+ batch_op.drop_column("index_begin")
29
+
30
+ # ### end Alembic commands ###
@@ -0,0 +1,28 @@
1
+ from typing import Sequence
2
+ from typing import Union
3
+
4
+ import sqlalchemy as sa
5
+ from alembic import op
6
+
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision: str = "e02e27876deb"
10
+ down_revision: Union[str, None] = "451e7836b53d"
11
+ branch_labels: Union[str, Sequence[str], None] = None
12
+ depends_on: Union[str, Sequence[str], None] = None
13
+
14
+
15
+ def upgrade() -> None:
16
+ # ### commands auto generated by Alembic - please adjust! ###
17
+ with op.batch_alter_table("square_planner_bookmarks", schema=None) as batch_op:
18
+ batch_op.add_column(sa.Column("name", sa.String(), nullable=False))
19
+
20
+ # ### end Alembic commands ###
21
+
22
+
23
+ def downgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("square_planner_bookmarks", schema=None) as batch_op:
26
+ batch_op.drop_column("name")
27
+
28
+ # ### end Alembic commands ###
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,157 @@
1
+ import datetime
2
+ import functools
3
+ import logging
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+ from typing import Optional
7
+
8
+ import geojson
9
+ import matplotlib
10
+ import numpy as np
11
+ import pandas as pd
12
+ import sqlalchemy
13
+ from tqdm import tqdm
14
+
15
+ from geo_activity_playground.core.datamodel import Activity
16
+ from geo_activity_playground.core.datamodel import ActivityMeta
17
+ from geo_activity_playground.core.datamodel import DB
18
+ from geo_activity_playground.core.datamodel import Kind
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def make_activity_meta() -> ActivityMeta:
24
+ return ActivityMeta(
25
+ calories=None,
26
+ commute=False,
27
+ consider_for_achievements=True,
28
+ equipment="Unknown",
29
+ kind="Unknown",
30
+ steps=None,
31
+ )
32
+
33
+
34
+ class ActivityRepository:
35
+ def __len__(self) -> int:
36
+ return len(self.get_activity_ids())
37
+
38
+ def has_activity(self, activity_id: int) -> bool:
39
+ return bool(
40
+ DB.session.scalars(
41
+ sqlalchemy.query(Activity).where(Activity.id == activity_id)
42
+ ).all()
43
+ )
44
+
45
+ def last_activity_date(self) -> Optional[datetime.datetime]:
46
+ result = DB.session.scalars(
47
+ sqlalchemy.select(Activity).order_by(Activity.start)
48
+ ).all()
49
+ if result:
50
+ return result[-1].start
51
+ else:
52
+ return None
53
+
54
+ def get_activity_ids(self, only_achievements: bool = False) -> list[int]:
55
+ query = sqlalchemy.select(Activity.id)
56
+ if only_achievements:
57
+ query = query.where(Kind.consider_for_achievements)
58
+ result = DB.session.scalars(query).all()
59
+ return result
60
+
61
+ def iter_activities(self, new_to_old=True, drop_na=False) -> list[Activity]:
62
+ query = sqlalchemy.select(Activity)
63
+ if drop_na:
64
+ query = query.where(Activity.start.is_not(None))
65
+ result = DB.session.scalars(query.order_by(Activity.start)).all()
66
+ direction = -1 if new_to_old else 1
67
+ return result[::direction]
68
+
69
+ def get_activity_by_id(self, id: int) -> Activity:
70
+ activity = DB.session.scalar(
71
+ sqlalchemy.select(Activity).where(Activity.id == int(id))
72
+ )
73
+ if activity is None:
74
+ raise ValueError(f"Cannot find activity {id} in DB.session.")
75
+ return activity
76
+
77
+ def get_time_series(self, id: int) -> pd.DataFrame:
78
+ return self.get_activity_by_id(id).time_series
79
+
80
+ @property
81
+ def meta(self) -> pd.DataFrame:
82
+ activities = self.iter_activities(new_to_old=False, drop_na=True)
83
+ df = pd.DataFrame([activity.to_dict() for activity in activities])
84
+ df["date"] = df["start"].dt.date
85
+ df["year"] = [start.year for start in df["start"]]
86
+ df["month"] = [start.month for start in df["start"]]
87
+ df["day"] = [start.day for start in df["start"]]
88
+ df["week"] = [start.isocalendar().week for start in df["start"]]
89
+ df["day_of_week"] = df["start"].dt.day_of_week
90
+ df["iso_year"] = [start.isocalendar().year for start in df["start"]]
91
+ df["hours"] = [
92
+ elapsed_time.total_seconds() / 3600 for elapsed_time in df["elapsed_time"]
93
+ ]
94
+ df["hours_moving"] = [
95
+ moving_time.total_seconds() / 3600 for moving_time in df["moving_time"]
96
+ ]
97
+ df.index = df["id"]
98
+ return df
99
+
100
+
101
+ def make_geojson_from_time_series(time_series: pd.DataFrame) -> str:
102
+ fc = geojson.FeatureCollection(
103
+ features=[
104
+ geojson.LineString(
105
+ [(lon, lat) for lat, lon in zip(group["latitude"], group["longitude"])]
106
+ )
107
+ for _, group in time_series.groupby("segment_id")
108
+ ]
109
+ )
110
+ return geojson.dumps(fc)
111
+
112
+
113
+ def inter_quartile_range(values):
114
+ return np.quantile(values, 0.75) - np.quantile(values, 0.25)
115
+
116
+
117
+ def make_geojson_color_line(time_series: pd.DataFrame) -> str:
118
+ low, high, clamp_speed = _make_speed_clamp(time_series["speed"])
119
+ cmap = matplotlib.colormaps["viridis"]
120
+ features = [
121
+ geojson.Feature(
122
+ geometry=geojson.LineString(
123
+ coordinates=[
124
+ [row["longitude"], row["latitude"]],
125
+ [next_row["longitude"], next_row["latitude"]],
126
+ ]
127
+ ),
128
+ properties={
129
+ "speed": next_row["speed"] if np.isfinite(next_row["speed"]) else 0.0,
130
+ "color": matplotlib.colors.to_hex(cmap(clamp_speed(next_row["speed"]))),
131
+ },
132
+ )
133
+ for _, group in time_series.groupby("segment_id")
134
+ for (_, row), (_, next_row) in zip(group.iterrows(), group.iloc[1:].iterrows())
135
+ ]
136
+ feature_collection = geojson.FeatureCollection(features)
137
+ return geojson.dumps(feature_collection)
138
+
139
+
140
+ def make_speed_color_bar(time_series: pd.DataFrame) -> dict[str, Any]:
141
+ low, high, clamp_speed = _make_speed_clamp(time_series["speed"])
142
+ cmap = matplotlib.colormaps["viridis"]
143
+ colors = [
144
+ (f"{speed:.1f}", matplotlib.colors.to_hex(cmap(clamp_speed(speed))))
145
+ for speed in np.linspace(low, high, 10)
146
+ ]
147
+ return {"low": low, "high": high, "colors": colors}
148
+
149
+
150
+ def _make_speed_clamp(speeds: pd.Series) -> tuple[float, float, Callable]:
151
+ speed_without_na = speeds.dropna()
152
+ low = min(speed_without_na)
153
+ high = min(
154
+ max(speed_without_na),
155
+ np.median(speed_without_na) + 1.5 * inter_quartile_range(speed_without_na),
156
+ )
157
+ return low, high, lambda speed: min(max((speed - low) / (high - low), 0.0), 1.0)
@@ -5,14 +5,14 @@ import logging
5
5
  import pathlib
6
6
  from typing import Optional
7
7
 
8
- from geo_activity_playground.core.paths import new_config_file
9
- from geo_activity_playground.core.paths import strava_dynamic_config_path
8
+ from .paths import new_config_file
9
+ from .paths import strava_dynamic_config_path
10
10
 
11
11
 
12
12
  try:
13
13
  import tomllib
14
14
  except ModuleNotFoundError:
15
- import tomli as tomllib
15
+ import tomli as tomllib # type: ignore
16
16
 
17
17
 
18
18
  logger = logging.getLogger(__name__)