pyspiral 0.4.4__cp310-abi3-macosx_11_0_arm64.whl → 0.6.0__cp310-abi3-macosx_11_0_arm64.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.
Files changed (85) hide show
  1. {pyspiral-0.4.4.dist-info → pyspiral-0.6.0.dist-info}/METADATA +10 -5
  2. pyspiral-0.6.0.dist-info/RECORD +99 -0
  3. {pyspiral-0.4.4.dist-info → pyspiral-0.6.0.dist-info}/WHEEL +1 -1
  4. spiral/__init__.py +10 -3
  5. spiral/_lib.abi3.so +0 -0
  6. spiral/adbc.py +29 -11
  7. spiral/api/__init__.py +14 -0
  8. spiral/api/client.py +5 -1
  9. spiral/api/key_space_indexes.py +23 -0
  10. spiral/api/projects.py +17 -2
  11. spiral/api/text_indexes.py +56 -0
  12. spiral/api/types.py +2 -0
  13. spiral/api/workers.py +40 -0
  14. spiral/cli/__init__.py +15 -6
  15. spiral/cli/admin.py +2 -4
  16. spiral/cli/app.py +4 -2
  17. spiral/cli/fs.py +5 -6
  18. spiral/cli/iceberg.py +97 -0
  19. spiral/cli/key_spaces.py +68 -0
  20. spiral/cli/login.py +6 -7
  21. spiral/cli/orgs.py +7 -8
  22. spiral/cli/printer.py +3 -3
  23. spiral/cli/projects.py +5 -6
  24. spiral/cli/tables.py +131 -0
  25. spiral/cli/telemetry.py +3 -4
  26. spiral/cli/text.py +115 -0
  27. spiral/cli/types.py +3 -4
  28. spiral/cli/workloads.py +7 -8
  29. spiral/client.py +111 -8
  30. spiral/core/authn/__init__.pyi +27 -0
  31. spiral/core/client/__init__.pyi +135 -63
  32. spiral/core/table/__init__.pyi +36 -26
  33. spiral/core/table/metastore/__init__.pyi +0 -4
  34. spiral/core/table/spec/__init__.pyi +0 -2
  35. spiral/{tables/dataset.py → dataset.py} +13 -7
  36. spiral/{tables/debug → debug}/manifests.py +17 -6
  37. spiral/{tables/debug → debug}/scan.py +7 -7
  38. spiral/expressions/base.py +3 -3
  39. spiral/expressions/udf.py +1 -1
  40. spiral/{iceberg/client.py → iceberg.py} +1 -3
  41. spiral/key_space_index.py +44 -0
  42. spiral/project.py +171 -18
  43. spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +1668 -1110
  44. spiral/protogen/_/google/protobuf/__init__.py +2190 -0
  45. spiral/protogen/_/message_pool.py +3 -0
  46. spiral/protogen/_/py.typed +0 -0
  47. spiral/protogen/_/scandal/__init__.py +138 -126
  48. spiral/protogen/_/spfs/__init__.py +72 -0
  49. spiral/protogen/_/spql/__init__.py +61 -0
  50. spiral/protogen/_/substrait/__init__.py +5256 -2459
  51. spiral/protogen/_/substrait/extensions/__init__.py +103 -49
  52. spiral/{tables/scan.py → scan.py} +37 -44
  53. spiral/settings.py +14 -3
  54. spiral/snapshot.py +55 -0
  55. spiral/streaming_/__init__.py +3 -0
  56. spiral/streaming_/reader.py +117 -0
  57. spiral/streaming_/stream.py +146 -0
  58. spiral/substrait_.py +9 -9
  59. spiral/table.py +257 -0
  60. spiral/text_index.py +17 -0
  61. spiral/{tables/transaction.py → transaction.py} +11 -15
  62. pyspiral-0.4.4.dist-info/RECORD +0 -98
  63. spiral/cli/iceberg/__init__.py +0 -7
  64. spiral/cli/iceberg/namespaces.py +0 -47
  65. spiral/cli/iceberg/tables.py +0 -60
  66. spiral/cli/indexes/__init__.py +0 -19
  67. spiral/cli/tables/__init__.py +0 -121
  68. spiral/core/index/__init__.pyi +0 -15
  69. spiral/iceberg/__init__.py +0 -3
  70. spiral/indexes/__init__.py +0 -5
  71. spiral/indexes/client.py +0 -137
  72. spiral/indexes/index.py +0 -34
  73. spiral/indexes/scan.py +0 -22
  74. spiral/protogen/_/spiral/table/__init__.py +0 -22
  75. spiral/protogen/substrait/__init__.py +0 -3399
  76. spiral/protogen/substrait/extensions/__init__.py +0 -115
  77. spiral/tables/__init__.py +0 -12
  78. spiral/tables/client.py +0 -130
  79. spiral/tables/maintenance.py +0 -12
  80. spiral/tables/snapshot.py +0 -78
  81. spiral/tables/table.py +0 -145
  82. {pyspiral-0.4.4.dist-info → pyspiral-0.6.0.dist-info}/entry_points.txt +0 -0
  83. /spiral/{protogen/_/spiral → debug}/__init__.py +0 -0
  84. /spiral/{tables/debug → debug}/metrics.py +0 -0
  85. /spiral/{tables/debug → protogen/_/google}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyspiral
3
- Version: 0.4.4
3
+ Version: 0.6.0
4
4
  Classifier: Intended Audience :: Science/Research
5
5
  Classifier: Operating System :: OS Independent
6
6
  Classifier: Programming Language :: Python
@@ -12,29 +12,34 @@ Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Rust
14
14
  Classifier: License :: Other/Proprietary License
15
- Requires-Dist: betterproto==2.0.0b7
15
+ Requires-Dist: betterproto2>=0.8.0
16
16
  Requires-Dist: google-re2>=1.1.20240702
17
17
  Requires-Dist: grpclib>=0.4.7
18
18
  Requires-Dist: hishel>=0.0.30
19
19
  Requires-Dist: httpx>=0.27.0
20
+ Requires-Dist: nanoid>=2.0.0
20
21
  Requires-Dist: numpy>=2
21
22
  Requires-Dist: pyarrow>=21.0.0
22
23
  Requires-Dist: pydantic-settings>=2.3.4
23
24
  Requires-Dist: pydantic[email]>=2.5.3
24
25
  Requires-Dist: pyjwt[crypto]>=2.9.0
26
+ Requires-Dist: pyperclip>=1.9.0
25
27
  Requires-Dist: questionary>=2.0.1
28
+ Requires-Dist: sqlglot[rs]>=25.25.1
26
29
  Requires-Dist: tqdm>=4.66.5
27
30
  Requires-Dist: typer>=0.16
28
31
  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
32
  Requires-Dist: polars>=1.31.0 ; extra == 'polars'
33
33
  Requires-Dist: duckdb>=1.3.2 ; extra == 'duckdb'
34
+ Requires-Dist: datasets>=4.0.0 ; extra == 'datasets'
34
35
  Requires-Dist: pyiceberg>=0.9.1 ; extra == 'pyiceberg'
36
+ Requires-Dist: mosaicml-streaming>=0.13.0 ; extra == 'streaming'
37
+ Requires-Dist: vortex-data>=0.52.1 ; extra == 'streaming'
35
38
  Provides-Extra: polars
36
39
  Provides-Extra: duckdb
40
+ Provides-Extra: datasets
37
41
  Provides-Extra: pyiceberg
42
+ Provides-Extra: streaming
38
43
  Summary: Python client for Spiral.
39
44
  Home-Page: https://spiraldb.com
40
45
  Author-email: SpiralDB <hello@spiraldb.com>
@@ -0,0 +1,99 @@
1
+ pyspiral-0.6.0.dist-info/METADATA,sha256=ETkF1eW1JfgWeoRKxgF7eDX0K9LWDOOixTO8mWW7GlA,1836
2
+ pyspiral-0.6.0.dist-info/WHEEL,sha256=Eg6gwEJKNVa1g53Yg4W5oLzLA6e9MoXTlIiHdDxmtOw,103
3
+ pyspiral-0.6.0.dist-info/entry_points.txt,sha256=uft7u-a6g40NLt4Q6BleWbK4NY0M8nZuYPpP8DV0EOk,45
4
+ spiral/__init__.py,sha256=iAicRWWphlRNKjIS_BFTSqIweCLwJTJTbyWF0BvqMLY,667
5
+ spiral/_lib.abi3.so,sha256=hvWlKDITuH96YLRuRwpd_wHN406BaWM7ud6x_ht6Nyc,62669904
6
+ spiral/adbc.py,sha256=7IxfWIeQN-fh0W5OdN_PP2x3pzQYg6ZUOLsHg3jktqw,14842
7
+ spiral/api/__init__.py,sha256=ULBlVq3PnfNOO6T5naE_ULmmii-83--qTuN2PpAUQN0,2241
8
+ spiral/api/admin.py,sha256=A1iVR1XYJSObZivPAD5UzmPuMgupXc9kaHNYYa_kwfs,585
9
+ spiral/api/client.py,sha256=c63u4Nv0XqXW3BpGAofMk44d-1_4RymKwbcMzq9qxeY,4649
10
+ spiral/api/filesystems.py,sha256=EA4iqhTeaIlvObvEUxHmZl0pQ24IOxUVWM3GPhFLw8o,4969
11
+ spiral/api/key_space_indexes.py,sha256=-38rZXTdkL4mLhp9h3CtqyIyutzzq88tV6bhK05MqYE,640
12
+ spiral/api/organizations.py,sha256=B-8zZ7lFJANGK7dUNbo_aU-cgI959JBP9VcWb6wdgi0,1895
13
+ spiral/api/projects.py,sha256=62Y1lqI_TpUh3WKQqrjbLWJHiZsI_X3g8u2RTbUwkoA,6162
14
+ spiral/api/telemetry.py,sha256=tfdA3E_EWJwFVxkQfkm8tiYGRubnx2LuE5nbfsk1oG4,474
15
+ spiral/api/text_indexes.py,sha256=_zVlGBytl-9-Unbu2POfZgLh40H1YRcagFtplgIG428,1828
16
+ spiral/api/types.py,sha256=lGdiKViRgIEJXD2ubwnyEIEwHkfRumlZjVEaHMV3Tm8,682
17
+ spiral/api/workers.py,sha256=0wZNUHMioDT53P1OBJfpjyDfIodHwwT6858z2IlRIM4,636
18
+ spiral/api/workloads.py,sha256=XAyXV7vgZcoyyoPoGvOT4jTpyFKFMvrrAfhL6d1h1kE,1748
19
+ spiral/arrow_.py,sha256=T1LZ7bh9aMDbXfpUsf0dR0E1roTQyAYSgZ2mL4s8J_4,7681
20
+ spiral/cli/__init__.py,sha256=LutjpWZu5Rvmba8C8bPa5vOCv74JuAoE1kvz0nd48dE,2476
21
+ spiral/cli/__main__.py,sha256=kNaKM2xgJo7GRogf83nYldLM-RGUR6vymdGwZxywQu0,71
22
+ spiral/cli/admin.py,sha256=-ubYqs8nKjnQStbQ68jpWx_9xh0TsaxI0wM1Hfko8_U,319
23
+ spiral/cli/app.py,sha256=HWCjMJLzSz_JaiLF046jzC9A4-yvzS6506D3cOR2Vgc,1773
24
+ spiral/cli/console.py,sha256=6JHbAQV6MFWz3P-VzqPOjhHpkIQagsCdzTMvmuDKMkU,2580
25
+ spiral/cli/fs.py,sha256=UREIJhjr6MfIdcKK7pjUKICd0wsQULhQiWRVWUnQ0dc,4376
26
+ spiral/cli/iceberg.py,sha256=Q14tcGcn1LixbFCYP0GhfYwFFXTmmi8tqBPYwalJEyE,3248
27
+ spiral/cli/key_spaces.py,sha256=EEgn7Zjc16CkeQO-4vWdwEqCTddTMiUAdLh4vG4AoYk,2218
28
+ spiral/cli/login.py,sha256=TgTr37ImgG1NKN8VbtqkxVAYaZFpMXMwPAb23gVldEw,649
29
+ spiral/cli/orgs.py,sha256=fmOuLxpeIFfKqePRi292Gv9k-EF5pPn_tbKd2BLl2Ig,2869
30
+ spiral/cli/printer.py,sha256=HcvSUpaMItzmhBUfIHROK1Z3SL8J8wDopS3Qo8H00uw,1781
31
+ spiral/cli/projects.py,sha256=UYrBlLcFacuXExdLX1sZByfvkz9MRtk_0oRAZvqHa0w,5105
32
+ spiral/cli/state.py,sha256=10wTIVQ0SJkY67Z6-KQ1LFlt3aVIPmZhoHFdTwp4kNA,130
33
+ spiral/cli/tables.py,sha256=8-9ay0mXS1Ew7DMoYFfHqC-Ro0TWsOTTinusS7M1slE,4639
34
+ spiral/cli/telemetry.py,sha256=Uxo1Q1FkKJ6n6QNGOUmL3j_pRRWRx0qWIhoP-U9BuR0,589
35
+ spiral/cli/text.py,sha256=DlWGe4JrkdERAiqyITNpk91Wqb63Re99rNYlIFsIamc,4031
36
+ spiral/cli/types.py,sha256=XYzo1GgX7dBBItoBSrHI4vO5C2lLmS2sktb-2GnGH3E,1362
37
+ spiral/cli/workloads.py,sha256=2_SLfQTFN6y73R9H0i9dk8VIOVagKxSxOpHXC56yptY,2015
38
+ spiral/client.py,sha256=Po9xgCH3NwVsCeRZMm3eJUPV77Rknyj-9MfCS1TbdTg,6623
39
+ spiral/core/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ spiral/core/authn/__init__.pyi,sha256=Jw_8ywTMDTwgAtGxMtFED63rU0jOgrv-eZtaZ5sR5t4,757
41
+ spiral/core/client/__init__.pyi,sha256=uONPrQbKvlNjnIDLT7c0wG9GMWwNveRd6aHJu6NuQ74,5228
42
+ spiral/core/table/__init__.pyi,sha256=uZHXdm160fNAAoz3jnFPtbZl8EFEyLwS3wo0r7jEMOo,3807
43
+ spiral/core/table/manifests/__init__.pyi,sha256=3V59-K1qr1z2dGfgRKXaHSVheK8NNw8Q8PFhfbeQd_4,1065
44
+ spiral/core/table/metastore/__init__.pyi,sha256=rc3u9MwEKRvL2kxOc8lBorddFRnM8o_o1frqtae86a4,1697
45
+ spiral/core/table/spec/__init__.pyi,sha256=0NyGeyEhV_ebwKWVU3sqSvdF2D9v8kEVwo6wYAHF99M,5579
46
+ spiral/dataset.py,sha256=NNqG-oOrhbmNC2OMZ9AYAm4YkwwBozeRI6zXtz4cspA,8008
47
+ spiral/datetime_.py,sha256=1TA1RYIRU22qcUuipIjVhAtGnPDVn2z9WttuhkmfkwY,964
48
+ spiral/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ spiral/debug/manifests.py,sha256=CGC5C1HG4XCMhlkNZI-woBAIO-EQSVSqXuDUsiV-d7g,2935
50
+ spiral/debug/metrics.py,sha256=XdRDcjggtsLNGCAjam6IxG9072pz_d2C8iLApNRFUtk,2044
51
+ spiral/debug/scan.py,sha256=9bMmVQFs5M6Rldm0fmrmmvn9LbSSTKBV5tIu37mEn78,8938
52
+ spiral/expressions/__init__.py,sha256=T8PIb0_UB9kynK0dpWbUD4No5lKRTG-wKnao8xOcXjY,6381
53
+ spiral/expressions/base.py,sha256=OOUDrbkLBE0lSkAmM-6FP2F2N8zhN_in3S_UDrWLDeQ,4805
54
+ spiral/expressions/http.py,sha256=begUydWoFHEqjeLkATvI_v66Ez6_rR-OQBWO5cHbb9c,2742
55
+ spiral/expressions/io.py,sha256=gJ2a0FKMmdxarWKENulPRwH7KDvSJTIh_OUxX306xAM,3045
56
+ spiral/expressions/list_.py,sha256=MMt5lf5H1M3O-x6N_PvqOLGq9NOk6Ukv0fPWwPC_uy4,1809
57
+ spiral/expressions/mp4.py,sha256=_xGVnkygddzxP9a8OACJ8_KXnejuVbYCVKBCXBQ798Y,2151
58
+ spiral/expressions/png.py,sha256=KO8X0OmMzUFwpg2I_j0JTyldPzVXDWIMzjWMWDV9vIY,506
59
+ spiral/expressions/qoi.py,sha256=gvIbb6fXb_Bb080sn9wkpbGGrPs2UEcTXCfuv4-kcYQ,506
60
+ spiral/expressions/refs.py,sha256=omeHBQ5o6N4xgZ3x5Xz7IRrWwYBBtQY8DYK0NNAxeGo,2109
61
+ spiral/expressions/str_.py,sha256=tY8RXW3JWvr1-bEfCZtk5FAf11wKJnXPuA9EoeJ9tA4,1265
62
+ spiral/expressions/struct.py,sha256=pGAnCDh6AK0BK1XfZ1qG4ce4ranIQEE1HQsgmzBcfwQ,2038
63
+ spiral/expressions/text.py,sha256=-02gBWYoyNQ3qQ1--9HTa8IryUDojYQVIp8C7rgnOWQ,1893
64
+ spiral/expressions/tiff.py,sha256=fQwIn0kLFBM2Y3YYIHmTgb_EIRHKT2fNc77nioDQQw4,8044
65
+ spiral/expressions/udf.py,sha256=yb9MIcrFftpNDxgBF228cvdv6TY-hEFikYz2fq_nzWo,1353
66
+ spiral/grpc_.py,sha256=f3czdP1Mxme42Y5--a5ogYq1TTiWn-J_MlGjwJ2mWwM,1015
67
+ spiral/iceberg.py,sha256=JGq62Qnf296r9_hRAoH85GQq45-uSBjwXWw_CvPi6G4,930
68
+ spiral/key_space_index.py,sha256=NAB_nONEjpMYbse8suz42w7Qb5OPHuKN9h9CT2NJe08,1460
69
+ spiral/project.py,sha256=CO_Pn6vPqaonNvRdCNRFcBWr4TqO2AsAUTH5xawIeCE,7283
70
+ spiral/protogen/_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
+ spiral/protogen/_/arrow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ spiral/protogen/_/arrow/flight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
+ spiral/protogen/_/arrow/flight/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
+ spiral/protogen/_/arrow/flight/protocol/sql/__init__.py,sha256=yt4_UDWfOaVpyCBeQa2aVXIfZzRSrcfIQHsXFCWv0qI,90023
75
+ spiral/protogen/_/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
+ spiral/protogen/_/google/protobuf/__init__.py,sha256=lsfhHEPczGOxrOmkstAqh64V0Kt8hQE_6N0tIpc27HU,75116
77
+ spiral/protogen/_/message_pool.py,sha256=4-cRhhiM6bmfpUJZ8qxc8LEyqHBHpLCcotjbyZxl7JM,71
78
+ spiral/protogen/_/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
+ spiral/protogen/_/scandal/__init__.py,sha256=-4m9DHPjtLFzpnesaAv8W_p8R_kfGjA5z3l0GPPbjD8,4965
80
+ spiral/protogen/_/spfs/__init__.py,sha256=4lnc88HhuH4t-JR9NjXz5r5WVESxCEbyUpV7Xfc-SBI,2028
81
+ spiral/protogen/_/spql/__init__.py,sha256=JJBlNacSIIoo5cazHFyLtdkGRLYgwNru1FstFpuPGg8,1548
82
+ spiral/protogen/_/substrait/__init__.py,sha256=-PqWiWMN0hl3Gntj5l1wpEhOMdGDgv3PskGPouaRct8,209839
83
+ spiral/protogen/_/substrait/extensions/__init__.py,sha256=sCMvwWCXWu2cSGiTEH0hRjkn0WTsePLDIxRBBNpENJs,5326
84
+ spiral/protogen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
+ spiral/protogen/util.py,sha256=smnvVo6nYH3FfDm9jqhNLaXz4bbTBaQezHQDCTvZyiQ,1486
86
+ spiral/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
+ spiral/scan.py,sha256=nItu2SNqp5-f2LPMl4EXrEUxV5tJJGEVQEPMSko3STY,7044
88
+ spiral/server.py,sha256=ztBmB5lBnUz-smQxR_tC8AI5SOhz17wH0MI3GuzDUdM,600
89
+ spiral/settings.py,sha256=Nap68xM-1ZvF3yDhkyRnNDIAVMIgxmIksglg_1iT0-0,3069
90
+ spiral/snapshot.py,sha256=_l2wrqUXz2RARjIDxOWw4aQpegJohvggIoWuCllzStA,1506
91
+ spiral/streaming_/__init__.py,sha256=s7MlW2ERsuZmZGExLFL6RcZon2e0tNBocBg5ANgki7k,61
92
+ spiral/streaming_/reader.py,sha256=CahNNeJznRuUUTtWNexoEBZtKh9bikfaI6UCnER3Jhw,3451
93
+ spiral/streaming_/stream.py,sha256=xFTtGB6CspEKstzBeyyaOeOR3KDiJc21m07ZpD1AXZQ,5669
94
+ spiral/substrait_.py,sha256=AKeOD4KIXvz2J4TYxnIneOiHddtBIyOhuNxVO_uH0eg,12592
95
+ spiral/table.py,sha256=Y5_FqZGjXwt7LT_SYzUA-M-zOBcOdJs7d_t919TAc1k,9605
96
+ spiral/text_index.py,sha256=FQ9rgIEGLSJryS9lFdMhKtPFey18BXoWbPXyvZPJJ04,442
97
+ spiral/transaction.py,sha256=O3vSaTc7zpeC5qbqnj-VWKwK6rrp_mYV2JuPHp2ZJ80,1464
98
+ spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
99
+ pyspiral-0.6.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.9.3)
2
+ Generator: maturin (1.9.4)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-abi3-macosx_11_0_arm64
spiral/__init__.py CHANGED
@@ -3,8 +3,15 @@
3
3
  # This is here to make sure we load the native extension first
4
4
  from spiral import _lib
5
5
 
6
- assert _lib
6
+ # Eagerly import the Spiral library
7
+ assert _lib, "Spiral library"
7
8
 
8
- from spiral.client import Spiral # noqa: E402, I001
9
+ from spiral.client import Spiral # noqa: E402
10
+ from spiral.key_space_index import KeySpaceIndex # noqa: E402
11
+ from spiral.project import Project # noqa: E402
12
+ from spiral.scan import Scan, ShuffleStrategy # noqa: E402
13
+ from spiral.snapshot import Snapshot # noqa: E402
14
+ from spiral.table import Table # noqa: E402
15
+ from spiral.text_index import TextIndex # noqa: E402
9
16
 
10
- __all__ = ["Spiral"]
17
+ __all__ = ["Spiral", "Project", "Table", "Snapshot", "Scan", "ShuffleStrategy", "TextIndex", "KeySpaceIndex"]
spiral/_lib.abi3.so CHANGED
Binary file
spiral/adbc.py CHANGED
@@ -8,7 +8,6 @@ import pyarrow as pa
8
8
  import pyarrow.compute as pc
9
9
  import sqlglot
10
10
  import sqlglot.expressions as exp
11
- from betterproto.lib.google.protobuf import Any
12
11
  from pyarrow.flight import (
13
12
  Action,
14
13
  FlightDescriptor,
@@ -35,6 +34,8 @@ from spiral.protogen._.arrow.flight.protocol.sql import (
35
34
  SqlInfo,
36
35
  SqlSupportedTransaction,
37
36
  )
37
+ from spiral.protogen._.google.protobuf import Any
38
+ from spiral.snapshot import Snapshot
38
39
 
39
40
  log = logging.getLogger(__name__)
40
41
  logging.getLogger("sqlx").setLevel(logging.WARNING)
@@ -64,7 +65,6 @@ def debuggable(func):
64
65
  return wrapper_decorator
65
66
 
66
67
 
67
- # TODO(marko): This should work for Iceberg tables.
68
68
  class ADBCServerBase:
69
69
  def get_sql_info(self, _req: CommandGetSqlInfo) -> pa.RecordBatchReader:
70
70
  """Default implementation that reports no support for any complex features."""
@@ -143,6 +143,17 @@ class SpiralADBCServer(ADBCServerBase):
143
143
 
144
144
  self.pool = ThreadPoolExecutor()
145
145
 
146
+ def open_snapshot(self, tbl) -> Snapshot:
147
+ """Open a table in the Spiral project and return it as a PyArrow Dataset."""
148
+ if tbl.catalog is None or tbl.catalog == "":
149
+ raise FlightError("Project (Data Catalog) must be specified to open a table.")
150
+
151
+ project = tbl.catalog
152
+ dataset = tbl.db or "default"
153
+ table = tbl.name
154
+
155
+ return self.sp.project(project).table(f"{dataset}.{table}").snapshot()
156
+
146
157
  def get_catalogs(self, req: CommandGetCatalogs) -> pa.RecordBatchReader:
147
158
  schema = pa.schema([pa.field("catalog_name", pa.string(), nullable=False)])
148
159
 
@@ -170,15 +181,16 @@ class SpiralADBCServer(ADBCServerBase):
170
181
  if req.catalog == "":
171
182
  # Empty string means databases _without_ a catalog, which we don't support
172
183
  return
184
+ catalog = req.catalog
173
185
 
174
186
  # Otherwise, catalog is either the project ID, or None.
175
- if req.catalog is None:
187
+ if catalog is None:
176
188
  projects = self.sp.list_projects()
177
189
  else:
178
190
  projects = [self.sp.project(req.catalog)]
179
191
 
180
192
  for project in projects:
181
- datasets = {dt.dataset for dt in project.tables.list_tables()}
193
+ datasets = {tbl.dataset for tbl in project.list_tables()}
182
194
 
183
195
  batch = pa.RecordBatch.from_arrays(
184
196
  [
@@ -219,9 +231,10 @@ class SpiralADBCServer(ADBCServerBase):
219
231
  projects = list(self.sp.list_projects())
220
232
  else:
221
233
  projects = [self.sp.project(req.catalog)]
234
+ projects = sorted(projects, key=lambda p: p.id)
222
235
 
223
236
  def _process_project(project):
224
- tables: list[TableResource] = project.tables.list_tables()
237
+ tables: list[TableResource] = project.list_tables()
225
238
 
226
239
  rows = []
227
240
  for table in tables:
@@ -233,7 +246,7 @@ class SpiralADBCServer(ADBCServerBase):
233
246
  }
234
247
 
235
248
  if req.include_schema:
236
- open_table = project.tables.table(f"{table.project_id}.{table.dataset}.{table.table}")
249
+ open_table = project.table(f"{table.dataset}.{table.table}")
237
250
  row["table_schema"] = open_table.snapshot().to_dataset().schema.serialize().to_pybytes()
238
251
 
239
252
  rows.append(row)
@@ -248,12 +261,13 @@ class SpiralADBCServer(ADBCServerBase):
248
261
  def statement_query(self, req: CommandStatementQuery, limit: int | None = None) -> pa.RecordBatchReader:
249
262
  # Extract the tables from the query, and bring them into the Python locals scope.
250
263
  expr = sqlglot.parse_one(req.query, dialect="duckdb")
264
+ datasets = {}
251
265
  for tbl in expr.find_all(exp.Table):
252
266
  # 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()
267
+ # This lets us register a PyArrow Dataset with DuckDB for the query.
268
+ snapshot = self.open_snapshot(tbl)
269
+ name = snapshot.table.table_id
270
+ datasets[name] = snapshot.to_dataset()
257
271
  tbl.replace(exp.table_(table=name))
258
272
 
259
273
  try:
@@ -262,7 +276,11 @@ class SpiralADBCServer(ADBCServerBase):
262
276
  raise FlightError("DuckDB is required for SQL queries.")
263
277
 
264
278
  try:
265
- sql = duckdb.sql(expr.sql(dialect="duckdb"))
279
+ # Create a DuckDB connection and register the datasets
280
+ conn = duckdb.connect()
281
+ for name, dataset in datasets.items():
282
+ conn.register(name, dataset)
283
+ sql = conn.sql(expr.sql(dialect="duckdb"))
266
284
  except Exception as e:
267
285
  raise FlightError(str(e))
268
286
 
spiral/api/__init__.py CHANGED
@@ -10,9 +10,11 @@ if TYPE_CHECKING:
10
10
 
11
11
  from .admin import AdminService
12
12
  from .filesystems import FileSystemService
13
+ from .key_space_indexes import KeySpaceIndexesService
13
14
  from .organizations import OrganizationService
14
15
  from .projects import ProjectService
15
16
  from .telemetry import TelemetryService
17
+ from .text_indexes import TextIndexesService
16
18
  from .workloads import WorkloadService
17
19
 
18
20
 
@@ -57,6 +59,18 @@ class SpiralAPI:
57
59
 
58
60
  return WorkloadService(self.client)
59
61
 
62
+ @property
63
+ def text_indexes(self) -> "TextIndexesService":
64
+ from .text_indexes import TextIndexesService
65
+
66
+ return TextIndexesService(self.client)
67
+
68
+ @property
69
+ def key_space_indexes(self) -> "KeySpaceIndexesService":
70
+ from .key_space_indexes import KeySpaceIndexesService
71
+
72
+ return KeySpaceIndexesService(self.client)
73
+
60
74
  @property
61
75
  def telemetry(self) -> "TelemetryService":
62
76
  from .telemetry import TelemetryService
spiral/api/client.py CHANGED
@@ -6,7 +6,7 @@ import httpx
6
6
  from httpx import HTTPStatusError
7
7
  from pydantic import BaseModel, Field, TypeAdapter
8
8
 
9
- from spiral.core.client import Authn
9
+ from spiral.core.authn import Authn
10
10
 
11
11
  log = logging.getLogger(__name__)
12
12
 
@@ -146,6 +146,10 @@ class _Client:
146
146
  # Enrich the exception with the response body
147
147
  raise SpiralHTTPError(body=resp.text, code=resp.status_code) from e
148
148
 
149
+ if response_cls == type[None]:
150
+ assert resp.text == ""
151
+ return None
152
+
149
153
  return TypeAdapter(response_cls).validate_python(resp.json())
150
154
 
151
155
  def paged(
@@ -0,0 +1,23 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .client import ServiceBase
4
+ from .types import IndexId, WorkerId
5
+ from .workers import ResourceClass
6
+
7
+
8
+ class SyncIndexRequest(BaseModel):
9
+ """Request to sync a text index."""
10
+
11
+ resources: ResourceClass
12
+
13
+
14
+ class SyncIndexResponse(BaseModel):
15
+ worker_id: WorkerId
16
+
17
+
18
+ class KeySpaceIndexesService(ServiceBase):
19
+ """Service for key space index operations."""
20
+
21
+ def sync_index(self, index_id: IndexId, request: SyncIndexRequest) -> SyncIndexResponse:
22
+ """Start a job to sync an index."""
23
+ return self.client.post(f"/v1/key-space-indexes/{index_id}/sync", request, SyncIndexResponse)
spiral/api/projects.py CHANGED
@@ -138,6 +138,12 @@ class TextIndexResource(BaseModel):
138
138
  name: str
139
139
 
140
140
 
141
+ class KeySpaceIndexResource(BaseModel):
142
+ id: str
143
+ project_id: ProjectId
144
+ name: str
145
+
146
+
141
147
  class ProjectService(ServiceBase):
142
148
  """Service for project operations."""
143
149
 
@@ -169,6 +175,15 @@ class ProjectService(ServiceBase):
169
175
  f"/v1/projects/{project_id}/text-indexes", PagedResponse[TextIndexResource], params=params
170
176
  )
171
177
 
178
+ def list_key_space_indexes(self, project_id: ProjectId, name: str | None = None) -> Paged[KeySpaceIndexResource]:
179
+ """List key space indexes in a project."""
180
+ params = {}
181
+ if name:
182
+ params["name"] = name
183
+ return self.client.paged(
184
+ f"/v1/projects/{project_id}/key-space-indexes", PagedResponse[KeySpaceIndexResource], params=params
185
+ )
186
+
172
187
  def get(self, project_id: ProjectId) -> Project:
173
188
  """Get a project."""
174
189
  return self.client.get(f"/v1/projects/{project_id}", Project)
@@ -192,6 +207,6 @@ class ProjectService(ServiceBase):
192
207
  """Get a grant."""
193
208
  return self.client.get(f"/v1/grants/{grant_id}", Grant)
194
209
 
195
- def revoke_grant(self, grant_id: str) -> None:
210
+ def revoke_grant(self, grant_id: str):
196
211
  """Revoke a grant."""
197
- return self.client.delete(f"/v1/grants/{grant_id}", None)
212
+ return self.client.delete(f"/v1/grants/{grant_id}", type[None])
@@ -0,0 +1,56 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .client import Paged, PagedResponse, ServiceBase
4
+ from .types import IndexId, ProjectId, WorkerId
5
+ from .workers import CPU, GcpRegion, Memory, ResourceClass
6
+
7
+
8
+ class TextSearchWorker(BaseModel):
9
+ worker_id: WorkerId
10
+ project_id: ProjectId
11
+ index_id: IndexId
12
+ url: str | None
13
+
14
+
15
+ class CreateWorkerRequest(BaseModel):
16
+ cpu: CPU
17
+ memory: Memory
18
+ region: GcpRegion
19
+
20
+
21
+ class CreateWorkerResponse(BaseModel):
22
+ worker_id: WorkerId
23
+
24
+
25
+ class SyncIndexRequest(BaseModel):
26
+ """Request to sync a text index."""
27
+
28
+ resources: ResourceClass
29
+
30
+
31
+ class SyncIndexResponse(BaseModel):
32
+ worker_id: WorkerId
33
+
34
+
35
+ class TextIndexesService(ServiceBase):
36
+ """Service for text index operations."""
37
+
38
+ def create_worker(self, index_id: IndexId, request: CreateWorkerRequest) -> CreateWorkerResponse:
39
+ """Create a new search worker."""
40
+ return self.client.post(f"/v1/text-indexes/{index_id}/workers", request, CreateWorkerResponse)
41
+
42
+ def list_workers(self, index_id: IndexId) -> Paged[WorkerId]:
43
+ """List text index workers for the given index."""
44
+ return self.client.paged(f"/v1/text-indexes/{index_id}/workers", PagedResponse[WorkerId])
45
+
46
+ def get_worker(self, worker_id: WorkerId) -> TextSearchWorker:
47
+ """Get a text index worker."""
48
+ return self.client.get(f"/v1/text-index-workers/{worker_id}", TextSearchWorker)
49
+
50
+ def shutdown_worker(self, worker_id: WorkerId) -> None:
51
+ """Shutdown a text index worker."""
52
+ return self.client.delete(f"/v1/text-index-workers/{worker_id}", type[None])
53
+
54
+ def sync_index(self, index_id: IndexId, request: SyncIndexRequest) -> SyncIndexResponse:
55
+ """Start a job to sync an index."""
56
+ return self.client.post(f"/v1/text-indexes/{index_id}/sync", request, SyncIndexResponse)
spiral/api/types.py CHANGED
@@ -13,6 +13,8 @@ UserId = str
13
13
  OrgId = str
14
14
  ProjectId = str
15
15
  RoleId = str
16
+ IndexId = str
17
+ WorkerId = str
16
18
 
17
19
  RootUri = Annotated[str, AfterValidator(_validate_root_uri)]
18
20
  DatasetName = Annotated[str, StringConstraints(max_length=128, pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]+$")]
spiral/api/workers.py ADDED
@@ -0,0 +1,40 @@
1
+ from enum import Enum, IntEnum
2
+
3
+
4
+ class CPU(IntEnum):
5
+ ONE = 1
6
+ TWO = 2
7
+ FOUR = 4
8
+ EIGHT = 8
9
+
10
+ def __str__(self):
11
+ return str(self.value)
12
+
13
+
14
+ class Memory(str, Enum):
15
+ MB_512 = "512Mi"
16
+ GB_1 = "1Gi"
17
+ GB_2 = "2Gi"
18
+ GB_4 = "4Gi"
19
+ GB_8 = "8Gi"
20
+
21
+ def __str__(self):
22
+ return self.value
23
+
24
+
25
+ class GcpRegion(str, Enum):
26
+ US_EAST4 = "us-east4"
27
+ EUROPE_WEST4 = "europe-west4"
28
+
29
+ def __str__(self):
30
+ return self.value
31
+
32
+
33
+ class ResourceClass(str, Enum):
34
+ """Resource class for text index sync."""
35
+
36
+ SMALL = "small"
37
+ LARGE = "large"
38
+
39
+ def __str__(self):
40
+ return self.value
spiral/cli/__init__.py CHANGED
@@ -1,16 +1,23 @@
1
1
  import asyncio
2
2
  import functools
3
3
  import inspect
4
- from typing import IO
4
+ from collections.abc import Callable
5
+ from typing import IO, Generic, ParamSpec, TypeVar, override
5
6
 
6
- import rich
7
7
  import typer
8
8
  from click import ClickException
9
9
  from grpclib import GRPCError
10
10
  from httpx import HTTPStatusError
11
+ from rich.console import Console
11
12
 
13
+ P = ParamSpec("P")
14
+ T = TypeVar("T")
12
15
 
13
- class AsyncTyper(typer.Typer):
16
+ CONSOLE = Console()
17
+ ERR_CONSOLE = Console(stderr=True, style="red")
18
+
19
+
20
+ class AsyncTyper(typer.Typer, Generic[P]):
14
21
  """Wrapper to allow async functions to be used as commands.
15
22
 
16
23
  We also pre-bake some configuration.
@@ -25,13 +32,15 @@ class AsyncTyper(typer.Typer):
25
32
  **kwargs,
26
33
  )
27
34
 
28
- def callback(self, *args, **kwargs):
35
+ @override
36
+ def callback(self, *args, **kwargs) -> Callable[[Callable[P, T]], Callable[P, T]]:
29
37
  decorator = super().callback(*args, **kwargs)
30
38
  for wrapper in (_wrap_exceptions, _maybe_run_async):
31
39
  decorator = functools.partial(wrapper, decorator)
32
40
  return decorator
33
41
 
34
- def command(self, *args, **kwargs):
42
+ @override
43
+ def command(self, *args, **kwargs) -> Callable[[Callable[P, T]], Callable[P, T]]:
35
44
  decorator = super().command(*args, **kwargs)
36
45
  for wrapper in (_wrap_exceptions, _maybe_run_async):
37
46
  decorator = functools.partial(wrapper, decorator)
@@ -50,7 +59,7 @@ class _ClickGRPCException(ClickException):
50
59
  return self.message
51
60
 
52
61
  def show(self, file: IO[str] | None = None) -> None:
53
- rich.print(f"Error: {self.format_message()}", file=file)
62
+ ERR_CONSOLE.print(f"Error: {self.format_message()}")
54
63
 
55
64
 
56
65
  def _maybe_run_async(decorator, f):
spiral/cli/admin.py CHANGED
@@ -1,7 +1,5 @@
1
- from rich import print
2
-
3
1
  from spiral.api.types import OrgId
4
- from spiral.cli import AsyncTyper, state
2
+ from spiral.cli import CONSOLE, AsyncTyper, state
5
3
 
6
4
  app = AsyncTyper()
7
5
 
@@ -13,4 +11,4 @@ def sync(
13
11
  state.settings.api._admin.sync_orgs()
14
12
 
15
13
  for membership in state.settings.api._admin.sync_memberships(org_id):
16
- print(membership)
14
+ CONSOLE.print(membership)
spiral/cli/app.py CHANGED
@@ -8,13 +8,14 @@ from spiral.cli import (
8
8
  console,
9
9
  fs,
10
10
  iceberg,
11
- indexes,
11
+ key_spaces,
12
12
  login,
13
13
  orgs,
14
14
  projects,
15
15
  state,
16
16
  tables,
17
17
  telemetry,
18
+ text,
18
19
  workloads,
19
20
  )
20
21
  from spiral.settings import LOG_DIR, Settings
@@ -36,7 +37,8 @@ app.add_typer(orgs.app, name="orgs")
36
37
  app.add_typer(projects.app, name="projects")
37
38
  app.add_typer(iceberg.app, name="iceberg")
38
39
  app.add_typer(tables.app, name="tables")
39
- app.add_typer(indexes.app, name="indexes")
40
+ app.add_typer(key_spaces.app, name="ks")
41
+ app.add_typer(text.app, name="text")
40
42
  app.add_typer(telemetry.app, name="telemetry")
41
43
  app.command("console")(console.command)
42
44
  app.command("login")(login.command)
spiral/cli/fs.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from typing import Annotated
2
2
 
3
3
  import questionary
4
- import rich
5
4
  from pydantic import SecretStr
6
5
  from typer import Option
7
6
 
@@ -13,7 +12,7 @@ from spiral.api.filesystems import (
13
12
  UpdateS3FileSystem,
14
13
  UpstreamFileSystem,
15
14
  )
16
- from spiral.cli import AsyncTyper, state
15
+ from spiral.cli import CONSOLE, AsyncTyper, state
17
16
  from spiral.cli.types import ProjectArg, ask_project
18
17
 
19
18
  app = AsyncTyper(short_help="File Systems.")
@@ -24,9 +23,9 @@ def show(project: ProjectArg):
24
23
  file_system = state.settings.api.file_system.get_file_system(project)
25
24
  match file_system:
26
25
  case BuiltinFileSystem(provider=provider):
27
- rich.print(f"provider: {provider}")
26
+ CONSOLE.print(f"provider: {provider}")
28
27
  case _:
29
- rich.print(file_system)
28
+ CONSOLE.print(file_system)
30
29
 
31
30
 
32
31
  def ask_provider():
@@ -103,10 +102,10 @@ def update(
103
102
  raise ValueError("Must specify either --s3 or --gcs.")
104
103
 
105
104
  res = state.settings.api.file_system.update_file_system(project, file_system)
106
- rich.print(res.file_system)
105
+ CONSOLE.print(res.file_system)
107
106
 
108
107
 
109
108
  @app.command(help="Lists the available built-in file system providers.")
110
109
  def list_providers():
111
110
  for provider in state.settings.api.file_system.list_providers():
112
- rich.print(provider)
111
+ CONSOLE.print(provider)