meerschaum 2.9.0rc2__py3-none-any.whl → 2.9.1__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 (40) hide show
  1. meerschaum/api/dash/callbacks/__init__.py +5 -2
  2. meerschaum/api/dash/callbacks/custom.py +17 -25
  3. meerschaum/api/dash/callbacks/dashboard.py +5 -21
  4. meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
  5. meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
  6. meerschaum/api/dash/components.py +110 -7
  7. meerschaum/api/dash/pages/__init__.py +1 -0
  8. meerschaum/api/dash/pages/settings/__init__.py +8 -0
  9. meerschaum/api/dash/pages/settings/password_reset.py +63 -0
  10. meerschaum/api/resources/static/css/dash.css +7 -0
  11. meerschaum/api/routes/_pipes.py +76 -36
  12. meerschaum/config/_version.py +1 -1
  13. meerschaum/connectors/__init__.py +1 -0
  14. meerschaum/connectors/api/_pipes.py +79 -30
  15. meerschaum/connectors/sql/_pipes.py +38 -5
  16. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -0
  17. meerschaum/connectors/valkey/_pipes.py +51 -39
  18. meerschaum/core/Pipe/__init__.py +1 -0
  19. meerschaum/core/Pipe/_sync.py +64 -4
  20. meerschaum/plugins/__init__.py +26 -4
  21. meerschaum/utils/dataframe.py +58 -3
  22. meerschaum/utils/dtypes/__init__.py +45 -17
  23. meerschaum/utils/dtypes/sql.py +182 -3
  24. meerschaum/utils/misc.py +1 -1
  25. meerschaum/utils/packages/_packages.py +6 -3
  26. meerschaum/utils/sql.py +122 -6
  27. meerschaum/utils/venv/__init__.py +4 -1
  28. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/METADATA +14 -9
  29. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/RECORD +35 -36
  30. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/WHEEL +1 -1
  31. meerschaum/_internal/gui/__init__.py +0 -43
  32. meerschaum/_internal/gui/app/__init__.py +0 -50
  33. meerschaum/_internal/gui/app/_windows.py +0 -74
  34. meerschaum/_internal/gui/app/actions.py +0 -30
  35. meerschaum/_internal/gui/app/pipes.py +0 -47
  36. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/LICENSE +0 -0
  37. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/NOTICE +0 -0
  38. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/entry_points.txt +0 -0
  39. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/top_level.txt +0 -0
  40. {meerschaum-2.9.0rc2.dist-info → meerschaum-2.9.1.dist-info}/zip-safe +0 -0
@@ -91,8 +91,8 @@ def make_action(
91
91
 
92
92
 
93
93
  def pre_sync_hook(
94
- function: Callable[[Any], Any],
95
- ) -> Callable[[Any], Any]:
94
+ function: Callable[[Any], Any],
95
+ ) -> Callable[[Any], Any]:
96
96
  """
97
97
  Register a function as a sync hook to be executed right before sync.
98
98
 
@@ -169,6 +169,7 @@ def web_page(
169
169
  page: Union[str, None, Callable[[Any], Any]] = None,
170
170
  login_required: bool = True,
171
171
  skip_navbar: bool = False,
172
+ page_group: Optional[str] = None,
172
173
  **kwargs
173
174
  ) -> Any:
174
175
  """
@@ -188,7 +189,7 @@ def web_page(
188
189
  page_str = None
189
190
 
190
191
  def _decorator(_func: Callable[[Any], Any]) -> Callable[[Any], Any]:
191
- nonlocal page_str
192
+ nonlocal page_str, page_group
192
193
 
193
194
  @functools.wraps(_func)
194
195
  def wrapper(*_args, **_kwargs):
@@ -198,10 +199,31 @@ def web_page(
198
199
  page_str = _func.__name__
199
200
 
200
201
  page_str = page_str.lstrip('/').rstrip('/').strip()
201
- _plugin_endpoints_to_pages[page_str] = {
202
+ page_key = (
203
+ ' '.join(
204
+ [
205
+ word.capitalize()
206
+ for word in (
207
+ page_str.replace('/dash', '').lstrip('/').rstrip('/').strip()
208
+ .replace('-', ' ').replace('_', ' ').split(' ')
209
+ )
210
+ ]
211
+ )
212
+ )
213
+
214
+ package_name = _func.__globals__['__name__']
215
+ plugin_name = (
216
+ package_name.split('.')[1]
217
+ if package_name.startswith('plugins.') else None
218
+ )
219
+ page_group = page_group or plugin_name
220
+ if page_group not in _plugin_endpoints_to_pages:
221
+ _plugin_endpoints_to_pages[page_group] = {}
222
+ _plugin_endpoints_to_pages[page_group][page_str] = {
202
223
  'function': _func,
203
224
  'login_required': login_required,
204
225
  'skip_navbar': skip_navbar,
226
+ 'page_key': page_key,
205
227
  }
206
228
  return wrapper
207
229
 
@@ -871,7 +871,10 @@ def get_bytes_cols(df: 'pd.DataFrame') -> List[str]:
871
871
  ]
872
872
 
873
873
 
874
- def get_geometry_cols(df: 'pd.DataFrame') -> List[str]:
874
+ def get_geometry_cols(
875
+ df: 'pd.DataFrame',
876
+ with_types_srids: bool = False,
877
+ ) -> Union[List[str], Dict[str, Any]]:
875
878
  """
876
879
  Get the columns which contain shapely objects from a Pandas DataFrame.
877
880
 
@@ -880,9 +883,13 @@ def get_geometry_cols(df: 'pd.DataFrame') -> List[str]:
880
883
  df: pd.DataFrame
881
884
  The DataFrame which may contain bytes strings.
882
885
 
886
+ with_types_srids: bool, default False
887
+ If `True`, return a dictionary mapping columns to geometry types and SRIDs.
888
+
883
889
  Returns
884
890
  -------
885
891
  A list of columns to treat as `geometry`.
892
+ If `with_types_srids`, return a dictionary mapping columns to tuples in the form (type, SRID).
886
893
  """
887
894
  if df is None:
888
895
  return []
@@ -898,7 +905,7 @@ def get_geometry_cols(df: 'pd.DataFrame') -> List[str]:
898
905
  col: df[col].first_valid_index()
899
906
  for col in df.columns
900
907
  }
901
- return [
908
+ geo_cols = [
902
909
  col
903
910
  for col, ix in cols_indices.items()
904
911
  if (
@@ -907,6 +914,38 @@ def get_geometry_cols(df: 'pd.DataFrame') -> List[str]:
907
914
  'shapely' in str(type(df.loc[ix][col]))
908
915
  )
909
916
  ]
917
+ if not with_types_srids:
918
+ return geo_cols
919
+
920
+ gpd = mrsm.attempt_import('geopandas', lazy=False)
921
+ geo_cols_types_srids = {}
922
+ for col in geo_cols:
923
+ try:
924
+ sample_geo_series = gpd.GeoSeries(df[col], crs=None)
925
+ geometry_types = {
926
+ geom.geom_type
927
+ for geom in sample_geo_series
928
+ if hasattr(geom, 'geom_type')
929
+ }
930
+ geometry_has_z = any(getattr(geom, 'has_z', False) for geom in sample_geo_series)
931
+ srid = (
932
+ (
933
+ sample_geo_series.crs.sub_crs_list[0].to_epsg()
934
+ if sample_geo_series.crs.is_compound
935
+ else sample_geo_series.crs.to_epsg()
936
+ )
937
+ if sample_geo_series.crs
938
+ else 0
939
+ )
940
+ geometry_type = list(geometry_types)[0] if len(geometry_types) == 1 else 'geometry'
941
+ if geometry_type != 'geometry' and geometry_has_z:
942
+ geometry_type = geometry_type + 'Z'
943
+ except Exception:
944
+ srid = 0
945
+ geometry_type = 'geometry'
946
+ geo_cols_types_srids[col] = (geometry_type, srid)
947
+
948
+ return geo_cols_types_srids
910
949
 
911
950
 
912
951
  def enforce_dtypes(
@@ -1658,6 +1697,8 @@ def to_json(
1658
1697
  orient: str = 'records',
1659
1698
  date_format: str = 'iso',
1660
1699
  date_unit: str = 'us',
1700
+ double_precision: int = 15,
1701
+ geometry_format: str = 'geojson',
1661
1702
  **kwargs: Any
1662
1703
  ) -> str:
1663
1704
  """
@@ -1677,11 +1718,19 @@ def to_json(
1677
1718
  date_unit: str, default 'us'
1678
1719
  The precision of the timestamps.
1679
1720
 
1721
+ double_precision: int, default 15
1722
+ The number of decimal places to use when encoding floating point values (maximum 15).
1723
+
1724
+ geometry_format: str, default 'geojson'
1725
+ The serialization format for geometry data.
1726
+ Accepted values are `geojson`, `wkb_hex`, and `wkt`.
1727
+
1680
1728
  Returns
1681
1729
  -------
1682
1730
  A JSON string.
1683
1731
  """
1684
1732
  import warnings
1733
+ import functools
1685
1734
  from meerschaum.utils.packages import import_pandas
1686
1735
  from meerschaum.utils.dtypes import (
1687
1736
  serialize_bytes,
@@ -1704,10 +1753,16 @@ def to_json(
1704
1753
  with warnings.catch_warnings():
1705
1754
  warnings.simplefilter("ignore")
1706
1755
  for col in geometry_cols:
1707
- df[col] = df[col].apply(serialize_geometry)
1756
+ df[col] = df[col].apply(
1757
+ functools.partial(
1758
+ serialize_geometry,
1759
+ geometry_format=geometry_format,
1760
+ )
1761
+ )
1708
1762
  return df.infer_objects(copy=False).fillna(pd.NA).to_json(
1709
1763
  date_format=date_format,
1710
1764
  date_unit=date_unit,
1765
+ double_precision=double_precision,
1711
1766
  orient=orient,
1712
1767
  **kwargs
1713
1768
  )
@@ -7,6 +7,7 @@ Utility functions for working with data types.
7
7
  """
8
8
 
9
9
  import traceback
10
+ import json
10
11
  import uuid
11
12
  from datetime import timezone, datetime
12
13
  from decimal import Decimal, Context, InvalidOperation, ROUND_HALF_UP
@@ -28,6 +29,7 @@ MRSM_ALIAS_DTYPES: Dict[str, str] = {
28
29
  'guid': 'uuid',
29
30
  'UUID': 'uuid',
30
31
  'geom': 'geometry',
32
+ 'geog': 'geography',
31
33
  }
32
34
  MRSM_PD_DTYPES: Dict[Union[str, None], str] = {
33
35
  'json': 'object',
@@ -76,6 +78,7 @@ def to_pandas_dtype(dtype: str) -> str:
76
78
  return get_pd_type_from_db_type(dtype)
77
79
 
78
80
  from meerschaum.utils.packages import attempt_import
81
+ _ = attempt_import('pyarrow', lazy=False)
79
82
  pandas = attempt_import('pandas', lazy=False)
80
83
 
81
84
  try:
@@ -294,10 +297,21 @@ def attempt_cast_to_geometry(value: Any) -> Any:
294
297
  """
295
298
  Given a value, attempt to coerce it into a `shapely` (`geometry`) object.
296
299
  """
297
- shapely_wkt, shapely_wkb = mrsm.attempt_import('shapely.wkt', 'shapely.wkb', lazy=False)
300
+ shapely, shapely_wkt, shapely_wkb = mrsm.attempt_import(
301
+ 'shapely',
302
+ 'shapely.wkt',
303
+ 'shapely.wkb',
304
+ lazy=False,
305
+ )
298
306
  if 'shapely' in str(type(value)):
299
307
  return value
300
308
 
309
+ if isinstance(value, (dict, list)):
310
+ try:
311
+ return shapely.from_geojson(json.dumps(value))
312
+ except Exception as e:
313
+ return value
314
+
301
315
  value_is_wkt = geometry_is_wkt(value)
302
316
  if value_is_wkt is None:
303
317
  return value
@@ -327,6 +341,9 @@ def geometry_is_wkt(value: Union[str, bytes]) -> Union[bool, None]:
327
341
  Return `None` if `value` should be parsed as neither.
328
342
  """
329
343
  import re
344
+ if not isinstance(value, (str, bytes)):
345
+ return None
346
+
330
347
  if isinstance(value, bytes):
331
348
  return False
332
349
 
@@ -521,7 +538,11 @@ def serialize_bytes(data: bytes) -> str:
521
538
  return base64.b64encode(data).decode('utf-8')
522
539
 
523
540
 
524
- def serialize_geometry(geom: Any, as_wkt: bool = False) -> str:
541
+ def serialize_geometry(
542
+ geom: Any,
543
+ geometry_format: str = 'wkb_hex',
544
+ as_wkt: bool = False,
545
+ ) -> Union[str, Dict[str, Any]]:
525
546
  """
526
547
  Serialize geometry data as a hex-encoded well-known-binary string.
527
548
 
@@ -530,16 +551,22 @@ def serialize_geometry(geom: Any, as_wkt: bool = False) -> str:
530
551
  geom: Any
531
552
  The potential geometry data to be serialized.
532
553
 
533
- as_wkt, bool, default False
534
- If `True`, serialize geometry data as well-known text (WKT)
535
- instead of well-known binary (WKB).
554
+ geometry_format: str, default 'wkb_hex'
555
+ The serialization format for geometry data.
556
+ Accepted formats are `wkb_hex` (well-known binary hex string),
557
+ `wkt` (well-known text), and `geojson`.
536
558
 
537
559
  Returns
538
560
  -------
539
561
  A string containing the geometry data.
540
562
  """
563
+ shapely = mrsm.attempt_import('shapely', lazy=False)
564
+ if geometry_format == 'geojson':
565
+ geojson_str = shapely.to_geojson(geom)
566
+ return json.loads(geojson_str)
567
+
541
568
  if hasattr(geom, 'wkb_hex'):
542
- return geom.wkb_hex if not as_wkt else geom.wkt
569
+ return geom.wkb_hex if geometry_format == 'wkb_hex' else geom.wkt
543
570
 
544
571
  return str(geom)
545
572
 
@@ -711,24 +738,25 @@ def get_geometry_type_srid(
711
738
  ('Point', 4376)
712
739
  """
713
740
  from meerschaum.utils.misc import is_int
741
+ ### NOTE: PostGIS syntax must also be parsed.
742
+ dtype = dtype.replace('(', '[').replace(')', ']')
714
743
  bare_dtype = dtype.split('[', maxsplit=1)[0]
715
744
  modifier = dtype.split(bare_dtype, maxsplit=1)[-1].lstrip('[').rstrip(']')
716
745
  if not modifier:
717
746
  return default_type, default_srid
718
747
 
719
- shapely_geometry_base = mrsm.attempt_import('shapely.geometry.base')
720
- geometry_types = {
721
- typ.lower(): typ
722
- for typ in shapely_geometry_base.GEOMETRY_TYPES
723
- }
724
-
725
- parts = [part.lower().replace('srid=', '').replace('type=', '').strip() for part in modifier.split(',')]
748
+ parts = [
749
+ part.split('=')[-1].strip()
750
+ for part in modifier.split(',')
751
+ ]
726
752
  parts_casted = [
727
753
  (
728
754
  int(part)
729
755
  if is_int(part)
730
756
  else part
731
- ) for part in parts]
757
+ )
758
+ for part in parts
759
+ ]
732
760
 
733
761
  srid = default_srid
734
762
  geometry_type = default_type
@@ -738,9 +766,9 @@ def get_geometry_type_srid(
738
766
  srid = part
739
767
  break
740
768
 
741
- for part in parts:
742
- if part.lower() in geometry_types:
743
- geometry_type = geometry_types.get(part)
769
+ for part in parts_casted:
770
+ if isinstance(part, str):
771
+ geometry_type = part
744
772
  break
745
773
 
746
774
  return geometry_type, srid
@@ -74,10 +74,12 @@ DB_TO_PD_DTYPES: Dict[str, Union[str, Dict[str, str]]] = {
74
74
  'DOUBLE': 'float64[pyarrow]',
75
75
  'DECIMAL': 'numeric',
76
76
  'BIGINT': 'int64[pyarrow]',
77
- 'INT': 'int64[pyarrow]',
78
- 'INTEGER': 'int64[pyarrow]',
77
+ 'INT': 'int32[pyarrow]',
78
+ 'INTEGER': 'int32[pyarrow]',
79
79
  'NUMBER': 'numeric',
80
80
  'NUMERIC': 'numeric',
81
+ 'GEOMETRY': 'geometry',
82
+ 'GEOMETRY(GEOMETRY)': 'geometry',
81
83
  'TIMESTAMP': 'datetime64[ns]',
82
84
  'TIMESTAMP WITHOUT TIMEZONE': 'datetime64[ns]',
83
85
  'TIMESTAMP WITH TIMEZONE': 'datetime64[ns, UTC]',
@@ -120,6 +122,8 @@ DB_TO_PD_DTYPES: Dict[str, Union[str, Dict[str, str]]] = {
120
122
  'BYTE': 'bytes',
121
123
  'LOB': 'bytes',
122
124
  'BINARY': 'bytes',
125
+ 'GEOMETRY': 'geometry',
126
+ 'GEOGRAPHY': 'geography',
123
127
  },
124
128
  'default': 'object',
125
129
  }
@@ -139,6 +143,90 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
139
143
  'cockroachdb': 'BIGINT',
140
144
  'default': 'INT',
141
145
  },
146
+ 'uint': {
147
+ 'timescaledb': 'BIGINT',
148
+ 'postgresql': 'BIGINT',
149
+ 'postgis': 'BIGINT',
150
+ 'mariadb': 'BIGINT',
151
+ 'mysql': 'BIGINT',
152
+ 'mssql': 'BIGINT',
153
+ 'oracle': 'INT',
154
+ 'sqlite': 'BIGINT',
155
+ 'duckdb': 'BIGINT',
156
+ 'citus': 'BIGINT',
157
+ 'cockroachdb': 'BIGINT',
158
+ 'default': 'INT',
159
+ },
160
+ 'int8': {
161
+ 'timescaledb': 'SMALLINT',
162
+ 'postgresql': 'SMALLINT',
163
+ 'postgis': 'SMALLINT',
164
+ 'mariadb': 'SMALLINT',
165
+ 'mysql': 'SMALLINT',
166
+ 'mssql': 'SMALLINT',
167
+ 'oracle': 'INT',
168
+ 'sqlite': 'INT',
169
+ 'duckdb': 'SMALLINT',
170
+ 'citus': 'SMALLINT',
171
+ 'cockroachdb': 'SMALLINT',
172
+ 'default': 'INT',
173
+ },
174
+ 'uint8': {
175
+ 'timescaledb': 'SMALLINT',
176
+ 'postgresql': 'SMALLINT',
177
+ 'postgis': 'SMALLINT',
178
+ 'mariadb': 'SMALLINT',
179
+ 'mysql': 'SMALLINT',
180
+ 'mssql': 'SMALLINT',
181
+ 'oracle': 'INT',
182
+ 'sqlite': 'INT',
183
+ 'duckdb': 'SMALLINT',
184
+ 'citus': 'SMALLINT',
185
+ 'cockroachdb': 'SMALLINT',
186
+ 'default': 'INT',
187
+ },
188
+ 'int16': {
189
+ 'timescaledb': 'SMALLINT',
190
+ 'postgresql': 'SMALLINT',
191
+ 'postgis': 'SMALLINT',
192
+ 'mariadb': 'SMALLINT',
193
+ 'mysql': 'SMALLINT',
194
+ 'mssql': 'SMALLINT',
195
+ 'oracle': 'INT',
196
+ 'sqlite': 'INT',
197
+ 'duckdb': 'SMALLINT',
198
+ 'citus': 'SMALLINT',
199
+ 'cockroachdb': 'SMALLINT',
200
+ 'default': 'INT',
201
+ },
202
+ 'int32': {
203
+ 'timescaledb': 'INT',
204
+ 'postgresql': 'INT',
205
+ 'postgis': 'INT',
206
+ 'mariadb': 'INT',
207
+ 'mysql': 'INT',
208
+ 'mssql': 'INT',
209
+ 'oracle': 'INT',
210
+ 'sqlite': 'INT',
211
+ 'duckdb': 'INT',
212
+ 'citus': 'INT',
213
+ 'cockroachdb': 'INT',
214
+ 'default': 'INT',
215
+ },
216
+ 'int64': {
217
+ 'timescaledb': 'BIGINT',
218
+ 'postgresql': 'BIGINT',
219
+ 'postgis': 'BIGINT',
220
+ 'mariadb': 'BIGINT',
221
+ 'mysql': 'BIGINT',
222
+ 'mssql': 'BIGINT',
223
+ 'oracle': 'INT',
224
+ 'sqlite': 'BIGINT',
225
+ 'duckdb': 'BIGINT',
226
+ 'citus': 'BIGINT',
227
+ 'cockroachdb': 'BIGINT',
228
+ 'default': 'INT',
229
+ },
142
230
  'float': {
143
231
  'timescaledb': 'DOUBLE PRECISION',
144
232
  'postgresql': 'DOUBLE PRECISION',
@@ -380,6 +468,90 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
380
468
  'cockroachdb': 'BigInteger',
381
469
  'default': 'BigInteger',
382
470
  },
471
+ 'uint': {
472
+ 'timescaledb': 'BigInteger',
473
+ 'postgresql': 'BigInteger',
474
+ 'postgis': 'BigInteger',
475
+ 'mariadb': 'BigInteger',
476
+ 'mysql': 'BigInteger',
477
+ 'mssql': 'BigInteger',
478
+ 'oracle': 'BigInteger',
479
+ 'sqlite': 'BigInteger',
480
+ 'duckdb': 'BigInteger',
481
+ 'citus': 'BigInteger',
482
+ 'cockroachdb': 'BigInteger',
483
+ 'default': 'BigInteger',
484
+ },
485
+ 'int8': {
486
+ 'timescaledb': 'SmallInteger',
487
+ 'postgresql': 'SmallInteger',
488
+ 'postgis': 'SmallInteger',
489
+ 'mariadb': 'SmallInteger',
490
+ 'mysql': 'SmallInteger',
491
+ 'mssql': 'SmallInteger',
492
+ 'oracle': 'SmallInteger',
493
+ 'sqlite': 'SmallInteger',
494
+ 'duckdb': 'SmallInteger',
495
+ 'citus': 'SmallInteger',
496
+ 'cockroachdb': 'SmallInteger',
497
+ 'default': 'SmallInteger',
498
+ },
499
+ 'uint8': {
500
+ 'timescaledb': 'SmallInteger',
501
+ 'postgresql': 'SmallInteger',
502
+ 'postgis': 'SmallInteger',
503
+ 'mariadb': 'SmallInteger',
504
+ 'mysql': 'SmallInteger',
505
+ 'mssql': 'SmallInteger',
506
+ 'oracle': 'SmallInteger',
507
+ 'sqlite': 'SmallInteger',
508
+ 'duckdb': 'SmallInteger',
509
+ 'citus': 'SmallInteger',
510
+ 'cockroachdb': 'SmallInteger',
511
+ 'default': 'SmallInteger',
512
+ },
513
+ 'int16': {
514
+ 'timescaledb': 'SmallInteger',
515
+ 'postgresql': 'SmallInteger',
516
+ 'postgis': 'SmallInteger',
517
+ 'mariadb': 'SmallInteger',
518
+ 'mysql': 'SmallInteger',
519
+ 'mssql': 'SmallInteger',
520
+ 'oracle': 'SmallInteger',
521
+ 'sqlite': 'SmallInteger',
522
+ 'duckdb': 'SmallInteger',
523
+ 'citus': 'SmallInteger',
524
+ 'cockroachdb': 'SmallInteger',
525
+ 'default': 'SmallInteger',
526
+ },
527
+ 'int32': {
528
+ 'timescaledb': 'Integer',
529
+ 'postgresql': 'Integer',
530
+ 'postgis': 'Integer',
531
+ 'mariadb': 'Integer',
532
+ 'mysql': 'Integer',
533
+ 'mssql': 'Integer',
534
+ 'oracle': 'Integer',
535
+ 'sqlite': 'Integer',
536
+ 'duckdb': 'Integer',
537
+ 'citus': 'Integer',
538
+ 'cockroachdb': 'Integer',
539
+ 'default': 'Integer',
540
+ },
541
+ 'int64': {
542
+ 'timescaledb': 'BigInteger',
543
+ 'postgresql': 'BigInteger',
544
+ 'postgis': 'BigInteger',
545
+ 'mariadb': 'BigInteger',
546
+ 'mysql': 'BigInteger',
547
+ 'mssql': 'BigInteger',
548
+ 'oracle': 'BigInteger',
549
+ 'sqlite': 'BigInteger',
550
+ 'duckdb': 'BigInteger',
551
+ 'citus': 'BigInteger',
552
+ 'cockroachdb': 'BigInteger',
553
+ 'default': 'BigInteger',
554
+ },
383
555
  'float': {
384
556
  'timescaledb': 'Float',
385
557
  'postgresql': 'Float',
@@ -596,7 +768,7 @@ def get_pd_type_from_db_type(db_type: str, allow_custom_dtypes: bool = True) ->
596
768
  -------
597
769
  The equivalent datatype for a pandas DataFrame.
598
770
  """
599
- from meerschaum.utils.dtypes import are_dtypes_equal
771
+ from meerschaum.utils.dtypes import are_dtypes_equal, get_geometry_type_srid
600
772
  def parse_custom(_pd_type: str, _db_type: str) -> str:
601
773
  if 'json' in _db_type.lower():
602
774
  return 'json'
@@ -604,6 +776,13 @@ def get_pd_type_from_db_type(db_type: str, allow_custom_dtypes: bool = True) ->
604
776
  precision, scale = get_numeric_precision_scale(None, dtype=_db_type.upper())
605
777
  if precision and scale:
606
778
  return f"numeric[{precision},{scale}]"
779
+ if are_dtypes_equal(_pd_type, 'geometry') and _pd_type != 'object':
780
+ geometry_type, srid = get_geometry_type_srid(_db_type.upper())
781
+ modifiers = [str(modifier) for modifier in (geometry_type, srid) if modifier]
782
+ typ = "geometry" if 'geography' not in _pd_type.lower() else 'geography'
783
+ if not modifiers:
784
+ return typ
785
+ return f"{typ}[{', '.join(modifiers)}]"
607
786
  return _pd_type
608
787
 
609
788
  pd_type = DB_TO_PD_DTYPES.get(db_type.upper().split('(', maxsplit=1)[0].strip(), None)
meerschaum/utils/misc.py CHANGED
@@ -175,7 +175,7 @@ def string_to_dict(
175
175
  import ast
176
176
  params_dict = {}
177
177
  for param in params_string.split(","):
178
- _keys = param.split(":")
178
+ _keys = param.split(":", maxsplit=1)
179
179
  keys = _keys[:-1]
180
180
  try:
181
181
  val = ast.literal_eval(_keys[-1])
@@ -88,6 +88,11 @@ packages: Dict[str, Dict[str, str]] = {
88
88
  'mssqlcli' : 'mssql-cli>=1.0.0',
89
89
  'gadwall' : 'gadwall>=0.2.0',
90
90
  },
91
+ 'gis' : {
92
+ 'pyproj' : 'pyproj>=3.7.1',
93
+ 'geopandas' : 'geopandas>=1.0.1',
94
+ 'shapely' : 'shapely>=2.0.7',
95
+ },
91
96
  'stack': {
92
97
  'compose' : 'docker-compose>=1.29.2',
93
98
  },
@@ -120,7 +125,6 @@ packages: Dict[str, Dict[str, str]] = {
120
125
  'jinja2' : 'jinja2==3.0.3',
121
126
  },
122
127
  'gui': {
123
- 'toga' : 'toga>=0.3.0-dev29',
124
128
  'webview' : 'pywebview>=3.6.3',
125
129
  'pycparser' : 'pycparser>=2.21.0',
126
130
  },
@@ -135,8 +139,6 @@ packages: Dict[str, Dict[str, str]] = {
135
139
  packages['sql'] = {
136
140
  'numpy' : 'numpy>=1.18.5',
137
141
  'pandas' : 'pandas[parquet]>=2.0.1',
138
- 'geopandas' : 'geopandas>=1.0.1',
139
- 'shapely' : 'shapely>=2.0.7',
140
142
  'pyarrow' : 'pyarrow>=16.1.0',
141
143
  'dask' : 'dask[complete]>=2024.12.1',
142
144
  'partd' : 'partd>=1.4.2',
@@ -150,6 +152,7 @@ packages['sql'] = {
150
152
  }
151
153
  packages['sql'].update(packages['drivers'])
152
154
  packages['sql'].update(packages['core'])
155
+ packages['sql'].update(packages['gis'])
153
156
  packages['dash'] = {
154
157
  'flask_compress' : 'Flask-Compress>=1.10.1',
155
158
  'dash' : 'dash>=2.6.2',