meerschaum 2.8.3__py3-none-any.whl → 2.9.0__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.
Files changed (66) hide show
  1. meerschaum/_internal/arguments/_parser.py +5 -0
  2. meerschaum/actions/drop.py +1 -1
  3. meerschaum/actions/start.py +14 -6
  4. meerschaum/actions/sync.py +9 -0
  5. meerschaum/api/__init__.py +9 -3
  6. meerschaum/api/_chunks.py +67 -0
  7. meerschaum/api/dash/callbacks/__init__.py +5 -2
  8. meerschaum/api/dash/callbacks/custom.py +21 -8
  9. meerschaum/api/dash/callbacks/dashboard.py +26 -4
  10. meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
  11. meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
  12. meerschaum/api/dash/components.py +136 -25
  13. meerschaum/api/dash/pages/__init__.py +1 -0
  14. meerschaum/api/dash/pages/dashboard.py +11 -9
  15. meerschaum/api/dash/pages/plugins.py +31 -27
  16. meerschaum/api/dash/pages/settings/__init__.py +8 -0
  17. meerschaum/api/dash/pages/settings/password_reset.py +63 -0
  18. meerschaum/api/dash/webterm.py +6 -3
  19. meerschaum/api/resources/static/css/dash.css +8 -1
  20. meerschaum/api/resources/templates/termpage.html +4 -0
  21. meerschaum/api/routes/_pipes.py +234 -82
  22. meerschaum/config/_default.py +4 -0
  23. meerschaum/config/_version.py +1 -1
  24. meerschaum/connectors/__init__.py +1 -0
  25. meerschaum/connectors/api/_APIConnector.py +12 -1
  26. meerschaum/connectors/api/_pipes.py +106 -45
  27. meerschaum/connectors/api/_plugins.py +51 -45
  28. meerschaum/connectors/api/_request.py +1 -1
  29. meerschaum/connectors/parse.py +1 -2
  30. meerschaum/connectors/sql/_SQLConnector.py +4 -1
  31. meerschaum/connectors/sql/_cli.py +1 -0
  32. meerschaum/connectors/sql/_create_engine.py +51 -4
  33. meerschaum/connectors/sql/_pipes.py +38 -6
  34. meerschaum/connectors/sql/_sql.py +35 -4
  35. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -0
  36. meerschaum/connectors/valkey/_pipes.py +51 -39
  37. meerschaum/core/Pipe/__init__.py +1 -0
  38. meerschaum/core/Pipe/_data.py +1 -2
  39. meerschaum/core/Pipe/_sync.py +64 -4
  40. meerschaum/core/Pipe/_verify.py +23 -8
  41. meerschaum/jobs/systemd.py +1 -1
  42. meerschaum/plugins/_Plugin.py +21 -5
  43. meerschaum/plugins/__init__.py +32 -8
  44. meerschaum/utils/dataframe.py +139 -2
  45. meerschaum/utils/dtypes/__init__.py +211 -1
  46. meerschaum/utils/dtypes/sql.py +296 -5
  47. meerschaum/utils/formatting/_shell.py +1 -4
  48. meerschaum/utils/misc.py +1 -1
  49. meerschaum/utils/packages/_packages.py +8 -2
  50. meerschaum/utils/process.py +27 -3
  51. meerschaum/utils/schedule.py +3 -3
  52. meerschaum/utils/sql.py +140 -12
  53. meerschaum/utils/venv/__init__.py +10 -2
  54. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/METADATA +17 -3
  55. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/RECORD +61 -61
  56. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/WHEEL +1 -1
  57. meerschaum/_internal/gui/__init__.py +0 -43
  58. meerschaum/_internal/gui/app/__init__.py +0 -50
  59. meerschaum/_internal/gui/app/_windows.py +0 -74
  60. meerschaum/_internal/gui/app/actions.py +0 -30
  61. meerschaum/_internal/gui/app/pipes.py +0 -47
  62. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/LICENSE +0 -0
  63. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/NOTICE +0 -0
  64. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/entry_points.txt +0 -0
  65. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/top_level.txt +0 -0
  66. {meerschaum-2.8.3.dist-info → meerschaum-2.9.0.dist-info}/zip-safe +0 -0
@@ -95,7 +95,7 @@ def schedule_function(
95
95
  A `SuccessTuple` upon exit.
96
96
  """
97
97
  import asyncio
98
- from meerschaum.utils.misc import filter_keywords, round_time
98
+ from meerschaum.utils.misc import filter_keywords
99
99
 
100
100
  global _scheduler
101
101
  kw['debug'] = debug
@@ -103,7 +103,7 @@ def schedule_function(
103
103
 
104
104
  _ = mrsm.attempt_import('attrs', lazy=False)
105
105
  apscheduler = mrsm.attempt_import('apscheduler', lazy=False)
106
- now = round_time(datetime.now(timezone.utc), timedelta(minutes=1))
106
+ now = datetime.now(timezone.utc)
107
107
  trigger = parse_schedule(schedule, now=now)
108
108
  _scheduler = apscheduler.AsyncScheduler(identity='mrsm-scheduler')
109
109
  try:
@@ -296,7 +296,7 @@ def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
296
296
  dateutil_parser = mrsm.attempt_import('dateutil.parser')
297
297
  starting_parts = schedule.split(STARTING_KEYWORD)
298
298
  starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()
299
- now = now or round_time(datetime.now(timezone.utc), timedelta(minutes=1))
299
+ now = now or datetime.now(timezone.utc)
300
300
  try:
301
301
  if starting_str == 'now':
302
302
  starting_ts = now
meerschaum/utils/sql.py CHANGED
@@ -41,13 +41,13 @@ version_queries = {
41
41
  }
42
42
  SKIP_IF_EXISTS_FLAVORS = {'mssql', 'oracle'}
43
43
  DROP_IF_EXISTS_FLAVORS = {
44
- 'timescaledb', 'postgresql', 'citus', 'mssql', 'mysql', 'mariadb', 'sqlite',
44
+ 'timescaledb', 'postgresql', 'postgis', 'citus', 'mssql', 'mysql', 'mariadb', 'sqlite',
45
45
  }
46
46
  DROP_INDEX_IF_EXISTS_FLAVORS = {
47
- 'mssql', 'timescaledb', 'postgresql', 'sqlite', 'citus',
47
+ 'mssql', 'timescaledb', 'postgresql', 'postgis', 'sqlite', 'citus',
48
48
  }
49
49
  SKIP_AUTO_INCREMENT_FLAVORS = {'citus', 'duckdb'}
50
- COALESCE_UNIQUE_INDEX_FLAVORS = {'timescaledb', 'postgresql', 'citus'}
50
+ COALESCE_UNIQUE_INDEX_FLAVORS = {'timescaledb', 'postgresql', 'postgis', 'citus'}
51
51
  UPDATE_QUERIES = {
52
52
  'default': """
53
53
  UPDATE {target_table_name} AS f
@@ -73,6 +73,12 @@ UPDATE_QUERIES = {
73
73
  FROM {patch_table_name}
74
74
  ON CONFLICT ({join_cols_str}) DO {update_or_nothing} {sets_subquery_none_excluded}
75
75
  """,
76
+ 'postgis-upsert': """
77
+ INSERT INTO {target_table_name} ({patch_cols_str})
78
+ SELECT {patch_cols_str}
79
+ FROM {patch_table_name}
80
+ ON CONFLICT ({join_cols_str}) DO {update_or_nothing} {sets_subquery_none_excluded}
81
+ """,
76
82
  'citus-upsert': """
77
83
  INSERT INTO {target_table_name} ({patch_cols_str})
78
84
  SELECT {patch_cols_str}
@@ -482,6 +488,7 @@ table_wrappers = {
482
488
  'citus' : ('"', '"'),
483
489
  'duckdb' : ('"', '"'),
484
490
  'postgresql' : ('"', '"'),
491
+ 'postgis' : ('"', '"'),
485
492
  'sqlite' : ('"', '"'),
486
493
  'mysql' : ('`', '`'),
487
494
  'mariadb' : ('`', '`'),
@@ -494,6 +501,7 @@ max_name_lens = {
494
501
  'mssql' : 128,
495
502
  'oracle' : 30,
496
503
  'postgresql' : 64,
504
+ 'postgis' : 64,
497
505
  'timescaledb': 64,
498
506
  'citus' : 64,
499
507
  'cockroachdb': 64,
@@ -501,10 +509,11 @@ max_name_lens = {
501
509
  'mysql' : 64,
502
510
  'mariadb' : 64,
503
511
  }
504
- json_flavors = {'postgresql', 'timescaledb', 'citus', 'cockroachdb'}
512
+ json_flavors = {'postgresql', 'postgis', 'timescaledb', 'citus', 'cockroachdb'}
505
513
  NO_SCHEMA_FLAVORS = {'oracle', 'sqlite', 'mysql', 'mariadb', 'duckdb'}
506
514
  DEFAULT_SCHEMA_FLAVORS = {
507
515
  'postgresql': 'public',
516
+ 'postgis': 'public',
508
517
  'timescaledb': 'public',
509
518
  'citus': 'public',
510
519
  'cockroachdb': 'public',
@@ -519,7 +528,7 @@ NO_CTE_FLAVORS = {'mysql', 'mariadb'}
519
528
  NO_SELECT_INTO_FLAVORS = {'sqlite', 'oracle', 'mysql', 'mariadb', 'duckdb'}
520
529
 
521
530
 
522
- def clean(substring: str) -> str:
531
+ def clean(substring: str) -> None:
523
532
  """
524
533
  Ensure a substring is clean enough to be inserted into a SQL query.
525
534
  Raises an exception when banned words are used.
@@ -549,6 +558,7 @@ def dateadd_str(
549
558
  Currently supported flavors:
550
559
 
551
560
  - `'postgresql'`
561
+ - `'postgis'`
552
562
  - `'timescaledb'`
553
563
  - `'citus'`
554
564
  - `'cockroachdb'`
@@ -653,7 +663,7 @@ def dateadd_str(
653
663
  )
654
664
 
655
665
  da = ""
656
- if flavor in ('postgresql', 'timescaledb', 'cockroachdb', 'citus'):
666
+ if flavor in ('postgresql', 'postgis', 'timescaledb', 'cockroachdb', 'citus'):
657
667
  begin = (
658
668
  f"CAST({begin} AS {db_type})" if begin != 'now'
659
669
  else f"CAST(NOW() AT TIME ZONE 'utc' AS {db_type})"
@@ -922,6 +932,7 @@ def build_where(
922
932
  params: Dict[str, Any],
923
933
  connector: Optional[mrsm.connectors.sql.SQLConnector] = None,
924
934
  with_where: bool = True,
935
+ flavor: str = 'postgresql',
925
936
  ) -> str:
926
937
  """
927
938
  Build the `WHERE` clause based on the input criteria.
@@ -941,6 +952,9 @@ def build_where(
941
952
  with_where: bool, default True:
942
953
  If `True`, include the leading `'WHERE'` string.
943
954
 
955
+ flavor: str, default 'postgresql'
956
+ If `connector` is `None`, fall back to this flavor.
957
+
944
958
  Returns
945
959
  -------
946
960
  A `str` of the `WHERE` clause from the input `params` dictionary for the connector's flavor.
@@ -969,13 +983,11 @@ def build_where(
969
983
  warn(f"Aborting build_where() due to possible SQL injection.")
970
984
  return ''
971
985
 
972
- if connector is None:
973
- from meerschaum import get_connector
974
- connector = get_connector('sql')
986
+ query_flavor = getattr(connector, 'flavor', flavor) if connector is not None else flavor
975
987
  where = ""
976
988
  leading_and = "\n AND "
977
989
  for key, value in params.items():
978
- _key = sql_item_name(key, connector.flavor, None)
990
+ _key = sql_item_name(key, query_flavor, None)
979
991
  ### search across a list (i.e. IN syntax)
980
992
  if isinstance(value, Iterable) and not isinstance(value, (dict, str)):
981
993
  includes = [
@@ -1286,7 +1298,24 @@ def get_table_cols_types(
1286
1298
  if cols_types_docs and not cols_types_docs_filtered:
1287
1299
  cols_types_docs_filtered = cols_types_docs
1288
1300
 
1289
- return {
1301
+ ### NOTE: Check for PostGIS GEOMETRY columns.
1302
+ geometry_cols_types = {}
1303
+ user_defined_cols = [
1304
+ doc
1305
+ for doc in cols_types_docs_filtered
1306
+ if str(doc.get('type', None)).upper() == 'USER-DEFINED'
1307
+ ]
1308
+ if user_defined_cols:
1309
+ geometry_cols_types.update(
1310
+ get_postgis_geo_columns_types(
1311
+ connectable,
1312
+ table,
1313
+ schema=schema,
1314
+ debug=debug,
1315
+ )
1316
+ )
1317
+
1318
+ cols_types = {
1290
1319
  (
1291
1320
  doc['column']
1292
1321
  if flavor != 'oracle' else (
@@ -1307,6 +1336,8 @@ def get_table_cols_types(
1307
1336
  )
1308
1337
  for doc in cols_types_docs_filtered
1309
1338
  }
1339
+ cols_types.update(geometry_cols_types)
1340
+ return cols_types
1310
1341
  except Exception as e:
1311
1342
  warn(f"Failed to fetch columns for table '{table}':\n{e}")
1312
1343
  return {}
@@ -1548,7 +1579,7 @@ def get_update_queries(
1548
1579
  from meerschaum.utils.debug import dprint
1549
1580
  from meerschaum.utils.dtypes import are_dtypes_equal
1550
1581
  from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES, get_pd_type_from_db_type
1551
- flavor = flavor or (connectable.flavor if isinstance(connectable, SQLConnector) else None)
1582
+ flavor = flavor or getattr(connectable, 'flavor', None)
1552
1583
  if not flavor:
1553
1584
  raise ValueError("Provide a flavor if using a SQLAlchemy session.")
1554
1585
  if (
@@ -1809,6 +1840,8 @@ def get_null_replacement(typ: str, flavor: str) -> str:
1809
1840
  """
1810
1841
  from meerschaum.utils.dtypes import are_dtypes_equal
1811
1842
  from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES
1843
+ if 'geometry' in typ.lower():
1844
+ return '010100000000008058346FCDC100008058346FCDC1'
1812
1845
  if 'int' in typ.lower() or typ.lower() in ('numeric', 'number'):
1813
1846
  return '-987654321'
1814
1847
  if 'bool' in typ.lower() or typ.lower() == 'bit':
@@ -2493,3 +2526,98 @@ def get_reset_autoincrement_queries(
2493
2526
  )
2494
2527
  for query in reset_queries
2495
2528
  ]
2529
+
2530
+
2531
+ def get_postgis_geo_columns_types(
2532
+ connectable: Union[
2533
+ 'mrsm.connectors.sql.SQLConnector',
2534
+ 'sqlalchemy.orm.session.Session',
2535
+ 'sqlalchemy.engine.base.Engine'
2536
+ ],
2537
+ table: str,
2538
+ schema: Optional[str] = 'public',
2539
+ debug: bool = False,
2540
+ ) -> Dict[str, str]:
2541
+ """
2542
+ Return the
2543
+ """
2544
+ from meerschaum.utils.dtypes import get_geometry_type_srid
2545
+ default_type, default_srid = get_geometry_type_srid()
2546
+ default_type = default_type.upper()
2547
+
2548
+ clean(table)
2549
+ clean(str(schema))
2550
+ schema = schema or 'public'
2551
+ truncated_schema_name = truncate_item_name(schema, flavor='postgis')
2552
+ truncated_table_name = truncate_item_name(table, flavor='postgis')
2553
+ query = (
2554
+ "SELECT \"f_geometry_column\" AS \"column\", 'GEOMETRY' AS \"func\", \"type\", \"srid\"\n"
2555
+ "FROM \"geometry_columns\"\n"
2556
+ f"WHERE \"f_table_schema\" = '{truncated_schema_name}'\n"
2557
+ f" AND \"f_table_name\" = '{truncated_table_name}'\n"
2558
+ "UNION ALL\n"
2559
+ "SELECT \"f_geography_column\" AS \"column\", 'GEOGRAPHY' AS \"func\", \"type\", \"srid\"\n"
2560
+ "FROM \"geography_columns\"\n"
2561
+ f"WHERE \"f_table_schema\" = '{truncated_schema_name}'\n"
2562
+ f" AND \"f_table_name\" = '{truncated_table_name}'\n"
2563
+ )
2564
+ debug_kwargs = {'debug': debug} if isinstance(connectable, mrsm.connectors.SQLConnector) else {}
2565
+ result_rows = [
2566
+ row
2567
+ for row in connectable.execute(query, **debug_kwargs).fetchall()
2568
+ ]
2569
+ cols_type_tuples = {
2570
+ row[0]: (row[1], row[2], row[3])
2571
+ for row in result_rows
2572
+ }
2573
+
2574
+ geometry_cols_types = {
2575
+ col: (
2576
+ f"{func}({typ.upper()}, {srid})"
2577
+ if srid
2578
+ else (
2579
+ func
2580
+ + (
2581
+ f'({typ.upper()})'
2582
+ if typ.upper() not in ('GEOMETRY', 'GEOGRAPHY')
2583
+ else ''
2584
+ )
2585
+ )
2586
+ )
2587
+ for col, (func, typ, srid) in cols_type_tuples.items()
2588
+ }
2589
+ return geometry_cols_types
2590
+
2591
+
2592
+ def get_create_schema_if_not_exists_queries(
2593
+ schema: str,
2594
+ flavor: str,
2595
+ ) -> List[str]:
2596
+ """
2597
+ Return the queries to create a schema if it does not yet exist.
2598
+ For databases which do not support schemas, an empty list will be returned.
2599
+ """
2600
+ if not schema:
2601
+ return []
2602
+
2603
+ if flavor in NO_SCHEMA_FLAVORS:
2604
+ return []
2605
+
2606
+ clean(schema)
2607
+
2608
+ if flavor == 'mssql':
2609
+ return [
2610
+ (
2611
+ f"IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = '{schema}')\n"
2612
+ "BEGIN\n"
2613
+ f" EXEC('CREATE SCHEMA {sql_item_name(schema, flavor)}');\n"
2614
+ "END;"
2615
+ )
2616
+ ]
2617
+
2618
+ if flavor == 'oracle':
2619
+ return []
2620
+
2621
+ return [
2622
+ f"CREATE SCHEMA IF NOT EXISTS {sql_item_name(schema, flavor)};"
2623
+ ]
@@ -86,7 +86,10 @@ def activate_venv(
86
86
  target = target_path.as_posix()
87
87
 
88
88
  if venv in active_venvs_order:
89
- sys.path.remove(target)
89
+ try:
90
+ sys.path.remove(target)
91
+ except Exception:
92
+ pass
90
93
  try:
91
94
  active_venvs_order.remove(venv)
92
95
  except Exception:
@@ -410,6 +413,8 @@ def init_venv(
410
413
  pass
411
414
 
412
415
  def wait_for_lock():
416
+ if platform.system() == 'Windows':
417
+ return
413
418
  max_lock_seconds = 30.0
414
419
  sleep_message_seconds = 5.0
415
420
  step_sleep_seconds = 0.1
@@ -595,7 +600,7 @@ def venv_exec(
595
600
  as_proc: bool = False,
596
601
  capture_output: bool = True,
597
602
  debug: bool = False,
598
- ) -> Union[bool, Tuple[int, bytes, bytes]]:
603
+ ) -> Union[bool, Tuple[int, bytes, bytes], 'subprocess.Popen']:
599
604
  """
600
605
  Execute Python code in a subprocess via a virtual environment's interpeter.
601
606
  Return `True` if the code successfully executes, `False` on failure.
@@ -630,6 +635,8 @@ def venv_exec(
630
635
  import subprocess
631
636
  import platform
632
637
  from meerschaum.utils.debug import dprint
638
+ from meerschaum.utils.process import _child_processes
639
+
633
640
  executable = venv_executable(venv=venv)
634
641
  cmd_list = [executable, '-c', code]
635
642
  if env is None:
@@ -656,6 +663,7 @@ def venv_exec(
656
663
  **group_kwargs
657
664
  )
658
665
  if as_proc:
666
+ _child_processes.append(process)
659
667
  return process
660
668
  stdout, stderr = process.communicate()
661
669
  exit_code = process.returncode
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: meerschaum
3
- Version: 2.8.3
3
+ Version: 2.9.0
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -87,6 +87,10 @@ Requires-Dist: mycli>=1.23.2; extra == "cli"
87
87
  Requires-Dist: litecli>=1.5.0; extra == "cli"
88
88
  Requires-Dist: mssql-cli>=1.0.0; extra == "cli"
89
89
  Requires-Dist: gadwall>=0.2.0; extra == "cli"
90
+ Provides-Extra: gis
91
+ Requires-Dist: pyproj>=3.7.1; extra == "gis"
92
+ Requires-Dist: geopandas>=1.0.1; extra == "gis"
93
+ Requires-Dist: shapely>=2.0.7; extra == "gis"
90
94
  Provides-Extra: stack
91
95
  Requires-Dist: docker-compose>=1.29.2; extra == "stack"
92
96
  Provides-Extra: build
@@ -114,7 +118,6 @@ Requires-Dist: mkdocs-linkcheck>=1.0.6; extra == "docs"
114
118
  Requires-Dist: mkdocs-redirects>=1.0.4; extra == "docs"
115
119
  Requires-Dist: jinja2==3.0.3; extra == "docs"
116
120
  Provides-Extra: gui
117
- Requires-Dist: toga>=0.3.0-dev29; extra == "gui"
118
121
  Requires-Dist: pywebview>=3.6.3; extra == "gui"
119
122
  Requires-Dist: pycparser>=2.21.0; extra == "gui"
120
123
  Provides-Extra: extras
@@ -132,6 +135,7 @@ Requires-Dist: partd>=1.4.2; extra == "sql"
132
135
  Requires-Dist: pytz; extra == "sql"
133
136
  Requires-Dist: joblib>=0.17.0; extra == "sql"
134
137
  Requires-Dist: SQLAlchemy>=2.0.5; extra == "sql"
138
+ Requires-Dist: GeoAlchemy2>=0.17.1; extra == "sql"
135
139
  Requires-Dist: databases>=0.4.0; extra == "sql"
136
140
  Requires-Dist: aiosqlite>=0.16.0; extra == "sql"
137
141
  Requires-Dist: asyncpg>=0.21.0; extra == "sql"
@@ -164,6 +168,9 @@ Requires-Dist: fasteners>=0.19.0; extra == "sql"
164
168
  Requires-Dist: virtualenv>=20.1.0; extra == "sql"
165
169
  Requires-Dist: attrs>=24.2.0; extra == "sql"
166
170
  Requires-Dist: uv>=0.2.11; extra == "sql"
171
+ Requires-Dist: pyproj>=3.7.1; extra == "sql"
172
+ Requires-Dist: geopandas>=1.0.1; extra == "sql"
173
+ Requires-Dist: shapely>=2.0.7; extra == "sql"
167
174
  Provides-Extra: dash
168
175
  Requires-Dist: Flask-Compress>=1.10.1; extra == "dash"
169
176
  Requires-Dist: dash>=2.6.2; extra == "dash"
@@ -192,6 +199,7 @@ Requires-Dist: partd>=1.4.2; extra == "api"
192
199
  Requires-Dist: pytz; extra == "api"
193
200
  Requires-Dist: joblib>=0.17.0; extra == "api"
194
201
  Requires-Dist: SQLAlchemy>=2.0.5; extra == "api"
202
+ Requires-Dist: GeoAlchemy2>=0.17.1; extra == "api"
195
203
  Requires-Dist: databases>=0.4.0; extra == "api"
196
204
  Requires-Dist: aiosqlite>=0.16.0; extra == "api"
197
205
  Requires-Dist: asyncpg>=0.21.0; extra == "api"
@@ -224,6 +232,9 @@ Requires-Dist: fasteners>=0.19.0; extra == "api"
224
232
  Requires-Dist: virtualenv>=20.1.0; extra == "api"
225
233
  Requires-Dist: attrs>=24.2.0; extra == "api"
226
234
  Requires-Dist: uv>=0.2.11; extra == "api"
235
+ Requires-Dist: pyproj>=3.7.1; extra == "api"
236
+ Requires-Dist: geopandas>=1.0.1; extra == "api"
237
+ Requires-Dist: shapely>=2.0.7; extra == "api"
227
238
  Requires-Dist: pprintpp>=0.4.0; extra == "api"
228
239
  Requires-Dist: asciitree>=0.3.3; extra == "api"
229
240
  Requires-Dist: typing-extensions>=4.7.1; extra == "api"
@@ -286,7 +297,9 @@ Requires-Dist: aiomysql>=0.0.21; extra == "full"
286
297
  Requires-Dist: sqlalchemy-cockroachdb>=2.0.0; extra == "full"
287
298
  Requires-Dist: duckdb>=1.0.0; extra == "full"
288
299
  Requires-Dist: duckdb-engine>=0.13.0; extra == "full"
289
- Requires-Dist: toga>=0.3.0-dev29; extra == "full"
300
+ Requires-Dist: pyproj>=3.7.1; extra == "full"
301
+ Requires-Dist: geopandas>=1.0.1; extra == "full"
302
+ Requires-Dist: shapely>=2.0.7; extra == "full"
290
303
  Requires-Dist: pywebview>=3.6.3; extra == "full"
291
304
  Requires-Dist: pycparser>=2.21.0; extra == "full"
292
305
  Requires-Dist: numpy>=1.18.5; extra == "full"
@@ -297,6 +310,7 @@ Requires-Dist: partd>=1.4.2; extra == "full"
297
310
  Requires-Dist: pytz; extra == "full"
298
311
  Requires-Dist: joblib>=0.17.0; extra == "full"
299
312
  Requires-Dist: SQLAlchemy>=2.0.5; extra == "full"
313
+ Requires-Dist: GeoAlchemy2>=0.17.1; extra == "full"
300
314
  Requires-Dist: databases>=0.4.0; extra == "full"
301
315
  Requires-Dist: aiosqlite>=0.16.0; extra == "full"
302
316
  Requires-Dist: asyncpg>=0.21.0; extra == "full"