singlestoredb 1.1.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +1 -1
- singlestoredb/config.py +6 -0
- singlestoredb/connection.py +23 -1
- singlestoredb/converters.py +390 -0
- singlestoredb/functions/ext/asgi.py +7 -1
- singlestoredb/fusion/handler.py +14 -8
- singlestoredb/fusion/handlers/stage.py +167 -84
- singlestoredb/fusion/handlers/workspace.py +250 -108
- singlestoredb/fusion/registry.py +27 -10
- singlestoredb/http/connection.py +18 -1
- singlestoredb/management/__init__.py +1 -0
- singlestoredb/management/organization.py +4 -0
- singlestoredb/management/utils.py +2 -2
- singlestoredb/management/workspace.py +79 -6
- singlestoredb/mysql/connection.py +92 -16
- singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +16 -0
- singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
- singlestoredb/mysql/cursors.py +13 -10
- singlestoredb/mysql/protocol.py +50 -1
- singlestoredb/notebook/__init__.py +15 -0
- singlestoredb/notebook/_objects.py +212 -0
- singlestoredb/tests/test.sql +49 -0
- singlestoredb/tests/test_connection.py +174 -0
- singlestoredb/utils/results.py +5 -1
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/METADATA +1 -1
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/RECORD +31 -27
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/LICENSE +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/WHEEL +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/entry_points.txt +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.3.0.dist-info}/top_level.txt +0 -0
singlestoredb/__init__.py
CHANGED
singlestoredb/config.py
CHANGED
|
@@ -215,6 +215,12 @@ register_option(
|
|
|
215
215
|
environ='SINGLESTOREDB_TRACK_ENV',
|
|
216
216
|
)
|
|
217
217
|
|
|
218
|
+
register_option(
|
|
219
|
+
'enable_extended_data_types', 'bool', check_bool, True,
|
|
220
|
+
'Should extended data types (BSON, vector) be enabled?',
|
|
221
|
+
environ='SINGLESTOREDB_ENABLE_EXTENDED_DATA_TYPES',
|
|
222
|
+
)
|
|
223
|
+
|
|
218
224
|
register_option(
|
|
219
225
|
'fusion.enabled', 'bool', check_bool, False,
|
|
220
226
|
'Should Fusion SQL queries be enabled?',
|
singlestoredb/connection.py
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
"""SingleStoreDB connections and cursors."""
|
|
3
3
|
import abc
|
|
4
4
|
import inspect
|
|
5
|
+
import io
|
|
6
|
+
import queue
|
|
5
7
|
import re
|
|
8
|
+
import sys
|
|
6
9
|
import warnings
|
|
7
10
|
import weakref
|
|
8
11
|
from collections.abc import Mapping
|
|
@@ -33,6 +36,11 @@ from .config import get_option
|
|
|
33
36
|
from .utils.results import Description
|
|
34
37
|
from .utils.results import Result
|
|
35
38
|
|
|
39
|
+
if sys.version_info < (3, 10):
|
|
40
|
+
InfileQueue = queue.Queue
|
|
41
|
+
else:
|
|
42
|
+
InfileQueue = queue.Queue[Union[bytes, str]]
|
|
43
|
+
|
|
36
44
|
|
|
37
45
|
# DB-API settings
|
|
38
46
|
apilevel = '2.0'
|
|
@@ -496,6 +504,14 @@ class Cursor(metaclass=abc.ABCMeta):
|
|
|
496
504
|
def execute(
|
|
497
505
|
self, query: str,
|
|
498
506
|
args: Optional[Union[Sequence[Any], Dict[str, Any], Any]] = None,
|
|
507
|
+
infile_stream: Optional[ # type: ignore
|
|
508
|
+
Union[
|
|
509
|
+
io.RawIOBase,
|
|
510
|
+
io.TextIOBase,
|
|
511
|
+
Iterator[Union[bytes, str]],
|
|
512
|
+
InfileQueue,
|
|
513
|
+
]
|
|
514
|
+
] = None,
|
|
499
515
|
) -> int:
|
|
500
516
|
"""
|
|
501
517
|
Execute a SQL statement.
|
|
@@ -510,6 +526,8 @@ class Cursor(metaclass=abc.ABCMeta):
|
|
|
510
526
|
The SQL statement to execute
|
|
511
527
|
args : Sequence or dict, optional
|
|
512
528
|
Parameters to substitute into the SQL code
|
|
529
|
+
infile_stream : io.RawIOBase or io.TextIOBase or Iterator[bytes|str], optional
|
|
530
|
+
Data stream for ``LOCAL INFILE`` statement
|
|
513
531
|
|
|
514
532
|
Examples
|
|
515
533
|
--------
|
|
@@ -1285,6 +1303,7 @@ def connect(
|
|
|
1285
1303
|
inf_as_null: Optional[bool] = None,
|
|
1286
1304
|
encoding_errors: Optional[str] = None,
|
|
1287
1305
|
track_env: Optional[bool] = None,
|
|
1306
|
+
enable_extended_data_types: Optional[bool] = None,
|
|
1288
1307
|
) -> Connection:
|
|
1289
1308
|
"""
|
|
1290
1309
|
Return a SingleStoreDB connection.
|
|
@@ -1338,7 +1357,8 @@ def connect(
|
|
|
1338
1357
|
autocommit : bool, optional
|
|
1339
1358
|
Enable autocommits
|
|
1340
1359
|
results_type : str, optional
|
|
1341
|
-
The form of the query results: tuples, namedtuples, dicts
|
|
1360
|
+
The form of the query results: tuples, namedtuples, dicts,
|
|
1361
|
+
numpy, polars, pandas, arrow
|
|
1342
1362
|
results_format : str, optional
|
|
1343
1363
|
Deprecated. This option has been renamed to results_type.
|
|
1344
1364
|
program_name : str, optional
|
|
@@ -1361,6 +1381,8 @@ def connect(
|
|
|
1361
1381
|
The error handler name for value decoding errors
|
|
1362
1382
|
track_env : bool, optional
|
|
1363
1383
|
Should the connection track the SINGLESTOREDB_URL environment variable?
|
|
1384
|
+
enable_extended_data_types : bool, optional
|
|
1385
|
+
Should extended data types (BSON, vector) be enabled?
|
|
1364
1386
|
|
|
1365
1387
|
Examples
|
|
1366
1388
|
--------
|
singlestoredb/converters.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Data value conversion utilities."""
|
|
3
3
|
import datetime
|
|
4
4
|
import re
|
|
5
|
+
import struct
|
|
5
6
|
from base64 import b64decode
|
|
6
7
|
from decimal import Decimal
|
|
7
8
|
from json import loads as json_loads
|
|
@@ -25,7 +26,20 @@ try:
|
|
|
25
26
|
except ImportError:
|
|
26
27
|
has_pygeos = False
|
|
27
28
|
|
|
29
|
+
try:
|
|
30
|
+
import numpy
|
|
31
|
+
has_numpy = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
has_numpy = False
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
import bson
|
|
37
|
+
has_bson = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
has_bson = False
|
|
40
|
+
|
|
28
41
|
|
|
42
|
+
# Cache fromisoformat methods if they exist
|
|
29
43
|
# Cache fromisoformat methods if they exist
|
|
30
44
|
_dt_datetime_fromisoformat = None
|
|
31
45
|
if hasattr(datetime.datetime, 'fromisoformat'):
|
|
@@ -527,6 +541,369 @@ def geometry_or_none(x: Optional[str]) -> Optional[Any]:
|
|
|
527
541
|
return x
|
|
528
542
|
|
|
529
543
|
|
|
544
|
+
def float32_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
545
|
+
"""
|
|
546
|
+
Covert value to float32 array.
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
x : str or None
|
|
551
|
+
JSON array
|
|
552
|
+
|
|
553
|
+
Returns
|
|
554
|
+
-------
|
|
555
|
+
float32 numpy array
|
|
556
|
+
If input value is not None and numpy is installed
|
|
557
|
+
float Python list
|
|
558
|
+
If input value is not None and numpy is not installed
|
|
559
|
+
None
|
|
560
|
+
If input value is None
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
if x is None:
|
|
564
|
+
return None
|
|
565
|
+
|
|
566
|
+
if has_numpy:
|
|
567
|
+
return numpy.array(json_loads(x), dtype=numpy.float32)
|
|
568
|
+
|
|
569
|
+
return map(float, json_loads(x))
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def float32_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
573
|
+
"""
|
|
574
|
+
Covert value to float32 array.
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
x : bytes or None
|
|
579
|
+
Little-endian block of bytes.
|
|
580
|
+
|
|
581
|
+
Returns
|
|
582
|
+
-------
|
|
583
|
+
float32 numpy array
|
|
584
|
+
If input value is not None and numpy is installed
|
|
585
|
+
float Python list
|
|
586
|
+
If input value is not None and numpy is not installed
|
|
587
|
+
None
|
|
588
|
+
If input value is None
|
|
589
|
+
|
|
590
|
+
"""
|
|
591
|
+
if x is None:
|
|
592
|
+
return None
|
|
593
|
+
|
|
594
|
+
if has_numpy:
|
|
595
|
+
return numpy.frombuffer(x, dtype=numpy.float32)
|
|
596
|
+
|
|
597
|
+
return struct.unpack(f'<{len(x)/4}f', x)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def float64_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
601
|
+
"""
|
|
602
|
+
Covert value to float64 array.
|
|
603
|
+
|
|
604
|
+
Parameters
|
|
605
|
+
----------
|
|
606
|
+
x : str or None
|
|
607
|
+
JSON array
|
|
608
|
+
|
|
609
|
+
Returns
|
|
610
|
+
-------
|
|
611
|
+
float64 numpy array
|
|
612
|
+
If input value is not None and numpy is installed
|
|
613
|
+
float Python list
|
|
614
|
+
If input value is not None and numpy is not installed
|
|
615
|
+
None
|
|
616
|
+
If input value is None
|
|
617
|
+
|
|
618
|
+
"""
|
|
619
|
+
if x is None:
|
|
620
|
+
return None
|
|
621
|
+
|
|
622
|
+
if has_numpy:
|
|
623
|
+
return numpy.array(json_loads(x), dtype=numpy.float64)
|
|
624
|
+
|
|
625
|
+
return map(float, json_loads(x))
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def float64_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
629
|
+
"""
|
|
630
|
+
Covert value to float64 array.
|
|
631
|
+
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
x : bytes or None
|
|
635
|
+
JSON array
|
|
636
|
+
|
|
637
|
+
Returns
|
|
638
|
+
-------
|
|
639
|
+
float64 numpy array
|
|
640
|
+
If input value is not None and numpy is installed
|
|
641
|
+
float Python list
|
|
642
|
+
If input value is not None and numpy is not installed
|
|
643
|
+
None
|
|
644
|
+
If input value is None
|
|
645
|
+
|
|
646
|
+
"""
|
|
647
|
+
if x is None:
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
if has_numpy:
|
|
651
|
+
return numpy.frombuffer(x, dtype=numpy.float64)
|
|
652
|
+
|
|
653
|
+
return struct.unpack(f'<{len(x)/8}d', x)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def int8_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
657
|
+
"""
|
|
658
|
+
Covert value to int8 array.
|
|
659
|
+
|
|
660
|
+
Parameters
|
|
661
|
+
----------
|
|
662
|
+
x : str or None
|
|
663
|
+
JSON array
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
-------
|
|
667
|
+
int8 numpy array
|
|
668
|
+
If input value is not None and numpy is installed
|
|
669
|
+
int Python list
|
|
670
|
+
If input value is not None and numpy is not installed
|
|
671
|
+
None
|
|
672
|
+
If input value is None
|
|
673
|
+
|
|
674
|
+
"""
|
|
675
|
+
if x is None:
|
|
676
|
+
return None
|
|
677
|
+
|
|
678
|
+
if has_numpy:
|
|
679
|
+
return numpy.array(json_loads(x), dtype=numpy.int8)
|
|
680
|
+
|
|
681
|
+
return map(int, json_loads(x))
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def int8_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
685
|
+
"""
|
|
686
|
+
Covert value to int8 array.
|
|
687
|
+
|
|
688
|
+
Parameters
|
|
689
|
+
----------
|
|
690
|
+
x : bytes or None
|
|
691
|
+
Little-endian block of bytes.
|
|
692
|
+
|
|
693
|
+
Returns
|
|
694
|
+
-------
|
|
695
|
+
int8 numpy array
|
|
696
|
+
If input value is not None and numpy is installed
|
|
697
|
+
int Python list
|
|
698
|
+
If input value is not None and numpy is not installed
|
|
699
|
+
None
|
|
700
|
+
If input value is None
|
|
701
|
+
|
|
702
|
+
"""
|
|
703
|
+
if x is None:
|
|
704
|
+
return None
|
|
705
|
+
|
|
706
|
+
if has_numpy:
|
|
707
|
+
return numpy.frombuffer(x, dtype=numpy.int8)
|
|
708
|
+
|
|
709
|
+
return struct.unpack(f'<{len(x)}b', x)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def int16_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
713
|
+
"""
|
|
714
|
+
Covert value to int16 array.
|
|
715
|
+
|
|
716
|
+
Parameters
|
|
717
|
+
----------
|
|
718
|
+
x : str or None
|
|
719
|
+
JSON array
|
|
720
|
+
|
|
721
|
+
Returns
|
|
722
|
+
-------
|
|
723
|
+
int16 numpy array
|
|
724
|
+
If input value is not None and numpy is installed
|
|
725
|
+
int Python list
|
|
726
|
+
If input value is not None and numpy is not installed
|
|
727
|
+
None
|
|
728
|
+
If input value is None
|
|
729
|
+
|
|
730
|
+
"""
|
|
731
|
+
if x is None:
|
|
732
|
+
return None
|
|
733
|
+
|
|
734
|
+
if has_numpy:
|
|
735
|
+
return numpy.array(json_loads(x), dtype=numpy.int16)
|
|
736
|
+
|
|
737
|
+
return map(int, json_loads(x))
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def int16_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
741
|
+
"""
|
|
742
|
+
Covert value to int16 array.
|
|
743
|
+
|
|
744
|
+
Parameters
|
|
745
|
+
----------
|
|
746
|
+
x : bytes or None
|
|
747
|
+
Little-endian block of bytes.
|
|
748
|
+
|
|
749
|
+
Returns
|
|
750
|
+
-------
|
|
751
|
+
int16 numpy array
|
|
752
|
+
If input value is not None and numpy is installed
|
|
753
|
+
int Python list
|
|
754
|
+
If input value is not None and numpy is not installed
|
|
755
|
+
None
|
|
756
|
+
If input value is None
|
|
757
|
+
|
|
758
|
+
"""
|
|
759
|
+
if x is None:
|
|
760
|
+
return None
|
|
761
|
+
|
|
762
|
+
if has_numpy:
|
|
763
|
+
return numpy.frombuffer(x, dtype=numpy.int16)
|
|
764
|
+
|
|
765
|
+
return struct.unpack(f'<{len(x)/2}h', x)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def int32_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
769
|
+
"""
|
|
770
|
+
Covert value to int32 array.
|
|
771
|
+
|
|
772
|
+
Parameters
|
|
773
|
+
----------
|
|
774
|
+
x : str or None
|
|
775
|
+
JSON array
|
|
776
|
+
|
|
777
|
+
Returns
|
|
778
|
+
-------
|
|
779
|
+
int32 numpy array
|
|
780
|
+
If input value is not None and numpy is installed
|
|
781
|
+
int Python list
|
|
782
|
+
If input value is not None and numpy is not installed
|
|
783
|
+
None
|
|
784
|
+
If input value is None
|
|
785
|
+
|
|
786
|
+
"""
|
|
787
|
+
if x is None:
|
|
788
|
+
return None
|
|
789
|
+
|
|
790
|
+
if has_numpy:
|
|
791
|
+
return numpy.array(json_loads(x), dtype=numpy.int32)
|
|
792
|
+
|
|
793
|
+
return map(int, json_loads(x))
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def int32_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
797
|
+
"""
|
|
798
|
+
Covert value to int32 array.
|
|
799
|
+
|
|
800
|
+
Parameters
|
|
801
|
+
----------
|
|
802
|
+
x : bytes or None
|
|
803
|
+
Little-endian block of bytes.
|
|
804
|
+
|
|
805
|
+
Returns
|
|
806
|
+
-------
|
|
807
|
+
int32 numpy array
|
|
808
|
+
If input value is not None and numpy is installed
|
|
809
|
+
int Python list
|
|
810
|
+
If input value is not None and numpy is not installed
|
|
811
|
+
None
|
|
812
|
+
If input value is None
|
|
813
|
+
|
|
814
|
+
"""
|
|
815
|
+
if x is None:
|
|
816
|
+
return None
|
|
817
|
+
|
|
818
|
+
if has_numpy:
|
|
819
|
+
return numpy.frombuffer(x, dtype=numpy.int32)
|
|
820
|
+
|
|
821
|
+
return struct.unpack(f'<{len(x)/4}l', x)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def int64_vector_json_or_none(x: Optional[str]) -> Optional[Any]:
|
|
825
|
+
"""
|
|
826
|
+
Covert value to int64 array.
|
|
827
|
+
|
|
828
|
+
Parameters
|
|
829
|
+
----------
|
|
830
|
+
x : str or None
|
|
831
|
+
JSON array
|
|
832
|
+
|
|
833
|
+
Returns
|
|
834
|
+
-------
|
|
835
|
+
int64 numpy array
|
|
836
|
+
If input value is not None and numpy is installed
|
|
837
|
+
int Python list
|
|
838
|
+
If input value is not None and numpy is not installed
|
|
839
|
+
None
|
|
840
|
+
If input value is None
|
|
841
|
+
|
|
842
|
+
"""
|
|
843
|
+
if x is None:
|
|
844
|
+
return None
|
|
845
|
+
|
|
846
|
+
if has_numpy:
|
|
847
|
+
return numpy.array(json_loads(x), dtype=numpy.int64)
|
|
848
|
+
|
|
849
|
+
return map(int, json_loads(x))
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def int64_vector_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
853
|
+
"""
|
|
854
|
+
Covert value to int64 array.
|
|
855
|
+
|
|
856
|
+
Parameters
|
|
857
|
+
----------
|
|
858
|
+
x : bytes or None
|
|
859
|
+
Little-endian block of bytes.
|
|
860
|
+
|
|
861
|
+
Returns
|
|
862
|
+
-------
|
|
863
|
+
int64 numpy array
|
|
864
|
+
If input value is not None and numpy is installed
|
|
865
|
+
int Python list
|
|
866
|
+
If input value is not None and numpy is not installed
|
|
867
|
+
None
|
|
868
|
+
If input value is None
|
|
869
|
+
|
|
870
|
+
"""
|
|
871
|
+
if x is None:
|
|
872
|
+
return None
|
|
873
|
+
|
|
874
|
+
# Bytes
|
|
875
|
+
if has_numpy:
|
|
876
|
+
return numpy.frombuffer(x, dtype=numpy.int64)
|
|
877
|
+
|
|
878
|
+
return struct.unpack(f'<{len(x)/8}l', x)
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def bson_or_none(x: Optional[bytes]) -> Optional[Any]:
|
|
882
|
+
"""
|
|
883
|
+
Convert a BSON value to a dictionary.
|
|
884
|
+
|
|
885
|
+
Parameters
|
|
886
|
+
----------
|
|
887
|
+
x : bytes or None
|
|
888
|
+
BSON formatted bytes
|
|
889
|
+
|
|
890
|
+
Returns
|
|
891
|
+
-------
|
|
892
|
+
dict
|
|
893
|
+
If input value is not None and bson package is installed
|
|
894
|
+
bytes
|
|
895
|
+
If input value is not None and bson package is not installed
|
|
896
|
+
None
|
|
897
|
+
If input value is None
|
|
898
|
+
|
|
899
|
+
"""
|
|
900
|
+
if x is None:
|
|
901
|
+
return None
|
|
902
|
+
if has_bson:
|
|
903
|
+
return bson.decode(x)
|
|
904
|
+
return x
|
|
905
|
+
|
|
906
|
+
|
|
530
907
|
# Map of database types and conversion functions
|
|
531
908
|
converters: Dict[int, Callable[..., Any]] = {
|
|
532
909
|
0: decimal_or_none,
|
|
@@ -557,4 +934,17 @@ converters: Dict[int, Callable[..., Any]] = {
|
|
|
557
934
|
# 253: identity,
|
|
558
935
|
# 254: identity,
|
|
559
936
|
255: geometry_or_none,
|
|
937
|
+
1001: bson_or_none,
|
|
938
|
+
2001: float32_vector_json_or_none,
|
|
939
|
+
2002: float64_vector_json_or_none,
|
|
940
|
+
2003: int8_vector_json_or_none,
|
|
941
|
+
2004: int16_vector_json_or_none,
|
|
942
|
+
2005: int32_vector_json_or_none,
|
|
943
|
+
2006: int64_vector_json_or_none,
|
|
944
|
+
3001: float32_vector_or_none,
|
|
945
|
+
3002: float64_vector_or_none,
|
|
946
|
+
3003: int8_vector_or_none,
|
|
947
|
+
3004: int16_vector_or_none,
|
|
948
|
+
3005: int32_vector_or_none,
|
|
949
|
+
3006: int64_vector_or_none,
|
|
560
950
|
}
|
|
@@ -455,10 +455,13 @@ class Application(object):
|
|
|
455
455
|
endpoints[name.encode('utf-8')] = func, info
|
|
456
456
|
|
|
457
457
|
# Fully qualified function name
|
|
458
|
-
|
|
458
|
+
elif '.' in funcs:
|
|
459
459
|
pkg_path, func_names = funcs.rsplit('.', 1)
|
|
460
460
|
pkg = importlib.import_module(pkg_path)
|
|
461
461
|
|
|
462
|
+
if pkg is None:
|
|
463
|
+
raise RuntimeError(f'Could not locate module: {pkg}')
|
|
464
|
+
|
|
462
465
|
# Add endpoint for each exported function
|
|
463
466
|
for name, alias in get_func_names(func_names):
|
|
464
467
|
item = getattr(pkg, name)
|
|
@@ -466,6 +469,9 @@ class Application(object):
|
|
|
466
469
|
func, info = make_func(alias, item)
|
|
467
470
|
endpoints[alias.encode('utf-8')] = func, info
|
|
468
471
|
|
|
472
|
+
else:
|
|
473
|
+
raise RuntimeError(f'Could not locate module: {funcs}')
|
|
474
|
+
|
|
469
475
|
elif isinstance(funcs, ModuleType):
|
|
470
476
|
for x in vars(funcs).values():
|
|
471
477
|
if not hasattr(x, '_singlestoredb_attrs'):
|
singlestoredb/fusion/handler.py
CHANGED
|
@@ -220,6 +220,7 @@ def _format_arguments(arg: str) -> str:
|
|
|
220
220
|
|
|
221
221
|
def _to_markdown(txt: str) -> str:
|
|
222
222
|
"""Convert formatting to markdown."""
|
|
223
|
+
txt = re.sub(r'`([^`]+)\s+\<([^\>]+)>`_', r'[\1](\2)', txt)
|
|
223
224
|
txt = txt.replace('``', '`')
|
|
224
225
|
|
|
225
226
|
# Format code blocks
|
|
@@ -228,11 +229,12 @@ def _to_markdown(txt: str) -> str:
|
|
|
228
229
|
while lines:
|
|
229
230
|
line = lines.pop(0)
|
|
230
231
|
if line.endswith('::'):
|
|
231
|
-
out.append(line[:-2])
|
|
232
|
+
out.append(line[:-2] + '.')
|
|
232
233
|
code = []
|
|
233
234
|
while lines and (not lines[0].strip() or lines[0].startswith(' ')):
|
|
234
235
|
code.append(lines.pop(0).rstrip())
|
|
235
|
-
|
|
236
|
+
code_str = re.sub(r'^\s*\n', r'', '\n'.join(code).rstrip())
|
|
237
|
+
out.extend([f'```sql\n{code_str}\n```\n'])
|
|
236
238
|
else:
|
|
237
239
|
out.append(line)
|
|
238
240
|
|
|
@@ -241,9 +243,7 @@ def _to_markdown(txt: str) -> str:
|
|
|
241
243
|
|
|
242
244
|
def build_help(syntax: str, grammar: str) -> str:
|
|
243
245
|
"""Build full help text."""
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
cmd = re.match(r'([A-Z0-9_ ]+)', syntax)
|
|
246
|
+
cmd = re.match(r'([A-Z0-9_ ]+)', syntax.strip())
|
|
247
247
|
if not cmd:
|
|
248
248
|
raise ValueError(f'no command found: {syntax}')
|
|
249
249
|
|
|
@@ -264,7 +264,7 @@ def build_help(syntax: str, grammar: str) -> str:
|
|
|
264
264
|
if 'description' in sections:
|
|
265
265
|
out.extend([sections['description'], '\n\n'])
|
|
266
266
|
|
|
267
|
-
out.
|
|
267
|
+
out.append(f'## Syntax\n\n```sql{syntax}\n```\n\n')
|
|
268
268
|
|
|
269
269
|
if 'arguments' in sections:
|
|
270
270
|
out.extend([
|
|
@@ -272,9 +272,15 @@ def build_help(syntax: str, grammar: str) -> str:
|
|
|
272
272
|
_format_arguments(sections['arguments']),
|
|
273
273
|
'\n\n',
|
|
274
274
|
])
|
|
275
|
+
if 'argument' in sections:
|
|
276
|
+
out.extend([
|
|
277
|
+
'## Argument\n\n',
|
|
278
|
+
_format_arguments(sections['argument']),
|
|
279
|
+
'\n\n',
|
|
280
|
+
])
|
|
275
281
|
|
|
276
282
|
if 'remarks' in sections:
|
|
277
|
-
out.extend(['## Remarks\n', sections['remarks'], '\n\n'])
|
|
283
|
+
out.extend(['## Remarks\n\n', sections['remarks'], '\n\n'])
|
|
278
284
|
|
|
279
285
|
if 'examples' in sections:
|
|
280
286
|
out.extend(['## Examples\n\n', _format_examples(sections['examples']), '\n\n'])
|
|
@@ -282,7 +288,7 @@ def build_help(syntax: str, grammar: str) -> str:
|
|
|
282
288
|
out.extend(['## Example\n\n', _format_examples(sections['example']), '\n\n'])
|
|
283
289
|
|
|
284
290
|
if 'see also' in sections:
|
|
285
|
-
out.extend(['## See Also\n', sections['see also'], '\n\n'])
|
|
291
|
+
out.extend(['## See Also\n\n', sections['see also'], '\n\n'])
|
|
286
292
|
|
|
287
293
|
return ''.join(out).rstrip() + '\n'
|
|
288
294
|
|