pyspiral 0.4.0__pp310-pypy310_pp73-macosx_10_12_x86_64.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.
- pyspiral-0.4.0.dist-info/METADATA +46 -0
- pyspiral-0.4.0.dist-info/RECORD +98 -0
- pyspiral-0.4.0.dist-info/WHEEL +4 -0
- pyspiral-0.4.0.dist-info/entry_points.txt +2 -0
- spiral/__init__.py +10 -0
- spiral/_lib.pypy310-pp73-darwin.so +0 -0
- spiral/adbc.py +393 -0
- spiral/api/__init__.py +64 -0
- spiral/api/admin.py +15 -0
- spiral/api/client.py +160 -0
- spiral/api/filesystems.py +153 -0
- spiral/api/organizations.py +77 -0
- spiral/api/projects.py +197 -0
- spiral/api/telemetry.py +19 -0
- spiral/api/types.py +20 -0
- spiral/api/workloads.py +52 -0
- spiral/arrow_.py +221 -0
- spiral/cli/__init__.py +79 -0
- spiral/cli/__main__.py +4 -0
- spiral/cli/admin.py +16 -0
- spiral/cli/app.py +65 -0
- spiral/cli/console.py +95 -0
- spiral/cli/fs.py +112 -0
- spiral/cli/iceberg/__init__.py +7 -0
- spiral/cli/iceberg/namespaces.py +47 -0
- spiral/cli/iceberg/tables.py +60 -0
- spiral/cli/indexes/__init__.py +19 -0
- spiral/cli/login.py +22 -0
- spiral/cli/orgs.py +90 -0
- spiral/cli/printer.py +53 -0
- spiral/cli/projects.py +136 -0
- spiral/cli/state.py +5 -0
- spiral/cli/tables/__init__.py +121 -0
- spiral/cli/telemetry.py +18 -0
- spiral/cli/types.py +51 -0
- spiral/cli/workloads.py +59 -0
- spiral/client.py +79 -0
- spiral/core/__init__.pyi +0 -0
- spiral/core/client/__init__.pyi +117 -0
- spiral/core/index/__init__.pyi +15 -0
- spiral/core/table/__init__.pyi +108 -0
- spiral/core/table/manifests/__init__.pyi +35 -0
- spiral/core/table/metastore/__init__.pyi +62 -0
- spiral/core/table/spec/__init__.pyi +214 -0
- spiral/datetime_.py +27 -0
- spiral/expressions/__init__.py +245 -0
- spiral/expressions/base.py +149 -0
- spiral/expressions/http.py +86 -0
- spiral/expressions/io.py +100 -0
- spiral/expressions/list_.py +68 -0
- spiral/expressions/mp4.py +62 -0
- spiral/expressions/png.py +18 -0
- spiral/expressions/qoi.py +18 -0
- spiral/expressions/refs.py +58 -0
- spiral/expressions/str_.py +39 -0
- spiral/expressions/struct.py +59 -0
- spiral/expressions/text.py +62 -0
- spiral/expressions/tiff.py +223 -0
- spiral/expressions/udf.py +46 -0
- spiral/grpc_.py +32 -0
- spiral/iceberg/__init__.py +3 -0
- spiral/iceberg/client.py +33 -0
- spiral/indexes/__init__.py +5 -0
- spiral/indexes/client.py +137 -0
- spiral/indexes/index.py +34 -0
- spiral/indexes/scan.py +22 -0
- spiral/project.py +46 -0
- spiral/protogen/_/__init__.py +0 -0
- spiral/protogen/_/arrow/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/protocol/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +1990 -0
- spiral/protogen/_/scandal/__init__.py +178 -0
- spiral/protogen/_/spiral/__init__.py +0 -0
- spiral/protogen/_/spiral/table/__init__.py +22 -0
- spiral/protogen/_/substrait/__init__.py +3399 -0
- spiral/protogen/_/substrait/extensions/__init__.py +115 -0
- spiral/protogen/__init__.py +0 -0
- spiral/protogen/substrait/__init__.py +3399 -0
- spiral/protogen/substrait/extensions/__init__.py +115 -0
- spiral/protogen/util.py +41 -0
- spiral/py.typed +0 -0
- spiral/server.py +17 -0
- spiral/settings.py +101 -0
- spiral/substrait_.py +279 -0
- spiral/tables/__init__.py +12 -0
- spiral/tables/client.py +130 -0
- spiral/tables/dataset.py +250 -0
- spiral/tables/debug/__init__.py +0 -0
- spiral/tables/debug/manifests.py +70 -0
- spiral/tables/debug/metrics.py +56 -0
- spiral/tables/debug/scan.py +248 -0
- spiral/tables/maintenance.py +12 -0
- spiral/tables/scan.py +193 -0
- spiral/tables/snapshot.py +78 -0
- spiral/tables/table.py +157 -0
- spiral/tables/transaction.py +52 -0
- spiral/types_.py +6 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: pyspiral
|
3
|
+
Version: 0.4.0
|
4
|
+
Classifier: Intended Audience :: Science/Research
|
5
|
+
Classifier: Operating System :: OS Independent
|
6
|
+
Classifier: Programming Language :: Python
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
13
|
+
Classifier: Programming Language :: Rust
|
14
|
+
Classifier: License :: Other/Proprietary License
|
15
|
+
Requires-Dist: betterproto==2.0.0b7
|
16
|
+
Requires-Dist: google-re2>=1.1.20240702
|
17
|
+
Requires-Dist: grpclib>=0.4.7
|
18
|
+
Requires-Dist: hishel>=0.0.30
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
20
|
+
Requires-Dist: numpy>=2
|
21
|
+
Requires-Dist: pyarrow>=21.0.0
|
22
|
+
Requires-Dist: pydantic-settings>=2.3.4
|
23
|
+
Requires-Dist: pydantic[email]>=2.5.3
|
24
|
+
Requires-Dist: pyjwt[crypto]>=2.9.0
|
25
|
+
Requires-Dist: questionary>=2.0.1
|
26
|
+
Requires-Dist: tqdm>=4.66.5
|
27
|
+
Requires-Dist: typer>=0.16
|
28
|
+
Requires-Dist: xxhash>=3.4.1
|
29
|
+
Requires-Dist: nanoid>=2.0.0
|
30
|
+
Requires-Dist: sqlglot[rs]>=25.25.1
|
31
|
+
Requires-Dist: pyperclip>=1.9.0
|
32
|
+
Requires-Dist: polars>=1.31.0 ; extra == 'polars'
|
33
|
+
Requires-Dist: duckdb>=1.3.2 ; extra == 'duckdb'
|
34
|
+
Requires-Dist: pyiceberg>=0.9.1 ; extra == 'pyiceberg'
|
35
|
+
Provides-Extra: polars
|
36
|
+
Provides-Extra: duckdb
|
37
|
+
Provides-Extra: pyiceberg
|
38
|
+
Summary: Python client for Spiral.
|
39
|
+
Home-Page: https://spiraldb.com
|
40
|
+
Author-email: SpiralDB <hello@spiraldb.com>
|
41
|
+
License: Proprietary License
|
42
|
+
Requires-Python: >=3.10
|
43
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
44
|
+
|
45
|
+
# PySpiral
|
46
|
+
|
@@ -0,0 +1,98 @@
|
|
1
|
+
pyspiral-0.4.0.dist-info/METADATA,sha256=B17wQZ-DamgU1NYanta7NGFB-8iPqpYmASeHv5QBQgk,1610
|
2
|
+
pyspiral-0.4.0.dist-info/WHEEL,sha256=JiwhipTW0_LsGDAT-A3mDb2ZFAL_o8nDI5ZvqBdvwBg,113
|
3
|
+
pyspiral-0.4.0.dist-info/entry_points.txt,sha256=uft7u-a6g40NLt4Q6BleWbK4NY0M8nZuYPpP8DV0EOk,45
|
4
|
+
spiral/__init__.py,sha256=Jv1vbcnnmcTsBLN5mSNjnX3ae4C_mgojXDSBFaqIhN0,208
|
5
|
+
spiral/_lib.pypy310-pp73-darwin.so,sha256=-KxxizeEA9H_eAvIutS_wHZdCySET1QpQkc5fYUKtrg,66358892
|
6
|
+
spiral/adbc.py,sha256=HcvR60uQeEK2oggSAK6y5VYtIrACIiCQ-85MEf18EZc,14199
|
7
|
+
spiral/api/__init__.py,sha256=_7BS1RhqEFjnt3XwFWZNCHVEQeSKpezPevAiGCsvDbE,1776
|
8
|
+
spiral/api/admin.py,sha256=A1iVR1XYJSObZivPAD5UzmPuMgupXc9kaHNYYa_kwfs,585
|
9
|
+
spiral/api/client.py,sha256=9-L6T8niQAXo90jRxllJD4hXXmcGfHj7CW9X3XTYa5Q,4551
|
10
|
+
spiral/api/filesystems.py,sha256=5Ky_otnresGj7WdsR8Xi7DDM3lkB8UES6Lru_xWAGDM,4559
|
11
|
+
spiral/api/organizations.py,sha256=B-8zZ7lFJANGK7dUNbo_aU-cgI959JBP9VcWb6wdgi0,1895
|
12
|
+
spiral/api/projects.py,sha256=JBGof9A2Ivasu2jrULMjHBwlna0M8WRrTNqU-Es4GJ8,5673
|
13
|
+
spiral/api/telemetry.py,sha256=tfdA3E_EWJwFVxkQfkm8tiYGRubnx2LuE5nbfsk1oG4,474
|
14
|
+
spiral/api/types.py,sha256=zx-BRKsi1GHg9aL9gMUaVQWYYMXJcP0A8OQUc7jSIAc,653
|
15
|
+
spiral/api/workloads.py,sha256=XAyXV7vgZcoyyoPoGvOT4jTpyFKFMvrrAfhL6d1h1kE,1748
|
16
|
+
spiral/arrow_.py,sha256=T1LZ7bh9aMDbXfpUsf0dR0E1roTQyAYSgZ2mL4s8J_4,7681
|
17
|
+
spiral/cli/__init__.py,sha256=ooAFz_iCpVCKHE0TiVElIynbP2PtTgD9cUw46Vh1lcw,2145
|
18
|
+
spiral/cli/__main__.py,sha256=kNaKM2xgJo7GRogf83nYldLM-RGUR6vymdGwZxywQu0,71
|
19
|
+
spiral/cli/admin.py,sha256=7WbU_tr05clUjmZ-RkKTlvcf1pbXIElRfHRJlCItFGk,326
|
20
|
+
spiral/cli/app.py,sha256=-k0rrLbfJRLay_2_MOCt57PLcx0VnNMCkrnKV7j7nos,1725
|
21
|
+
spiral/cli/console.py,sha256=6JHbAQV6MFWz3P-VzqPOjhHpkIQagsCdzTMvmuDKMkU,2580
|
22
|
+
spiral/cli/fs.py,sha256=dVPoAoAbuQ9yJlfI-JiFgS9VdnPmeBMygVHgehJRj34,4367
|
23
|
+
spiral/cli/iceberg/__init__.py,sha256=IQV_gwCFSj6Ubxs58VM9Pal1ymgG2bxdDgOPuk9E5bs,214
|
24
|
+
spiral/cli/iceberg/namespaces.py,sha256=x9pvHlcXtcATYYjqimHa6CtkyL3taQUJ--ni_Bfoemc,1510
|
25
|
+
spiral/cli/iceberg/tables.py,sha256=nSR4-t54otJfCmubB6vXnbOkbqPVGV0sHBlc-t9cIVg,1930
|
26
|
+
spiral/cli/indexes/__init__.py,sha256=-USfxCIdckzZKBNQ-DXqe3V5ttWVo_Fsa1Mfcx5hdIw,467
|
27
|
+
spiral/cli/login.py,sha256=InKMnpV8NATW5RPgB3ZL-DSVPzUuUByyK4Fx7pZEgfg,607
|
28
|
+
spiral/cli/orgs.py,sha256=V-4ZTT3FwFQLcs1-BenC8uCgvWOJcxkZPSdCPfsexhc,2848
|
29
|
+
spiral/cli/printer.py,sha256=W83KAE-7meoDD1yRltLQrZqrA2olGapBGy_2USWkY08,1778
|
30
|
+
spiral/cli/projects.py,sha256=TKXu_VzkIUccwXzdlg-wQMkrB-Py33g052NrbuJx-D4,5096
|
31
|
+
spiral/cli/state.py,sha256=10wTIVQ0SJkY67Z6-KQ1LFlt3aVIPmZhoHFdTwp4kNA,130
|
32
|
+
spiral/cli/tables/__init__.py,sha256=lkGLDeU28IVnuxJdlYSUh6QSB9fQ4_1MeZJL73iXcHo,3660
|
33
|
+
spiral/cli/telemetry.py,sha256=ABDCyV5QJGOIJp4AxvK0LG5xNPIysP37K5haL38T7P4,586
|
34
|
+
spiral/cli/types.py,sha256=YG1eHhRLaqlVU_18DQBuF_YMsabhMZLBY0V9CvbSxjY,1369
|
35
|
+
spiral/cli/workloads.py,sha256=SbxgwiBlX1AuqpOLV3gs7DFkH-Tbeend7qJTwq0Je84,1994
|
36
|
+
spiral/client.py,sha256=K-OuMOTgYxOA9vef5jSANjmPRBfGrzQ65fg6Fd-rHMY,2683
|
37
|
+
spiral/core/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
+
spiral/core/client/__init__.pyi,sha256=FFpUCPyQjH_VxldflqWmvniSugLuDiQv11vMim9HhDY,3466
|
39
|
+
spiral/core/index/__init__.pyi,sha256=NPOG1ztFO6siBGpmJU3boRzX26xfxw--2TiCydosGvo,314
|
40
|
+
spiral/core/table/__init__.pyi,sha256=dwOaxcOl6ZIlxoLjOnC3CNUgGetWfnEV1Jx06aCH8M8,3265
|
41
|
+
spiral/core/table/manifests/__init__.pyi,sha256=3V59-K1qr1z2dGfgRKXaHSVheK8NNw8Q8PFhfbeQd_4,1065
|
42
|
+
spiral/core/table/metastore/__init__.pyi,sha256=dMqySDnsjPUTBuFU2MaQGyocKEoGkWpeTQmUP2iIKbc,1880
|
43
|
+
spiral/core/table/spec/__init__.pyi,sha256=D4GQp9RWwyLKTlRW7eDXcQE-xA5rF2iBcXZ8y7b48EE,5595
|
44
|
+
spiral/datetime_.py,sha256=1TA1RYIRU22qcUuipIjVhAtGnPDVn2z9WttuhkmfkwY,964
|
45
|
+
spiral/expressions/__init__.py,sha256=QAEtghxWoT-obmMsHpWwPloRh5EZFpeQ7H_i9F4PG5c,6556
|
46
|
+
spiral/expressions/base.py,sha256=q_W9XslcdFQtOIE_d1VkEmLickaXKOAoIcFeMoh-nqQ,4751
|
47
|
+
spiral/expressions/http.py,sha256=begUydWoFHEqjeLkATvI_v66Ez6_rR-OQBWO5cHbb9c,2742
|
48
|
+
spiral/expressions/io.py,sha256=gJ2a0FKMmdxarWKENulPRwH7KDvSJTIh_OUxX306xAM,3045
|
49
|
+
spiral/expressions/list_.py,sha256=MMt5lf5H1M3O-x6N_PvqOLGq9NOk6Ukv0fPWwPC_uy4,1809
|
50
|
+
spiral/expressions/mp4.py,sha256=R-fcVYRI6KaH1Nwpmqnsc1VYd9wA7Nuiy2UDcNxEzpw,2165
|
51
|
+
spiral/expressions/png.py,sha256=KO8X0OmMzUFwpg2I_j0JTyldPzVXDWIMzjWMWDV9vIY,506
|
52
|
+
spiral/expressions/qoi.py,sha256=gvIbb6fXb_Bb080sn9wkpbGGrPs2UEcTXCfuv4-kcYQ,506
|
53
|
+
spiral/expressions/refs.py,sha256=ISMtJtUL--BjHF6rsvgN3Um4QcvVqQE9URngOxjQrhw,2115
|
54
|
+
spiral/expressions/str_.py,sha256=tY8RXW3JWvr1-bEfCZtk5FAf11wKJnXPuA9EoeJ9tA4,1265
|
55
|
+
spiral/expressions/struct.py,sha256=pGAnCDh6AK0BK1XfZ1qG4ce4ranIQEE1HQsgmzBcfwQ,2038
|
56
|
+
spiral/expressions/text.py,sha256=-02gBWYoyNQ3qQ1--9HTa8IryUDojYQVIp8C7rgnOWQ,1893
|
57
|
+
spiral/expressions/tiff.py,sha256=fQwIn0kLFBM2Y3YYIHmTgb_EIRHKT2fNc77nioDQQw4,8044
|
58
|
+
spiral/expressions/udf.py,sha256=r6398z2Aj7KnXtwEvCiGNbgOXa6xsb_bnnG-FEvFxV4,1370
|
59
|
+
spiral/grpc_.py,sha256=f3czdP1Mxme42Y5--a5ogYq1TTiWn-J_MlGjwJ2mWwM,1015
|
60
|
+
spiral/iceberg/__init__.py,sha256=jSIlTxWauAbJV5gsWglZisFbnfNNzLYN90scoYcdWzc,65
|
61
|
+
spiral/iceberg/client.py,sha256=E6FyE_h2HLgDW1cAFg1XgglJr6rbVOCWjRtRmqoMVkM,1003
|
62
|
+
spiral/indexes/__init__.py,sha256=TXLQ-_3xso3lFIp2lM58_ip9OPNwPKFv1FdsWiUF-d8,178
|
63
|
+
spiral/indexes/client.py,sha256=NsFBILEHMjyCUruFrUEKucRQRrN4OvqgbL4pmzWs07g,5600
|
64
|
+
spiral/indexes/index.py,sha256=4CmSFlZYp46B2CjqtiyZ7VF5EH3duiutz3nWFnyApLA,973
|
65
|
+
spiral/indexes/scan.py,sha256=B2m-UgNuawNB90HXK33GTQfMy2WLdNNxiiB6cIjFW2Y,697
|
66
|
+
spiral/project.py,sha256=0uJ1Jb88Ie-cCNnSdX3QfFtCUqrjLka4zCm_TxCpVak,1189
|
67
|
+
spiral/protogen/_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
|
+
spiral/protogen/_/arrow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
69
|
+
spiral/protogen/_/arrow/flight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
|
+
spiral/protogen/_/arrow/flight/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
|
+
spiral/protogen/_/arrow/flight/protocol/sql/__init__.py,sha256=_xhj9QkWEW1qZ-iVxcQ8k4EjYr7KJ5ofitJGqVUGQi4,79921
|
72
|
+
spiral/protogen/_/scandal/__init__.py,sha256=X5YJqErZDIXxTESw8fLqJp3P2wZlqAglBzPs3LpTd-w,5145
|
73
|
+
spiral/protogen/_/spiral/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
74
|
+
spiral/protogen/_/spiral/table/__init__.py,sha256=o_aNyTuJBIRY6MlAWceMsjbfaSUuZphRxiG_IXmC0mU,629
|
75
|
+
spiral/protogen/_/substrait/__init__.py,sha256=pV4-T-lwAHKkfFrNYSUGY4IkbIvuKjSo_imzF7BLj_s,126526
|
76
|
+
spiral/protogen/_/substrait/extensions/__init__.py,sha256=yD7dg0TBqn-GK_L0qeVof1GKnwSLg_kPyQSV3kcSljs,3655
|
77
|
+
spiral/protogen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
|
+
spiral/protogen/substrait/__init__.py,sha256=pV4-T-lwAHKkfFrNYSUGY4IkbIvuKjSo_imzF7BLj_s,126526
|
79
|
+
spiral/protogen/substrait/extensions/__init__.py,sha256=yD7dg0TBqn-GK_L0qeVof1GKnwSLg_kPyQSV3kcSljs,3655
|
80
|
+
spiral/protogen/util.py,sha256=smnvVo6nYH3FfDm9jqhNLaXz4bbTBaQezHQDCTvZyiQ,1486
|
81
|
+
spiral/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
|
+
spiral/server.py,sha256=ztBmB5lBnUz-smQxR_tC8AI5SOhz17wH0MI3GuzDUdM,600
|
83
|
+
spiral/settings.py,sha256=PIQV2ljtB3pEOWoMRVSRzSGJNrXviO2JBgZ5ZY_Nq2E,2794
|
84
|
+
spiral/substrait_.py,sha256=RNSmfbGFT_5uyo8AFtzS9A7IHW3DkacMTw2vKnj0Das,12762
|
85
|
+
spiral/tables/__init__.py,sha256=iiP7BkHA117em37_e75jtdvoZC10xCXtld18gRnPbTw,430
|
86
|
+
spiral/tables/client.py,sha256=l_wJJRf3BPD5lg4Q1Ll2lAqQIuBCnKwC6JtsAui91Tc,4915
|
87
|
+
spiral/tables/dataset.py,sha256=DuHeKVCJfXLsbxmde9QW6yvesW5uhswG6qAxV5X0ZgA,7890
|
88
|
+
spiral/tables/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
89
|
+
spiral/tables/debug/manifests.py,sha256=E_-DiMBg2EPL97cl9hLWhiqEsFtjEBgh_C7jZy8EWYc,2594
|
90
|
+
spiral/tables/debug/metrics.py,sha256=XdRDcjggtsLNGCAjam6IxG9072pz_d2C8iLApNRFUtk,2044
|
91
|
+
spiral/tables/debug/scan.py,sha256=-IWX_UjO4QP9Hj7PtZ1rLlbswJcryOin56GT-exqFm4,8942
|
92
|
+
spiral/tables/maintenance.py,sha256=7Xa2Jdu_OY1Qu6iN1sPVdywVZtk_Mv3EaC3G93cmQvI,305
|
93
|
+
spiral/tables/scan.py,sha256=3lPf5fSyF1fHGdGJ-pvu5HxPWoonf_XL7neWTqzB-0I,7582
|
94
|
+
spiral/tables/snapshot.py,sha256=2NTuVEp2uJ1pV3Q5tLj7FOzPSc9axlfb6uOITwHnj0g,2229
|
95
|
+
spiral/tables/table.py,sha256=4B2drwwfaoL6aIJ-5Ll-Bqza-EBeDIfMkuszSOZqSpk,5326
|
96
|
+
spiral/tables/transaction.py,sha256=3a64R-mf_cmR54BNn8U-05jmWonp6Ivxhe6u01Dyjzo,1573
|
97
|
+
spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
|
98
|
+
pyspiral-0.4.0.dist-info/RECORD,,
|
spiral/__init__.py
ADDED
Binary file
|
spiral/adbc.py
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
import abc
|
2
|
+
import functools
|
3
|
+
import logging
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
5
|
+
from urllib.parse import urlparse
|
6
|
+
|
7
|
+
import pyarrow as pa
|
8
|
+
import pyarrow.compute as pc
|
9
|
+
import sqlglot
|
10
|
+
import sqlglot.expressions as exp
|
11
|
+
from betterproto.lib.google.protobuf import Any
|
12
|
+
from pyarrow.flight import (
|
13
|
+
Action,
|
14
|
+
FlightDescriptor,
|
15
|
+
FlightEndpoint,
|
16
|
+
FlightError,
|
17
|
+
FlightInfo,
|
18
|
+
FlightMetadataWriter,
|
19
|
+
FlightServerBase,
|
20
|
+
MetadataRecordBatchReader,
|
21
|
+
RecordBatchStream,
|
22
|
+
ServerCallContext,
|
23
|
+
Ticket,
|
24
|
+
)
|
25
|
+
|
26
|
+
from spiral import Spiral
|
27
|
+
from spiral.api.projects import TableResource
|
28
|
+
from spiral.protogen._.arrow.flight.protocol import sql as rpc
|
29
|
+
from spiral.protogen._.arrow.flight.protocol.sql import (
|
30
|
+
CommandGetCatalogs,
|
31
|
+
CommandGetDbSchemas,
|
32
|
+
CommandGetSqlInfo,
|
33
|
+
CommandGetTables,
|
34
|
+
CommandStatementQuery,
|
35
|
+
SqlInfo,
|
36
|
+
SqlSupportedTransaction,
|
37
|
+
)
|
38
|
+
|
39
|
+
log = logging.getLogger(__name__)
|
40
|
+
logging.getLogger("sqlx").setLevel(logging.WARNING)
|
41
|
+
|
42
|
+
|
43
|
+
def debuggable(func):
|
44
|
+
"""A decorator to enable GUI (i.e. PyCharm) debugging in the
|
45
|
+
decorated Arrow Flight RPC Server function.
|
46
|
+
|
47
|
+
See: https://github.com/apache/arrow/issues/36844
|
48
|
+
for more details...
|
49
|
+
"""
|
50
|
+
|
51
|
+
@functools.wraps(func)
|
52
|
+
def wrapper_decorator(*args, **kwargs):
|
53
|
+
try:
|
54
|
+
import pydevd
|
55
|
+
|
56
|
+
pydevd.connected = True
|
57
|
+
pydevd.settrace(suspend=False)
|
58
|
+
except ImportError:
|
59
|
+
# Not running in debugger
|
60
|
+
pass
|
61
|
+
value = func(*args, **kwargs)
|
62
|
+
return value
|
63
|
+
|
64
|
+
return wrapper_decorator
|
65
|
+
|
66
|
+
|
67
|
+
# TODO(marko): This should work for Iceberg tables.
|
68
|
+
class ADBCServerBase:
|
69
|
+
def get_sql_info(self, _req: CommandGetSqlInfo) -> pa.RecordBatchReader:
|
70
|
+
"""Default implementation that reports no support for any complex features."""
|
71
|
+
info = {
|
72
|
+
SqlInfo.FLIGHT_SQL_SERVER_NAME: "Spiral ADBC Server",
|
73
|
+
SqlInfo.FLIGHT_SQL_SERVER_VERSION: "0.0.1",
|
74
|
+
SqlInfo.FLIGHT_SQL_SERVER_ARROW_VERSION: pa.__version__,
|
75
|
+
SqlInfo.FLIGHT_SQL_SERVER_READ_ONLY: True,
|
76
|
+
SqlInfo.FLIGHT_SQL_SERVER_TRANSACTION: SqlSupportedTransaction.NONE.value,
|
77
|
+
}
|
78
|
+
|
79
|
+
# See https://github.com/apache/arrow-adbc/blob/38c21c2311a59803559cb0091b3f34180c28b25f/rust/core/src/schemas.rs#L35
|
80
|
+
union_fields = [
|
81
|
+
pa.field("string_value", pa.string()),
|
82
|
+
pa.field("bool_value", pa.bool_()),
|
83
|
+
pa.field("int64_value", pa.int64()),
|
84
|
+
pa.field("int32_bitmask", pa.int32()),
|
85
|
+
pa.field("string_list", pa.list_(pa.string())),
|
86
|
+
pa.field(
|
87
|
+
"int32_to_int32_list_map",
|
88
|
+
pa.map_(pa.int32(), pa.list_(pa.int32()), keys_sorted=False),
|
89
|
+
),
|
90
|
+
]
|
91
|
+
schema = pa.schema(
|
92
|
+
[
|
93
|
+
pa.field("info_name", pa.uint32(), nullable=False),
|
94
|
+
pa.field("info_value", pa.dense_union(union_fields), nullable=False),
|
95
|
+
]
|
96
|
+
)
|
97
|
+
|
98
|
+
# PyArrow doesn't support creating a dense union for us :(
|
99
|
+
types = []
|
100
|
+
offsets = []
|
101
|
+
ints = []
|
102
|
+
bools = []
|
103
|
+
strs = []
|
104
|
+
for value in info.values():
|
105
|
+
if isinstance(value, str):
|
106
|
+
types.append(0)
|
107
|
+
offsets.append(len(strs))
|
108
|
+
strs.append(value)
|
109
|
+
elif isinstance(value, bool):
|
110
|
+
types.append(1)
|
111
|
+
offsets.append(len(bools))
|
112
|
+
bools.append(value)
|
113
|
+
else:
|
114
|
+
types.append(1)
|
115
|
+
offsets.append(len(ints))
|
116
|
+
ints.append(value)
|
117
|
+
|
118
|
+
values = pa.UnionArray.from_dense(
|
119
|
+
pa.array(types, type=pa.int8()),
|
120
|
+
pa.array(offsets, type=pa.int32()),
|
121
|
+
[pa.array(data, type=f.type) for data, f in zip([strs, bools, ints, [], [], []], union_fields)],
|
122
|
+
[f.name for f in union_fields],
|
123
|
+
)
|
124
|
+
|
125
|
+
return pa.table(data=[pa.array(list(info.keys()), type=pa.uint32()), values], schema=schema).to_reader()
|
126
|
+
|
127
|
+
@abc.abstractmethod
|
128
|
+
def get_catalogs(self, req: CommandGetCatalogs) -> pa.RecordBatchReader: ...
|
129
|
+
|
130
|
+
@abc.abstractmethod
|
131
|
+
def get_db_schemas(self, req: CommandGetDbSchemas) -> pa.RecordBatchReader: ...
|
132
|
+
|
133
|
+
@abc.abstractmethod
|
134
|
+
def get_tables(self, req: CommandGetTables) -> pa.RecordBatchReader: ...
|
135
|
+
|
136
|
+
@abc.abstractmethod
|
137
|
+
def statement_query(self, req: CommandStatementQuery, limit: int | None = None) -> pa.RecordBatchReader: ...
|
138
|
+
|
139
|
+
|
140
|
+
class SpiralADBCServer(ADBCServerBase):
|
141
|
+
def __init__(self, spiral: Spiral):
|
142
|
+
self.sp = spiral
|
143
|
+
|
144
|
+
self.pool = ThreadPoolExecutor()
|
145
|
+
|
146
|
+
def get_catalogs(self, req: CommandGetCatalogs) -> pa.RecordBatchReader:
|
147
|
+
schema = pa.schema([pa.field("catalog_name", pa.string(), nullable=False)])
|
148
|
+
|
149
|
+
@debuggable
|
150
|
+
def batches():
|
151
|
+
yield pa.RecordBatch.from_arrays(
|
152
|
+
[[p.id for p in self.sp.list_projects()]],
|
153
|
+
schema=schema,
|
154
|
+
)
|
155
|
+
|
156
|
+
return pa.RecordBatchReader.from_batches(schema, batches())
|
157
|
+
|
158
|
+
def get_db_schemas(self, req: CommandGetDbSchemas) -> pa.RecordBatchReader:
|
159
|
+
"""Get the schemas from the database."""
|
160
|
+
|
161
|
+
schema = pa.schema(
|
162
|
+
[
|
163
|
+
pa.field("catalog_name", pa.string()),
|
164
|
+
pa.field("db_schema_name", pa.string(), nullable=False),
|
165
|
+
]
|
166
|
+
)
|
167
|
+
|
168
|
+
@debuggable
|
169
|
+
def batches():
|
170
|
+
if req.catalog == "":
|
171
|
+
# Empty string means databases _without_ a catalog, which we don't support
|
172
|
+
return
|
173
|
+
|
174
|
+
# Otherwise, catalog is either the project ID, or None.
|
175
|
+
if req.catalog is None:
|
176
|
+
projects = self.sp.list_projects()
|
177
|
+
else:
|
178
|
+
projects = [self.sp.project(req.catalog)]
|
179
|
+
|
180
|
+
for project in projects:
|
181
|
+
datasets = {dt.dataset for dt in project.tables.list_tables()}
|
182
|
+
|
183
|
+
batch = pa.RecordBatch.from_arrays(
|
184
|
+
[
|
185
|
+
[project.id] * len(datasets),
|
186
|
+
list(datasets),
|
187
|
+
],
|
188
|
+
schema=schema,
|
189
|
+
)
|
190
|
+
|
191
|
+
if req.db_schema_filter_pattern:
|
192
|
+
mask = pc.match_like(batch["db_schema_name"], req.db_schema_filter_pattern)
|
193
|
+
batch = batch.filter(mask)
|
194
|
+
|
195
|
+
yield batch
|
196
|
+
|
197
|
+
return pa.RecordBatchReader.from_batches(schema, batches())
|
198
|
+
|
199
|
+
def get_tables(self, req: CommandGetTables) -> pa.RecordBatchReader:
|
200
|
+
schema = pa.schema(
|
201
|
+
[
|
202
|
+
pa.field("catalog_name", pa.string()),
|
203
|
+
pa.field("db_schema_name", pa.string()),
|
204
|
+
pa.field("table_name", pa.string(), nullable=False),
|
205
|
+
pa.field("table_type", pa.string(), nullable=False),
|
206
|
+
]
|
207
|
+
+ [pa.field("table_schema", pa.binary(), nullable=False)]
|
208
|
+
if req.include_schema
|
209
|
+
else []
|
210
|
+
)
|
211
|
+
|
212
|
+
@debuggable
|
213
|
+
def batches():
|
214
|
+
if req.catalog == "":
|
215
|
+
# Empty string means databases _without_ a catalog, which we don't support
|
216
|
+
return
|
217
|
+
|
218
|
+
if req.catalog is None:
|
219
|
+
projects = list(self.sp.list_projects())
|
220
|
+
else:
|
221
|
+
projects = [self.sp.project(req.catalog)]
|
222
|
+
|
223
|
+
def _process_project(project):
|
224
|
+
tables: list[TableResource] = project.tables.list_tables()
|
225
|
+
|
226
|
+
rows = []
|
227
|
+
for table in tables:
|
228
|
+
row = {
|
229
|
+
"catalog_name": project.id,
|
230
|
+
"db_schema_name": table.dataset,
|
231
|
+
"table_name": table.table,
|
232
|
+
"table_type": "TABLE",
|
233
|
+
}
|
234
|
+
|
235
|
+
if req.include_schema:
|
236
|
+
open_table = project.tables.table(f"{table.project_id}.{table.dataset}.{table.table}")
|
237
|
+
row["table_schema"] = open_table.snapshot().to_dataset().schema.serialize().to_pybytes()
|
238
|
+
|
239
|
+
rows.append(row)
|
240
|
+
|
241
|
+
return pa.RecordBatch.from_pylist(rows, schema=schema)
|
242
|
+
|
243
|
+
yield from self.pool.map(_process_project, projects)
|
244
|
+
|
245
|
+
return pa.RecordBatchReader.from_batches(schema, batches())
|
246
|
+
|
247
|
+
@debuggable
|
248
|
+
def statement_query(self, req: CommandStatementQuery, limit: int | None = None) -> pa.RecordBatchReader:
|
249
|
+
# Extract the tables from the query, and bring them into the Python locals scope.
|
250
|
+
expr = sqlglot.parse_one(req.query, dialect="duckdb")
|
251
|
+
for tbl in expr.find_all(exp.Table):
|
252
|
+
# We swap the three-part identifier out for a single identifier
|
253
|
+
# This lets us insert a PyArrow Dataset into Python locals such that
|
254
|
+
# DuckDB will pick up on it for the query.
|
255
|
+
name = exp.table_name(tbl)
|
256
|
+
locals()[name] = self.sp.tables.table(f"{tbl.catalog}.{tbl.db}.{tbl.name}").snapshot().to_dataset()
|
257
|
+
tbl.replace(exp.table_(table=name))
|
258
|
+
|
259
|
+
try:
|
260
|
+
import duckdb
|
261
|
+
except ImportError:
|
262
|
+
raise FlightError("DuckDB is required for SQL queries.")
|
263
|
+
|
264
|
+
try:
|
265
|
+
sql = duckdb.sql(expr.sql(dialect="duckdb"))
|
266
|
+
except Exception as e:
|
267
|
+
raise FlightError(str(e))
|
268
|
+
|
269
|
+
if limit is not None:
|
270
|
+
sql = sql.limit(limit)
|
271
|
+
|
272
|
+
return sql.fetch_arrow_reader(batch_size=1_000)
|
273
|
+
|
274
|
+
|
275
|
+
class ADBCFlightServer(FlightServerBase):
|
276
|
+
"""An implementation of a FlightSQL ADBC server."""
|
277
|
+
|
278
|
+
def __init__(self, abdc: ADBCServerBase, *, location=None, **kwargs):
|
279
|
+
super().__init__(location=location, **kwargs)
|
280
|
+
self.location = location
|
281
|
+
self.adbc = abdc
|
282
|
+
|
283
|
+
self.host = "localhost"
|
284
|
+
self.tls = False
|
285
|
+
if location:
|
286
|
+
parts = urlparse(location)
|
287
|
+
self.host = parts.hostname
|
288
|
+
self.tls = parts.scheme.endswith("s")
|
289
|
+
|
290
|
+
@debuggable
|
291
|
+
def do_action(self, context: ServerCallContext, action: Action):
|
292
|
+
log.info("DoAction %s: %s", context.peer(), action)
|
293
|
+
super().do_action(context, action)
|
294
|
+
|
295
|
+
@debuggable
|
296
|
+
def do_exchange(self, context: ServerCallContext, descriptor: FlightDescriptor, reader, writer):
|
297
|
+
log.info("DoExchange %s: %s", context.peer(), descriptor)
|
298
|
+
super().do_exchange(context, descriptor, reader, writer)
|
299
|
+
|
300
|
+
@debuggable
|
301
|
+
def do_get(self, context: ServerCallContext, ticket: Ticket):
|
302
|
+
log.info("DoGet %s: %s", context.peer(), ticket)
|
303
|
+
req = self.parse_command(ticket.ticket)
|
304
|
+
match req:
|
305
|
+
case CommandGetSqlInfo():
|
306
|
+
return RecordBatchStream(self.adbc.get_sql_info(req))
|
307
|
+
case CommandGetCatalogs():
|
308
|
+
return RecordBatchStream(self.adbc.get_catalogs(req))
|
309
|
+
case CommandGetDbSchemas():
|
310
|
+
return RecordBatchStream(self.adbc.get_db_schemas(req))
|
311
|
+
case CommandGetTables():
|
312
|
+
return RecordBatchStream(self.adbc.get_tables(req))
|
313
|
+
case CommandStatementQuery():
|
314
|
+
return RecordBatchStream(self.adbc.statement_query(req))
|
315
|
+
case _:
|
316
|
+
raise NotImplementedError(f"Unsupported do_Get: {req}")
|
317
|
+
|
318
|
+
@debuggable
|
319
|
+
def do_put(
|
320
|
+
self,
|
321
|
+
context: ServerCallContext,
|
322
|
+
descriptor: FlightDescriptor,
|
323
|
+
reader: MetadataRecordBatchReader,
|
324
|
+
writer: FlightMetadataWriter,
|
325
|
+
):
|
326
|
+
log.info("DoPut %s: %s", context.peer(), descriptor)
|
327
|
+
super().do_put(context, descriptor, reader, writer)
|
328
|
+
|
329
|
+
@debuggable
|
330
|
+
def get_flight_info(self, context: ServerCallContext, descriptor: FlightDescriptor) -> FlightInfo:
|
331
|
+
log.info("GetFlightInfo %s: %s", context.peer(), descriptor)
|
332
|
+
req = self.parse_command(descriptor.command)
|
333
|
+
match req:
|
334
|
+
case CommandGetSqlInfo():
|
335
|
+
# Each metadata type contributes to the schema.
|
336
|
+
schema = self.adbc.get_sql_info(req).schema
|
337
|
+
case CommandGetCatalogs():
|
338
|
+
schema = self.adbc.get_catalogs(req).schema
|
339
|
+
case CommandGetDbSchemas():
|
340
|
+
schema = self.adbc.get_db_schemas(req).schema
|
341
|
+
case CommandGetTables():
|
342
|
+
schema = self.adbc.get_tables(req).schema
|
343
|
+
case CommandStatementQuery():
|
344
|
+
schema = self.adbc.statement_query(req, limit=0).schema
|
345
|
+
case _:
|
346
|
+
raise NotImplementedError(f"Unsupported command: {req}")
|
347
|
+
|
348
|
+
return self._make_flight_info(self.descriptor_to_key(descriptor), descriptor, schema)
|
349
|
+
|
350
|
+
@staticmethod
|
351
|
+
def parse_command(command: bytes):
|
352
|
+
command = Any().parse(command)
|
353
|
+
|
354
|
+
if not command.type_url.startswith("type.googleapis.com/arrow.flight.protocol.sql."):
|
355
|
+
raise NotImplementedError(f"Unsupported command: {command.type_url}")
|
356
|
+
|
357
|
+
proto_cls_name = command.type_url[len("type.googleapis.com/arrow.flight.protocol.sql.") :]
|
358
|
+
proto_cls = getattr(rpc, proto_cls_name)
|
359
|
+
return proto_cls().parse(command.value)
|
360
|
+
|
361
|
+
@staticmethod
|
362
|
+
def descriptor_to_key(descriptor):
|
363
|
+
return descriptor.command
|
364
|
+
|
365
|
+
@debuggable
|
366
|
+
def get_schema(self, context: ServerCallContext, descriptor: FlightDescriptor):
|
367
|
+
log.info("GetSchema %s: %s", context.peer(), descriptor)
|
368
|
+
return super().get_schema(context, descriptor)
|
369
|
+
|
370
|
+
@debuggable
|
371
|
+
def list_actions(self, context: ServerCallContext):
|
372
|
+
log.info("ListActions %s", context.peer())
|
373
|
+
super().list_actions(context)
|
374
|
+
|
375
|
+
@debuggable
|
376
|
+
def list_flights(self, context: ServerCallContext, criteria):
|
377
|
+
log.info("ListFlights %s: %s", context.peer(), criteria)
|
378
|
+
super().list_flights(context, criteria)
|
379
|
+
|
380
|
+
def _make_flight_info(self, key, descriptor, schema: pa.Schema):
|
381
|
+
# If we pass zero locations, the FlightSQL client should attempt to use the original connection.
|
382
|
+
endpoints = [FlightEndpoint(key, [])]
|
383
|
+
return FlightInfo(schema, descriptor, endpoints, -1, -1)
|
384
|
+
|
385
|
+
|
386
|
+
if __name__ == "__main__":
|
387
|
+
import logging
|
388
|
+
|
389
|
+
logging.basicConfig()
|
390
|
+
logging.getLogger("spiral").setLevel(logging.DEBUG)
|
391
|
+
|
392
|
+
server = ADBCFlightServer(SpiralADBCServer(Spiral()), location="grpc://localhost:5005")
|
393
|
+
server.serve()
|
spiral/api/__init__.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
import os
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
from .client import _Client
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from spiral.core.client import Authn
|
10
|
+
|
11
|
+
from .admin import AdminService
|
12
|
+
from .filesystems import FileSystemService
|
13
|
+
from .organizations import OrganizationService
|
14
|
+
from .projects import ProjectService
|
15
|
+
from .telemetry import TelemetryService
|
16
|
+
from .workloads import WorkloadService
|
17
|
+
|
18
|
+
|
19
|
+
class SpiralAPI:
|
20
|
+
def __init__(self, authn: "Authn", base_url: str | None = None):
|
21
|
+
self.base_url = base_url or os.environ.get("SPIRAL_URL", "https://api.spiraldb.com")
|
22
|
+
self.client = _Client(
|
23
|
+
httpx.Client(
|
24
|
+
base_url=self.base_url,
|
25
|
+
timeout=None if ("PYTEST_VERSION" in os.environ or bool(os.environ.get("SPIRAL_DEV", None))) else 60,
|
26
|
+
),
|
27
|
+
authn,
|
28
|
+
)
|
29
|
+
|
30
|
+
@property
|
31
|
+
def _admin(self) -> "AdminService":
|
32
|
+
from .admin import AdminService
|
33
|
+
|
34
|
+
return AdminService(self.client)
|
35
|
+
|
36
|
+
@property
|
37
|
+
def organization(self) -> "OrganizationService":
|
38
|
+
from .organizations import OrganizationService
|
39
|
+
|
40
|
+
return OrganizationService(self.client)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def project(self) -> "ProjectService":
|
44
|
+
from .projects import ProjectService
|
45
|
+
|
46
|
+
return ProjectService(self.client)
|
47
|
+
|
48
|
+
@property
|
49
|
+
def file_system(self) -> "FileSystemService":
|
50
|
+
from .filesystems import FileSystemService
|
51
|
+
|
52
|
+
return FileSystemService(self.client)
|
53
|
+
|
54
|
+
@property
|
55
|
+
def workload(self) -> "WorkloadService":
|
56
|
+
from .workloads import WorkloadService
|
57
|
+
|
58
|
+
return WorkloadService(self.client)
|
59
|
+
|
60
|
+
@property
|
61
|
+
def telemetry(self) -> "TelemetryService":
|
62
|
+
from .telemetry import TelemetryService
|
63
|
+
|
64
|
+
return TelemetryService(self.client)
|
spiral/api/admin.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
from .client import Paged, PagedResponse, ServiceBase
|
2
|
+
from .organizations import OrgMembership
|
3
|
+
from .types import OrgId
|
4
|
+
|
5
|
+
|
6
|
+
class AdminService(ServiceBase):
|
7
|
+
def sync_memberships(self, org_id: OrgId | None = None) -> Paged[OrgMembership]:
|
8
|
+
params = {}
|
9
|
+
if org_id:
|
10
|
+
params["org_id"] = str(org_id)
|
11
|
+
return self.client.paged("/v1/admin/sync-memberships", PagedResponse[OrgMembership], params=params)
|
12
|
+
|
13
|
+
def sync_orgs(self) -> Paged[OrgId]:
|
14
|
+
params = {}
|
15
|
+
return self.client.paged("/v1/admin/sync-orgs", PagedResponse[OrgId], params=params)
|