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.
- {pyspiral-0.6.3.dist-info → pyspiral-0.6.5.dist-info}/METADATA +3 -3
- {pyspiral-0.6.3.dist-info → pyspiral-0.6.5.dist-info}/RECORD +34 -32
- {pyspiral-0.6.3.dist-info → pyspiral-0.6.5.dist-info}/WHEEL +1 -1
- spiral/_lib.abi3.so +0 -0
- spiral/api/client.py +1 -1
- spiral/api/filesystems.py +9 -40
- spiral/api/projects.py +9 -2
- spiral/cli/app.py +42 -6
- spiral/cli/fs.py +25 -60
- spiral/cli/login.py +6 -3
- spiral/cli/projects.py +34 -22
- spiral/client.py +2 -2
- spiral/core/_tools/__init__.pyi +5 -0
- spiral/core/authn/__init__.pyi +1 -1
- spiral/core/client/__init__.pyi +14 -3
- spiral/core/table/__init__.pyi +3 -0
- spiral/debug/manifests.py +26 -18
- spiral/expressions/__init__.py +2 -2
- spiral/expressions/base.py +9 -3
- spiral/iterable_dataset.py +106 -0
- spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +1 -1
- spiral/protogen/_/google/protobuf/__init__.py +121 -1
- spiral/protogen/_/scandal/__init__.py +1 -1
- spiral/protogen/_/spfs/__init__.py +1 -1
- spiral/protogen/_/spql/__init__.py +1 -1
- spiral/protogen/_/substrait/__init__.py +2 -2
- spiral/protogen/_/substrait/extensions/__init__.py +1 -1
- spiral/scan.py +22 -34
- spiral/settings.py +3 -1
- spiral/snapshot.py +16 -0
- spiral/streaming_/stream.py +7 -3
- spiral/table.py +53 -94
- spiral/transaction.py +1 -1
- {pyspiral-0.6.3.dist-info → pyspiral-0.6.5.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyspiral
|
3
|
-
Version: 0.6.
|
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.
|
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.
|
2
|
-
pyspiral-0.6.
|
3
|
-
pyspiral-0.6.
|
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=
|
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=
|
10
|
-
spiral/api/filesystems.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
38
|
+
spiral/client.py,sha256=ANKzL6yQ-7X4Livq4xCZsWVdQSU3j1J7gSJv6dzeaJs,6630
|
39
39
|
spiral/core/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
spiral/core/
|
41
|
-
spiral/core/
|
42
|
-
spiral/core/
|
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=
|
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=
|
53
|
-
spiral/expressions/base.py,sha256=
|
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=
|
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=
|
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
|
80
|
-
spiral/protogen/_/spfs/__init__.py,sha256=
|
81
|
-
spiral/protogen/_/spql/__init__.py,sha256=
|
82
|
-
spiral/protogen/_/substrait/__init__.py,sha256=-
|
83
|
-
spiral/protogen/_/substrait/extensions/__init__.py,sha256=
|
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=
|
89
|
+
spiral/scan.py,sha256=SCDZ9UXA4g0Jq9BQ9Zt7cEK2NBq64Hqh_SttR4tF6jo,6252
|
88
90
|
spiral/server.py,sha256=ztBmB5lBnUz-smQxR_tC8AI5SOhz17wH0MI3GuzDUdM,600
|
89
|
-
spiral/settings.py,sha256=
|
90
|
-
spiral/snapshot.py,sha256=
|
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
|
95
|
+
spiral/streaming_/stream.py,sha256=-prGp73h0XDsdKW0mAEamy4AXhd1oF5fBbNbbY1k2-A,5931
|
94
96
|
spiral/substrait_.py,sha256=AKeOD4KIXvz2J4TYxnIneOiHddtBIyOhuNxVO_uH0eg,12592
|
95
|
-
spiral/table.py,sha256=
|
97
|
+
spiral/table.py,sha256=iWoWm0XAcmW99NQXgEsdDWvq1Z9tUSd6t0kn4o1WmSo,9019
|
96
98
|
spiral/text_index.py,sha256=FQ9rgIEGLSJryS9lFdMhKtPFey18BXoWbPXyvZPJJ04,442
|
97
|
-
spiral/transaction.py,sha256=
|
99
|
+
spiral/transaction.py,sha256=rjoOQc9bvs1zAAhb8cLtJOOn2146UAMZjcSVcEXeNFY,1839
|
98
100
|
spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
|
99
|
-
pyspiral-0.6.
|
101
|
+
pyspiral-0.6.5.dist-info/RECORD,,
|
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 =
|
47
|
-
region: str
|
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:
|
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,
|
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
|
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
|
-
|
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
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
|
75
|
-
raise ValueError("--
|
76
|
-
|
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
|
-
|
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 =
|
98
|
-
if directory:
|
99
|
-
file_system.directory = directory
|
64
|
+
file_system = GCSFileSystem(bucket=bucket, region=region)
|
100
65
|
|
101
66
|
else:
|
102
|
-
raise ValueError("
|
67
|
+
raise ValueError(f"Unknown file system type: {type_}")
|
103
68
|
|
104
|
-
|
105
|
-
CONSOLE.print(
|
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
|
-
|
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
|
-
|
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
|
-
|
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[
|
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
|
-
|
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
|
-
|
56
|
-
Option(help="Pass an
|
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
|
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
|
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
|
-
|
71
|
+
aws_iam_role: Annotated[
|
67
72
|
str | None,
|
68
|
-
Option(help="Pass a
|
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="
|
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,
|
77
|
-
raise typer.BadParameter(
|
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 {
|
102
|
-
raise typer.BadParameter("Only one of --user
|
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
|
105
|
-
principal = OrgUserPrincipalConditions(org_id=org_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
|
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
|
115
|
-
service_account, unique_id =
|
116
|
-
principal =
|
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 =
|
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
|
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.
|