meerschaum 2.9.0.dev1__py3-none-any.whl → 2.9.0rc2__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.
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/sql/_SQLConnector.py +3 -0
- meerschaum/connectors/sql/_cli.py +1 -0
- meerschaum/connectors/sql/_create_engine.py +51 -4
- meerschaum/connectors/sql/_pipes.py +13 -2
- meerschaum/connectors/sql/_sql.py +35 -4
- meerschaum/utils/dataframe.py +91 -2
- meerschaum/utils/dtypes/__init__.py +182 -1
- meerschaum/utils/dtypes/sql.py +114 -2
- meerschaum/utils/packages/_packages.py +2 -0
- meerschaum/utils/sql.py +17 -5
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/METADATA +7 -1
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/RECORD +20 -20
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/LICENSE +0 -0
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/NOTICE +0 -0
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/WHEEL +0 -0
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.0.dev1.dist-info → meerschaum-2.9.0rc2.dist-info}/zip-safe +0 -0
meerschaum/config/_default.py
CHANGED
meerschaum/config/_version.py
CHANGED
@@ -151,6 +151,9 @@ class SQLConnector(Connector):
|
|
151
151
|
if uri.startswith('timescaledb://'):
|
152
152
|
uri = uri.replace('timescaledb://', 'postgresql+psycopg://', 1)
|
153
153
|
flavor = 'timescaledb'
|
154
|
+
if uri.startswith('postgis://'):
|
155
|
+
uri = uri.replace('postgis://', 'postgresql+psycopg://', 1)
|
156
|
+
flavor = 'postgis'
|
154
157
|
kw['uri'] = uri
|
155
158
|
from_uri_params = self.from_uri(kw['uri'], as_dict=True)
|
156
159
|
label = label or from_uri_params.get('label', None)
|
@@ -7,6 +7,7 @@ This module contains the logic that builds the sqlalchemy engine string.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
import traceback
|
10
|
+
import meerschaum as mrsm
|
10
11
|
from meerschaum.utils.debug import dprint
|
11
12
|
|
12
13
|
### determine driver and requirements from flavor
|
@@ -47,6 +48,16 @@ flavor_configs = {
|
|
47
48
|
'port': 5432,
|
48
49
|
},
|
49
50
|
},
|
51
|
+
'postgis': {
|
52
|
+
'engine': 'postgresql+psycopg',
|
53
|
+
'create_engine': default_create_engine_args,
|
54
|
+
'omit_create_engine': {'method',},
|
55
|
+
'to_sql': {},
|
56
|
+
'requirements': default_requirements,
|
57
|
+
'defaults': {
|
58
|
+
'port': 5432,
|
59
|
+
},
|
60
|
+
},
|
50
61
|
'citus': {
|
51
62
|
'engine': 'postgresql+psycopg',
|
52
63
|
'create_engine': default_create_engine_args,
|
@@ -162,6 +173,7 @@ install_flavor_drivers = {
|
|
162
173
|
'mariadb': ['pymysql'],
|
163
174
|
'timescaledb': ['psycopg'],
|
164
175
|
'postgresql': ['psycopg'],
|
176
|
+
'postgis': ['psycopg', 'geoalchemy'],
|
165
177
|
'citus': ['psycopg'],
|
166
178
|
'cockroachdb': ['psycopg', 'sqlalchemy_cockroachdb', 'sqlalchemy_cockroachdb.psycopg'],
|
167
179
|
'mssql': ['pyodbc'],
|
@@ -198,8 +210,7 @@ def create_engine(
|
|
198
210
|
warn=False,
|
199
211
|
)
|
200
212
|
if self.flavor == 'mssql':
|
201
|
-
|
202
|
-
pyodbc.pooling = False
|
213
|
+
_init_mssql_sqlalchemy()
|
203
214
|
if self.flavor in require_patching_flavors:
|
204
215
|
from meerschaum.utils.packages import determine_version, _monkey_patch_get_distribution
|
205
216
|
import pathlib
|
@@ -257,8 +268,8 @@ def create_engine(
|
|
257
268
|
|
258
269
|
### Sometimes the timescaledb:// flavor can slip in.
|
259
270
|
if _uri and self.flavor in _uri:
|
260
|
-
if self.flavor
|
261
|
-
engine_str = engine_str.replace(
|
271
|
+
if self.flavor in ('timescaledb', 'postgis'):
|
272
|
+
engine_str = engine_str.replace(self.flavor, 'postgresql', 1)
|
262
273
|
elif _uri.startswith('postgresql://'):
|
263
274
|
engine_str = engine_str.replace('postgresql://', 'postgresql+psycopg2://')
|
264
275
|
|
@@ -313,3 +324,39 @@ def create_engine(
|
|
313
324
|
if include_uri:
|
314
325
|
return engine, engine_str
|
315
326
|
return engine
|
327
|
+
|
328
|
+
|
329
|
+
def _init_mssql_sqlalchemy():
|
330
|
+
"""
|
331
|
+
When first instantiating a SQLAlchemy connection to MSSQL,
|
332
|
+
monkey-patch `pyodbc` handling in SQLAlchemy.
|
333
|
+
"""
|
334
|
+
pyodbc, sqlalchemy_dialects_mssql_pyodbc = mrsm.attempt_import(
|
335
|
+
'pyodbc',
|
336
|
+
'sqlalchemy.dialects.mssql.pyodbc',
|
337
|
+
lazy=False,
|
338
|
+
warn=False,
|
339
|
+
)
|
340
|
+
pyodbc.pooling = False
|
341
|
+
|
342
|
+
MSDialect_pyodbc = sqlalchemy_dialects_mssql_pyodbc.MSDialect_pyodbc
|
343
|
+
|
344
|
+
def _handle_geometry(val):
|
345
|
+
from binascii import hexlify
|
346
|
+
hex_str = f"0x{hexlify(val).decode().upper()}"
|
347
|
+
return hex_str
|
348
|
+
|
349
|
+
def custom_on_connect(self):
|
350
|
+
super_ = super(MSDialect_pyodbc, self).on_connect()
|
351
|
+
|
352
|
+
def _on_connect(conn):
|
353
|
+
if super_ is not None:
|
354
|
+
super_(conn)
|
355
|
+
|
356
|
+
self._setup_timestampoffset_type(conn)
|
357
|
+
conn.add_output_converter(-151, _handle_geometry)
|
358
|
+
|
359
|
+
return _on_connect
|
360
|
+
|
361
|
+
### TODO: Parse proprietary MSSQL geometry bytes into WKB.
|
362
|
+
# MSDialect_pyodbc.on_connect = custom_on_connect
|
@@ -731,7 +731,7 @@ def get_create_index_queries(
|
|
731
731
|
) + f"{primary_key_name})"
|
732
732
|
),
|
733
733
|
])
|
734
|
-
elif self.flavor in ('citus', 'postgresql', 'duckdb'):
|
734
|
+
elif self.flavor in ('citus', 'postgresql', 'duckdb', 'postgis'):
|
735
735
|
primary_queries.extend([
|
736
736
|
(
|
737
737
|
f"ALTER TABLE {_pipe_name}\n"
|
@@ -1052,6 +1052,7 @@ def get_pipe_data(
|
|
1052
1052
|
attempt_cast_to_numeric,
|
1053
1053
|
attempt_cast_to_uuid,
|
1054
1054
|
attempt_cast_to_bytes,
|
1055
|
+
attempt_cast_to_geometry,
|
1055
1056
|
are_dtypes_equal,
|
1056
1057
|
)
|
1057
1058
|
from meerschaum.utils.dtypes.sql import get_pd_type_from_db_type
|
@@ -1138,6 +1139,11 @@ def get_pipe_data(
|
|
1138
1139
|
for col, typ in pipe.dtypes.items()
|
1139
1140
|
if typ == 'bytes' and col in dtypes
|
1140
1141
|
]
|
1142
|
+
geometry_columns = [
|
1143
|
+
col
|
1144
|
+
for col, typ in pipe.dtypes.items()
|
1145
|
+
if typ.startswith('geometry') and col in dtypes
|
1146
|
+
]
|
1141
1147
|
|
1142
1148
|
kw['coerce_float'] = kw.get('coerce_float', (len(numeric_columns) == 0))
|
1143
1149
|
|
@@ -1162,6 +1168,11 @@ def get_pipe_data(
|
|
1162
1168
|
continue
|
1163
1169
|
df[col] = df[col].apply(attempt_cast_to_bytes)
|
1164
1170
|
|
1171
|
+
for col in geometry_columns:
|
1172
|
+
if col not in df.columns:
|
1173
|
+
continue
|
1174
|
+
df[col] = df[col].apply(attempt_cast_to_geometry)
|
1175
|
+
|
1165
1176
|
if self.flavor == 'sqlite':
|
1166
1177
|
ignore_dt_cols = [
|
1167
1178
|
col
|
@@ -1511,7 +1522,7 @@ def get_pipe_attributes(
|
|
1511
1522
|
if isinstance(parameters, str) and parameters[0] == '{':
|
1512
1523
|
parameters = json.loads(parameters)
|
1513
1524
|
attributes['parameters'] = parameters
|
1514
|
-
except Exception
|
1525
|
+
except Exception:
|
1515
1526
|
attributes['parameters'] = {}
|
1516
1527
|
|
1517
1528
|
return attributes
|
@@ -17,7 +17,7 @@ from meerschaum.utils.debug import dprint
|
|
17
17
|
from meerschaum.utils.warnings import warn
|
18
18
|
|
19
19
|
### database flavors that can use bulk insert
|
20
|
-
_bulk_flavors = {'postgresql', 'timescaledb', 'citus', 'mssql'}
|
20
|
+
_bulk_flavors = {'postgresql', 'postgis', 'timescaledb', 'citus', 'mssql'}
|
21
21
|
### flavors that do not support chunks
|
22
22
|
_disallow_chunks_flavors = ['duckdb']
|
23
23
|
_max_chunks_flavors = {'sqlite': 1000}
|
@@ -798,6 +798,7 @@ def to_sql(
|
|
798
798
|
get_numeric_cols,
|
799
799
|
get_uuid_cols,
|
800
800
|
get_bytes_cols,
|
801
|
+
get_geometry_cols,
|
801
802
|
)
|
802
803
|
from meerschaum.utils.dtypes import (
|
803
804
|
are_dtypes_equal,
|
@@ -805,7 +806,9 @@ def to_sql(
|
|
805
806
|
encode_bytes_for_bytea,
|
806
807
|
serialize_bytes,
|
807
808
|
serialize_decimal,
|
809
|
+
serialize_geometry,
|
808
810
|
json_serialize_value,
|
811
|
+
get_geometry_type_srid,
|
809
812
|
)
|
810
813
|
from meerschaum.utils.dtypes.sql import (
|
811
814
|
PD_TO_SQLALCHEMY_DTYPES_FLAVORS,
|
@@ -822,6 +825,7 @@ def to_sql(
|
|
822
825
|
|
823
826
|
bytes_cols = get_bytes_cols(df)
|
824
827
|
numeric_cols = get_numeric_cols(df)
|
828
|
+
geometry_cols = get_geometry_cols(df)
|
825
829
|
### NOTE: This excludes non-numeric serialized Decimals (e.g. SQLite).
|
826
830
|
numeric_cols_dtypes = {
|
827
831
|
col: typ
|
@@ -830,7 +834,6 @@ def to_sql(
|
|
830
834
|
col in df.columns
|
831
835
|
and 'numeric' in str(typ).lower()
|
832
836
|
)
|
833
|
-
|
834
837
|
}
|
835
838
|
numeric_cols.extend([col for col in numeric_cols_dtypes if col not in numeric_cols])
|
836
839
|
numeric_cols_precisions_scales = {
|
@@ -841,6 +844,22 @@ def to_sql(
|
|
841
844
|
)
|
842
845
|
for col, typ in numeric_cols_dtypes.items()
|
843
846
|
}
|
847
|
+
geometry_cols_dtypes = {
|
848
|
+
col: typ
|
849
|
+
for col, typ in kw.get('dtype', {}).items()
|
850
|
+
if (
|
851
|
+
col in df.columns
|
852
|
+
and 'geometry' in str(typ).lower() or 'geography' in str(typ).lower()
|
853
|
+
)
|
854
|
+
}
|
855
|
+
geometry_cols.extend([col for col in geometry_cols_dtypes if col not in geometry_cols])
|
856
|
+
geometry_cols_types_srids = {
|
857
|
+
col: (typ.geometry_type, typ.srid)
|
858
|
+
if hasattr(typ, 'srid')
|
859
|
+
else get_geometry_type_srid()
|
860
|
+
for col, typ in geometry_cols_dtypes.items()
|
861
|
+
}
|
862
|
+
|
844
863
|
cols_pd_types = {
|
845
864
|
col: get_pd_type_from_db_type(str(typ))
|
846
865
|
for col, typ in kw.get('dtype', {}).items()
|
@@ -856,8 +875,9 @@ def to_sql(
|
|
856
875
|
}
|
857
876
|
|
858
877
|
enable_bulk_insert = mrsm.get_config(
|
859
|
-
'system', 'connectors', 'sql', 'bulk_insert'
|
860
|
-
|
878
|
+
'system', 'connectors', 'sql', 'bulk_insert', self.flavor,
|
879
|
+
warn=False,
|
880
|
+
) or False
|
861
881
|
stats = {'target': name}
|
862
882
|
### resort to defaults if None
|
863
883
|
copied = False
|
@@ -901,6 +921,17 @@ def to_sql(
|
|
901
921
|
)
|
902
922
|
)
|
903
923
|
|
924
|
+
for col in geometry_cols:
|
925
|
+
geometry_type, srid = geometry_cols_types_srids.get(col, get_geometry_type_srid())
|
926
|
+
with warnings.catch_warnings():
|
927
|
+
warnings.simplefilter("ignore")
|
928
|
+
df[col] = df[col].apply(
|
929
|
+
functools.partial(
|
930
|
+
serialize_geometry,
|
931
|
+
as_wkt=(self.flavor == 'mssql')
|
932
|
+
)
|
933
|
+
)
|
934
|
+
|
904
935
|
stats['method'] = method.__name__ if hasattr(method, '__name__') else str(method)
|
905
936
|
|
906
937
|
default_chunksize = self._sys_config.get('chunksize', None)
|
meerschaum/utils/dataframe.py
CHANGED
@@ -153,6 +153,7 @@ def filter_unseen_df(
|
|
153
153
|
attempt_cast_to_numeric,
|
154
154
|
attempt_cast_to_uuid,
|
155
155
|
attempt_cast_to_bytes,
|
156
|
+
attempt_cast_to_geometry,
|
156
157
|
coerce_timezone,
|
157
158
|
serialize_decimal,
|
158
159
|
)
|
@@ -350,6 +351,10 @@ def filter_unseen_df(
|
|
350
351
|
new_bytes_cols = get_bytes_cols(new_df)
|
351
352
|
bytes_cols = set(new_bytes_cols + old_bytes_cols)
|
352
353
|
|
354
|
+
old_geometry_cols = get_geometry_cols(old_df)
|
355
|
+
new_geometry_cols = get_geometry_cols(new_df)
|
356
|
+
geometry_cols = set(new_geometry_cols + old_geometry_cols)
|
357
|
+
|
353
358
|
joined_df = merge(
|
354
359
|
new_df.infer_objects(copy=False).fillna(NA),
|
355
360
|
old_df.infer_objects(copy=False).fillna(NA),
|
@@ -400,6 +405,14 @@ def filter_unseen_df(
|
|
400
405
|
except Exception:
|
401
406
|
warn(f"Unable to parse bytes column '{bytes_col}':\n{traceback.format_exc()}")
|
402
407
|
|
408
|
+
for geometry_col in geometry_cols:
|
409
|
+
if geometry_col not in delta_df.columns:
|
410
|
+
continue
|
411
|
+
try:
|
412
|
+
delta_df[geometry_col] = delta_df[geometry_col].apply(attempt_cast_to_geometry)
|
413
|
+
except Exception:
|
414
|
+
warn(f"Unable to parse bytes column '{bytes_col}':\n{traceback.format_exc()}")
|
415
|
+
|
403
416
|
return delta_df
|
404
417
|
|
405
418
|
|
@@ -858,6 +871,44 @@ def get_bytes_cols(df: 'pd.DataFrame') -> List[str]:
|
|
858
871
|
]
|
859
872
|
|
860
873
|
|
874
|
+
def get_geometry_cols(df: 'pd.DataFrame') -> List[str]:
|
875
|
+
"""
|
876
|
+
Get the columns which contain shapely objects from a Pandas DataFrame.
|
877
|
+
|
878
|
+
Parameters
|
879
|
+
----------
|
880
|
+
df: pd.DataFrame
|
881
|
+
The DataFrame which may contain bytes strings.
|
882
|
+
|
883
|
+
Returns
|
884
|
+
-------
|
885
|
+
A list of columns to treat as `geometry`.
|
886
|
+
"""
|
887
|
+
if df is None:
|
888
|
+
return []
|
889
|
+
|
890
|
+
is_dask = 'dask' in df.__module__
|
891
|
+
if is_dask:
|
892
|
+
df = get_first_valid_dask_partition(df)
|
893
|
+
|
894
|
+
if len(df) == 0:
|
895
|
+
return []
|
896
|
+
|
897
|
+
cols_indices = {
|
898
|
+
col: df[col].first_valid_index()
|
899
|
+
for col in df.columns
|
900
|
+
}
|
901
|
+
return [
|
902
|
+
col
|
903
|
+
for col, ix in cols_indices.items()
|
904
|
+
if (
|
905
|
+
ix is not None
|
906
|
+
and
|
907
|
+
'shapely' in str(type(df.loc[ix][col]))
|
908
|
+
)
|
909
|
+
]
|
910
|
+
|
911
|
+
|
861
912
|
def enforce_dtypes(
|
862
913
|
df: 'pd.DataFrame',
|
863
914
|
dtypes: Dict[str, str],
|
@@ -911,6 +962,7 @@ def enforce_dtypes(
|
|
911
962
|
attempt_cast_to_numeric,
|
912
963
|
attempt_cast_to_uuid,
|
913
964
|
attempt_cast_to_bytes,
|
965
|
+
attempt_cast_to_geometry,
|
914
966
|
coerce_timezone as _coerce_timezone,
|
915
967
|
)
|
916
968
|
from meerschaum.utils.dtypes.sql import get_numeric_precision_scale
|
@@ -937,6 +989,11 @@ def enforce_dtypes(
|
|
937
989
|
for col, typ in dtypes.items()
|
938
990
|
if typ.startswith('numeric')
|
939
991
|
]
|
992
|
+
geometry_cols = [
|
993
|
+
col
|
994
|
+
for col, typ in dtypes.items()
|
995
|
+
if typ.startswith('geometry') or typ.startswith('geography')
|
996
|
+
]
|
940
997
|
uuid_cols = [
|
941
998
|
col
|
942
999
|
for col, typ in dtypes.items()
|
@@ -1026,6 +1083,28 @@ def enforce_dtypes(
|
|
1026
1083
|
if col in df.columns:
|
1027
1084
|
df[col] = _coerce_timezone(df[col], strip_utc=strip_timezone)
|
1028
1085
|
|
1086
|
+
if geometry_cols:
|
1087
|
+
geopandas = mrsm.attempt_import('geopandas')
|
1088
|
+
if debug:
|
1089
|
+
dprint(f"Checking for geometry: {geometry_cols}")
|
1090
|
+
parsed_geom_cols = []
|
1091
|
+
for col in geometry_cols:
|
1092
|
+
try:
|
1093
|
+
df[col] = df[col].apply(attempt_cast_to_geometry)
|
1094
|
+
parsed_geom_cols.append(col)
|
1095
|
+
except Exception as e:
|
1096
|
+
if debug:
|
1097
|
+
dprint(f"Unable to parse column '{col}' as geometry:\n{e}")
|
1098
|
+
|
1099
|
+
if parsed_geom_cols:
|
1100
|
+
if debug:
|
1101
|
+
dprint(f"Converting to GeoDataFrame (geometry column: '{parsed_geom_cols[0]}')...")
|
1102
|
+
df = geopandas.GeoDataFrame(df, geometry=parsed_geom_cols[0])
|
1103
|
+
try:
|
1104
|
+
df.rename_geometry(parsed_geom_cols[0], inplace=True)
|
1105
|
+
except ValueError:
|
1106
|
+
pass
|
1107
|
+
|
1029
1108
|
df_dtypes = {c: str(t) for c, t in df.dtypes.items()}
|
1030
1109
|
if are_dtypes_equal(df_dtypes, pipe_pandas_dtypes):
|
1031
1110
|
if debug:
|
@@ -1602,13 +1681,19 @@ def to_json(
|
|
1602
1681
|
-------
|
1603
1682
|
A JSON string.
|
1604
1683
|
"""
|
1684
|
+
import warnings
|
1605
1685
|
from meerschaum.utils.packages import import_pandas
|
1606
|
-
from meerschaum.utils.dtypes import
|
1686
|
+
from meerschaum.utils.dtypes import (
|
1687
|
+
serialize_bytes,
|
1688
|
+
serialize_decimal,
|
1689
|
+
serialize_geometry,
|
1690
|
+
)
|
1607
1691
|
pd = import_pandas()
|
1608
1692
|
uuid_cols = get_uuid_cols(df)
|
1609
1693
|
bytes_cols = get_bytes_cols(df)
|
1610
1694
|
numeric_cols = get_numeric_cols(df)
|
1611
|
-
|
1695
|
+
geometry_cols = get_geometry_cols(df)
|
1696
|
+
if safe_copy and bool(uuid_cols or bytes_cols or geometry_cols or numeric_cols):
|
1612
1697
|
df = df.copy()
|
1613
1698
|
for col in uuid_cols:
|
1614
1699
|
df[col] = df[col].astype(str)
|
@@ -1616,6 +1701,10 @@ def to_json(
|
|
1616
1701
|
df[col] = df[col].apply(serialize_bytes)
|
1617
1702
|
for col in numeric_cols:
|
1618
1703
|
df[col] = df[col].apply(serialize_decimal)
|
1704
|
+
with warnings.catch_warnings():
|
1705
|
+
warnings.simplefilter("ignore")
|
1706
|
+
for col in geometry_cols:
|
1707
|
+
df[col] = df[col].apply(serialize_geometry)
|
1619
1708
|
return df.infer_objects(copy=False).fillna(pd.NA).to_json(
|
1620
1709
|
date_format=date_format,
|
1621
1710
|
date_unit=date_unit,
|
@@ -12,7 +12,7 @@ from datetime import timezone, datetime
|
|
12
12
|
from decimal import Decimal, Context, InvalidOperation, ROUND_HALF_UP
|
13
13
|
|
14
14
|
import meerschaum as mrsm
|
15
|
-
from meerschaum.utils.typing import Dict, Union, Any, Optional
|
15
|
+
from meerschaum.utils.typing import Dict, Union, Any, Optional, Tuple
|
16
16
|
from meerschaum.utils.warnings import warn
|
17
17
|
|
18
18
|
MRSM_ALIAS_DTYPES: Dict[str, str] = {
|
@@ -27,10 +27,13 @@ MRSM_ALIAS_DTYPES: Dict[str, str] = {
|
|
27
27
|
'bytea': 'bytes',
|
28
28
|
'guid': 'uuid',
|
29
29
|
'UUID': 'uuid',
|
30
|
+
'geom': 'geometry',
|
30
31
|
}
|
31
32
|
MRSM_PD_DTYPES: Dict[Union[str, None], str] = {
|
32
33
|
'json': 'object',
|
33
34
|
'numeric': 'object',
|
35
|
+
'geometry': 'object',
|
36
|
+
'geography': 'object',
|
34
37
|
'uuid': 'object',
|
35
38
|
'datetime': 'datetime64[ns, UTC]',
|
36
39
|
'bool': 'bool[pyarrow]',
|
@@ -60,6 +63,12 @@ def to_pandas_dtype(dtype: str) -> str:
|
|
60
63
|
if dtype.startswith('numeric'):
|
61
64
|
return MRSM_PD_DTYPES['numeric']
|
62
65
|
|
66
|
+
if dtype.startswith('geometry'):
|
67
|
+
return MRSM_PD_DTYPES['geometry']
|
68
|
+
|
69
|
+
if dtype.startswith('geography'):
|
70
|
+
return MRSM_PD_DTYPES['geography']
|
71
|
+
|
63
72
|
### NOTE: Kind of a hack, but if the first word of the given dtype is in all caps,
|
64
73
|
### treat it as a SQL db type.
|
65
74
|
if dtype.split(' ')[0].isupper():
|
@@ -147,6 +156,10 @@ def are_dtypes_equal(
|
|
147
156
|
if ldtype in bytes_dtypes and rdtype in bytes_dtypes:
|
148
157
|
return True
|
149
158
|
|
159
|
+
geometry_dtypes = ('geometry', 'object', 'geography')
|
160
|
+
if ldtype in geometry_dtypes and rdtype in geometry_dtypes:
|
161
|
+
return True
|
162
|
+
|
150
163
|
if ldtype.lower() == rdtype.lower():
|
151
164
|
return True
|
152
165
|
|
@@ -277,6 +290,56 @@ def attempt_cast_to_bytes(value: Any) -> Any:
|
|
277
290
|
return value
|
278
291
|
|
279
292
|
|
293
|
+
def attempt_cast_to_geometry(value: Any) -> Any:
|
294
|
+
"""
|
295
|
+
Given a value, attempt to coerce it into a `shapely` (`geometry`) object.
|
296
|
+
"""
|
297
|
+
shapely_wkt, shapely_wkb = mrsm.attempt_import('shapely.wkt', 'shapely.wkb', lazy=False)
|
298
|
+
if 'shapely' in str(type(value)):
|
299
|
+
return value
|
300
|
+
|
301
|
+
value_is_wkt = geometry_is_wkt(value)
|
302
|
+
if value_is_wkt is None:
|
303
|
+
return value
|
304
|
+
|
305
|
+
try:
|
306
|
+
return (
|
307
|
+
shapely_wkt.loads(value)
|
308
|
+
if value_is_wkt
|
309
|
+
else shapely_wkb.loads(value)
|
310
|
+
)
|
311
|
+
except Exception:
|
312
|
+
return value
|
313
|
+
|
314
|
+
|
315
|
+
def geometry_is_wkt(value: Union[str, bytes]) -> Union[bool, None]:
|
316
|
+
"""
|
317
|
+
Determine whether an input value should be treated as WKT or WKB geometry data.
|
318
|
+
|
319
|
+
Parameters
|
320
|
+
----------
|
321
|
+
value: Union[str, bytes]
|
322
|
+
The input data to be parsed into geometry data.
|
323
|
+
|
324
|
+
Returns
|
325
|
+
-------
|
326
|
+
A `bool` (`True` if `value` is WKT and `False` if it should be treated as WKB).
|
327
|
+
Return `None` if `value` should be parsed as neither.
|
328
|
+
"""
|
329
|
+
import re
|
330
|
+
if isinstance(value, bytes):
|
331
|
+
return False
|
332
|
+
|
333
|
+
wkt_pattern = r'^\s*(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\s*\(.*\)\s*$'
|
334
|
+
if re.match(wkt_pattern, value, re.IGNORECASE):
|
335
|
+
return True
|
336
|
+
|
337
|
+
if all(c in '0123456789ABCDEFabcdef' for c in value) and len(value) % 2 == 0:
|
338
|
+
return False
|
339
|
+
|
340
|
+
return None
|
341
|
+
|
342
|
+
|
280
343
|
def value_is_null(value: Any) -> bool:
|
281
344
|
"""
|
282
345
|
Determine if a value is a null-like string.
|
@@ -458,6 +521,37 @@ def serialize_bytes(data: bytes) -> str:
|
|
458
521
|
return base64.b64encode(data).decode('utf-8')
|
459
522
|
|
460
523
|
|
524
|
+
def serialize_geometry(geom: Any, as_wkt: bool = False) -> str:
|
525
|
+
"""
|
526
|
+
Serialize geometry data as a hex-encoded well-known-binary string.
|
527
|
+
|
528
|
+
Parameters
|
529
|
+
----------
|
530
|
+
geom: Any
|
531
|
+
The potential geometry data to be serialized.
|
532
|
+
|
533
|
+
as_wkt, bool, default False
|
534
|
+
If `True`, serialize geometry data as well-known text (WKT)
|
535
|
+
instead of well-known binary (WKB).
|
536
|
+
|
537
|
+
Returns
|
538
|
+
-------
|
539
|
+
A string containing the geometry data.
|
540
|
+
"""
|
541
|
+
if hasattr(geom, 'wkb_hex'):
|
542
|
+
return geom.wkb_hex if not as_wkt else geom.wkt
|
543
|
+
|
544
|
+
return str(geom)
|
545
|
+
|
546
|
+
|
547
|
+
def deserialize_geometry(geom_wkb: Union[str, bytes]):
|
548
|
+
"""
|
549
|
+
Deserialize a WKB string into a shapely geometry object.
|
550
|
+
"""
|
551
|
+
shapely = mrsm.attempt_import(lazy=False)
|
552
|
+
return shapely.wkb.loads(geom_wkb)
|
553
|
+
|
554
|
+
|
461
555
|
def deserialize_bytes_string(data: str | None, force_hex: bool = False) -> bytes | None:
|
462
556
|
"""
|
463
557
|
Given a serialized ASCII string of bytes data, return the original bytes.
|
@@ -559,7 +653,94 @@ def json_serialize_value(x: Any, default_to_str: bool = True) -> str:
|
|
559
653
|
if isinstance(x, Decimal):
|
560
654
|
return serialize_decimal(x)
|
561
655
|
|
656
|
+
if 'shapely' in str(type(x)):
|
657
|
+
return serialize_geometry(x)
|
658
|
+
|
562
659
|
if value_is_null(x):
|
563
660
|
return None
|
564
661
|
|
565
662
|
return str(x) if default_to_str else x
|
663
|
+
|
664
|
+
|
665
|
+
def get_geometry_type_srid(
|
666
|
+
dtype: str = 'geometry',
|
667
|
+
default_type: str = 'geometry',
|
668
|
+
default_srid: int = 4326,
|
669
|
+
) -> Union[Tuple[str, int], Tuple[str, None]]:
|
670
|
+
"""
|
671
|
+
Given the specified geometry `dtype`, return a tuple in the form (type, SRID).
|
672
|
+
|
673
|
+
Parameters
|
674
|
+
----------
|
675
|
+
dtype: Optional[str], default None
|
676
|
+
Optionally provide a specific `geometry` syntax (e.g. `geometry[MultiLineString, 4326]`).
|
677
|
+
You may specify a supported `shapely` geometry type and an SRID in the dtype modifier:
|
678
|
+
|
679
|
+
- `Point`
|
680
|
+
- `LineString`
|
681
|
+
- `LinearRing`
|
682
|
+
- `Polygon`
|
683
|
+
- `MultiPoint`
|
684
|
+
- `MultiLineString`
|
685
|
+
- `MultiPolygon`
|
686
|
+
- `GeometryCollection`
|
687
|
+
|
688
|
+
Returns
|
689
|
+
-------
|
690
|
+
A tuple in the form (type, SRID).
|
691
|
+
Defaults to `(default_type, default_srid)`.
|
692
|
+
|
693
|
+
Examples
|
694
|
+
--------
|
695
|
+
>>> from meerschaum.utils.dtypes import get_geometry_type_srid
|
696
|
+
>>> get_geometry_type_srid()
|
697
|
+
('geometry', 4326)
|
698
|
+
>>> get_geometry_type_srid('geometry[]')
|
699
|
+
('geometry', 4326)
|
700
|
+
>>> get_geometry_type_srid('geometry[Point, 0]')
|
701
|
+
('Point', 0)
|
702
|
+
>>> get_geometry_type_srid('geometry[0, Point]')
|
703
|
+
('Point', 0)
|
704
|
+
>>> get_geometry_type_srid('geometry[0]')
|
705
|
+
('geometry', 0)
|
706
|
+
>>> get_geometry_type_srid('geometry[MULTILINESTRING, 4326]')
|
707
|
+
('MultiLineString', 4326)
|
708
|
+
>>> get_geometry_type_srid('geography')
|
709
|
+
('geometry', 4326)
|
710
|
+
>>> get_geometry_type_srid('geography[POINT]')
|
711
|
+
('Point', 4376)
|
712
|
+
"""
|
713
|
+
from meerschaum.utils.misc import is_int
|
714
|
+
bare_dtype = dtype.split('[', maxsplit=1)[0]
|
715
|
+
modifier = dtype.split(bare_dtype, maxsplit=1)[-1].lstrip('[').rstrip(']')
|
716
|
+
if not modifier:
|
717
|
+
return default_type, default_srid
|
718
|
+
|
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(',')]
|
726
|
+
parts_casted = [
|
727
|
+
(
|
728
|
+
int(part)
|
729
|
+
if is_int(part)
|
730
|
+
else part
|
731
|
+
) for part in parts]
|
732
|
+
|
733
|
+
srid = default_srid
|
734
|
+
geometry_type = default_type
|
735
|
+
|
736
|
+
for part in parts_casted:
|
737
|
+
if isinstance(part, int):
|
738
|
+
srid = part
|
739
|
+
break
|
740
|
+
|
741
|
+
for part in parts:
|
742
|
+
if part.lower() in geometry_types:
|
743
|
+
geometry_type = geometry_types.get(part)
|
744
|
+
break
|
745
|
+
|
746
|
+
return geometry_type, srid
|
meerschaum/utils/dtypes/sql.py
CHANGED
@@ -128,6 +128,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
128
128
|
'int': {
|
129
129
|
'timescaledb': 'BIGINT',
|
130
130
|
'postgresql': 'BIGINT',
|
131
|
+
'postgis': 'BIGINT',
|
131
132
|
'mariadb': 'BIGINT',
|
132
133
|
'mysql': 'BIGINT',
|
133
134
|
'mssql': 'BIGINT',
|
@@ -141,6 +142,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
141
142
|
'float': {
|
142
143
|
'timescaledb': 'DOUBLE PRECISION',
|
143
144
|
'postgresql': 'DOUBLE PRECISION',
|
145
|
+
'postgis': 'DOUBLE PRECISION',
|
144
146
|
'mariadb': 'DOUBLE PRECISION',
|
145
147
|
'mysql': 'DOUBLE PRECISION',
|
146
148
|
'mssql': 'FLOAT',
|
@@ -154,6 +156,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
154
156
|
'double': {
|
155
157
|
'timescaledb': 'DOUBLE PRECISION',
|
156
158
|
'postgresql': 'DOUBLE PRECISION',
|
159
|
+
'postgis': 'DOUBLE PRECISION',
|
157
160
|
'mariadb': 'DOUBLE PRECISION',
|
158
161
|
'mysql': 'DOUBLE PRECISION',
|
159
162
|
'mssql': 'FLOAT',
|
@@ -167,6 +170,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
167
170
|
'datetime64[ns]': {
|
168
171
|
'timescaledb': 'TIMESTAMP',
|
169
172
|
'postgresql': 'TIMESTAMP',
|
173
|
+
'postgis': 'TIMESTAMP',
|
170
174
|
'mariadb': 'DATETIME',
|
171
175
|
'mysql': 'DATETIME',
|
172
176
|
'mssql': 'DATETIME2',
|
@@ -180,6 +184,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
180
184
|
'datetime64[ns, UTC]': {
|
181
185
|
'timescaledb': 'TIMESTAMPTZ',
|
182
186
|
'postgresql': 'TIMESTAMPTZ',
|
187
|
+
'postgis': 'TIMESTAMPTZ',
|
183
188
|
'mariadb': 'DATETIME',
|
184
189
|
'mysql': 'DATETIME',
|
185
190
|
'mssql': 'DATETIMEOFFSET',
|
@@ -193,6 +198,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
193
198
|
'datetime': {
|
194
199
|
'timescaledb': 'TIMESTAMPTZ',
|
195
200
|
'postgresql': 'TIMESTAMPTZ',
|
201
|
+
'postgis': 'TIMESTAMPTZ',
|
196
202
|
'mariadb': 'DATETIME',
|
197
203
|
'mysql': 'DATETIME',
|
198
204
|
'mssql': 'DATETIMEOFFSET',
|
@@ -206,6 +212,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
206
212
|
'datetimetz': {
|
207
213
|
'timescaledb': 'TIMESTAMPTZ',
|
208
214
|
'postgresql': 'TIMESTAMPTZ',
|
215
|
+
'postgis': 'TIMESTAMPTZ',
|
209
216
|
'mariadb': 'DATETIME',
|
210
217
|
'mysql': 'DATETIME',
|
211
218
|
'mssql': 'DATETIMEOFFSET',
|
@@ -219,6 +226,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
219
226
|
'bool': {
|
220
227
|
'timescaledb': 'BOOLEAN',
|
221
228
|
'postgresql': 'BOOLEAN',
|
229
|
+
'postgis': 'BOOLEAN',
|
222
230
|
'mariadb': 'BOOLEAN',
|
223
231
|
'mysql': 'BOOLEAN',
|
224
232
|
'mssql': 'BIT',
|
@@ -232,6 +240,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
232
240
|
'object': {
|
233
241
|
'timescaledb': 'TEXT',
|
234
242
|
'postgresql': 'TEXT',
|
243
|
+
'postgis': 'TEXT',
|
235
244
|
'mariadb': 'TEXT',
|
236
245
|
'mysql': 'TEXT',
|
237
246
|
'mssql': 'NVARCHAR(MAX)',
|
@@ -245,6 +254,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
245
254
|
'string': {
|
246
255
|
'timescaledb': 'TEXT',
|
247
256
|
'postgresql': 'TEXT',
|
257
|
+
'postgis': 'TEXT',
|
248
258
|
'mariadb': 'TEXT',
|
249
259
|
'mysql': 'TEXT',
|
250
260
|
'mssql': 'NVARCHAR(MAX)',
|
@@ -258,6 +268,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
258
268
|
'unicode': {
|
259
269
|
'timescaledb': 'TEXT',
|
260
270
|
'postgresql': 'TEXT',
|
271
|
+
'postgis': 'TEXT',
|
261
272
|
'mariadb': 'TEXT',
|
262
273
|
'mysql': 'TEXT',
|
263
274
|
'mssql': 'NVARCHAR(MAX)',
|
@@ -271,6 +282,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
271
282
|
'json': {
|
272
283
|
'timescaledb': 'JSONB',
|
273
284
|
'postgresql': 'JSONB',
|
285
|
+
'postgis': 'JSONB',
|
274
286
|
'mariadb': 'TEXT',
|
275
287
|
'mysql': 'TEXT',
|
276
288
|
'mssql': 'NVARCHAR(MAX)',
|
@@ -284,6 +296,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
284
296
|
'numeric': {
|
285
297
|
'timescaledb': 'NUMERIC',
|
286
298
|
'postgresql': 'NUMERIC',
|
299
|
+
'postgis': 'NUMERIC',
|
287
300
|
'mariadb': f'DECIMAL{NUMERIC_PRECISION_FLAVORS["mariadb"]}',
|
288
301
|
'mysql': f'DECIMAL{NUMERIC_PRECISION_FLAVORS["mysql"]}',
|
289
302
|
'mssql': f'NUMERIC{NUMERIC_PRECISION_FLAVORS["mssql"]}',
|
@@ -297,6 +310,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
297
310
|
'uuid': {
|
298
311
|
'timescaledb': 'UUID',
|
299
312
|
'postgresql': 'UUID',
|
313
|
+
'postgis': 'UUID',
|
300
314
|
'mariadb': 'CHAR(36)',
|
301
315
|
'mysql': 'CHAR(36)',
|
302
316
|
'mssql': 'UNIQUEIDENTIFIER',
|
@@ -311,6 +325,7 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
311
325
|
'bytes': {
|
312
326
|
'timescaledb': 'BYTEA',
|
313
327
|
'postgresql': 'BYTEA',
|
328
|
+
'postgis': 'BYTEA',
|
314
329
|
'mariadb': 'BLOB',
|
315
330
|
'mysql': 'BLOB',
|
316
331
|
'mssql': 'VARBINARY(MAX)',
|
@@ -321,11 +336,40 @@ PD_TO_DB_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
321
336
|
'cockroachdb': 'BYTEA',
|
322
337
|
'default': 'BLOB',
|
323
338
|
},
|
339
|
+
'geometry': {
|
340
|
+
'timescaledb': 'TEXT',
|
341
|
+
'postgresql': 'TEXT',
|
342
|
+
'postgis': 'GEOMETRY',
|
343
|
+
'mariadb': 'TEXT',
|
344
|
+
'mysql': 'TEXT',
|
345
|
+
'mssql': 'NVARCHAR(MAX)',
|
346
|
+
'oracle': 'NVARCHAR2(2000)',
|
347
|
+
'sqlite': 'TEXT',
|
348
|
+
'duckdb': 'TEXT',
|
349
|
+
'citus': 'TEXT',
|
350
|
+
'cockroachdb': 'TEXT',
|
351
|
+
'default': 'TEXT',
|
352
|
+
},
|
353
|
+
'geography': {
|
354
|
+
'timescaledb': 'TEXT',
|
355
|
+
'postgresql': 'TEXT',
|
356
|
+
'postgis': 'GEOGRAPHY',
|
357
|
+
'mariadb': 'TEXT',
|
358
|
+
'mysql': 'TEXT',
|
359
|
+
'mssql': 'NVARCHAR(MAX)',
|
360
|
+
'oracle': 'NVARCHAR2(2000)',
|
361
|
+
'sqlite': 'TEXT',
|
362
|
+
'duckdb': 'TEXT',
|
363
|
+
'citus': 'TEXT',
|
364
|
+
'cockroachdb': 'TEXT',
|
365
|
+
'default': 'TEXT',
|
366
|
+
},
|
324
367
|
}
|
325
368
|
PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
326
369
|
'int': {
|
327
370
|
'timescaledb': 'BigInteger',
|
328
371
|
'postgresql': 'BigInteger',
|
372
|
+
'postgis': 'BigInteger',
|
329
373
|
'mariadb': 'BigInteger',
|
330
374
|
'mysql': 'BigInteger',
|
331
375
|
'mssql': 'BigInteger',
|
@@ -339,6 +383,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
339
383
|
'float': {
|
340
384
|
'timescaledb': 'Float',
|
341
385
|
'postgresql': 'Float',
|
386
|
+
'postgis': 'Float',
|
342
387
|
'mariadb': 'Float',
|
343
388
|
'mysql': 'Float',
|
344
389
|
'mssql': 'Float',
|
@@ -352,6 +397,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
352
397
|
'datetime': {
|
353
398
|
'timescaledb': 'DateTime(timezone=True)',
|
354
399
|
'postgresql': 'DateTime(timezone=True)',
|
400
|
+
'postgis': 'DateTime(timezone=True)',
|
355
401
|
'mariadb': 'DateTime(timezone=True)',
|
356
402
|
'mysql': 'DateTime(timezone=True)',
|
357
403
|
'mssql': 'sqlalchemy.dialects.mssql.DATETIMEOFFSET',
|
@@ -365,6 +411,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
365
411
|
'datetime64[ns]': {
|
366
412
|
'timescaledb': 'DateTime',
|
367
413
|
'postgresql': 'DateTime',
|
414
|
+
'postgis': 'DateTime',
|
368
415
|
'mariadb': 'DateTime',
|
369
416
|
'mysql': 'DateTime',
|
370
417
|
'mssql': 'sqlalchemy.dialects.mssql.DATETIME2',
|
@@ -378,6 +425,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
378
425
|
'datetime64[ns, UTC]': {
|
379
426
|
'timescaledb': 'DateTime(timezone=True)',
|
380
427
|
'postgresql': 'DateTime(timezone=True)',
|
428
|
+
'postgis': 'DateTime(timezone=True)',
|
381
429
|
'mariadb': 'DateTime(timezone=True)',
|
382
430
|
'mysql': 'DateTime(timezone=True)',
|
383
431
|
'mssql': 'sqlalchemy.dialects.mssql.DATETIMEOFFSET',
|
@@ -391,6 +439,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
391
439
|
'bool': {
|
392
440
|
'timescaledb': 'Boolean',
|
393
441
|
'postgresql': 'Boolean',
|
442
|
+
'postgis': 'Boolean',
|
394
443
|
'mariadb': 'Integer',
|
395
444
|
'mysql': 'Integer',
|
396
445
|
'mssql': 'sqlalchemy.dialects.mssql.BIT',
|
@@ -404,6 +453,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
404
453
|
'object': {
|
405
454
|
'timescaledb': 'UnicodeText',
|
406
455
|
'postgresql': 'UnicodeText',
|
456
|
+
'postgis': 'UnicodeText',
|
407
457
|
'mariadb': 'UnicodeText',
|
408
458
|
'mysql': 'UnicodeText',
|
409
459
|
'mssql': 'UnicodeText',
|
@@ -417,6 +467,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
417
467
|
'string': {
|
418
468
|
'timescaledb': 'UnicodeText',
|
419
469
|
'postgresql': 'UnicodeText',
|
470
|
+
'postgis': 'UnicodeText',
|
420
471
|
'mariadb': 'UnicodeText',
|
421
472
|
'mysql': 'UnicodeText',
|
422
473
|
'mssql': 'UnicodeText',
|
@@ -430,6 +481,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
430
481
|
'json': {
|
431
482
|
'timescaledb': 'sqlalchemy.dialects.postgresql.JSONB',
|
432
483
|
'postgresql': 'sqlalchemy.dialects.postgresql.JSONB',
|
484
|
+
'postgis': 'sqlalchemy.dialects.postgresql.JSONB',
|
433
485
|
'mariadb': 'UnicodeText',
|
434
486
|
'mysql': 'UnicodeText',
|
435
487
|
'mssql': 'UnicodeText',
|
@@ -443,6 +495,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
443
495
|
'numeric': {
|
444
496
|
'timescaledb': 'Numeric',
|
445
497
|
'postgresql': 'Numeric',
|
498
|
+
'postgis': 'Numeric',
|
446
499
|
'mariadb': 'Numeric',
|
447
500
|
'mysql': 'Numeric',
|
448
501
|
'mssql': 'Numeric',
|
@@ -456,6 +509,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
456
509
|
'uuid': {
|
457
510
|
'timescaledb': 'Uuid',
|
458
511
|
'postgresql': 'Uuid',
|
512
|
+
'postgis': 'Uuid',
|
459
513
|
'mariadb': 'sqlalchemy.dialects.mysql.CHAR(36)',
|
460
514
|
'mysql': 'sqlalchemy.dialects.mysql.CHAR(36)',
|
461
515
|
'mssql': 'Uuid',
|
@@ -469,6 +523,7 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
469
523
|
'bytes': {
|
470
524
|
'timescaledb': 'LargeBinary',
|
471
525
|
'postgresql': 'LargeBinary',
|
526
|
+
'postgis': 'LargeBinary',
|
472
527
|
'mariadb': 'LargeBinary',
|
473
528
|
'mysql': 'LargeBinary',
|
474
529
|
'mssql': 'LargeBinary',
|
@@ -479,11 +534,40 @@ PD_TO_SQLALCHEMY_DTYPES_FLAVORS: Dict[str, Dict[str, str]] = {
|
|
479
534
|
'cockroachdb': 'LargeBinary',
|
480
535
|
'default': 'LargeBinary',
|
481
536
|
},
|
537
|
+
'geometry': {
|
538
|
+
'timescaledb': 'UnicodeText',
|
539
|
+
'postgresql': 'UnicodeText',
|
540
|
+
'postgis': 'geoalchemy2.Geometry',
|
541
|
+
'mariadb': 'UnicodeText',
|
542
|
+
'mysql': 'UnicodeText',
|
543
|
+
'mssql': 'UnicodeText',
|
544
|
+
'oracle': 'UnicodeText',
|
545
|
+
'sqlite': 'UnicodeText',
|
546
|
+
'duckdb': 'UnicodeText',
|
547
|
+
'citus': 'UnicodeText',
|
548
|
+
'cockroachdb': 'UnicodeText',
|
549
|
+
'default': 'UnicodeText',
|
550
|
+
},
|
551
|
+
'geography': {
|
552
|
+
'timescaledb': 'UnicodeText',
|
553
|
+
'postgresql': 'UnicodeText',
|
554
|
+
'postgis': 'geoalchemy2.Geography',
|
555
|
+
'mariadb': 'UnicodeText',
|
556
|
+
'mysql': 'UnicodeText',
|
557
|
+
'mssql': 'UnicodeText',
|
558
|
+
'oracle': 'UnicodeText',
|
559
|
+
'sqlite': 'UnicodeText',
|
560
|
+
'duckdb': 'UnicodeText',
|
561
|
+
'citus': 'UnicodeText',
|
562
|
+
'cockroachdb': 'UnicodeText',
|
563
|
+
'default': 'UnicodeText',
|
564
|
+
},
|
482
565
|
}
|
483
566
|
|
484
567
|
AUTO_INCREMENT_COLUMN_FLAVORS: Dict[str, str] = {
|
485
568
|
'timescaledb': 'GENERATED BY DEFAULT AS IDENTITY',
|
486
569
|
'postgresql': 'GENERATED BY DEFAULT AS IDENTITY',
|
570
|
+
'postgis': 'GENERATED BY DEFAULT AS IDENTITY',
|
487
571
|
'mariadb': 'AUTO_INCREMENT',
|
488
572
|
'mysql': 'AUTO_INCREMENT',
|
489
573
|
'mssql': 'IDENTITY(1,1)',
|
@@ -565,7 +649,7 @@ def get_db_type_from_pd_type(
|
|
565
649
|
"""
|
566
650
|
from meerschaum.utils.warnings import warn
|
567
651
|
from meerschaum.utils.packages import attempt_import
|
568
|
-
from meerschaum.utils.dtypes import are_dtypes_equal, MRSM_ALIAS_DTYPES
|
652
|
+
from meerschaum.utils.dtypes import are_dtypes_equal, MRSM_ALIAS_DTYPES, get_geometry_type_srid
|
569
653
|
from meerschaum.utils.misc import parse_arguments_str
|
570
654
|
sqlalchemy_types = attempt_import('sqlalchemy.types', lazy=False)
|
571
655
|
|
@@ -576,18 +660,30 @@ def get_db_type_from_pd_type(
|
|
576
660
|
)
|
577
661
|
|
578
662
|
precision, scale = None, None
|
663
|
+
geometry_type, geometry_srid = None, None
|
579
664
|
og_pd_type = pd_type
|
580
665
|
if pd_type in MRSM_ALIAS_DTYPES:
|
581
666
|
pd_type = MRSM_ALIAS_DTYPES[pd_type]
|
582
667
|
|
583
668
|
### Check whether we are able to match this type (e.g. pyarrow support).
|
584
669
|
found_db_type = False
|
585
|
-
if
|
670
|
+
if (
|
671
|
+
pd_type not in types_registry
|
672
|
+
and not any(
|
673
|
+
pd_type.startswith(f'{typ}[')
|
674
|
+
for typ in ('numeric', 'geometry', 'geography')
|
675
|
+
)
|
676
|
+
):
|
586
677
|
for mapped_pd_type in types_registry:
|
587
678
|
if are_dtypes_equal(mapped_pd_type, pd_type):
|
588
679
|
pd_type = mapped_pd_type
|
589
680
|
found_db_type = True
|
590
681
|
break
|
682
|
+
elif (pd_type.startswith('geometry[') or pd_type.startswith('geography[')):
|
683
|
+
og_pd_type = pd_type
|
684
|
+
pd_type = 'geometry' if 'geometry' in pd_type else 'geography'
|
685
|
+
geometry_type, geometry_srid = get_geometry_type_srid(og_pd_type)
|
686
|
+
found_db_type = True
|
591
687
|
elif pd_type.startswith('numeric['):
|
592
688
|
og_pd_type = pd_type
|
593
689
|
pd_type = 'numeric'
|
@@ -628,6 +724,11 @@ def get_db_type_from_pd_type(
|
|
628
724
|
if precision is not None and scale is not None:
|
629
725
|
db_type_bare = db_type.split('(', maxsplit=1)[0]
|
630
726
|
return f"{db_type_bare}({precision},{scale})"
|
727
|
+
if geometry_type is not None and geometry_srid is not None:
|
728
|
+
if 'geometry' not in db_type.lower() and 'geography' not in db_type.lower():
|
729
|
+
return db_type
|
730
|
+
db_type_bare = db_type.split('(', maxsplit=1)[0]
|
731
|
+
return f"{db_type_bare}({geometry_type.upper()}, {geometry_srid})"
|
631
732
|
return db_type
|
632
733
|
|
633
734
|
if db_type.startswith('sqlalchemy.dialects'):
|
@@ -643,6 +744,17 @@ def get_db_type_from_pd_type(
|
|
643
744
|
return cls
|
644
745
|
return cls(*cls_args, **cls_kwargs)
|
645
746
|
|
747
|
+
if 'geometry' in db_type.lower() or 'geography' in db_type.lower():
|
748
|
+
geoalchemy2 = attempt_import('geoalchemy2', lazy=False)
|
749
|
+
geometry_class = (
|
750
|
+
geoalchemy2.Geometry
|
751
|
+
if 'geometry' in db_type.lower()
|
752
|
+
else geoalchemy2.Geography
|
753
|
+
)
|
754
|
+
if geometry_type is None or geometry_srid is None:
|
755
|
+
return geometry_class
|
756
|
+
return geometry_class(geometry_type=geometry_type, srid=geometry_srid)
|
757
|
+
|
646
758
|
if 'numeric' in db_type.lower():
|
647
759
|
if precision is None or scale is None:
|
648
760
|
return sqlalchemy_types.Numeric
|
@@ -136,12 +136,14 @@ packages['sql'] = {
|
|
136
136
|
'numpy' : 'numpy>=1.18.5',
|
137
137
|
'pandas' : 'pandas[parquet]>=2.0.1',
|
138
138
|
'geopandas' : 'geopandas>=1.0.1',
|
139
|
+
'shapely' : 'shapely>=2.0.7',
|
139
140
|
'pyarrow' : 'pyarrow>=16.1.0',
|
140
141
|
'dask' : 'dask[complete]>=2024.12.1',
|
141
142
|
'partd' : 'partd>=1.4.2',
|
142
143
|
'pytz' : 'pytz',
|
143
144
|
'joblib' : 'joblib>=0.17.0',
|
144
145
|
'sqlalchemy' : 'SQLAlchemy>=2.0.5',
|
146
|
+
'geoalchemy' : 'GeoAlchemy2>=0.17.1',
|
145
147
|
'databases' : 'databases>=0.4.0',
|
146
148
|
'aiosqlite' : 'aiosqlite>=0.16.0',
|
147
149
|
'asyncpg' : 'asyncpg>=0.21.0',
|
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',
|
@@ -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})"
|
@@ -1809,6 +1819,8 @@ def get_null_replacement(typ: str, flavor: str) -> str:
|
|
1809
1819
|
"""
|
1810
1820
|
from meerschaum.utils.dtypes import are_dtypes_equal
|
1811
1821
|
from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES
|
1822
|
+
if 'geometry' in typ.lower():
|
1823
|
+
return '010100000000008058346FCDC100008058346FCDC1'
|
1812
1824
|
if 'int' in typ.lower() or typ.lower() in ('numeric', 'number'):
|
1813
1825
|
return '-987654321'
|
1814
1826
|
if 'bool' in typ.lower() or typ.lower() == 'bit':
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: meerschaum
|
3
|
-
Version: 2.9.
|
3
|
+
Version: 2.9.0rc2
|
4
4
|
Summary: Sync Time-Series Pipes with Meerschaum
|
5
5
|
Home-page: https://meerschaum.io
|
6
6
|
Author: Bennett Meares
|
@@ -127,12 +127,14 @@ Provides-Extra: sql
|
|
127
127
|
Requires-Dist: numpy>=1.18.5; extra == "sql"
|
128
128
|
Requires-Dist: pandas[parquet]>=2.0.1; extra == "sql"
|
129
129
|
Requires-Dist: geopandas>=1.0.1; extra == "sql"
|
130
|
+
Requires-Dist: shapely>=2.0.7; extra == "sql"
|
130
131
|
Requires-Dist: pyarrow>=16.1.0; extra == "sql"
|
131
132
|
Requires-Dist: dask[complete]>=2024.12.1; extra == "sql"
|
132
133
|
Requires-Dist: partd>=1.4.2; extra == "sql"
|
133
134
|
Requires-Dist: pytz; extra == "sql"
|
134
135
|
Requires-Dist: joblib>=0.17.0; extra == "sql"
|
135
136
|
Requires-Dist: SQLAlchemy>=2.0.5; extra == "sql"
|
137
|
+
Requires-Dist: GeoAlchemy2>=0.17.1; extra == "sql"
|
136
138
|
Requires-Dist: databases>=0.4.0; extra == "sql"
|
137
139
|
Requires-Dist: aiosqlite>=0.16.0; extra == "sql"
|
138
140
|
Requires-Dist: asyncpg>=0.21.0; extra == "sql"
|
@@ -188,12 +190,14 @@ Requires-Dist: valkey>=6.0.0; extra == "api"
|
|
188
190
|
Requires-Dist: numpy>=1.18.5; extra == "api"
|
189
191
|
Requires-Dist: pandas[parquet]>=2.0.1; extra == "api"
|
190
192
|
Requires-Dist: geopandas>=1.0.1; extra == "api"
|
193
|
+
Requires-Dist: shapely>=2.0.7; extra == "api"
|
191
194
|
Requires-Dist: pyarrow>=16.1.0; extra == "api"
|
192
195
|
Requires-Dist: dask[complete]>=2024.12.1; extra == "api"
|
193
196
|
Requires-Dist: partd>=1.4.2; extra == "api"
|
194
197
|
Requires-Dist: pytz; extra == "api"
|
195
198
|
Requires-Dist: joblib>=0.17.0; extra == "api"
|
196
199
|
Requires-Dist: SQLAlchemy>=2.0.5; extra == "api"
|
200
|
+
Requires-Dist: GeoAlchemy2>=0.17.1; extra == "api"
|
197
201
|
Requires-Dist: databases>=0.4.0; extra == "api"
|
198
202
|
Requires-Dist: aiosqlite>=0.16.0; extra == "api"
|
199
203
|
Requires-Dist: asyncpg>=0.21.0; extra == "api"
|
@@ -294,12 +298,14 @@ Requires-Dist: pycparser>=2.21.0; extra == "full"
|
|
294
298
|
Requires-Dist: numpy>=1.18.5; extra == "full"
|
295
299
|
Requires-Dist: pandas[parquet]>=2.0.1; extra == "full"
|
296
300
|
Requires-Dist: geopandas>=1.0.1; extra == "full"
|
301
|
+
Requires-Dist: shapely>=2.0.7; extra == "full"
|
297
302
|
Requires-Dist: pyarrow>=16.1.0; extra == "full"
|
298
303
|
Requires-Dist: dask[complete]>=2024.12.1; extra == "full"
|
299
304
|
Requires-Dist: partd>=1.4.2; extra == "full"
|
300
305
|
Requires-Dist: pytz; extra == "full"
|
301
306
|
Requires-Dist: joblib>=0.17.0; extra == "full"
|
302
307
|
Requires-Dist: SQLAlchemy>=2.0.5; extra == "full"
|
308
|
+
Requires-Dist: GeoAlchemy2>=0.17.1; extra == "full"
|
303
309
|
Requires-Dist: databases>=0.4.0; extra == "full"
|
304
310
|
Requires-Dist: aiosqlite>=0.16.0; extra == "full"
|
305
311
|
Requires-Dist: asyncpg>=0.21.0; extra == "full"
|
@@ -135,7 +135,7 @@ meerschaum/api/routes/_webterm.py,sha256=S7RXV8vvaTFbmVeehh4UhyXb4NCgcsyOQzoAG7j
|
|
135
135
|
meerschaum/api/tables/__init__.py,sha256=e2aNC0CdlWICTUMx1i9RauF8Pm426J0RZJbsJWv4SWo,482
|
136
136
|
meerschaum/config/__init__.py,sha256=5ZBq71P9t3nb74r5CGvMfNuauPscfegBX-nkaAUi5C4,11541
|
137
137
|
meerschaum/config/_dash.py,sha256=BJHl4xMrQB-YHUEU7ldEW8q_nOPoIRSOqLrfGElc6Dw,187
|
138
|
-
meerschaum/config/_default.py,sha256=
|
138
|
+
meerschaum/config/_default.py,sha256=41onvi-NJK_agSkBnsZJwDz09accGLkH8AhkeCuIYZk,6582
|
139
139
|
meerschaum/config/_edit.py,sha256=M9yX_SDD24gV5kWITZpy7p9AWTizJsIAGWAs3WZx-Ws,9087
|
140
140
|
meerschaum/config/_environment.py,sha256=Vv4DLDfc2vKLbCLsMvkQDj77K4kEvHKEBmUBo-wCrgo,4419
|
141
141
|
meerschaum/config/_formatting.py,sha256=OMuqS1EWOsj_34wSs2tOqGIWci3bTMIZ5l-uelZgsIM,6672
|
@@ -146,7 +146,7 @@ meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6
|
|
146
146
|
meerschaum/config/_read_config.py,sha256=RLC3HHi_1ndj7ITVDKLD9_uULY3caGRwSz3ATYE-ixA,15014
|
147
147
|
meerschaum/config/_shell.py,sha256=46_m49Txc5q1rGfCgO49ca48BODx45DQJi8D0zz1R18,4245
|
148
148
|
meerschaum/config/_sync.py,sha256=jHcWRkxd82_BgX8Xo8agsWvf7BSbv3qHLWmYl6ehp_0,4242
|
149
|
-
meerschaum/config/_version.py,sha256=
|
149
|
+
meerschaum/config/_version.py,sha256=qSHUj8MxnJ9gRyXkAhkZxz2mL83tODyagATrnfHLw2c,74
|
150
150
|
meerschaum/config/paths.py,sha256=JjibeGN3YAdSNceRwsd42aNmeUrIgM6ndzC8qZAmNI0,621
|
151
151
|
meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
152
152
|
meerschaum/config/stack/__init__.py,sha256=2UukC0Lmk-aVL1o1qXzumqmuIrw3vu9fD7iCuz4XD4I,10544
|
@@ -173,15 +173,15 @@ meerschaum/connectors/api/_uri.py,sha256=HWxqGx4R1cHZ3ywy9Ro9ePbFxxusw4RLaC3hpGt
|
|
173
173
|
meerschaum/connectors/api/_users.py,sha256=kzb7ENgXwQ19OJYKOuuWzx2rwVuUZCly9dTnyvVuT2Q,5275
|
174
174
|
meerschaum/connectors/plugin/PluginConnector.py,sha256=aQ1QaB7MordCFimZqoGLb0R12PfDUN_nWks2J5mzeAs,2084
|
175
175
|
meerschaum/connectors/plugin/__init__.py,sha256=pwF7TGY4WNz2_HaVdmK4rPQ9ZwTOEuPHgzOqsGcoXJw,198
|
176
|
-
meerschaum/connectors/sql/_SQLConnector.py,sha256=
|
176
|
+
meerschaum/connectors/sql/_SQLConnector.py,sha256=FmosRX4PKR7AAW-PgX69R_32AsA7E7preQXJf4xc3WI,12240
|
177
177
|
meerschaum/connectors/sql/__init__.py,sha256=3cqYiDkVasn7zWdtOTAZbT4bo95AuvGOmDD2TkaAxtw,205
|
178
|
-
meerschaum/connectors/sql/_cli.py,sha256=
|
179
|
-
meerschaum/connectors/sql/_create_engine.py,sha256=
|
178
|
+
meerschaum/connectors/sql/_cli.py,sha256=smwMBxq-euAeefbdZSXGTr83cm04GVGT1WIFEftMLa4,5142
|
179
|
+
meerschaum/connectors/sql/_create_engine.py,sha256=RRiTNNVNwE_sabeTRWrlZZoFB6jFXDfcEcXkENuo8Rc,12035
|
180
180
|
meerschaum/connectors/sql/_fetch.py,sha256=mVe5zQo7SM9PSUU3Vjhacg4Bq1-Vttb7KkXL4p5YQdQ,12818
|
181
181
|
meerschaum/connectors/sql/_instance.py,sha256=xCc8M0xWMzF5Tu_1uWIFivAoHey5N1ccFhN_Z7u04zk,6304
|
182
|
-
meerschaum/connectors/sql/_pipes.py,sha256=
|
182
|
+
meerschaum/connectors/sql/_pipes.py,sha256=RAE3ACk7U0-Z2jnXBFJt_laKIqA4EABxPuDApPHkTc8,129185
|
183
183
|
meerschaum/connectors/sql/_plugins.py,sha256=OVEdZ_UHTi-x5sF-5lu2TmR9ONxddp6SwDOmFo5TpU8,8051
|
184
|
-
meerschaum/connectors/sql/_sql.py,sha256=
|
184
|
+
meerschaum/connectors/sql/_sql.py,sha256=EziDFp9mLU-VPvFczG94ZUtGy-JmKxXB4wbLtCfkykk,43786
|
185
185
|
meerschaum/connectors/sql/_uri.py,sha256=BFzu5pjlbL3kxLH13vHWlpKGYTPfg8wuA2j58O9NsCM,3440
|
186
186
|
meerschaum/connectors/sql/_users.py,sha256=mRyjsUCfPV52nfTQUbpu9gMXfV_DHXNqEhw4N-lSS4Q,9954
|
187
187
|
meerschaum/connectors/sql/tools.py,sha256=jz8huOaRCwGlYdtGfAqAh7SoK8uydYBrasKQba9FT38,187
|
@@ -223,7 +223,7 @@ meerschaum/plugins/__init__.py,sha256=Tl5B0Q4rIfgkPpgknJH3UKKB3fS_cAWI9TspKosvBP
|
|
223
223
|
meerschaum/plugins/bootstrap.py,sha256=VwjpZAuYdqPJW0YoVgAoM_taHkdQHqP902-8T7OWWCI,11339
|
224
224
|
meerschaum/utils/__init__.py,sha256=QrK1K9hIbPCRCM5k2nZGFqGnrqhA0Eh-iSmCU7FG6Cs,612
|
225
225
|
meerschaum/utils/_get_pipes.py,sha256=tu4xKPoDn79Dz2kWM13cXTP4DSCkn-3G9M8KiLftopw,11073
|
226
|
-
meerschaum/utils/dataframe.py,sha256=
|
226
|
+
meerschaum/utils/dataframe.py,sha256=SfwFGqBTGVcA5ulWEXOV2GZ1633JPcRXxF_l6oQ7L5o,52020
|
227
227
|
meerschaum/utils/debug.py,sha256=GyIzJmunkoPnOcZNYVQdT4Sgd-aOb5MI2VbIgATOjIQ,3695
|
228
228
|
meerschaum/utils/interactive.py,sha256=t-6jWozXSqL7lYGDHuwiOjTgr-UKhdcg61q_eR5mikI,3196
|
229
229
|
meerschaum/utils/misc.py,sha256=8TOQQlFyF_aYnc8tnx98lccXr9tFrdlS-ngXeOQjHHY,47407
|
@@ -232,7 +232,7 @@ meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
|
|
232
232
|
meerschaum/utils/process.py,sha256=as0-CjG4mqFP0TydVvmAmgki6er4thS5BqUopeiq98Q,8216
|
233
233
|
meerschaum/utils/prompt.py,sha256=qj1As1tuiL0GZTku_YOC6I5DmOU6L5otDR7DW7LA5fM,19397
|
234
234
|
meerschaum/utils/schedule.py,sha256=Vrcd2Qs-UPVn6xBayNUIgludf0Mlb6Wrgq6ATdyhV8c,11451
|
235
|
-
meerschaum/utils/sql.py,sha256=
|
235
|
+
meerschaum/utils/sql.py,sha256=wOm_9bA2HRn-TGYqyiqwU3ILk3JBOH08X8e5k0TEDes,80786
|
236
236
|
meerschaum/utils/threading.py,sha256=awjbVL_QR6G-o_9Qk85utac9cSdqkiC8tQSdERCdrG8,2814
|
237
237
|
meerschaum/utils/typing.py,sha256=U3MC347sh1umpa3Xr1k71eADyDmk4LB6TnVCpq8dVzI,2830
|
238
238
|
meerschaum/utils/warnings.py,sha256=n-phr3BftNNgyPnvnXC_VMSjtCvjiCZ-ewmVfcROhkc,6611
|
@@ -243,23 +243,23 @@ meerschaum/utils/daemon/RotatingFile.py,sha256=8_bXegBjjzNRlNEjFZ_EHU4pSaDfjXZTw
|
|
243
243
|
meerschaum/utils/daemon/StdinFile.py,sha256=qdZ8E_RSOkURypwnS50mWeyWyRig1bAY9tKWMTVKajc,3307
|
244
244
|
meerschaum/utils/daemon/__init__.py,sha256=ziRPyu_IM3l7Xd58y3Uvt0fZLoirJ9nuboFIxxult6c,8741
|
245
245
|
meerschaum/utils/daemon/_names.py,sha256=d2ZwTxBoTAqXZkCfZ5LuX2XrkQmLNUq1OTlUqfoH5dA,4515
|
246
|
-
meerschaum/utils/dtypes/__init__.py,sha256=
|
247
|
-
meerschaum/utils/dtypes/sql.py,sha256=
|
246
|
+
meerschaum/utils/dtypes/__init__.py,sha256=nFlslv8aL_UFscWNJOwY19F5KFvXsXAR0_UX0rjF6D8,20669
|
247
|
+
meerschaum/utils/dtypes/sql.py,sha256=MhuFT3wY8cx_o556a-oa3sKVKmAus0_O8ETbIIRlXik,26074
|
248
248
|
meerschaum/utils/formatting/__init__.py,sha256=bA8qwBeTNIVHVQOBK682bJsKSKik1yS6xYJAoi0RErk,15528
|
249
249
|
meerschaum/utils/formatting/_jobs.py,sha256=izsqPJhTtUkXUUtWnbXtReYsUYwulXtci3pBj72Ne64,6637
|
250
250
|
meerschaum/utils/formatting/_pipes.py,sha256=gwl8-xCN5GYqBZJ7SkY20BebcofY0nU5X8Y4Emf5dz8,19570
|
251
251
|
meerschaum/utils/formatting/_pprint.py,sha256=wyTmjHFnsHbxfyuytjTWzH-D42Z65GuIisQ_W6UnRPg,3096
|
252
252
|
meerschaum/utils/formatting/_shell.py,sha256=2bFvtwNXapjl9jdlc0fg79PRWHbYVcllKiVcG5g36qI,3678
|
253
253
|
meerschaum/utils/packages/__init__.py,sha256=TdKaj2tmN4bFwzusOfMv24P5ET7Zv73vyoOf9GOIr5E,64427
|
254
|
-
meerschaum/utils/packages/_packages.py,sha256=
|
254
|
+
meerschaum/utils/packages/_packages.py,sha256=BWBJRlwWqZD4SPcM-SbVxEJ2pwclUhioSeP1ldSiYWE,9047
|
255
255
|
meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
|
256
256
|
meerschaum/utils/venv/_Venv.py,sha256=gc1TCeAj-kTZbQFAT9xl1bi4HXFV5ApT0dPOJfxwr78,3748
|
257
257
|
meerschaum/utils/venv/__init__.py,sha256=6FDfOSBsGgw2RIXvBuFEwlF5740RIHs4Qum0ekati9I,27249
|
258
|
-
meerschaum-2.9.
|
259
|
-
meerschaum-2.9.
|
260
|
-
meerschaum-2.9.
|
261
|
-
meerschaum-2.9.
|
262
|
-
meerschaum-2.9.
|
263
|
-
meerschaum-2.9.
|
264
|
-
meerschaum-2.9.
|
265
|
-
meerschaum-2.9.
|
258
|
+
meerschaum-2.9.0rc2.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
|
259
|
+
meerschaum-2.9.0rc2.dist-info/METADATA,sha256=18-lm3TfFldZ0KZdp6YkgF5HDpzfPii7thoQ7ibqMuA,24930
|
260
|
+
meerschaum-2.9.0rc2.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
|
261
|
+
meerschaum-2.9.0rc2.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
262
|
+
meerschaum-2.9.0rc2.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
|
263
|
+
meerschaum-2.9.0rc2.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
|
264
|
+
meerschaum-2.9.0rc2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
265
|
+
meerschaum-2.9.0rc2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|