meerschaum 2.9.0rc3__py3-none-any.whl → 2.9.2__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/_internal/shell/Shell.py +79 -30
- meerschaum/api/dash/callbacks/__init__.py +5 -2
- meerschaum/api/dash/callbacks/custom.py +17 -25
- meerschaum/api/dash/callbacks/dashboard.py +5 -21
- meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
- meerschaum/api/dash/components.py +110 -7
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/pipes.py +9 -6
- meerschaum/api/dash/pages/settings/__init__.py +8 -0
- meerschaum/api/dash/pages/settings/password_reset.py +63 -0
- meerschaum/api/resources/static/css/dash.css +7 -0
- meerschaum/api/resources/templates/termpage.html +2 -0
- meerschaum/api/routes/_pipes.py +52 -37
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/__init__.py +1 -0
- meerschaum/connectors/api/_pipes.py +79 -30
- meerschaum/connectors/sql/_pipes.py +38 -5
- meerschaum/connectors/valkey/_pipes.py +1 -0
- meerschaum/core/Pipe/_data.py +10 -1
- meerschaum/core/Pipe/_verify.py +1 -0
- meerschaum/plugins/__init__.py +26 -4
- meerschaum/utils/dataframe.py +8 -1
- meerschaum/utils/dtypes/__init__.py +14 -13
- meerschaum/utils/misc.py +34 -1
- meerschaum/utils/packages/_packages.py +0 -1
- meerschaum/utils/sql.py +42 -4
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/METADATA +3 -4
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/RECORD +35 -36
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/WHEEL +1 -1
- meerschaum/_internal/gui/__init__.py +0 -43
- meerschaum/_internal/gui/app/__init__.py +0 -50
- meerschaum/_internal/gui/app/_windows.py +0 -74
- meerschaum/_internal/gui/app/actions.py +0 -30
- meerschaum/_internal/gui/app/pipes.py +0 -47
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info/licenses}/LICENSE +0 -0
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info/licenses}/NOTICE +0 -0
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.0rc3.dist-info → meerschaum-2.9.2.dist-info}/zip-safe +0 -0
@@ -55,6 +55,8 @@ window.addEventListener(
|
|
55
55
|
if (typeof lk === "string"){
|
56
56
|
quote_str = lk.includes(" ") ? "'" : "";
|
57
57
|
location_keys_str += " " + quote_str + lk + quote_str;
|
58
|
+
} else if (lk === null) {
|
59
|
+
location_keys_str += " None";
|
58
60
|
}
|
59
61
|
}
|
60
62
|
if (location_keys_str === " -l"){
|
meerschaum/api/routes/_pipes.py
CHANGED
@@ -387,8 +387,6 @@ def get_pipe_data(
|
|
387
387
|
date_unit: str = 'us',
|
388
388
|
double_precision: int = 15,
|
389
389
|
geometry_format: str = 'wkb_hex',
|
390
|
-
as_chunks: bool = False,
|
391
|
-
chunk_interval: Optional[int] = None,
|
392
390
|
curr_user = (
|
393
391
|
fastapi.Depends(manager) if not no_auth else None
|
394
392
|
),
|
@@ -414,14 +412,11 @@ def get_pipe_data(
|
|
414
412
|
`'ms'` (milliseconds), `'us'` (microseconds), and `'ns'`.
|
415
413
|
|
416
414
|
double_precision: int, default 15
|
417
|
-
The number of decimal places to use when encoding floating point values (maximum 15).
|
415
|
+
The number of decimal places to use when encoding floating point values (maximum `15`).
|
418
416
|
|
419
417
|
geometry_format: str, default 'wkb_hex'
|
420
418
|
The serialization format for geometry data.
|
421
419
|
Accepted values are `geojson`, `wkb_hex`, and `wkt`.
|
422
|
-
|
423
|
-
as_chunks: bool, default False
|
424
|
-
If `True`, return a chunk token to be consumed by the `/chunks` endpoint.
|
425
420
|
"""
|
426
421
|
if limit > MAX_RESPONSE_ROW_LIMIT:
|
427
422
|
raise fastapi.HTTPException(
|
@@ -488,25 +483,6 @@ def get_pipe_data(
|
|
488
483
|
detail=f"Cannot retrieve data from protected table '{pipe.target}'.",
|
489
484
|
)
|
490
485
|
|
491
|
-
if as_chunks:
|
492
|
-
chunks_cursor_token = generate_chunks_cursor_token(
|
493
|
-
pipe,
|
494
|
-
select_columns=_select_columns,
|
495
|
-
omit_columns=_omit_columns,
|
496
|
-
begin=begin,
|
497
|
-
end=end,
|
498
|
-
params=_params,
|
499
|
-
limit=limit,
|
500
|
-
order=order,
|
501
|
-
debug=debug,
|
502
|
-
)
|
503
|
-
return fastapi.Response(
|
504
|
-
json.dumps({
|
505
|
-
'chunks_cursor': chunks_cursor_token,
|
506
|
-
}),
|
507
|
-
media_type='application/json',
|
508
|
-
)
|
509
|
-
|
510
486
|
df = pipe.get_data(
|
511
487
|
select_columns=_select_columns,
|
512
488
|
omit_columns=_omit_columns,
|
@@ -579,37 +555,70 @@ def get_pipe_chunk_bounds(
|
|
579
555
|
)
|
580
556
|
|
581
557
|
|
582
|
-
@app.
|
583
|
-
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/
|
558
|
+
@app.delete(
|
559
|
+
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/drop',
|
584
560
|
tags=['Pipes: Data'],
|
585
561
|
)
|
586
|
-
def
|
562
|
+
def drop_pipe(
|
587
563
|
connector_keys: str,
|
588
564
|
metric_key: str,
|
589
565
|
location_key: str,
|
590
|
-
|
591
|
-
|
566
|
+
instance_keys: Optional[str] = None,
|
567
|
+
curr_user = (
|
568
|
+
fastapi.Depends(manager) if not no_auth else None
|
569
|
+
),
|
570
|
+
):
|
592
571
|
"""
|
593
|
-
|
572
|
+
Drop a pipe's target table.
|
594
573
|
"""
|
574
|
+
allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
575
|
+
if not allow_actions:
|
576
|
+
return False, (
|
577
|
+
"The administrator for this server has not allowed actions.\n\n"
|
578
|
+
"Please contact the system administrator, or if you are running this server, "
|
579
|
+
"open the configuration file with `edit config system` and search for 'permissions'."
|
580
|
+
" Under the keys `api:permissions:actions`, " +
|
581
|
+
"you can toggle non-admin actions."
|
582
|
+
)
|
583
|
+
pipe_object = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
584
|
+
results = get_api_connector(instance_keys=instance_keys).drop_pipe(pipe_object, debug=debug)
|
585
|
+
pipes(instance_keys, refresh=True)
|
586
|
+
return results
|
595
587
|
|
596
588
|
|
597
589
|
@app.delete(
|
598
|
-
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/
|
590
|
+
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/clear',
|
599
591
|
tags=['Pipes: Data'],
|
600
592
|
)
|
601
|
-
def
|
593
|
+
def clear_pipe(
|
602
594
|
connector_keys: str,
|
603
595
|
metric_key: str,
|
604
596
|
location_key: str,
|
605
597
|
instance_keys: Optional[str] = None,
|
598
|
+
begin: Union[str, int, None] = None,
|
599
|
+
end: Union[str, int, None] = None,
|
600
|
+
params: Optional[str] = None,
|
606
601
|
curr_user = (
|
607
602
|
fastapi.Depends(manager) if not no_auth else None
|
608
603
|
),
|
609
604
|
):
|
610
605
|
"""
|
611
|
-
|
606
|
+
Delete rows from a pipe's target table.
|
612
607
|
"""
|
608
|
+
_params = {}
|
609
|
+
if params == 'null':
|
610
|
+
params = None
|
611
|
+
if params is not None:
|
612
|
+
try:
|
613
|
+
_params = json.loads(params)
|
614
|
+
except Exception:
|
615
|
+
_params = None
|
616
|
+
if not isinstance(_params, dict):
|
617
|
+
raise fastapi.HTTPException(
|
618
|
+
status_code=409,
|
619
|
+
detail="Params must be a valid JSON-encoded dictionary.",
|
620
|
+
)
|
621
|
+
|
613
622
|
allow_actions = mrsm.get_config('system', 'api', 'permissions', 'actions', 'non_admin')
|
614
623
|
if not allow_actions:
|
615
624
|
return False, (
|
@@ -619,13 +628,19 @@ def drop_pipe(
|
|
619
628
|
" Under the keys `api:permissions:actions`, " +
|
620
629
|
"you can toggle non-admin actions."
|
621
630
|
)
|
622
|
-
|
623
|
-
|
631
|
+
pipe = get_pipe(connector_keys, metric_key, location_key, instance_keys)
|
632
|
+
begin, end = pipe.parse_date_bounds(begin, end)
|
633
|
+
results = get_api_connector(instance_keys=instance_keys).clear_pipe(
|
634
|
+
pipe,
|
635
|
+
begin=begin,
|
636
|
+
end=end,
|
637
|
+
params=_params,
|
638
|
+
debug=debug,
|
639
|
+
)
|
624
640
|
pipes(instance_keys, refresh=True)
|
625
641
|
return results
|
626
642
|
|
627
643
|
|
628
|
-
|
629
644
|
@app.get(
|
630
645
|
pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/csv',
|
631
646
|
tags=['Pipes: Data'],
|
meerschaum/config/_version.py
CHANGED
@@ -414,19 +414,24 @@ def get_pipe_id(
|
|
414
414
|
self,
|
415
415
|
pipe: mrsm.Pipe,
|
416
416
|
debug: bool = False,
|
417
|
-
) -> int:
|
417
|
+
) -> Union[int, str, None]:
|
418
418
|
"""Get a Pipe's ID from the API."""
|
419
419
|
from meerschaum.utils.misc import is_int
|
420
420
|
r_url = pipe_r_url(pipe)
|
421
421
|
response = self.get(
|
422
422
|
r_url + '/id',
|
423
|
-
|
423
|
+
params={
|
424
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
425
|
+
},
|
426
|
+
debug=debug,
|
424
427
|
)
|
425
428
|
if debug:
|
426
429
|
dprint(f"Got pipe ID: {response.text}")
|
427
430
|
try:
|
428
431
|
if is_int(response.text):
|
429
432
|
return int(response.text)
|
433
|
+
if response.text and response.text[0] != '{':
|
434
|
+
return response.text
|
430
435
|
except Exception as e:
|
431
436
|
warn(f"Failed to get the ID for {pipe}:\n{e}")
|
432
437
|
return None
|
@@ -450,7 +455,13 @@ def get_pipe_attributes(
|
|
450
455
|
If the pipe does not exist, return an empty dictionary.
|
451
456
|
"""
|
452
457
|
r_url = pipe_r_url(pipe)
|
453
|
-
response = self.get(
|
458
|
+
response = self.get(
|
459
|
+
r_url + '/attributes',
|
460
|
+
params={
|
461
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
462
|
+
},
|
463
|
+
debug=debug
|
464
|
+
)
|
454
465
|
try:
|
455
466
|
return json.loads(response.text)
|
456
467
|
except Exception as e:
|
@@ -489,9 +500,13 @@ def get_sync_time(
|
|
489
500
|
r_url = pipe_r_url(pipe)
|
490
501
|
response = self.get(
|
491
502
|
r_url + '/sync_time',
|
492
|
-
json
|
493
|
-
params
|
494
|
-
|
503
|
+
json=params,
|
504
|
+
params={
|
505
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
506
|
+
'newest': newest,
|
507
|
+
'debug': debug,
|
508
|
+
},
|
509
|
+
debug=debug,
|
495
510
|
)
|
496
511
|
if not response:
|
497
512
|
warn(f"Failed to get the sync time for {pipe}:\n" + response.text)
|
@@ -532,7 +547,13 @@ def pipe_exists(
|
|
532
547
|
from meerschaum.utils.debug import dprint
|
533
548
|
from meerschaum.utils.warnings import warn
|
534
549
|
r_url = pipe_r_url(pipe)
|
535
|
-
response = self.get(
|
550
|
+
response = self.get(
|
551
|
+
r_url + '/exists',
|
552
|
+
params={
|
553
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
554
|
+
},
|
555
|
+
debug=debug,
|
556
|
+
)
|
536
557
|
if not response:
|
537
558
|
warn(f"Failed to check if {pipe} exists:\n{response.text}")
|
538
559
|
return False
|
@@ -570,8 +591,8 @@ def create_metadata(
|
|
570
591
|
def get_pipe_rowcount(
|
571
592
|
self,
|
572
593
|
pipe: mrsm.Pipe,
|
573
|
-
begin:
|
574
|
-
end:
|
594
|
+
begin: Union[str, datetime, int, None] = None,
|
595
|
+
end: Union[str, datetime, int, None] = None,
|
575
596
|
params: Optional[Dict[str, Any]] = None,
|
576
597
|
remote: bool = False,
|
577
598
|
debug: bool = False,
|
@@ -583,10 +604,10 @@ def get_pipe_rowcount(
|
|
583
604
|
pipe: 'meerschaum.Pipe':
|
584
605
|
The pipe whose row count we are counting.
|
585
606
|
|
586
|
-
begin:
|
607
|
+
begin: Union[str, datetime, int, None], default None
|
587
608
|
If provided, bound the count by this datetime.
|
588
609
|
|
589
|
-
end:
|
610
|
+
end: Union[str, datetime, int, None], default None
|
590
611
|
If provided, bound the count by this datetime.
|
591
612
|
|
592
613
|
params: Optional[Dict[str, Any]], default None
|
@@ -608,6 +629,7 @@ def get_pipe_rowcount(
|
|
608
629
|
'begin': begin,
|
609
630
|
'end': end,
|
610
631
|
'remote': remote,
|
632
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
611
633
|
},
|
612
634
|
debug = debug
|
613
635
|
)
|
@@ -645,7 +667,10 @@ def drop_pipe(
|
|
645
667
|
r_url = pipe_r_url(pipe)
|
646
668
|
response = self.delete(
|
647
669
|
r_url + '/drop',
|
648
|
-
|
670
|
+
params={
|
671
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
672
|
+
},
|
673
|
+
debug=debug,
|
649
674
|
)
|
650
675
|
if debug:
|
651
676
|
dprint(response.text)
|
@@ -668,6 +693,9 @@ def drop_pipe(
|
|
668
693
|
def clear_pipe(
|
669
694
|
self,
|
670
695
|
pipe: mrsm.Pipe,
|
696
|
+
begin: Union[str, datetime, int, None] = None,
|
697
|
+
end: Union[str, datetime, int, None] = None,
|
698
|
+
params: Optional[Dict[str, Any]] = None,
|
671
699
|
debug: bool = False,
|
672
700
|
**kw
|
673
701
|
) -> SuccessTuple:
|
@@ -683,20 +711,33 @@ def clear_pipe(
|
|
683
711
|
-------
|
684
712
|
A success tuple.
|
685
713
|
"""
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
location_keys=pipe.location_key,
|
696
|
-
force=True,
|
714
|
+
r_url = pipe_r_url(pipe)
|
715
|
+
response = self.delete(
|
716
|
+
r_url + '/clear',
|
717
|
+
params={
|
718
|
+
'begin': begin,
|
719
|
+
'end': end,
|
720
|
+
'params': json.dumps(params),
|
721
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
722
|
+
},
|
697
723
|
debug=debug,
|
698
|
-
**kw
|
699
724
|
)
|
725
|
+
if debug:
|
726
|
+
dprint(response.text)
|
727
|
+
|
728
|
+
try:
|
729
|
+
data = response.json()
|
730
|
+
except Exception as e:
|
731
|
+
return False, f"Failed to clear {pipe} with constraints {begin=}, {end=}, {params=}."
|
732
|
+
|
733
|
+
if isinstance(data, list):
|
734
|
+
response_tuple = data[0], data[1]
|
735
|
+
elif 'detail' in response.json():
|
736
|
+
response_tuple = response.__bool__(), data['detail']
|
737
|
+
else:
|
738
|
+
response_tuple = response.__bool__(), response.text
|
739
|
+
|
740
|
+
return response_tuple
|
700
741
|
|
701
742
|
|
702
743
|
def get_pipe_columns_types(
|
@@ -728,7 +769,10 @@ def get_pipe_columns_types(
|
|
728
769
|
r_url = pipe_r_url(pipe) + '/columns/types'
|
729
770
|
response = self.get(
|
730
771
|
r_url,
|
731
|
-
|
772
|
+
params={
|
773
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
774
|
+
},
|
775
|
+
debug=debug,
|
732
776
|
)
|
733
777
|
j = response.json()
|
734
778
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
@@ -760,7 +804,10 @@ def get_pipe_columns_indices(
|
|
760
804
|
r_url = pipe_r_url(pipe) + '/columns/indices'
|
761
805
|
response = self.get(
|
762
806
|
r_url,
|
763
|
-
|
807
|
+
params={
|
808
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
809
|
+
},
|
810
|
+
debug=debug,
|
764
811
|
)
|
765
812
|
j = response.json()
|
766
813
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
@@ -779,14 +826,16 @@ def get_pipe_index_names(self, pipe: mrsm.Pipe, debug: bool = False) -> Dict[str
|
|
779
826
|
r_url = pipe_r_url(pipe) + '/indices/names'
|
780
827
|
response = self.get(
|
781
828
|
r_url,
|
782
|
-
|
829
|
+
params={
|
830
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
831
|
+
},
|
832
|
+
debug=debug,
|
783
833
|
)
|
784
834
|
j = response.json()
|
785
835
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
786
836
|
warn(j['detail'])
|
787
|
-
return
|
837
|
+
return {}
|
788
838
|
if not isinstance(j, dict):
|
789
839
|
warn(response.text)
|
790
|
-
return
|
840
|
+
return {}
|
791
841
|
return j
|
792
|
-
|
@@ -499,6 +499,7 @@ def get_create_index_queries(
|
|
499
499
|
get_rename_table_queries,
|
500
500
|
COALESCE_UNIQUE_INDEX_FLAVORS,
|
501
501
|
)
|
502
|
+
from meerschaum.utils.dtypes import are_dtypes_equal
|
502
503
|
from meerschaum.utils.dtypes.sql import (
|
503
504
|
get_db_type_from_pd_type,
|
504
505
|
get_pd_type_from_db_type,
|
@@ -793,8 +794,19 @@ def get_create_index_queries(
|
|
793
794
|
cols_names = [sql_item_name(col, self.flavor, None) for col in cols if col]
|
794
795
|
if not cols_names:
|
795
796
|
continue
|
797
|
+
|
796
798
|
cols_names_str = ", ".join(cols_names)
|
797
|
-
|
799
|
+
index_query_params_clause = f" ({cols_names_str})"
|
800
|
+
if self.flavor == 'postgis':
|
801
|
+
for col in cols:
|
802
|
+
col_typ = existing_cols_pd_types.get(cols[0], 'object')
|
803
|
+
if col_typ != 'object' and are_dtypes_equal(col_typ, 'geometry'):
|
804
|
+
index_query_params_clause = f" USING GIST ({cols_names_str})"
|
805
|
+
break
|
806
|
+
|
807
|
+
index_queries[ix_key] = [
|
808
|
+
f"CREATE INDEX {ix_name} ON {_pipe_name}{index_query_params_clause}"
|
809
|
+
]
|
798
810
|
|
799
811
|
indices_cols_str = ', '.join(
|
800
812
|
list({
|
@@ -1544,7 +1556,11 @@ def create_pipe_table_from_df(
|
|
1544
1556
|
get_datetime_cols,
|
1545
1557
|
get_bytes_cols,
|
1546
1558
|
)
|
1547
|
-
from meerschaum.utils.sql import
|
1559
|
+
from meerschaum.utils.sql import (
|
1560
|
+
get_create_table_queries,
|
1561
|
+
sql_item_name,
|
1562
|
+
get_create_schema_if_not_exists_queries,
|
1563
|
+
)
|
1548
1564
|
from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type
|
1549
1565
|
primary_key = pipe.columns.get('primary', None)
|
1550
1566
|
primary_key_typ = (
|
@@ -1601,15 +1617,21 @@ def create_pipe_table_from_df(
|
|
1601
1617
|
if autoincrement:
|
1602
1618
|
_ = new_dtypes.pop(primary_key, None)
|
1603
1619
|
|
1620
|
+
schema = self.get_pipe_schema(pipe)
|
1604
1621
|
create_table_queries = get_create_table_queries(
|
1605
1622
|
new_dtypes,
|
1606
1623
|
pipe.target,
|
1607
1624
|
self.flavor,
|
1608
|
-
schema=
|
1625
|
+
schema=schema,
|
1609
1626
|
primary_key=primary_key,
|
1610
1627
|
primary_key_db_type=primary_key_db_type,
|
1611
1628
|
datetime_column=dt_col,
|
1612
1629
|
)
|
1630
|
+
if schema:
|
1631
|
+
create_table_queries = (
|
1632
|
+
get_create_schema_if_not_exists_queries(schema, self.flavor)
|
1633
|
+
+ create_table_queries
|
1634
|
+
)
|
1613
1635
|
success = all(
|
1614
1636
|
self.exec_queries(create_table_queries, break_on_error=True, rollback=True, debug=debug)
|
1615
1637
|
)
|
@@ -2085,6 +2107,7 @@ def sync_pipe_inplace(
|
|
2085
2107
|
get_update_queries,
|
2086
2108
|
get_null_replacement,
|
2087
2109
|
get_create_table_queries,
|
2110
|
+
get_create_schema_if_not_exists_queries,
|
2088
2111
|
get_table_cols_types,
|
2089
2112
|
session_execute,
|
2090
2113
|
dateadd_str,
|
@@ -2164,18 +2187,28 @@ def sync_pipe_inplace(
|
|
2164
2187
|
warn(drop_stale_msg)
|
2165
2188
|
return drop_stale_success, drop_stale_msg
|
2166
2189
|
|
2167
|
-
sqlalchemy, sqlalchemy_orm = mrsm.attempt_import(
|
2190
|
+
sqlalchemy, sqlalchemy_orm = mrsm.attempt_import(
|
2191
|
+
'sqlalchemy',
|
2192
|
+
'sqlalchemy.orm',
|
2193
|
+
)
|
2168
2194
|
if not pipe.exists(debug=debug):
|
2195
|
+
schema = self.get_pipe_schema(pipe)
|
2169
2196
|
create_pipe_queries = get_create_table_queries(
|
2170
2197
|
metadef,
|
2171
2198
|
pipe.target,
|
2172
2199
|
self.flavor,
|
2173
|
-
schema=
|
2200
|
+
schema=schema,
|
2174
2201
|
primary_key=primary_key,
|
2175
2202
|
primary_key_db_type=primary_key_db_type,
|
2176
2203
|
autoincrement=autoincrement,
|
2177
2204
|
datetime_column=dt_col,
|
2178
2205
|
)
|
2206
|
+
if schema:
|
2207
|
+
create_pipe_queries = (
|
2208
|
+
get_create_schema_if_not_exists_queries(schema, self.flavor)
|
2209
|
+
+ create_pipe_queries
|
2210
|
+
)
|
2211
|
+
|
2179
2212
|
results = self.exec_queries(create_pipe_queries, debug=debug)
|
2180
2213
|
if not all(results):
|
2181
2214
|
_ = clean_up_temp_tables()
|
meerschaum/core/Pipe/_data.py
CHANGED
@@ -679,12 +679,15 @@ def get_chunk_bounds(
|
|
679
679
|
include_greater_than_end = not bounded and end is None
|
680
680
|
if begin is None:
|
681
681
|
begin = self.get_sync_time(newest=False, debug=debug)
|
682
|
+
consolidate_end_chunk = False
|
682
683
|
if end is None:
|
683
684
|
end = self.get_sync_time(newest=True, debug=debug)
|
684
685
|
if end is not None and hasattr(end, 'tzinfo'):
|
685
686
|
end += timedelta(minutes=1)
|
687
|
+
consolidate_end_chunk = True
|
686
688
|
elif are_dtypes_equal(str(type(end)), 'int'):
|
687
689
|
end += 1
|
690
|
+
consolidate_end_chunk = True
|
688
691
|
if begin is None and end is None:
|
689
692
|
return [(None, None)]
|
690
693
|
|
@@ -707,9 +710,15 @@ def get_chunk_bounds(
|
|
707
710
|
num_chunks += 1
|
708
711
|
if num_chunks >= max_chunks:
|
709
712
|
raise ValueError(
|
710
|
-
f"Too many chunks of size '{interval_str(chunk_interval)}'
|
713
|
+
f"Too many chunks of size '{interval_str(chunk_interval)}' "
|
714
|
+
f"between '{begin}' and '{end}'."
|
711
715
|
)
|
712
716
|
|
717
|
+
if num_chunks > 1 and consolidate_end_chunk:
|
718
|
+
last_bounds, second_last_bounds = chunk_bounds[-1], chunk_bounds[-2]
|
719
|
+
chunk_bounds = chunk_bounds[:-2]
|
720
|
+
chunk_bounds.append((second_last_bounds[0], last_bounds[1]))
|
721
|
+
|
713
722
|
### The chunk interval might be too large.
|
714
723
|
if not chunk_bounds and end >= begin:
|
715
724
|
chunk_bounds = [(begin, end)]
|
meerschaum/core/Pipe/_verify.py
CHANGED
@@ -282,6 +282,7 @@ def verify(
|
|
282
282
|
)
|
283
283
|
bounds_success_tuples[first_chunk_bounds] = (first_success, first_msg)
|
284
284
|
info(f"Completed first chunk for {self}:\n {first_label}\n")
|
285
|
+
chunk_bounds = chunk_bounds[1:]
|
285
286
|
|
286
287
|
pool = get_pool(workers=workers)
|
287
288
|
batches = self.get_chunk_bounds_batches(chunk_bounds, batchsize=batchsize, workers=workers)
|
meerschaum/plugins/__init__.py
CHANGED
@@ -91,8 +91,8 @@ def make_action(
|
|
91
91
|
|
92
92
|
|
93
93
|
def pre_sync_hook(
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
|
meerschaum/utils/dataframe.py
CHANGED
@@ -922,7 +922,12 @@ def get_geometry_cols(
|
|
922
922
|
for col in geo_cols:
|
923
923
|
try:
|
924
924
|
sample_geo_series = gpd.GeoSeries(df[col], crs=None)
|
925
|
-
geometry_types = {
|
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)
|
926
931
|
srid = (
|
927
932
|
(
|
928
933
|
sample_geo_series.crs.sub_crs_list[0].to_epsg()
|
@@ -933,6 +938,8 @@ def get_geometry_cols(
|
|
933
938
|
else 0
|
934
939
|
)
|
935
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'
|
936
943
|
except Exception:
|
937
944
|
srid = 0
|
938
945
|
geometry_type = 'geometry'
|