pyspiral 0.6.3__cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.6.5__cp310-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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyspiral
3
- Version: 0.6.3
3
+ Version: 0.6.5
4
4
  Classifier: Intended Audience :: Science/Research
5
5
  Classifier: Operating System :: OS Independent
6
6
  Classifier: Programming Language :: Python
@@ -12,7 +12,7 @@ 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: betterproto2>=0.8.0
15
+ Requires-Dist: betterproto2>=0.9.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
@@ -21,7 +21,7 @@ Requires-Dist: nanoid>=2.0.0
21
21
  Requires-Dist: numpy>=2
22
22
  Requires-Dist: pyarrow>=21.0.0
23
23
  Requires-Dist: pydantic-settings>=2.3.4
24
- Requires-Dist: pydantic[email]>=2.5.3
24
+ Requires-Dist: pydantic[email]>=2.5.3,<2.12
25
25
  Requires-Dist: pyjwt[crypto]>=2.9.0
26
26
  Requires-Dist: pyperclip>=1.9.0
27
27
  Requires-Dist: questionary>=2.0.1
@@ -1,16 +1,16 @@
1
- pyspiral-0.6.3.dist-info/METADATA,sha256=93TxmaIrXRNq5xlBQAx76ClIGjWdnh9bfL_f6KrB9K0,1836
2
- pyspiral-0.6.3.dist-info/WHEEL,sha256=PxcKzGLVtZeSnGJDErQ-Emkn2AvBXbmzIogfnaf7-q8,130
3
- pyspiral-0.6.3.dist-info/entry_points.txt,sha256=uft7u-a6g40NLt4Q6BleWbK4NY0M8nZuYPpP8DV0EOk,45
1
+ pyspiral-0.6.5.dist-info/METADATA,sha256=3oqXIVdsaDE1PmfUgS5M4zyoSiVWlcS9718MClPjQQw,1842
2
+ pyspiral-0.6.5.dist-info/WHEEL,sha256=sHl2MPySRQtLBS4t9I9tl1bAeFFBhTGABHYdwnegkVM,130
3
+ pyspiral-0.6.5.dist-info/entry_points.txt,sha256=uft7u-a6g40NLt4Q6BleWbK4NY0M8nZuYPpP8DV0EOk,45
4
4
  spiral/__init__.py,sha256=5c0faqg-kHZBDwriQ7LzLAMcFolIucp-IA1EzNvCZ3k,711
5
- spiral/_lib.abi3.so,sha256=01snL6F8rV5RN7cEd8qp0jEukg_M2887lIfA1K2W-l0,55696760
5
+ spiral/_lib.abi3.so,sha256=XQ5Tfs9s0f7TrMkrc5It8n6C__b_AWLgfCf-cpL5K-s,57866344
6
6
  spiral/adbc.py,sha256=7IxfWIeQN-fh0W5OdN_PP2x3pzQYg6ZUOLsHg3jktqw,14842
7
7
  spiral/api/__init__.py,sha256=ULBlVq3PnfNOO6T5naE_ULmmii-83--qTuN2PpAUQN0,2241
8
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
9
+ spiral/api/client.py,sha256=xGc3RKRrerDGGt3QA7Y_friEa-OkZXSQI2Yd1KeFDdw,4668
10
+ spiral/api/filesystems.py,sha256=yEHgHfo7t1_becm0UFedc3nd49_G77hHjYwtYQ6P9XU,4240
11
11
  spiral/api/key_space_indexes.py,sha256=-38rZXTdkL4mLhp9h3CtqyIyutzzq88tV6bhK05MqYE,640
12
12
  spiral/api/organizations.py,sha256=B-8zZ7lFJANGK7dUNbo_aU-cgI959JBP9VcWb6wdgi0,1895
13
- spiral/api/projects.py,sha256=62Y1lqI_TpUh3WKQqrjbLWJHiZsI_X3g8u2RTbUwkoA,6162
13
+ spiral/api/projects.py,sha256=1JC7VjqZJfwR6zfhBZr3OCwaf6zb-MXMOBTE_NztmcE,6356
14
14
  spiral/api/telemetry.py,sha256=tfdA3E_EWJwFVxkQfkm8tiYGRubnx2LuE5nbfsk1oG4,474
15
15
  spiral/api/text_indexes.py,sha256=_zVlGBytl-9-Unbu2POfZgLh40H1YRcagFtplgIG428,1828
16
16
  spiral/api/types.py,sha256=lGdiKViRgIEJXD2ubwnyEIEwHkfRumlZjVEaHMV3Tm8,682
@@ -20,37 +20,38 @@ spiral/arrow_.py,sha256=T1LZ7bh9aMDbXfpUsf0dR0E1roTQyAYSgZ2mL4s8J_4,7681
20
20
  spiral/cli/__init__.py,sha256=LutjpWZu5Rvmba8C8bPa5vOCv74JuAoE1kvz0nd48dE,2476
21
21
  spiral/cli/__main__.py,sha256=kNaKM2xgJo7GRogf83nYldLM-RGUR6vymdGwZxywQu0,71
22
22
  spiral/cli/admin.py,sha256=-ubYqs8nKjnQStbQ68jpWx_9xh0TsaxI0wM1Hfko8_U,319
23
- spiral/cli/app.py,sha256=HWCjMJLzSz_JaiLF046jzC9A4-yvzS6506D3cOR2Vgc,1773
23
+ spiral/cli/app.py,sha256=lv37s8nvptxrJuloe9W603Oz5-1n5_BPzbbKdIvBkb4,2759
24
24
  spiral/cli/console.py,sha256=6JHbAQV6MFWz3P-VzqPOjhHpkIQagsCdzTMvmuDKMkU,2580
25
- spiral/cli/fs.py,sha256=UREIJhjr6MfIdcKK7pjUKICd0wsQULhQiWRVWUnQ0dc,4376
25
+ spiral/cli/fs.py,sha256=vaPcSc2YghhHeipxNitIdsHaBhFwlwkvPFqYsFSN9P0,2927
26
26
  spiral/cli/iceberg.py,sha256=Q14tcGcn1LixbFCYP0GhfYwFFXTmmi8tqBPYwalJEyE,3248
27
27
  spiral/cli/key_spaces.py,sha256=x3IFRP5d47pKiAHeWExYMOBaT2TwxbWjVM01SUqKrwI,2943
28
- spiral/cli/login.py,sha256=TgTr37ImgG1NKN8VbtqkxVAYaZFpMXMwPAb23gVldEw,649
28
+ spiral/cli/login.py,sha256=2tw6uN5rEpiMMAmjQSB3-JUPf3C0Wc1eTGCDxhYtJps,731
29
29
  spiral/cli/orgs.py,sha256=fmOuLxpeIFfKqePRi292Gv9k-EF5pPn_tbKd2BLl2Ig,2869
30
30
  spiral/cli/printer.py,sha256=HcvSUpaMItzmhBUfIHROK1Z3SL8J8wDopS3Qo8H00uw,1781
31
- spiral/cli/projects.py,sha256=UYrBlLcFacuXExdLX1sZByfvkz9MRtk_0oRAZvqHa0w,5105
31
+ spiral/cli/projects.py,sha256=1M1nGrBT-t0aY9RV5Cnmzy7YrhIvmHwdkpa3y9j8rG8,5756
32
32
  spiral/cli/state.py,sha256=10wTIVQ0SJkY67Z6-KQ1LFlt3aVIPmZhoHFdTwp4kNA,130
33
33
  spiral/cli/tables.py,sha256=48lZ0wPQSCTul1vn-Qx6Be5eGnw75Abtw2zxMK9dCPg,4613
34
34
  spiral/cli/telemetry.py,sha256=Uxo1Q1FkKJ6n6QNGOUmL3j_pRRWRx0qWIhoP-U9BuR0,589
35
35
  spiral/cli/text.py,sha256=DlWGe4JrkdERAiqyITNpk91Wqb63Re99rNYlIFsIamc,4031
36
36
  spiral/cli/types.py,sha256=XYzo1GgX7dBBItoBSrHI4vO5C2lLmS2sktb-2GnGH3E,1362
37
37
  spiral/cli/workloads.py,sha256=2_SLfQTFN6y73R9H0i9dk8VIOVagKxSxOpHXC56yptY,2015
38
- spiral/client.py,sha256=Po9xgCH3NwVsCeRZMm3eJUPV77Rknyj-9MfCS1TbdTg,6623
38
+ spiral/client.py,sha256=ANKzL6yQ-7X4Livq4xCZsWVdQSU3j1J7gSJv6dzeaJs,6630
39
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=iEhZgbySG5LScfrtkiiHW1iHghgehsrVmPP-v5Pv_vk,5740
42
- spiral/core/table/__init__.pyi,sha256=sjjShdgM_Uh8Roou1k02MnrqYpdAX4QuyRlIRlnyp1M,3073
40
+ spiral/core/_tools/__init__.pyi,sha256=b2KLfTOQ67pjfbYt07o0IGiTu5o2bZw69lllV8v0Dps,143
41
+ spiral/core/authn/__init__.pyi,sha256=z_GWyIS62fuiYQrYO8hzw4W8oGaiciqS1u5qtAt54VY,769
42
+ spiral/core/client/__init__.pyi,sha256=wEZi15Hf7Jg-Qo1N-k97ss1YqwSQehuoZVqPxj_4EvA,6126
43
+ spiral/core/table/__init__.pyi,sha256=ajxO2N92hTQ4evsl7QBWB8ivz-cDNxXnAv0jytRw0ZY,3183
43
44
  spiral/core/table/manifests/__init__.pyi,sha256=eVfDpmhYSjafIvvALqAkZe5baN3Y1HpKpxYEbjwd4gQ,1043
44
45
  spiral/core/table/metastore/__init__.pyi,sha256=rc3u9MwEKRvL2kxOc8lBorddFRnM8o_o1frqtae86a4,1697
45
46
  spiral/core/table/spec/__init__.pyi,sha256=0NyGeyEhV_ebwKWVU3sqSvdF2D9v8kEVwo6wYAHF99M,5579
46
47
  spiral/dataset.py,sha256=NNqG-oOrhbmNC2OMZ9AYAm4YkwwBozeRI6zXtz4cspA,8008
47
48
  spiral/datetime_.py,sha256=1TA1RYIRU22qcUuipIjVhAtGnPDVn2z9WttuhkmfkwY,964
48
49
  spiral/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- spiral/debug/manifests.py,sha256=oaPB4534pQdqvPXCZetVNSvvhpdXTrv_1pN-_bAkeAo,2893
50
+ spiral/debug/manifests.py,sha256=7f1O3ba9mrA5nXpOF9cEIQuUAteP5wiBkFy_diQJ7No,3216
50
51
  spiral/debug/metrics.py,sha256=XdRDcjggtsLNGCAjam6IxG9072pz_d2C8iLApNRFUtk,2044
51
52
  spiral/debug/scan.py,sha256=UEm_aRnql5pwDPTpZgakMLNjlzkKL4RurBFFqH_BLAQ,9526
52
- spiral/expressions/__init__.py,sha256=T8PIb0_UB9kynK0dpWbUD4No5lKRTG-wKnao8xOcXjY,6381
53
- spiral/expressions/base.py,sha256=OOUDrbkLBE0lSkAmM-6FP2F2N8zhN_in3S_UDrWLDeQ,4805
53
+ spiral/expressions/__init__.py,sha256=QQrnKrrWnDk7W5I9yhPS21ME0cUkEou30hga8MVkt1I,6396
54
+ spiral/expressions/base.py,sha256=4qlXbi4IusZi5b4QEadWhXtmuYd0ETzOB1NWMWYIsTs,5163
54
55
  spiral/expressions/http.py,sha256=begUydWoFHEqjeLkATvI_v66Ez6_rR-OQBWO5cHbb9c,2742
55
56
  spiral/expressions/io.py,sha256=gJ2a0FKMmdxarWKENulPRwH7KDvSJTIh_OUxX306xAM,3045
56
57
  spiral/expressions/list_.py,sha256=MMt5lf5H1M3O-x6N_PvqOLGq9NOk6Ukv0fPWwPC_uy4,1809
@@ -65,35 +66,36 @@ spiral/expressions/tiff.py,sha256=fQwIn0kLFBM2Y3YYIHmTgb_EIRHKT2fNc77nioDQQw4,80
65
66
  spiral/expressions/udf.py,sha256=yb9MIcrFftpNDxgBF228cvdv6TY-hEFikYz2fq_nzWo,1353
66
67
  spiral/grpc_.py,sha256=f3czdP1Mxme42Y5--a5ogYq1TTiWn-J_MlGjwJ2mWwM,1015
67
68
  spiral/iceberg.py,sha256=JGq62Qnf296r9_hRAoH85GQq45-uSBjwXWw_CvPi6G4,930
69
+ spiral/iterable_dataset.py,sha256=Eekg9ad8tcwXcloHWReBbvCSr5ZappRHn2ldKTvwqS0,4622
68
70
  spiral/key_space_index.py,sha256=NAB_nONEjpMYbse8suz42w7Qb5OPHuKN9h9CT2NJe08,1460
69
71
  spiral/project.py,sha256=CO_Pn6vPqaonNvRdCNRFcBWr4TqO2AsAUTH5xawIeCE,7283
70
72
  spiral/protogen/_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
73
  spiral/protogen/_/arrow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
74
  spiral/protogen/_/arrow/flight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
75
  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
76
+ spiral/protogen/_/arrow/flight/protocol/sql/__init__.py,sha256=ooZZsDCRFpktUCH11OdxMRa_GLQYnY9w-1fBr5a7vBk,90023
75
77
  spiral/protogen/_/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
- spiral/protogen/_/google/protobuf/__init__.py,sha256=lsfhHEPczGOxrOmkstAqh64V0Kt8hQE_6N0tIpc27HU,75116
78
+ spiral/protogen/_/google/protobuf/__init__.py,sha256=H0FVEXusqww2j5dl7Ee05tR6qMG_hQioUp1qFfDgnco,80036
77
79
  spiral/protogen/_/message_pool.py,sha256=4-cRhhiM6bmfpUJZ8qxc8LEyqHBHpLCcotjbyZxl7JM,71
78
80
  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
81
+ spiral/protogen/_/scandal/__init__.py,sha256=liUQAICLd2sPccCmqo0_c1duSbNj_m8p_IgmdnHsB3E,4965
82
+ spiral/protogen/_/spfs/__init__.py,sha256=zMMEDIfPXQNBkisLI-iMWbJABye-vK42Gf2BUQQYR_c,2028
83
+ spiral/protogen/_/spql/__init__.py,sha256=PEC4bI-PHdJ4Zd8Jb1k6Xk2iFYoYqIUbTGlL2JVGnT0,1548
84
+ spiral/protogen/_/substrait/__init__.py,sha256=-ngqHcYfio6s1B4M1_e1VsDymUcFK9qdM17ECA31qLw,209837
85
+ spiral/protogen/_/substrait/extensions/__init__.py,sha256=nhnEnho70GAT8WPj2xtwJUzk5GJ6X2e-HTvyk7emGsk,5326
84
86
  spiral/protogen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
87
  spiral/protogen/util.py,sha256=smnvVo6nYH3FfDm9jqhNLaXz4bbTBaQezHQDCTvZyiQ,1486
86
88
  spiral/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
- spiral/scan.py,sha256=20-NSGsoXYf6uKQ7yEdbbwT8ijIK7KxKTctycsl0AIk,7073
89
+ spiral/scan.py,sha256=SCDZ9UXA4g0Jq9BQ9Zt7cEK2NBq64Hqh_SttR4tF6jo,6252
88
90
  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/settings.py,sha256=JRQSwjJyNaCqQdQLxiqB_O_LZRQXMLyshJBrI2LZHwM,3113
92
+ spiral/snapshot.py,sha256=tYEIKYYqS9Eusb8rsrG46VD7fiNPA9yVOR5ajMMtT_g,2018
91
93
  spiral/streaming_/__init__.py,sha256=s7MlW2ERsuZmZGExLFL6RcZon2e0tNBocBg5ANgki7k,61
92
94
  spiral/streaming_/reader.py,sha256=Kpqknv2jn12jUhHOEEDArj0JZwrWb8XjoOGs9HrdVyA,4047
93
- spiral/streaming_/stream.py,sha256=nxJEisPfZ2-Ebkm83hz_3v8NH27FxBku-1jw7UDlQuM,5881
95
+ spiral/streaming_/stream.py,sha256=-prGp73h0XDsdKW0mAEamy4AXhd1oF5fBbNbbY1k2-A,5931
94
96
  spiral/substrait_.py,sha256=AKeOD4KIXvz2J4TYxnIneOiHddtBIyOhuNxVO_uH0eg,12592
95
- spiral/table.py,sha256=ZQFq5tuovDjQcpi38b5FUMuHNGI5XV0MnZbC6vbza1o,10312
97
+ spiral/table.py,sha256=iWoWm0XAcmW99NQXgEsdDWvq1Z9tUSd6t0kn4o1WmSo,9019
96
98
  spiral/text_index.py,sha256=FQ9rgIEGLSJryS9lFdMhKtPFey18BXoWbPXyvZPJJ04,442
97
- spiral/transaction.py,sha256=nSykH4UGs9hGtWuSWK9YyT9jfEuvzfkKoUgMM5Xt4zU,1841
99
+ spiral/transaction.py,sha256=rjoOQc9bvs1zAAhb8cLtJOOn2146UAMZjcSVcEXeNFY,1839
98
100
  spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
99
- pyspiral-0.6.3.dist-info/RECORD,,
101
+ pyspiral-0.6.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.9.4)
2
+ Generator: maturin (1.9.6)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64
spiral/_lib.abi3.so CHANGED
Binary file
spiral/api/client.py CHANGED
@@ -129,7 +129,7 @@ class _Client:
129
129
  ) -> ResponseT:
130
130
  req_data: dict[str, Any] = {}
131
131
  if req is not None:
132
- req_data = dict(json=TypeAdapter(req.__class__).dump_python(req, mode="json"))
132
+ req_data = dict(json=TypeAdapter(req.__class__).dump_python(req, mode="json", exclude_none=True))
133
133
 
134
134
  token = self.authn.token()
135
135
  resp = self.http.request(
spiral/api/filesystems.py CHANGED
@@ -43,10 +43,13 @@ class S3FileSystem(BaseModel):
43
43
  """File system backed by an S3-compatible bucket."""
44
44
 
45
45
  type: Literal["s3"] = "s3"
46
- endpoint: str = "https://s3.amazonaws.com"
47
- region: str = "auto"
46
+ endpoint: str | None = None
47
+ region: str
48
48
  bucket: str
49
- directory: DirectoryPath | None
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
53
 
51
54
 
52
55
  class GCSFileSystem(BaseModel):
@@ -55,7 +58,7 @@ class GCSFileSystem(BaseModel):
55
58
  type: Literal["gcs"] = "gcs"
56
59
  region: str
57
60
  bucket: str
58
- directory: DirectoryPath | None
61
+ directory: DirectoryPath | None = None
59
62
 
60
63
 
61
64
  FileSystem = Annotated[
@@ -78,40 +81,6 @@ class Mount(BaseModel):
78
81
  principal: str
79
82
 
80
83
 
81
- class AWSSecretAccessKey(BaseModel):
82
- """AWS secret access key credentials to be used with an S3 file system.
83
- The access key must have read/write access to the bucket specified in the file system.
84
- """
85
-
86
- access_key_id: str
87
- secret_access_key: str
88
-
89
-
90
- class UpdateS3FileSystem(S3FileSystem):
91
- credentials: AWSSecretAccessKey
92
-
93
-
94
- class GCPServiceAccount(BaseModel):
95
- """Google Cloud Platform service account credentials to be used with a GCS file system.
96
- The service account must have read/write access to the bucket specified in the file system.
97
- """
98
-
99
- service_account: str
100
-
101
-
102
- class UpdateGCSFileSystem(GCSFileSystem):
103
- credentials: GCPServiceAccount
104
-
105
-
106
- UpdateFileSystemRequest = Annotated[
107
- BuiltinFileSystem | UpstreamFileSystem | UpdateS3FileSystem | UpdateGCSFileSystem, Field(discriminator="type")
108
- ]
109
-
110
-
111
- class UpdateFileSystemResponse(BaseModel):
112
- file_system: FileSystem
113
-
114
-
115
84
  class CreateMountRequest(BaseModel):
116
85
  directory: DirectoryPath
117
86
  mode: Mode
@@ -136,9 +105,9 @@ class FileSystemService(ServiceBase):
136
105
  response = self.client.get("/v1/file-systems/builtin-providers", dict)
137
106
  return response.get("providers", [])
138
107
 
139
- def update_file_system(self, project_id: ProjectId, request: UpdateFileSystemRequest) -> UpdateFileSystemResponse:
108
+ def update_file_system(self, project_id: ProjectId, request: FileSystem) -> FileSystem:
140
109
  """Update project's default file system."""
141
- return self.client.post(f"/v1/file-systems/{project_id}", request, UpdateFileSystemResponse)
110
+ return self.client.post(f"/v1/file-systems/{project_id}", request, FileSystem)
142
111
 
143
112
  def get_file_system(self, project_id: ProjectId) -> FileSystem:
144
113
  """Get project's default file system."""
spiral/api/projects.py CHANGED
@@ -99,19 +99,26 @@ class ModalPrincipalConditions(BaseModel):
99
99
  conditions: ModalConditions | None = None
100
100
 
101
101
 
102
- class GCPPrincipalConditions(BaseModel):
102
+ class GcpServiceAccountPrincipalConditions(BaseModel):
103
103
  type: Literal["gcp"] = "gcp"
104
104
  service_account: str
105
105
  unique_id: str
106
106
 
107
107
 
108
+ class AwsAssumedRolePrincipalConditions(BaseModel):
109
+ type: Literal["aws"] = "aws"
110
+ account_id: str
111
+ role_name: str
112
+
113
+
108
114
  PrincipalConditions = Annotated[
109
115
  OrgRolePrincipalConditions
110
116
  | OrgUserPrincipalConditions
111
117
  | WorkloadPrincipalConditions
112
118
  | GitHubPrincipalConditions
113
119
  | ModalPrincipalConditions
114
- | GCPPrincipalConditions,
120
+ | GcpServiceAccountPrincipalConditions
121
+ | AwsAssumedRolePrincipalConditions,
115
122
  Field(discriminator="type"),
116
123
  ]
117
124
 
spiral/cli/app.py CHANGED
@@ -1,6 +1,10 @@
1
1
  import logging
2
2
  import os
3
+ from importlib import metadata
3
4
  from logging.handlers import RotatingFileHandler
5
+ from typing import Annotated
6
+
7
+ import typer
4
8
 
5
9
  from spiral.cli import (
6
10
  AsyncTyper,
@@ -18,16 +22,48 @@ from spiral.cli import (
18
22
  text,
19
23
  workloads,
20
24
  )
21
- from spiral.settings import LOG_DIR, Settings
25
+ from spiral.settings import LOG_DIR, PACKAGE_NAME, Settings
22
26
 
23
27
  app = AsyncTyper(name="spiral")
24
28
 
25
29
 
26
- @app.callback()
27
- def _callback(verbose: bool = False):
28
- if verbose:
30
+ def version_callback(ctx: typer.Context, value: bool):
31
+ """
32
+ Display the version of the Spiral CLI.
33
+ """
34
+ # True when generating completion, we can just return
35
+ if ctx.resilient_parsing:
36
+ return
37
+
38
+ if value:
39
+ ver = metadata.version(PACKAGE_NAME)
40
+ print(f"spiral {ver}")
41
+ raise typer.Exit()
42
+
43
+
44
+ def verbose_callback(ctx: typer.Context, value: bool):
45
+ """
46
+ Use more verbose output.
47
+ """
48
+ # True when generating completion, we can just return
49
+ if ctx.resilient_parsing:
50
+ return
51
+
52
+ if value:
29
53
  logging.getLogger().setLevel(level=logging.INFO)
30
54
 
55
+
56
+ @app.callback(invoke_without_command=True)
57
+ def _callback(
58
+ ctx: typer.Context,
59
+ version: Annotated[
60
+ bool | None,
61
+ typer.Option("--version", callback=version_callback, help=version_callback.__doc__, is_eager=True),
62
+ ] = None,
63
+ verbose: Annotated[
64
+ bool | None, typer.Option("--verbose", callback=verbose_callback, help=verbose_callback.__doc__)
65
+ ] = None,
66
+ ):
31
67
  # Load the settings (we reload in the callback to support testing under different env vars)
32
68
  state.settings = Settings()
33
69
 
@@ -42,12 +78,12 @@ app.add_typer(text.app, name="text")
42
78
  app.add_typer(telemetry.app, name="telemetry")
43
79
  app.command("console")(console.command)
44
80
  app.command("login")(login.command)
45
- app.command("whoami")(login.whoami)
46
81
 
47
82
  # Register unless we're building docs. Because Typer docs command does not skip hidden commands...
48
83
  if not bool(os.environ.get("SPIRAL_DOCS", False)):
49
- app.add_typer(workloads.app, name="workloads", hidden=True)
50
84
  app.add_typer(admin.app, name="admin", hidden=True)
85
+ app.add_typer(workloads.app, name="workloads", hidden=True)
86
+ app.command("whoami", hidden=True)(login.whoami)
51
87
  app.command("logout", hidden=True)(login.logout)
52
88
 
53
89
 
spiral/cli/fs.py CHANGED
@@ -1,15 +1,12 @@
1
- from typing import Annotated
1
+ from typing import Literal
2
2
 
3
3
  import questionary
4
- from pydantic import SecretStr
5
4
  from typer import Option
6
5
 
7
6
  from spiral.api.filesystems import (
8
- AWSSecretAccessKey,
9
7
  BuiltinFileSystem,
10
- GCPServiceAccount,
11
- UpdateGCSFileSystem,
12
- UpdateS3FileSystem,
8
+ GCSFileSystem,
9
+ S3FileSystem,
13
10
  UpstreamFileSystem,
14
11
  )
15
12
  from spiral.cli import CONSOLE, AsyncTyper, state
@@ -21,11 +18,7 @@ app = AsyncTyper(short_help="File Systems.")
21
18
  @app.command(help="Show the file system configured for project.")
22
19
  def show(project: ProjectArg):
23
20
  file_system = state.settings.api.file_system.get_file_system(project)
24
- match file_system:
25
- case BuiltinFileSystem(provider=provider):
26
- CONSOLE.print(f"provider: {provider}")
27
- case _:
28
- CONSOLE.print(file_system)
21
+ CONSOLE.print(file_system)
29
22
 
30
23
 
31
24
  def ask_provider():
@@ -33,76 +26,48 @@ def ask_provider():
33
26
  return questionary.select("Select a file system provider", choices=res).ask()
34
27
 
35
28
 
36
- BuiltinProviderOpt = Annotated[
37
- str,
38
- Option(help="Built-in provider to use for the file system.", show_default=False, default_factory=ask_provider),
39
- ]
40
-
41
-
42
29
  @app.command(help="Update a project's default file system.")
43
30
  def update(
44
31
  project: ProjectArg,
45
- builtin: bool = Option(False, help="Use a built-in file system provider."),
46
- upstream: bool = Option(
47
- False, help="Use another project as default file system. Only if another project is an external provider."
48
- ),
49
- s3: bool = Option(False, help="Use S3 compatible provider."),
50
- gcs: bool = Option(False, help="Use GCS provider."),
51
- provider: str = Option(None, help="Built-in provider to use for the file system."),
52
- endpoint: str = Option(None, help="Endpoint for S3 provider."),
53
- region: str = Option(None, help="Region for S3 or GCS provider. Required for GCS."),
54
- bucket: str = Option(None, help="Bucket name for S3 or GCS provider."),
55
- directory: str = Option(None, help="Directory for S3 or GCS provider."),
56
- access_key_id: str = Option(None, help="Access key ID for S3 provider. Required for S3."),
57
- secret_access_key: str = Option(None, help="Secret access key for S3 provider. Required for S3."),
58
- credentials_path: str = Option(
59
- None, help="Path to service account credentials file for GCS provider. Required for GCS."
32
+ type_: Literal["builtin", "s3", "gcs", "upstream"] = Option(None, "--type", help="Type of the file system."),
33
+ provider: str = Option(None, help="Provider, when using `builtin` type."),
34
+ endpoint: str = Option(None, help="Endpoint, when using `s3` type."),
35
+ region: str = Option(
36
+ None, help="Region, when using `s3` or `gcs` type (defaults to `auto` for `s3` when `endpoint` is set)."
60
37
  ),
38
+ bucket: str = Option(None, help="Bucket, when using `s3` or `gcs` type."),
39
+ role_arn: str = Option(None, help="Role ARN to assume, when using `s3` type."),
61
40
  ):
62
- if not any([builtin, s3, gcs, upstream]):
63
- raise ValueError("Must specify one of --builtin, --upstream, --s3, or --gcs.")
64
-
65
- if builtin:
41
+ if type_ == "builtin":
66
42
  provider = provider or ask_provider()
67
43
  file_system = BuiltinFileSystem(provider=provider)
68
44
 
69
- elif upstream:
45
+ elif type_ == "upstream":
70
46
  upstream_project = ask_project(title="Select a project to use as file system.")
71
47
  file_system = UpstreamFileSystem(project_id=upstream_project)
72
48
 
73
- elif s3:
74
- if access_key_id is None or secret_access_key is None:
75
- raise ValueError("--access-key-id and --secret-access-key are required for S3 provider.")
76
- credentials = AWSSecretAccessKey(access_key_id=access_key_id, secret_access_key=secret_access_key)
77
-
49
+ elif type_ == "s3":
50
+ if role_arn is None:
51
+ raise ValueError("--role-arn is required for S3 provider.")
52
+ if not role_arn.startswith("arn:aws:iam::") or ":role/" not in role_arn:
53
+ raise ValueError("Invalid role ARN format. Expected `arn:aws:iam::<account>:role/<role_name>`")
78
54
  if bucket is None:
79
55
  raise ValueError("--bucket is required for S3 provider.")
80
- file_system = UpdateS3FileSystem(bucket=bucket, credentials=credentials)
56
+ region = region or ("auto" if endpoint else None)
57
+ file_system = S3FileSystem(bucket=bucket, role_arn=role_arn, region=region)
81
58
  if endpoint:
82
59
  file_system.endpoint = endpoint
83
- if region:
84
- file_system.region = region
85
- if directory:
86
- file_system.directory = directory
87
-
88
- elif gcs:
89
- if credentials_path is None:
90
- raise ValueError("--credentials-path is required for GCS provider.")
91
- with open(credentials_path) as f:
92
- service_account = f.read()
93
- credentials = GCPServiceAccount(credentials=SecretStr(service_account))
94
60
 
61
+ elif type_ == "gcs":
95
62
  if region is None or bucket is None:
96
63
  raise ValueError("--region and --bucket is required for GCS provider.")
97
- file_system = UpdateGCSFileSystem(bucket=bucket, region=region, credentials=credentials)
98
- if directory:
99
- file_system.directory = directory
64
+ file_system = GCSFileSystem(bucket=bucket, region=region)
100
65
 
101
66
  else:
102
- raise ValueError("Must specify either --s3 or --gcs.")
67
+ raise ValueError(f"Unknown file system type: {type_}")
103
68
 
104
- res = state.settings.api.file_system.update_file_system(project, file_system)
105
- CONSOLE.print(res.file_system)
69
+ fs = state.settings.api.file_system.update_file_system(project, file_system)
70
+ CONSOLE.print(fs)
106
71
 
107
72
 
108
73
  @app.command(help="Lists the available built-in file system providers.")
spiral/cli/login.py CHANGED
@@ -3,16 +3,19 @@ import jwt
3
3
  from spiral.cli import CONSOLE, state
4
4
 
5
5
 
6
- def command(org_id: str | None = None, force: bool = False):
6
+ def command(org_id: str | None = None, force: bool = False, show_token: bool = False):
7
7
  token = state.settings.device_code_auth.authenticate(force=force, org_id=org_id)
8
8
  CONSOLE.print("Successfully logged in.")
9
- CONSOLE.print(token.expose_secret(), soft_wrap=True)
9
+ if show_token:
10
+ CONSOLE.print(token.expose_secret(), soft_wrap=True)
10
11
 
11
12
 
12
13
  def whoami():
13
14
  """Display the current user's information."""
14
15
  payload = jwt.decode(state.settings.authn.token().expose_secret(), options={"verify_signature": False})
15
- CONSOLE.print(f"{payload['org_id']}")
16
+
17
+ if "org_id" in payload:
18
+ CONSOLE.print(f"{payload['org_id']}")
16
19
  CONSOLE.print(f"{payload['sub']}")
17
20
 
18
21
 
spiral/cli/projects.py CHANGED
@@ -1,12 +1,13 @@
1
- from typing import Annotated
1
+ from typing import Annotated, Literal
2
2
 
3
3
  import typer
4
4
  from typer import Option
5
5
 
6
6
  from spiral.api.organizations import OrgRole
7
7
  from spiral.api.projects import (
8
+ AwsAssumedRolePrincipalConditions,
8
9
  CreateProjectRequest,
9
- GCPPrincipalConditions,
10
+ GcpServiceAccountPrincipalConditions,
10
11
  GitHubConditions,
11
12
  GitHubPrincipalConditions,
12
13
  Grant,
@@ -41,40 +42,47 @@ def create(
41
42
  CONSOLE.print(f"Created project {res.project.id}")
42
43
 
43
44
 
44
- @app.command(help="Grant a role on a project.")
45
+ @app.command(help="Grant a role on a project to a principal.")
45
46
  def grant(
46
47
  project: ProjectArg,
47
- role: Annotated[str, Option(help="Role to grant.")],
48
+ role: Annotated[Literal["viewer", "editor", "admin"], Option(help="Project role to grant.")],
48
49
  org_id: Annotated[
49
50
  str | None, Option(help="Pass an organization ID to grant a role to an organization user(s).")
50
51
  ] = None,
51
- user_id: Annotated[
52
+ org_user: Annotated[
52
53
  str | None, Option(help="Pass a user ID when using --org-id to grant a role to grant a role to a user.")
53
54
  ] = None,
54
55
  org_role: Annotated[
55
- str | None,
56
- Option(help="Pass an organization role when using --org-id to grant a role to all users with that role."),
56
+ Literal["owner", "member", "guest"] | None,
57
+ Option(help="Pass an org role when using --org-id to grant a role to all users with that role."),
57
58
  ] = None,
58
59
  workload_id: Annotated[str | None, Option(help="Pass a workload ID to grant a role to a workload.")] = None,
59
60
  github: Annotated[
60
- str | None, Option(help="Pass an `<org>/<repo>` string to grant a role to a job running in GitHub Actions.")
61
+ str | None, Option(help="Pass an `{org}/{repo}` string to grant a role to a job running in GitHub Actions.")
61
62
  ] = None,
62
63
  modal: Annotated[
63
64
  str | None,
64
- Option(help="Pass a `<workspace_id>/<env_name>` string to grant a role to a job running in Modal environment."),
65
+ Option(help="Pass a `{workspace_id}/{env_name}` string to grant a role to a job running in Modal environment."),
66
+ ] = None,
67
+ gcp_service_account: Annotated[
68
+ str | None,
69
+ Option(help="Pass a `{service_account_email}/{unique_id}` to grant a role to a GCP service account."),
65
70
  ] = None,
66
- gcp: Annotated[
71
+ aws_iam_role: Annotated[
67
72
  str | None,
68
- Option(help="Pass a `<service_account_email>/<unique_id>` to grant a role to a GCP service account."),
73
+ Option(help="Pass a `{account_id}/{role_name}` to grant a Spiral role to an AWS IAM Role."),
69
74
  ] = None,
70
75
  conditions: list[str] | None = Option(
71
76
  default=None,
72
- help="`<key>=<value>` token conditions to apply to the grant when using --github or --modal.",
77
+ help="`{key}={value}` token conditions to apply to the grant",
73
78
  ),
74
79
  ):
75
80
  # Check mutual exclusion
76
- if sum(int(bool(opt)) for opt in {org_id, workload_id, github, modal, gcp}) != 1:
77
- raise typer.BadParameter("Only one of --org-id, --github or --modal may be specified.")
81
+ if sum(int(bool(opt)) for opt in {org_id, workload_id, github, modal, gcp_service_account, aws_iam_role}) != 1:
82
+ raise typer.BadParameter(
83
+ "Only one of [--org-id, --workload-id, --github, --modal, --gcp-service-account, --aws-iam-role] "
84
+ "may be specified."
85
+ )
78
86
 
79
87
  if github:
80
88
  org, repo = github.split("/", 1)
@@ -98,22 +106,26 @@ def grant(
98
106
 
99
107
  elif org_id:
100
108
  # Check mutual exclusion
101
- if sum(int(bool(opt)) for opt in {user_id, org_role}) != 1:
102
- raise typer.BadParameter("Only one of --user-id or --org-role may be specified.")
109
+ if sum(int(bool(opt)) for opt in {org_user, org_role}) != 1:
110
+ raise typer.BadParameter("Only one of --org-user or --org-role may be specified.")
103
111
 
104
- if user_id is not None:
105
- principal = OrgUserPrincipalConditions(org_id=org_id, user_id=user_id)
112
+ if org_user is not None:
113
+ principal = OrgUserPrincipalConditions(org_id=org_id, user_id=org_user)
106
114
  elif org_role is not None:
107
115
  principal = OrgRolePrincipalConditions(org_id=org_id, role=OrgRole(org_role))
108
116
  else:
109
- raise NotImplementedError("Only user or role principal is supported at this time.")
117
+ raise typer.BadParameter("One of --org-user or --org-role must be specified with --org-id.")
110
118
 
111
119
  elif workload_id:
112
120
  principal = WorkloadPrincipalConditions(workload_id=workload_id)
113
121
 
114
- elif gcp:
115
- service_account, unique_id = gcp.split("/", 1)
116
- principal = GCPPrincipalConditions(service_account=service_account, unique_id=unique_id)
122
+ elif gcp_service_account:
123
+ service_account, unique_id = gcp_service_account.split("/", 1)
124
+ principal = GcpServiceAccountPrincipalConditions(service_account=service_account, unique_id=unique_id)
125
+
126
+ elif aws_iam_role:
127
+ account_id, role_name = aws_iam_role.split("/", 1)
128
+ principal = AwsAssumedRolePrincipalConditions(account_id=account_id, role_name=role_name)
117
129
 
118
130
  else:
119
131
  raise ValueError("Invalid grant principal")
spiral/client.py CHANGED
@@ -98,7 +98,7 @@ class Spiral:
98
98
  *projections: ExprLike,
99
99
  where: ExprLike | None = None,
100
100
  asof: datetime | int | None = None,
101
- exclude_keys: bool = False,
101
+ exclude_keys: bool | None = None,
102
102
  ) -> Scan:
103
103
  """Starts a read transaction on the Spiral.
104
104
 
@@ -136,7 +136,7 @@ class Spiral:
136
136
  filters: ExprLike | None = None,
137
137
  freshness_window: timedelta | None = None,
138
138
  ) -> pa.RecordBatchReader:
139
- """Queries the index with the given rank by and filters clauses. Returns a steam of scored keys.
139
+ """Queries the index with the given rank by and filters clauses. Returns a stream of scored keys.
140
140
 
141
141
  Args:
142
142
  top_k: The number of top results to return.
@@ -0,0 +1,5 @@
1
+ from ..table.spec import Schema
2
+
3
+ def pretty_key(key: bytes, schema: Schema) -> str:
4
+ """Represent a key in a human-readable way."""
5
+ ...