singlestoredb 1.1.0__cp38-abi3-win32.whl → 1.2.0__cp38-abi3-win32.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_accel.pyd +0 -0
- singlestoredb/__init__.py +1 -1
- singlestoredb/config.py +6 -0
- singlestoredb/connection.py +3 -0
- 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/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 +12 -0
- 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 +5 -4
- 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-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/METADATA +1 -1
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/RECORD +30 -26
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/LICENSE +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/WHEEL +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/entry_points.txt +0 -0
- {singlestoredb-1.1.0.dist-info → singlestoredb-1.2.0.dist-info}/top_level.txt +0 -0
_singlestoredb_accel.pyd
CHANGED
|
Binary file
|
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
|
@@ -1285,6 +1285,7 @@ def connect(
|
|
|
1285
1285
|
inf_as_null: Optional[bool] = None,
|
|
1286
1286
|
encoding_errors: Optional[str] = None,
|
|
1287
1287
|
track_env: Optional[bool] = None,
|
|
1288
|
+
enable_extended_data_types: Optional[bool] = None,
|
|
1288
1289
|
) -> Connection:
|
|
1289
1290
|
"""
|
|
1290
1291
|
Return a SingleStoreDB connection.
|
|
@@ -1361,6 +1362,8 @@ def connect(
|
|
|
1361
1362
|
The error handler name for value decoding errors
|
|
1362
1363
|
track_env : bool, optional
|
|
1363
1364
|
Should the connection track the SINGLESTOREDB_URL environment variable?
|
|
1365
|
+
enable_extended_data_types : bool, optional
|
|
1366
|
+
Should extended data types (BSON, vector) be enabled?
|
|
1364
1367
|
|
|
1365
1368
|
Examples
|
|
1366
1369
|
--------
|
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
|
|