pyspiral 0.8.1__cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.9.8__cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.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 (54) hide show
  1. {pyspiral-0.8.1.dist-info → pyspiral-0.9.8.dist-info}/METADATA +4 -2
  2. {pyspiral-0.8.1.dist-info → pyspiral-0.9.8.dist-info}/RECORD +53 -44
  3. spiral/__init__.py +3 -2
  4. spiral/_lib.abi3.so +0 -0
  5. spiral/adbc.py +2 -2
  6. spiral/api/__init__.py +26 -19
  7. spiral/api/client.py +86 -9
  8. spiral/api/filesystems.py +24 -6
  9. spiral/api/organizations.py +2 -1
  10. spiral/api/projects.py +5 -3
  11. spiral/api/tables.py +77 -0
  12. spiral/api/workloads.py +4 -4
  13. spiral/arrow_.py +4 -155
  14. spiral/cli/admin.py +19 -0
  15. spiral/cli/app.py +11 -5
  16. spiral/cli/chooser.py +30 -0
  17. spiral/cli/fs.py +43 -9
  18. spiral/cli/iceberg.py +1 -1
  19. spiral/cli/key_spaces.py +4 -4
  20. spiral/cli/orgs.py +10 -19
  21. spiral/cli/projects.py +6 -5
  22. spiral/cli/tables.py +70 -21
  23. spiral/cli/telemetry.py +13 -6
  24. spiral/cli/text.py +4 -4
  25. spiral/cli/transactions.py +84 -0
  26. spiral/cli/{types.py → types_.py} +8 -8
  27. spiral/cli/workloads.py +64 -36
  28. spiral/client.py +87 -13
  29. spiral/core/client/__init__.pyi +37 -20
  30. spiral/core/expr/pushdown/__init__.pyi +3 -0
  31. spiral/core/expr/s3/__init__.pyi +3 -0
  32. spiral/core/table/__init__.pyi +25 -22
  33. spiral/dataloader.py +13 -2
  34. spiral/debug/manifests.py +46 -18
  35. spiral/debug/scan.py +4 -6
  36. spiral/demo.py +207 -0
  37. spiral/enrichment.py +18 -23
  38. spiral/expressions/__init__.py +3 -75
  39. spiral/expressions/base.py +5 -10
  40. spiral/expressions/pushdown.py +12 -0
  41. spiral/expressions/udf.py +7 -1
  42. spiral/huggingface.py +456 -0
  43. spiral/input.py +131 -0
  44. spiral/project.py +32 -12
  45. spiral/ray_.py +85 -0
  46. spiral/scan.py +257 -65
  47. spiral/server.py +20 -0
  48. spiral/settings.py +1 -1
  49. spiral/snapshot.py +10 -5
  50. spiral/table.py +16 -11
  51. spiral/transaction.py +101 -15
  52. spiral/iterable_dataset.py +0 -106
  53. {pyspiral-0.8.1.dist-info → pyspiral-0.9.8.dist-info}/WHEEL +0 -0
  54. {pyspiral-0.8.1.dist-info → pyspiral-0.9.8.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyspiral
3
- Version: 0.8.1
3
+ Version: 0.9.8
4
4
  Classifier: Intended Audience :: Science/Research
5
5
  Classifier: Operating System :: OS Independent
6
6
  Classifier: Programming Language :: Python
@@ -31,17 +31,19 @@ Requires-Dist: xxhash>=3.4.1
31
31
  Requires-Dist: polars>=1.31.0 ; extra == 'polars'
32
32
  Requires-Dist: duckdb>=1.3.2 ; extra == 'duckdb'
33
33
  Requires-Dist: pyiceberg[s3fs]>=0.9.1 ; extra == 'iceberg'
34
- Requires-Dist: datasets>=4.0.0 ; extra == 'huggingface'
34
+ Requires-Dist: datasets>=4.3.0 ; extra == 'huggingface'
35
35
  Requires-Dist: mosaicml-streaming>=0.13.0 ; extra == 'streaming'
36
36
  Requires-Dist: vortex-data>=0.52.1 ; extra == 'streaming'
37
37
  Requires-Dist: dask>=2025.10.0 ; extra == 'dask'
38
38
  Requires-Dist: distributed>=2025.10.0 ; extra == 'dask'
39
+ Requires-Dist: ray[data]>=2.0.0 ; python_full_version < '3.14' and extra == 'ray'
39
40
  Provides-Extra: polars
40
41
  Provides-Extra: duckdb
41
42
  Provides-Extra: iceberg
42
43
  Provides-Extra: huggingface
43
44
  Provides-Extra: streaming
44
45
  Provides-Extra: dask
46
+ Provides-Extra: ray
45
47
  Summary: Python client for Spiral.
46
48
  Home-Page: https://spiraldb.com
47
49
  Author-email: SpiralDB <hello@spiraldb.com>
@@ -1,83 +1,91 @@
1
- pyspiral-0.8.1.dist-info/METADATA,sha256=iJRR8MZocrH9H-LyCyeXk7TYbLQNjdwUueFtzhrFC4k,1953
2
- pyspiral-0.8.1.dist-info/WHEEL,sha256=bx69zWyc-B09u77H7BhtavKljYQJK822O1mV8jKC1Q4,147
3
- pyspiral-0.8.1.dist-info/entry_points.txt,sha256=R96Y3FpYX6XbQu9qMPfUTgiCcf4qM9OBQQZTDdBkZwA,74
4
- spiral/__init__.py,sha256=PwaYBWFBtB7cYi7peMmhk_Lm5XzjRoLwOtLbUhc1ZDo,1449
5
- spiral/_lib.abi3.so,sha256=n2Hb9QJG6wwuO8sFX2OTyISycsQdlyjaM29WcnVwEe4,66210936
6
- spiral/adbc.py,sha256=7IxfWIeQN-fh0W5OdN_PP2x3pzQYg6ZUOLsHg3jktqw,14842
7
- spiral/api/__init__.py,sha256=fguWdWeMnxWALRpqfKaUY5LmdQPsxTeyVIwIxElcRws,2240
1
+ pyspiral-0.9.8.dist-info/METADATA,sha256=BZlkhMmVj7_ZWXDWYxf6xJ4fRt6rU4-4_PdP_7hg1vY,2055
2
+ pyspiral-0.9.8.dist-info/WHEEL,sha256=bx69zWyc-B09u77H7BhtavKljYQJK822O1mV8jKC1Q4,147
3
+ pyspiral-0.9.8.dist-info/entry_points.txt,sha256=R96Y3FpYX6XbQu9qMPfUTgiCcf4qM9OBQQZTDdBkZwA,74
4
+ spiral/__init__.py,sha256=8g-RFlbqvKOMgQtkXrtfXFjoqkDbym5n6RZSXsrmgcA,1492
5
+ spiral/_lib.abi3.so,sha256=kdHOPQlXHmFmjv2Zl-2meMZJlL3deRRtaFIp7MmY_Ag,71685080
6
+ spiral/adbc.py,sha256=Mc2wdC_fqvE4jqlgHqCI7M9Y-jRH4SaAjxJMibmIvbc,14854
7
+ spiral/api/__init__.py,sha256=JRLzPC3BYjkEBdcqAYBYv43C2V4Z51tClug6ztyEqBw,2319
8
8
  spiral/api/admin.py,sha256=A1iVR1XYJSObZivPAD5UzmPuMgupXc9kaHNYYa_kwfs,585
9
- spiral/api/client.py,sha256=Bu3u2jXrvHpu2KbQrlxKoxq8GA0KC_4HzWsBkdocmXo,4700
10
- spiral/api/filesystems.py,sha256=yEHgHfo7t1_becm0UFedc3nd49_G77hHjYwtYQ6P9XU,4240
9
+ spiral/api/client.py,sha256=mrnFdmwDB6FYuNKl7QIJqyBVx-Y3ENYgNPXEQ6nLJuI,7932
10
+ spiral/api/filesystems.py,sha256=8g_YdFjFFZLybQqV1xSH91SxVvOer4O6sGECexWXRnw,4548
11
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=1JC7VjqZJfwR6zfhBZr3OCwaf6zb-MXMOBTE_NztmcE,6356
12
+ spiral/api/organizations.py,sha256=eXAzxrKPmd3IVFfEaEbqbhqG0AjBM4IDz3O-ZoxJI5w,1928
13
+ spiral/api/projects.py,sha256=G9SXAYg6PH_dTbogB_roZQVUi3rXaFPI9_ytFg25i98,6430
14
+ spiral/api/tables.py,sha256=AH-Kdp2Jy6lReSoK7GcY9L8WsWMWAACaBjYcK3kbNlM,2070
14
15
  spiral/api/telemetry.py,sha256=tfdA3E_EWJwFVxkQfkm8tiYGRubnx2LuE5nbfsk1oG4,474
15
16
  spiral/api/text_indexes.py,sha256=_zVlGBytl-9-Unbu2POfZgLh40H1YRcagFtplgIG428,1828
16
17
  spiral/api/types.py,sha256=HpHsoBuf7IdlXb7Dw-BkBkEvxBVIhkI8JviqhuoP9pY,696
17
18
  spiral/api/workers.py,sha256=0wZNUHMioDT53P1OBJfpjyDfIodHwwT6858z2IlRIM4,636
18
- spiral/api/workloads.py,sha256=XAyXV7vgZcoyyoPoGvOT4jTpyFKFMvrrAfhL6d1h1kE,1748
19
- spiral/arrow_.py,sha256=fUpXmjUjG-rGfqMhKR332QzC7zrfIU2yjLaWKYzefwU,6778
19
+ spiral/api/workloads.py,sha256=GBZ4tLa_-NtZvV-P5GTJgPSxBQ_YiLyWaOpr9ELojOo,1764
20
+ spiral/arrow_.py,sha256=pV36BbkPL2Gvq5z_O7w2iFySkZTecrTlWYcCkplhVto,1828
20
21
  spiral/cli/__init__.py,sha256=GdTQZVArIw19zSKi92ZtwD8pXQExuubnaN854XLTSzY,2505
21
22
  spiral/cli/__main__.py,sha256=kNaKM2xgJo7GRogf83nYldLM-RGUR6vymdGwZxywQu0,71
22
- spiral/cli/admin.py,sha256=G_yCj8YKUiLuIZml3rqZLKS_2hBU0r4ILDdO_ZvRnCo,315
23
- spiral/cli/app.py,sha256=eaY1SRf2wI1_T_cwrGUbldsU1QqfGrpAmN98gH1lJOE,2842
23
+ spiral/cli/admin.py,sha256=sC_XUZvi7t91qHMR5vea_KD3lXUcygil1MUw7zVFmpE,945
24
+ spiral/cli/app.py,sha256=tr8vR1g_9aJBnn2Dea_a-E8Wpn73brbDHrm_zRg-wVA,3048
25
+ spiral/cli/chooser.py,sha256=JmlETVEfHd9JOkL4ILPly1TgyESVa8vZqNDgFMcnQm8,1091
24
26
  spiral/cli/console.py,sha256=6JHbAQV6MFWz3P-VzqPOjhHpkIQagsCdzTMvmuDKMkU,2580
25
- spiral/cli/fs.py,sha256=chg4W6j7rPYxUiTUSVCkHoiF3vG5lcpqUKDYwp5VYtY,2919
26
- spiral/cli/iceberg.py,sha256=wdMyl0j821MLnXNZ6Kwm65ogh98C-pjMJm3Y6YqlnTI,3249
27
- spiral/cli/key_spaces.py,sha256=84MibTdjI5bFK7lhL0w1WOlw-uBZtFnPPlTQuI2PPGw,3524
27
+ spiral/cli/fs.py,sha256=WcUeyh9sFoWJ2xn25-xKzLEi2u9KLdmoVXwyKynxrK8,4194
28
+ spiral/cli/iceberg.py,sha256=r5qJTy2YACGQALPwU5VQXsQvkY5Qv07qXgpA4qvmVGU,3250
29
+ spiral/cli/key_spaces.py,sha256=0Mv7jwY8coF_wBW8klLJHPpiXqLwFNfVzT06zokzqfU,3526
28
30
  spiral/cli/login.py,sha256=2l2i38XNHGKtV4DP6PZPN4LHxceCn3AdHDE5nM2iK5M,760
29
- spiral/cli/orgs.py,sha256=Y3hBPqBchPS72uJNn_OVx1fBIc2lO2EiKJsfRHAX1yE,2890
31
+ spiral/cli/orgs.py,sha256=L68jO-SEHlocy-tMQYmQ7LAW0WuY0r7q5yr3QBUm0CM,2538
30
32
  spiral/cli/printer.py,sha256=HcvSUpaMItzmhBUfIHROK1Z3SL8J8wDopS3Qo8H00uw,1781
31
- spiral/cli/projects.py,sha256=awB2Q8gM1MtZOltWZ9ZPCbdUr-4WpsGHMR1rY12kv-s,5748
33
+ spiral/cli/projects.py,sha256=maH8uGJT_TopIEoPADiz5Qe4SziH0jYJW9bJK1QtWL4,5831
32
34
  spiral/cli/state.py,sha256=3sKQuFtV2vCn3E1Dv7Sw9-IK5jiXCVBEQ9Ze17NZXDs,129
33
- spiral/cli/tables.py,sha256=nrzPHUekYY3MF0nWHdfLUmfWrIucYJp_JwdsWqOFYXI,7161
34
- spiral/cli/telemetry.py,sha256=9kp7lmimShsGoLRUic5aOEQ4hti-pPFMBFc4cdlPDmk,587
35
- spiral/cli/text.py,sha256=DlWGe4JrkdERAiqyITNpk91Wqb63Re99rNYlIFsIamc,4031
36
- spiral/cli/types.py,sha256=o-HJhfoyPW3JD5p55yec6QNTtVGzbG-PG3kwjvZz5Zs,1358
37
- spiral/cli/workloads.py,sha256=HL2_CkmlCm_nzTLjFogAW57Tev7RR73Qan_SZjsAoWk,2009
38
- spiral/client.py,sha256=oN9VlCkclqjFERHaggcxhc5ZvcCta9f25ZZrP9nZhts,9360
35
+ spiral/cli/tables.py,sha256=r4cogd-4Az-KYoIAcrJ4Fxi1lPj5AEBB7_qdCdpmhgQ,8967
36
+ spiral/cli/telemetry.py,sha256=bhbFyMtQ2Wc_-Rl1u4VlQcr8Yt3hNlW-Gi2NbBHC09c,794
37
+ spiral/cli/text.py,sha256=u2XR9SOs0vIz5NjV5P5Lj3PC1XOlsD8RFQrGwc0AMUY,4033
38
+ spiral/cli/transactions.py,sha256=t3yB2pN3ishYQjlSkP_hVkENgBnqi0QvdBMyVS8pKXU,2748
39
+ spiral/cli/types_.py,sha256=8FEaKDNVtzrQswuX7KHKL1znHeJK7AKrDTRTPer5PWM,1335
40
+ spiral/cli/workloads.py,sha256=NOoYZQ2OewtIAgmWXhPpwGwio233VHhdDfdDgqFMVc8,2999
41
+ spiral/client.py,sha256=bpzXee4HqNaP6Q646LWa4wesQgA7tbqeA3rUS7rVXFU,12099
39
42
  spiral/core/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
43
  spiral/core/_tools/__init__.pyi,sha256=b2KLfTOQ67pjfbYt07o0IGiTu5o2bZw69lllV8v0Dps,143
41
44
  spiral/core/authn/__init__.pyi,sha256=deZvPlCyiPC6PpXxpEZVglxL5mUJ1Qqg20ieEQgU6ik,582
42
- spiral/core/client/__init__.pyi,sha256=zIaI9t1_dbQTQ3vd2CUBNwg7elIZVOFz4rL8pKW-8CM,7229
45
+ spiral/core/client/__init__.pyi,sha256=a5jsYTjBQVkH3WtxMHNHFK_U8z5yx-mh-FeSa5N0bYY,7655
43
46
  spiral/core/config/__init__.pyi,sha256=1BaB7fTGly_fW-qTSQtxbGrYErzkqxuJokDFPupP7d0,955
44
47
  spiral/core/expr/__init__.pyi,sha256=3HSKjkotiEkxBvGBALXEBIie0JiyI9bCpehwA3nMQkU,571
45
48
  spiral/core/expr/images/__init__.pyi,sha256=wnE_wZXq7a4iqTg3SVm-ssxGw1WQZyk5dGOPaP4Btko,73
46
49
  spiral/core/expr/list_/__init__.pyi,sha256=Q_9c87eIQfZbqlaw_rq3fvs93YEsW7K5VYk6VZ4g6mU,126
50
+ spiral/core/expr/pushdown/__init__.pyi,sha256=zcpBHFz-uriK0WccBUrPOuR8Mi8jPzEzpv9kGpp_PRw,60
47
51
  spiral/core/expr/refs/__init__.pyi,sha256=nZZP3l_Z6bLx6V8lTcH3Jgo--xwfADOU2XdTAvM5IMk,127
52
+ spiral/core/expr/s3/__init__.pyi,sha256=GlgT6HtRqNbf31enHhkoM6HXHHxlgic7mApob_R3tOQ,76
48
53
  spiral/core/expr/str_/__init__.pyi,sha256=Bm6fZK-d4fNbJuuBhVoWMACXUbQQ2SjlhgrOpdOHIPM,86
49
54
  spiral/core/expr/struct_/__init__.pyi,sha256=MXckd98eV_x3X0RhEWvlkA3DcDXRtLs5pNnTQkc09nE,296
50
55
  spiral/core/expr/text/__init__.pyi,sha256=ed83n1xcsGY7_QDhMmJGnSQ20UrJFXcdv1AveSEcS1c,175
51
56
  spiral/core/expr/udf/__init__.pyi,sha256=zsZs081KVhY3-1JidqTkWMW81Qd_ScoTGZvasIhIK-4,358
52
57
  spiral/core/expr/video/__init__.pyi,sha256=nQJEcSsigZuRpMjkI_O4EEtMK_n2zRvorcL_KEeD5vU,95
53
- spiral/core/table/__init__.pyi,sha256=nffQgwjzokhM_lRFyKXnHIfdXDv0OKZB3r5pQjGvtyw,4536
58
+ spiral/core/table/__init__.pyi,sha256=hdAu59xHgMWsA1I2Vmz7sRq4DJhh8OVhnHPDgGoz6CQ,4802
54
59
  spiral/core/table/manifests/__init__.pyi,sha256=eVfDpmhYSjafIvvALqAkZe5baN3Y1HpKpxYEbjwd4gQ,1043
55
60
  spiral/core/table/metastore/__init__.pyi,sha256=rc3u9MwEKRvL2kxOc8lBorddFRnM8o_o1frqtae86a4,1697
56
61
  spiral/core/table/spec/__init__.pyi,sha256=839FNXvUS1PD-jYx2pXIIlTEjKszZUGfVD7WKSpflIQ,5659
57
- spiral/dataloader.py,sha256=W9siY4BF4p_rwTTSS4KgsaQsPLxxza6XmQhrdBzzMJ8,10592
62
+ spiral/dataloader.py,sha256=FAaV_B3HTH_gz2FQDDG_5gLjQYR3jScgyHaOa8fSoQk,10766
58
63
  spiral/dataset.py,sha256=xse0evrNDKPXNrqaS5ZklyvPsrTPaFov5A2uwwMd9sU,8429
59
64
  spiral/datetime_.py,sha256=elXaUWtZuuLVcu9E0aXnvYRPB9XWqZbLDToozQYQYjU,950
60
65
  spiral/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- spiral/debug/manifests.py,sha256=7f1O3ba9mrA5nXpOF9cEIQuUAteP5wiBkFy_diQJ7No,3216
66
+ spiral/debug/manifests.py,sha256=DvhDUkW9Ca8YfsPchArnajzEdedd1jg0Zx-xSRmnrmw,4282
62
67
  spiral/debug/metrics.py,sha256=_B1LoHejOQk7sfKX1dhVmHrcB3HNZzhr2M4iQfsyOUQ,2058
63
- spiral/debug/scan.py,sha256=bYLY4nZNdo0cv7Ldcy3hkXix3aFClHOCG6QbOtn8954,9549
64
- spiral/enrichment.py,sha256=DpnCtKcdqwvogCu3ReR1iDktSQqA_GOXXLTzvHKb64w,10713
65
- spiral/expressions/__init__.py,sha256=ZsD8g7vB0G7xy19GUiH4m79kw7KEkTQRwJl5Gn1cgtw,8049
66
- spiral/expressions/base.py,sha256=ooTtXy5QkCmPNMYa7lJuFAguFpBrd59UWIxOGxhQ5h0,6261
68
+ spiral/debug/scan.py,sha256=PYU4FJiE5aZ0Rc7LkTK62UEjlLwdAm-gNe869KHpNTs,9475
69
+ spiral/demo.py,sha256=28jH4Y0VmBifFCEEPxsUVfzqBqWW-pTUI1__N8nwwPA,6793
70
+ spiral/enrichment.py,sha256=wp9EKN0baQwqwV7I2aL87BgTt5zSY0mvn8M2I6mxUe8,10771
71
+ spiral/expressions/__init__.py,sha256=bb54V06ybqrf9P7E8gyKktLrs01m94-NktZpN6mnfRA,5042
72
+ spiral/expressions/base.py,sha256=J9apXEX47ufrofHo37pFr-6Qj2t0mxCsVtabXFsCrD4,6080
67
73
  spiral/expressions/file.py,sha256=7D9jIENJcoT0KFharBLkzK9dZgO4DYn5K_KCt0twefg,518
68
74
  spiral/expressions/http.py,sha256=OOHh0WBxg3vwza_m74-rkoQWSclRMI60aPAbQ6yKZi0,486
69
75
  spiral/expressions/list_.py,sha256=-OHzTkTYvTY_Q2IuATfK5QNx7KEyic3DzZLEYn8otIk,2050
76
+ spiral/expressions/pushdown.py,sha256=6ZHke34_YLFYiqp14tXIRWH3K2yVHADeNiRDY_tF5N8,371
70
77
  spiral/expressions/s3.py,sha256=PhQhMP-d8PLsSRtGCZbytnm7lI9VbDAbuSs2LBM4G7Q,505
71
78
  spiral/expressions/str_.py,sha256=tY8RXW3JWvr1-bEfCZtk5FAf11wKJnXPuA9EoeJ9tA4,1265
72
79
  spiral/expressions/struct.py,sha256=pGAnCDh6AK0BK1XfZ1qG4ce4ranIQEE1HQsgmzBcfwQ,2038
73
80
  spiral/expressions/text.py,sha256=-02gBWYoyNQ3qQ1--9HTa8IryUDojYQVIp8C7rgnOWQ,1893
74
81
  spiral/expressions/tiff.py,sha256=B1N6ck1-CcIPSU9_Vnol7fXNnTbhV1CnMxvtAG5wmx0,7979
75
- spiral/expressions/udf.py,sha256=5mu8azZ7pLI3D9YaXFH33ukIY37BdohC1eFfyLBTNx0,1925
82
+ spiral/expressions/udf.py,sha256=XhePtyzrMgX0SQ5mOmf2XrdkhN7BSyyZpLZtF862B1U,2046
76
83
  spiral/grpc_.py,sha256=f3czdP1Mxme42Y5--a5ogYq1TTiWn-J_MlGjwJ2mWwM,1015
84
+ spiral/huggingface.py,sha256=eJPX0npe4IBfZ8bwXPc374W22kN5iK00xOzTIklxPwA,15809
77
85
  spiral/iceberg.py,sha256=02OkA348eFxkEbgreeTuVlzavVvZmM4hldrZI76PZ9I,914
78
- spiral/iterable_dataset.py,sha256=Eekg9ad8tcwXcloHWReBbvCSr5ZappRHn2ldKTvwqS0,4622
86
+ spiral/input.py,sha256=yCqojJDwdkXg92tfLcgfR0ltSrDwCwxS_GjoP9r9HGU,4195
79
87
  spiral/key_space_index.py,sha256=NAB_nONEjpMYbse8suz42w7Qb5OPHuKN9h9CT2NJe08,1460
80
- spiral/project.py,sha256=dkYc5iWZzz_HMKcu1EXUNNsI7hnEyGy8VrnKdVmKjjE,8199
88
+ spiral/project.py,sha256=bUqfROIouk_2WSZXc8DPbFwJuzPac8ucxOM7qHpw6gE,8796
81
89
  spiral/protogen/_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
90
  spiral/protogen/_/arrow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
91
  spiral/protogen/_/arrow/flight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -95,16 +103,17 @@ spiral/protogen/_/substrait/extensions/__init__.py,sha256=nhnEnho70GAT8WPj2xtwJU
95
103
  spiral/protogen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
104
  spiral/protogen/util.py,sha256=smnvVo6nYH3FfDm9jqhNLaXz4bbTBaQezHQDCTvZyiQ,1486
97
105
  spiral/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
- spiral/scan.py,sha256=sc29S4gXq2zaOa-Q3V_IwK310htw80Y2U3fo40huRac,13678
99
- spiral/server.py,sha256=ztBmB5lBnUz-smQxR_tC8AI5SOhz17wH0MI3GuzDUdM,600
100
- spiral/settings.py,sha256=Ae17TH_1kKiyDo6ePjPMeLfbG2pydKSQPRuJbVkro6E,938
101
- spiral/snapshot.py,sha256=cTobi5jtiANxalGA-isokQHblNmXGtuUvgUGGNVybsI,1555
106
+ spiral/ray_.py,sha256=iEpkEHu6V2ATtX1KHrRDF9R6dsiazHwltr5GUF35Svg,2749
107
+ spiral/scan.py,sha256=c-lLaoLydnGV5ydYgS8SFdFz0qIbiWnTVhn7QpU1Xdc,20003
108
+ spiral/server.py,sha256=Q1FcOAV0EsDespdBJI25R5K2mihP_i1xNRe99SYUhsY,1401
109
+ spiral/settings.py,sha256=zeiEhWC2H94r3o5-jsOqSIHu-hthRYO48ShWnXvGrQ8,895
110
+ spiral/snapshot.py,sha256=nf0ywmFy1Z2v6NCDBKBzfwmht5nGqWv2V_BifP_Q6Ag,1995
102
111
  spiral/streaming_/__init__.py,sha256=s7MlW2ERsuZmZGExLFL6RcZon2e0tNBocBg5ANgki7k,61
103
112
  spiral/streaming_/reader.py,sha256=tl_lC9xgh1-QFhsZn4xQT7It3PVTzHCEUT2BG2dWBRQ,4166
104
113
  spiral/streaming_/stream.py,sha256=efqhExky4YgI1f3Me5ctfayFbTExoyS3TRMkrPIjvv0,5918
105
114
  spiral/substrait_.py,sha256=AKeOD4KIXvz2J4TYxnIneOiHddtBIyOhuNxVO_uH0eg,12592
106
- spiral/table.py,sha256=B8QWhPbpXP46Q6qJY-LQCLKI9l7EwK7Ceja9yqG_5zg,7630
115
+ spiral/table.py,sha256=qnppfbien_Ytogr9eQuK9pOtgRnV6iWgHw_ynvZtOno,8038
107
116
  spiral/text_index.py,sha256=FQ9rgIEGLSJryS9lFdMhKtPFey18BXoWbPXyvZPJJ04,442
108
- spiral/transaction.py,sha256=12i3qSYgTgra95I2SChbBbL__sQtkshx3Y5Aivyfo2M,5009
117
+ spiral/transaction.py,sha256=EEz_1VnFojr0D553TF8_YntZE0UWlWvxq6C31P6ab_M,7866
109
118
  spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
110
- pyspiral-0.8.1.dist-info/RECORD,,
119
+ pyspiral-0.9.8.dist-info/RECORD,,
spiral/__init__.py CHANGED
@@ -22,7 +22,7 @@ from spiral.scan import Scan # noqa: E402
22
22
  from spiral.snapshot import Snapshot # noqa: E402
23
23
  from spiral.table import Table # noqa: E402
24
24
  from spiral.text_index import TextIndex # noqa: E402
25
- from spiral.transaction import Transaction # noqa: E402
25
+ from spiral.transaction import Transaction, TransactionOps # noqa: E402
26
26
 
27
27
  __all__ = [
28
28
  "Spiral",
@@ -30,6 +30,7 @@ __all__ = [
30
30
  "Table",
31
31
  "Snapshot",
32
32
  "Transaction",
33
+ "TransactionOps",
33
34
  "Enrichment",
34
35
  "Scan",
35
36
  "Shard",
@@ -41,7 +42,7 @@ __all__ = [
41
42
  "Iceberg",
42
43
  ]
43
44
 
44
- __version__ = importlib.metadata.version("pyspiral")
45
+ __version__: str = importlib.metadata.version("pyspiral")
45
46
 
46
47
 
47
48
  def _warn_msg():
spiral/_lib.abi3.so CHANGED
Binary file
spiral/adbc.py CHANGED
@@ -247,7 +247,7 @@ class SpiralADBCServer(ADBCServerBase):
247
247
 
248
248
  if req.include_schema:
249
249
  open_table = project.table(f"{table.dataset}.{table.table}")
250
- row["table_schema"] = open_table.snapshot().to_dataset().schema.serialize().to_pybytes()
250
+ row["table_schema"] = open_table.snapshot().to_arrow_dataset().schema.serialize().to_pybytes()
251
251
 
252
252
  rows.append(row)
253
253
 
@@ -267,7 +267,7 @@ class SpiralADBCServer(ADBCServerBase):
267
267
  # This lets us register a PyArrow Dataset with DuckDB for the query.
268
268
  snapshot = self.open_snapshot(tbl)
269
269
  name = snapshot.table.table_id
270
- datasets[name] = snapshot.to_dataset()
270
+ datasets[name] = snapshot.to_arrow_dataset()
271
271
  tbl.replace(exp.table_(table=name))
272
272
 
273
273
  try:
spiral/api/__init__.py CHANGED
@@ -9,22 +9,23 @@ if TYPE_CHECKING:
9
9
  from spiral.core.authn import Authn
10
10
 
11
11
  from .admin import AdminService
12
- from .filesystems import FileSystemService
12
+ from .filesystems import FileSystemsService
13
13
  from .key_space_indexes import KeySpaceIndexesService
14
- from .organizations import OrganizationService
15
- from .projects import ProjectService
14
+ from .organizations import OrganizationsService
15
+ from .projects import ProjectsService
16
+ from .tables import TablesService
16
17
  from .telemetry import TelemetryService
17
18
  from .text_indexes import TextIndexesService
18
- from .workloads import WorkloadService
19
+ from .workloads import WorkloadsService
19
20
 
20
21
 
21
22
  class SpiralAPI:
22
- def __init__(self, authn: "Authn", base_url: str | None = None):
23
- self.base_url = base_url or os.environ.get("SPIRAL_URL", "https://api.spiraldb.com")
23
+ def __init__(self, base_url: str, authn: "Authn"):
24
+ self.base_url = base_url
24
25
  self.client = _Client(
25
26
  httpx.Client(
26
27
  base_url=self.base_url,
27
- timeout=None if ("PYTEST_VERSION" in os.environ or bool(os.environ.get("SPIRAL_DEV", None))) else 60,
28
+ timeout=None if "PYTEST_VERSION" in os.environ else 60,
28
29
  ),
29
30
  authn,
30
31
  )
@@ -36,28 +37,34 @@ class SpiralAPI:
36
37
  return AdminService(self.client)
37
38
 
38
39
  @property
39
- def organization(self) -> "OrganizationService":
40
- from .organizations import OrganizationService
40
+ def organizations(self) -> "OrganizationsService":
41
+ from .organizations import OrganizationsService
41
42
 
42
- return OrganizationService(self.client)
43
+ return OrganizationsService(self.client)
43
44
 
44
45
  @property
45
- def project(self) -> "ProjectService":
46
- from .projects import ProjectService
46
+ def projects(self) -> "ProjectsService":
47
+ from .projects import ProjectsService
47
48
 
48
- return ProjectService(self.client)
49
+ return ProjectsService(self.client)
49
50
 
50
51
  @property
51
- def file_system(self) -> "FileSystemService":
52
- from .filesystems import FileSystemService
52
+ def file_systems(self) -> "FileSystemsService":
53
+ from .filesystems import FileSystemsService
53
54
 
54
- return FileSystemService(self.client)
55
+ return FileSystemsService(self.client)
55
56
 
56
57
  @property
57
- def workload(self) -> "WorkloadService":
58
- from .workloads import WorkloadService
58
+ def workloads(self) -> "WorkloadsService":
59
+ from .workloads import WorkloadsService
59
60
 
60
- return WorkloadService(self.client)
61
+ return WorkloadsService(self.client)
62
+
63
+ @property
64
+ def tables(self) -> "TablesService":
65
+ from .tables import TablesService
66
+
67
+ return TablesService(self.client)
61
68
 
62
69
  @property
63
70
  def text_indexes(self) -> "TextIndexesService":
spiral/api/client.py CHANGED
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import time
4
5
  from collections.abc import Iterable, Iterator, Mapping
6
+ from datetime import UTC, datetime
7
+ from email.utils import parsedate_to_datetime
5
8
  from typing import Any, Generic, TypeVar
6
9
 
7
10
  import httpx
@@ -35,7 +38,7 @@ class Paged(Iterable[E], Generic[E]):
35
38
  client: _Client,
36
39
  path: str,
37
40
  page_token: str | None,
38
- page_size: int,
41
+ page_size: int | None,
39
42
  response_cls: type[PagedResponse[E]],
40
43
  params: Mapping[str, str] | None = None,
41
44
  ):
@@ -48,9 +51,8 @@ class Paged(Iterable[E], Generic[E]):
48
51
  self._params = params or {}
49
52
  if page_token is not None:
50
53
  self._params["page_token"] = str(page_token)
51
- # TODO(marko): Support paging.
52
- # if page_size is not None:
53
- # self._params["page_size"] = str(page_size)
54
+ if page_size is not None:
55
+ self._params["page_size"] = str(page_size)
54
56
 
55
57
  self._response: PagedResponse[E] = client.get(path, response_cls, params=self._params)
56
58
 
@@ -58,6 +60,13 @@ class Paged(Iterable[E], Generic[E]):
58
60
  def page(self) -> PagedResponse[E]:
59
61
  return self._response
60
62
 
63
+ def _fetch_next_page(self):
64
+ assert self._response.next_page_token
65
+
66
+ params = self._params.copy()
67
+ params["page_token"] = self._response.next_page_token
68
+ self._response = self._client.get(self._path, self._response_cls, params=params)
69
+
61
70
  def __iter__(self) -> Iterator[E]:
62
71
  while True:
63
72
  yield from self._response.items
@@ -65,9 +74,7 @@ class Paged(Iterable[E], Generic[E]):
65
74
  if self._response.next_page_token is None:
66
75
  break
67
76
 
68
- params = self._params.copy()
69
- params["page_token"] = self._response.next_page_token
70
- self._response = self._client.get(self._path, self._response_cls, params=params)
77
+ self._fetch_next_page()
71
78
 
72
79
 
73
80
  class ServiceBase:
@@ -90,6 +97,73 @@ class _Client:
90
97
  self.http = http
91
98
  self.authn = authn
92
99
 
100
+ def _handle_deprecation(self, response: httpx.Response, path: str) -> None:
101
+ """Handle deprecation headers from API responses.
102
+
103
+ - Logs warnings if the endpoint is deprecated
104
+ - Sleeps progressively longer as sunset date approaches
105
+ - Logs errors if past the sunset date
106
+ """
107
+ deprecation_header = response.headers.get("Deprecation")
108
+ sunset_header = response.headers.get("Sunset")
109
+
110
+ if not deprecation_header:
111
+ return
112
+
113
+ try:
114
+ deprecation_date = parsedate_to_datetime(deprecation_header)
115
+ sunset_date = parsedate_to_datetime(sunset_header) if sunset_header else None
116
+ except (ValueError, TypeError):
117
+ log.warning("Failed to parse deprecation headers for path %s", path)
118
+ return
119
+
120
+ sunset_str = sunset_date.isoformat() if sunset_date else "unknown"
121
+ log.warning(
122
+ "SpiralDB is using a deprecated API endpoint, please migrate to a supported version "
123
+ "(path=%s, deprecation_date=%s, sunset_date=%s)",
124
+ path,
125
+ deprecation_date.isoformat(),
126
+ sunset_str,
127
+ )
128
+
129
+ if sunset_date:
130
+ now = datetime.now(UTC)
131
+
132
+ if now > sunset_date:
133
+ # Past sunset date - log error and use maximum sleep
134
+ days_past_sunset = (now - sunset_date).days
135
+ log.error(
136
+ "SpiralDB API endpoint has been sunset, please migrate to a supported version "
137
+ "(path=%s, sunset_date=%s, days_past_sunset=%d)",
138
+ path,
139
+ sunset_date.isoformat(),
140
+ days_past_sunset,
141
+ )
142
+ sleep_ms = 5000 # Max sleep after sunset
143
+ else:
144
+ # Before sunset - calculate progressive sleep
145
+ time_until_sunset = (sunset_date - now).total_seconds()
146
+ time_since_deprecation = (now - deprecation_date).total_seconds()
147
+ total_deprecation_window = max((sunset_date - deprecation_date).total_seconds(), 1.0)
148
+
149
+ # Calculate progress: 0.0 (just deprecated) to 1.0 (at sunset)
150
+ progress = max(0.0, min(1.0, time_since_deprecation / total_deprecation_window))
151
+
152
+ # Exponential backoff: 0ms → 5000ms as we approach sunset
153
+ sleep_ms = int((progress**2) * 5000.0)
154
+
155
+ if sleep_ms > 0:
156
+ days_until_sunset = int(time_until_sunset / 86400) + 1
157
+ log.warning(
158
+ "Sleeping due to deprecated endpoint usage (path=%s, sleep_ms=%d, days_until_sunset=%d)",
159
+ path,
160
+ sleep_ms,
161
+ days_until_sunset,
162
+ )
163
+
164
+ if sleep_ms > 0:
165
+ time.sleep(sleep_ms / 1000.0)
166
+
93
167
  def get(
94
168
  self, path: str, response_cls: type[ResponseT], *, params: Mapping[str, str | list[str]] | None = None
95
169
  ) -> ResponseT:
@@ -142,6 +216,9 @@ class _Client:
142
216
  **req_data,
143
217
  )
144
218
 
219
+ # Handle deprecation headers before processing response
220
+ self._handle_deprecation(resp, path)
221
+
145
222
  try:
146
223
  resp.raise_for_status()
147
224
  except HTTPStatusError as e:
@@ -149,7 +226,6 @@ class _Client:
149
226
  raise SpiralHTTPError(body=resp.text, code=resp.status_code) from e
150
227
 
151
228
  if response_cls == type[None]:
152
- assert resp.text == ""
153
229
  return None
154
230
 
155
231
  return TypeAdapter(response_cls).validate_python(resp.json())
@@ -160,7 +236,8 @@ class _Client:
160
236
  response_cls: type[PagedResponse[E]],
161
237
  *,
162
238
  page_token: str | None = None,
163
- page_size: int = 50,
239
+ page_size: int | None = None,
164
240
  params: Mapping[str, str] | None = None,
165
241
  ) -> Paged[E]:
242
+ # TODO(DK): When paging is uniformly supported, set a default page size *here* rather than in the callers.
166
243
  return Paged(self, path, page_token, page_size, response_cls, params)
spiral/api/filesystems.py CHANGED
@@ -40,16 +40,33 @@ class UpstreamFileSystem(BaseModel):
40
40
 
41
41
 
42
42
  class S3FileSystem(BaseModel):
43
- """File system backed by an S3-compatible bucket."""
43
+ """File system backed by an AWS S3 bucket."""
44
44
 
45
45
  type: Literal["s3"] = "s3"
46
46
  endpoint: str | None = None
47
47
  region: str
48
48
  bucket: str
49
49
  directory: DirectoryPath | None = None
50
- # ARN of the role to assume when accessing the bucket https://docs.spiraldb.com/filesystems#aws
51
- # role_arn: str | None = None
52
- role_arn: str # TODO(marko): Make optional once we support third-party S3-compatible storage.
50
+ # ARN of the role to assume when accessing the bucket
51
+ role_arn: str
52
+
53
+
54
+ class VaultSecret(BaseModel):
55
+ type: Literal["secret"] = "secret"
56
+ # Reference to a secret stored in vault
57
+ secret: str
58
+
59
+
60
+ class S3LikeFileSystem(BaseModel):
61
+ """File system backed by an AWS S3 bucket that is compatible with S3 APIs."""
62
+
63
+ type: Literal["s3like"] = "s3like"
64
+ endpoint: str
65
+ region: str
66
+ bucket: str
67
+ directory: DirectoryPath | None = None
68
+ access_key_id: str
69
+ secret_access_key: str | VaultSecret
53
70
 
54
71
 
55
72
  class GCSFileSystem(BaseModel):
@@ -62,7 +79,8 @@ class GCSFileSystem(BaseModel):
62
79
 
63
80
 
64
81
  FileSystem = Annotated[
65
- BuiltinFileSystem | UpstreamFileSystem | S3FileSystem | GCSFileSystem, Field(discriminator="type")
82
+ BuiltinFileSystem | UpstreamFileSystem | S3FileSystem | S3LikeFileSystem | GCSFileSystem,
83
+ Field(discriminator="type"),
66
84
  ]
67
85
 
68
86
 
@@ -97,7 +115,7 @@ class GetMountAndFileSystemResponse(BaseModel):
97
115
  fs_loc: FsLoc
98
116
 
99
117
 
100
- class FileSystemService(ServiceBase):
118
+ class FileSystemsService(ServiceBase):
101
119
  """Service for file system operations."""
102
120
 
103
121
  def list_providers(self) -> list[str]:
@@ -37,6 +37,7 @@ class PortalLinkIntent(str, Enum):
37
37
  AUDIT_LOGS = "audit-logs"
38
38
  LOG_STREAMS = "log-streams"
39
39
  DOMAIN_VERIFICATION = "domain-verification"
40
+ BYOK = "bring-your-own-key"
40
41
 
41
42
 
42
43
  class PortalLinkRequest(BaseModel):
@@ -57,7 +58,7 @@ class InviteUserResponse(BaseModel):
57
58
  invite_id: str
58
59
 
59
60
 
60
- class OrganizationService(ServiceBase):
61
+ class OrganizationsService(ServiceBase):
61
62
  """Service for organization operations."""
62
63
 
63
64
  def create(self, request: CreateOrgRequest) -> CreateOrgResponse:
spiral/api/projects.py CHANGED
@@ -151,7 +151,7 @@ class KeySpaceIndexResource(BaseModel):
151
151
  name: str
152
152
 
153
153
 
154
- class ProjectService(ServiceBase):
154
+ class ProjectsService(ServiceBase):
155
155
  """Service for project operations."""
156
156
 
157
157
  def create(self, request: CreateProjectRequest) -> CreateProjectResponse:
@@ -163,7 +163,7 @@ class ProjectService(ServiceBase):
163
163
  return self.client.paged("/v1/projects", PagedResponse[Project])
164
164
 
165
165
  def list_tables(
166
- self, project_id: ProjectId, dataset: str | None = None, table: str | None = None
166
+ self, project_id: ProjectId, dataset: str | None = None, table: str | None = None, page_size: int | None = None
167
167
  ) -> Paged[TableResource]:
168
168
  """List tables in a project."""
169
169
  params = {}
@@ -171,7 +171,9 @@ class ProjectService(ServiceBase):
171
171
  params["dataset"] = dataset
172
172
  if table:
173
173
  params["table"] = table
174
- return self.client.paged(f"/v1/projects/{project_id}/tables", PagedResponse[TableResource], params=params)
174
+ return self.client.paged(
175
+ f"/v1/projects/{project_id}/tables", PagedResponse[TableResource], params=params, page_size=page_size
176
+ )
175
177
 
176
178
  def list_text_indexes(self, project_id: ProjectId, name: str | None = None) -> Paged[TextIndexResource]:
177
179
  """List text indexes in a project."""
spiral/api/tables.py ADDED
@@ -0,0 +1,77 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from ..types_ import Timestamp
6
+ from .client import _Client
7
+
8
+
9
+ class Transaction(BaseModel):
10
+ """Represents a committed transaction in SpiralDB."""
11
+
12
+ txn_idx: int
13
+ committed_at: Timestamp
14
+ # TODO(marko): Define a proper Operation model
15
+ operations: list[dict[str, Any]]
16
+
17
+
18
+ class TransactionsListResponse(BaseModel):
19
+ """Response for listing transactions."""
20
+
21
+ items: list[Transaction]
22
+ next_page_token: Timestamp | None = None
23
+
24
+
25
+ class TablesService:
26
+ """Service for managing table transactions."""
27
+
28
+ def __init__(self, client: _Client):
29
+ self.client = client
30
+
31
+ def list_transactions(
32
+ self,
33
+ table_id: str,
34
+ *,
35
+ since: Timestamp | None = None,
36
+ ) -> list[Transaction]:
37
+ """List transactions for a table.
38
+
39
+ Args:
40
+ table_id: The ID of the table
41
+ since: Only return transactions committed after this timestamp (microseconds since epoch)
42
+
43
+ Returns:
44
+ List of transactions
45
+ """
46
+ params = {"ordering": "asc"}
47
+ if since is not None:
48
+ params["page_token"] = str(since)
49
+
50
+ all_transactions = []
51
+
52
+ while True:
53
+ response = self.client.get(
54
+ f"/v1/tables/{table_id}/transactions-list",
55
+ TransactionsListResponse,
56
+ params=params,
57
+ )
58
+
59
+ # Parse transactions from the API response
60
+ all_transactions.extend(response.items)
61
+
62
+ # Check for next page
63
+ if response.next_page_token is None:
64
+ break
65
+
66
+ params["page_token"] = str(response.next_page_token)
67
+
68
+ return all_transactions
69
+
70
+ def revert_transaction(self, table_id: str, txn_idx: int) -> None:
71
+ """Revert a transaction by marking it as reverted.
72
+
73
+ Args:
74
+ table_id: The ID of the table
75
+ txn_idx: The index of the transaction to revert
76
+ """
77
+ self.client.delete(f"/v1/tables/{table_id}/transactions/{txn_idx}", type[None])