pyspiral 0.3.1__cp310-abi3-macosx_11_0_arm64.whl → 0.4.1__cp310-abi3-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/METADATA +9 -13
  2. pyspiral-0.4.1.dist-info/RECORD +98 -0
  3. {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/WHEEL +1 -1
  4. spiral/__init__.py +6 -9
  5. spiral/_lib.abi3.so +0 -0
  6. spiral/adbc.py +21 -14
  7. spiral/api/__init__.py +14 -175
  8. spiral/api/admin.py +12 -26
  9. spiral/api/client.py +160 -0
  10. spiral/api/filesystems.py +100 -72
  11. spiral/api/organizations.py +45 -58
  12. spiral/api/projects.py +171 -134
  13. spiral/api/telemetry.py +19 -0
  14. spiral/api/types.py +20 -0
  15. spiral/api/workloads.py +32 -25
  16. spiral/{arrow.py → arrow_.py} +12 -0
  17. spiral/cli/__init__.py +2 -5
  18. spiral/cli/admin.py +7 -12
  19. spiral/cli/app.py +23 -6
  20. spiral/cli/console.py +1 -1
  21. spiral/cli/fs.py +82 -17
  22. spiral/cli/iceberg/__init__.py +7 -0
  23. spiral/cli/iceberg/namespaces.py +47 -0
  24. spiral/cli/iceberg/tables.py +60 -0
  25. spiral/cli/indexes/__init__.py +19 -0
  26. spiral/cli/login.py +14 -5
  27. spiral/cli/orgs.py +90 -0
  28. spiral/cli/printer.py +9 -1
  29. spiral/cli/projects.py +136 -0
  30. spiral/cli/state.py +2 -0
  31. spiral/cli/tables/__init__.py +121 -0
  32. spiral/cli/telemetry.py +18 -0
  33. spiral/cli/types.py +8 -10
  34. spiral/cli/{workload.py → workloads.py} +11 -11
  35. spiral/{catalog.py → client.py} +23 -37
  36. spiral/core/client/__init__.pyi +117 -0
  37. spiral/core/index/__init__.pyi +15 -0
  38. spiral/core/{core → table}/__init__.pyi +44 -17
  39. spiral/core/{manifests → table/manifests}/__init__.pyi +5 -23
  40. spiral/core/table/metastore/__init__.pyi +62 -0
  41. spiral/core/{spec → table/spec}/__init__.pyi +41 -66
  42. spiral/datetime_.py +27 -0
  43. spiral/expressions/__init__.py +26 -18
  44. spiral/expressions/base.py +5 -5
  45. spiral/expressions/list_.py +1 -1
  46. spiral/expressions/mp4.py +2 -9
  47. spiral/expressions/png.py +1 -1
  48. spiral/expressions/qoi.py +1 -1
  49. spiral/expressions/refs.py +3 -9
  50. spiral/expressions/struct.py +7 -5
  51. spiral/expressions/text.py +62 -0
  52. spiral/expressions/udf.py +3 -3
  53. spiral/iceberg/__init__.py +3 -0
  54. spiral/iceberg/client.py +33 -0
  55. spiral/indexes/__init__.py +5 -0
  56. spiral/indexes/client.py +137 -0
  57. spiral/indexes/index.py +34 -0
  58. spiral/indexes/scan.py +22 -0
  59. spiral/project.py +19 -110
  60. spiral/{proto → protogen}/_/scandal/__init__.py +23 -135
  61. spiral/protogen/_/spiral/table/__init__.py +22 -0
  62. spiral/protogen/substrait/__init__.py +3399 -0
  63. spiral/protogen/substrait/extensions/__init__.py +115 -0
  64. spiral/server.py +17 -0
  65. spiral/settings.py +29 -91
  66. spiral/substrait_.py +9 -5
  67. spiral/tables/__init__.py +12 -0
  68. spiral/tables/client.py +130 -0
  69. spiral/{dataset.py → tables/dataset.py} +9 -199
  70. spiral/tables/debug/manifests.py +70 -0
  71. spiral/tables/debug/metrics.py +56 -0
  72. spiral/{debug.py → tables/debug/scan.py} +6 -9
  73. spiral/{maintenance.py → tables/maintenance.py} +1 -1
  74. spiral/{scan_.py → tables/scan.py} +63 -89
  75. spiral/tables/snapshot.py +78 -0
  76. spiral/{table.py → tables/table.py} +59 -73
  77. spiral/{txn.py → tables/transaction.py} +7 -3
  78. pyspiral-0.3.1.dist-info/RECORD +0 -85
  79. spiral/api/tables.py +0 -91
  80. spiral/api/tokens.py +0 -56
  81. spiral/authn/authn.py +0 -89
  82. spiral/authn/device.py +0 -206
  83. spiral/authn/github_.py +0 -33
  84. spiral/authn/modal_.py +0 -18
  85. spiral/cli/org.py +0 -90
  86. spiral/cli/project.py +0 -109
  87. spiral/cli/table.py +0 -20
  88. spiral/cli/token.py +0 -27
  89. spiral/core/metastore/__init__.pyi +0 -91
  90. spiral/proto/_/spfs/__init__.py +0 -36
  91. spiral/proto/_/spiral/table/__init__.py +0 -276
  92. spiral/proto/_/spiraldb/metastore/__init__.py +0 -499
  93. spiral/proto/__init__.py +0 -0
  94. spiral/proto/scandal/__init__.py +0 -45
  95. spiral/proto/spiral/__init__.py +0 -0
  96. spiral/proto/spiral/table/__init__.py +0 -96
  97. {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/entry_points.txt +0 -0
  98. /spiral/{authn/__init__.py → core/__init__.pyi} +0 -0
  99. /spiral/{core → protogen/_}/__init__.py +0 -0
  100. /spiral/{proto/_ → protogen/_/arrow}/__init__.py +0 -0
  101. /spiral/{proto/_/arrow → protogen/_/arrow/flight}/__init__.py +0 -0
  102. /spiral/{proto/_/arrow/flight → protogen/_/arrow/flight/protocol}/__init__.py +0 -0
  103. /spiral/{proto → protogen}/_/arrow/flight/protocol/sql/__init__.py +0 -0
  104. /spiral/{proto/_/arrow/flight/protocol → protogen/_/spiral}/__init__.py +0 -0
  105. /spiral/{proto → protogen/_}/substrait/__init__.py +0 -0
  106. /spiral/{proto → protogen/_}/substrait/extensions/__init__.py +0 -0
  107. /spiral/{proto/_/spiral → protogen}/__init__.py +0 -0
  108. /spiral/{proto → protogen}/util.py +0 -0
  109. /spiral/{proto/_/spiraldb → tables/debug}/__init__.py +0 -0
@@ -0,0 +1,78 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from spiral.core.table import TableSnapshot
4
+ from spiral.expressions import ExprLike
5
+ from spiral.tables.scan import Scan
6
+ from spiral.types_ import Timestamp
7
+
8
+ if TYPE_CHECKING:
9
+ import duckdb
10
+ import polars as pl
11
+ import pyarrow.dataset
12
+
13
+ from spiral.tables import Tables
14
+ from spiral.tables.table import Table
15
+
16
+
17
+ class Snapshot:
18
+ """Spiral table snapshot.
19
+
20
+ A snapshot represents a point-in-time view of a table.
21
+ """
22
+
23
+ def __init__(self, tables: "Tables", snapshot: TableSnapshot):
24
+ self._tables = tables
25
+ self._snapshot = snapshot
26
+
27
+ @property
28
+ def asof(self) -> Timestamp:
29
+ """Returns the asof timestamp of the snapshot."""
30
+ return self._snapshot.asof
31
+
32
+ @property
33
+ def client(self) -> "Tables":
34
+ """Returns the client used by the snapshot."""
35
+ return self._tables
36
+
37
+ @property
38
+ def table(self) -> "Table":
39
+ """Returns the table associated with the snapshot."""
40
+ from spiral.tables.table import Table
41
+
42
+ return Table(self._tables, self._snapshot.table)
43
+
44
+ def to_dataset(self) -> "pyarrow.dataset.Dataset":
45
+ """Returns a PyArrow Dataset representing the table."""
46
+ from .dataset import TableDataset
47
+
48
+ return TableDataset(self)
49
+
50
+ def to_polars(self) -> "pl.LazyFrame":
51
+ """Returns a Polars LazyFrame for the Spiral table."""
52
+ import polars as pl
53
+
54
+ return pl.scan_pyarrow_dataset(self.to_dataset())
55
+
56
+ def to_duckdb(self) -> "duckdb.DuckDBPyRelation":
57
+ """Returns a DuckDB relation for the Spiral table."""
58
+ import duckdb
59
+
60
+ return duckdb.from_arrow(self.to_dataset())
61
+
62
+ def scan(
63
+ self,
64
+ *projections: ExprLike,
65
+ where: ExprLike | None = None,
66
+ exclude_keys: bool = False,
67
+ ) -> Scan:
68
+ """Reads the snapshot. If projections are not provided, the entire table is read."""
69
+ if not projections:
70
+ # Use table as the default projection.
71
+ projections = [self._snapshot.table.__expr__]
72
+
73
+ return self._tables.scan(
74
+ *projections,
75
+ where=where,
76
+ asof=self._snapshot.asof,
77
+ exclude_keys=exclude_keys,
78
+ )
@@ -1,22 +1,17 @@
1
1
  from datetime import datetime
2
- from typing import TYPE_CHECKING, Literal
2
+ from typing import TYPE_CHECKING
3
3
 
4
- import pyarrow as pa
5
-
6
- from spiral.core.core import Table as CoreTable
7
- from spiral.core.core import TableMaintenance, TableTransaction
8
- from spiral.core.spec import Schema
4
+ from spiral.core.table import Table as CoreTable
5
+ from spiral.core.table.spec import Schema
9
6
  from spiral.expressions.base import Expr, ExprLike
10
- from spiral.maintenance import Maintenance
11
- from spiral.settings import FILE_FORMAT
12
- from spiral.txn import Transaction
7
+ from spiral.settings import settings
8
+ from spiral.tables.maintenance import Maintenance
9
+ from spiral.tables.scan import Scan
10
+ from spiral.tables.snapshot import Snapshot
11
+ from spiral.tables.transaction import Transaction
13
12
 
14
13
  if TYPE_CHECKING:
15
- import duckdb
16
- import polars as pl
17
- import pyarrow.dataset
18
-
19
- from spiral.scan_ import Scan
14
+ from spiral.tables import Tables
20
15
 
21
16
 
22
17
  class Table(Expr):
@@ -25,31 +20,55 @@ class Table(Expr):
25
20
  Different catalog implementations should ultimately construct a Table object.
26
21
  """
27
22
 
28
- def __init__(
29
- self,
30
- table: CoreTable,
31
- name: str | None = None,
32
- ):
23
+ # TODO(marko): Make identifier required.
24
+ def __init__(self, tables: "Tables", table: CoreTable, *, identifier: str | None = None):
33
25
  super().__init__(table.__expr__)
34
26
 
27
+ self._tables = tables
35
28
  self._table = table
36
- self._name = name or self._table.id
37
- self._key_schema = self._table.key_schema.to_arrow()
29
+ self._identifier = identifier
30
+ self._key_schema = self._table.key_schema
38
31
  self._key_columns = set(self._key_schema.names)
39
32
 
33
+ @property
34
+ def client(self) -> "Tables":
35
+ """Returns the client used by the table."""
36
+ return self._tables
37
+
40
38
  @property
41
39
  def table_id(self) -> str:
42
40
  return self._table.id
43
41
 
42
+ @property
43
+ def identifier(self) -> str:
44
+ """Returns the fully qualified identifier of the table."""
45
+ return self._identifier or self._table.id
46
+
47
+ @property
48
+ def dataset(self) -> str | None:
49
+ """Returns the dataset of the table."""
50
+ if self._identifier is None:
51
+ return None
52
+ _, dataset, _ = self._identifier.split(".")
53
+ return dataset
54
+
55
+ @property
56
+ def name(self) -> str | None:
57
+ """Returns the name of the table."""
58
+ if self._identifier is None:
59
+ return None
60
+ _, _, name = self._identifier.split(".")
61
+ return name
62
+
44
63
  @property
45
64
  def last_modified_at(self) -> int:
46
65
  return self._table.get_wal(asof=None).last_modified_at
47
66
 
48
67
  def __str__(self):
49
- return self._name
68
+ return self.identifier
50
69
 
51
70
  def __repr__(self):
52
- return f'Table("{self._name}")'
71
+ return f'Table("{self.identifier}")'
53
72
 
54
73
  def __getitem__(self, item: str) -> Expr:
55
74
  from spiral import expressions as se
@@ -77,7 +96,7 @@ class Table(Expr):
77
96
  return se.merge(se.pack({key: se.key(key) for key in key_paths}), super().select(*other_paths, exclude=exclude))
78
97
 
79
98
  @property
80
- def key_schema(self) -> pa.Schema:
99
+ def key_schema(self) -> Schema:
81
100
  """Returns the key schema of the table."""
82
101
  return self._key_schema
83
102
 
@@ -89,83 +108,50 @@ class Table(Expr):
89
108
  """
90
109
  return self._table.get_schema(asof=None)
91
110
 
92
- def to_dataset(self) -> "pyarrow.dataset.Dataset":
93
- """Returns a PyArrow Dataset representing the table."""
94
- from .dataset import TableDataset
95
-
96
- return TableDataset(self)
97
-
98
- def to_polars(self) -> "pl.LazyFrame":
99
- """Returns a Polars LazyFrame for the Spiral table."""
100
- import polars as pl
101
-
102
- return pl.scan_pyarrow_dataset(self.to_dataset())
103
-
104
- def to_duckdb(self) -> "duckdb.DuckDBPyRelation":
105
- """Returns a DuckDB relation for the Spiral table."""
106
- import duckdb
107
-
108
- return duckdb.from_arrow(self.to_dataset())
109
-
110
111
  def scan(
111
112
  self,
112
113
  *projections: ExprLike,
113
114
  where: ExprLike | None = None,
114
- asof: datetime | int | str = None,
115
+ asof: datetime | int | None = None,
115
116
  exclude_keys: bool = False,
116
- ) -> "Scan":
117
- """Reads the table. If projections are not provided, the entire table is read.
118
-
119
- See `spiral.scan` for more information.
120
- """
121
- from spiral.scan_ import scan
122
-
117
+ ) -> Scan:
118
+ """Reads the table. If projections are not provided, the entire table is read."""
123
119
  if not projections:
124
120
  projections = [self]
125
121
 
126
- return scan(
127
- *projections,
128
- where=where,
129
- asof=asof,
130
- exclude_keys=exclude_keys,
131
- )
122
+ return self._tables.scan(*projections, where=where, asof=asof, exclude_keys=exclude_keys)
132
123
 
133
- # NOTE: "vortex" is valid format. We don't want that visible in the API docs.
134
124
  def write(
135
125
  self,
136
126
  expr: ExprLike,
137
127
  *,
138
- format: Literal["parquet"] | None = None,
139
128
  partition_size_bytes: int | None = None,
140
129
  ) -> None:
141
130
  """Write an item to the table inside a single transaction.
142
131
 
143
132
  :param expr: The expression to write. Must evaluate to a struct array.
144
- :param format: the format to write the data in. Defaults to "parquet".
145
133
  :param partition_size_bytes: The maximum partition size in bytes.
146
134
  """
147
- format = format or FILE_FORMAT
148
-
149
- with self.txn(format=format) as txn:
135
+ with self.txn() as txn:
150
136
  txn.write(
151
137
  expr,
152
138
  partition_size_bytes=partition_size_bytes,
153
139
  )
154
140
 
155
- # NOTE: "vortex" is valid format. We don't want that visible in the API docs.
156
- def txn(self, format: Literal["parquet"] | None = None) -> Transaction:
141
+ def snapshot(self, asof: datetime | int | None = None) -> Snapshot:
142
+ """Returns a snapshot of the table at the given timestamp."""
143
+ if isinstance(asof, datetime):
144
+ asof = int(asof.timestamp() * 1_000_000)
145
+ return Snapshot(self._tables, self._table.get_snapshot(asof=asof))
146
+
147
+ def txn(self) -> Transaction:
157
148
  """Begins a new transaction. Transaction must be committed for writes to become visible.
158
149
 
159
150
  IMPORTANT: While transaction can be used to atomically write data to the table,
160
151
  it is important that the primary key columns are unique within the transaction.
161
-
162
- :param format: The format to use for the transaction. Defaults to "parquet".
163
152
  """
164
- return Transaction(TableTransaction(self._table.metastore, format or FILE_FORMAT))
165
-
166
- def maintenance(self, format: Literal["parquet"] | None = None) -> Maintenance:
167
- """Maintenance tasks for the table.
153
+ return Transaction(self._tables._spiral.open_transaction(self._table, settings().file_format))
168
154
 
169
- :param format: The format to use. Defaults to "parquet".
170
- """
171
- return Maintenance(TableMaintenance(self._table.metastore, format or FILE_FORMAT))
155
+ def maintenance(self) -> Maintenance:
156
+ """Access maintenance operations for a table."""
157
+ return Maintenance(self._tables._spiral.open_maintenance(self._table, settings().file_format))
@@ -1,5 +1,9 @@
1
- from spiral.core.core import TableTransaction
2
- from spiral.expressions import ExprLike
1
+ from typing import TYPE_CHECKING
2
+
3
+ from spiral.core.table import TableTransaction
4
+
5
+ if TYPE_CHECKING:
6
+ from spiral.expressions.base import ExprLike
3
7
 
4
8
 
5
9
  class Transaction:
@@ -26,7 +30,7 @@ class Transaction:
26
30
  else:
27
31
  self._transaction.abort()
28
32
 
29
- def write(self, expr: ExprLike, *, partition_size_bytes: int | None = None):
33
+ def write(self, expr: "ExprLike", *, partition_size_bytes: int | None = None):
30
34
  """Write an item to the table inside a single transaction.
31
35
 
32
36
  :param expr: The expression to write. Must evaluate to a struct array.
@@ -1,85 +0,0 @@
1
- pyspiral-0.3.1.dist-info/METADATA,sha256=QWHcYqfg46JUluQy_NZ3TKilWdvRFwOCqcJPYMo4g3c,1782
2
- pyspiral-0.3.1.dist-info/WHEEL,sha256=WcVLymwUFXZZBOS7GhUoID5qvQcDPxnkUAYBKypRBtM,103
3
- pyspiral-0.3.1.dist-info/entry_points.txt,sha256=uft7u-a6g40NLt4Q6BleWbK4NY0M8nZuYPpP8DV0EOk,45
4
- spiral/catalog.py,sha256=o6b8yhVzHmk_CPNZMKyJU0H8xpjJKKcf2rjcopTSBKc,2912
5
- spiral/scan_.py,sha256=TNAR_ELKRMJec6fIxhOwpW12WTwZc4sDLtfGdiok-lw,8239
6
- spiral/substrait_.py,sha256=LmdRhyTX7vhgEbpxV6FUssyL-BiBEIPBeUZApeaKDM4,12671
7
- spiral/core/core/__init__.pyi,sha256=zQhEB9X0B4EJGZf6AJYZdRt1tDQelF5tCsUaVdH7p4I,2706
8
- spiral/core/spec/__init__.pyi,sha256=RAnvJjuQPxbcyPtjldvOU8zjVlHhnTKHAFa77sqxBdw,6714
9
- spiral/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- spiral/core/manifests/__init__.pyi,sha256=DXr4Ab_Xo11AzrJlSj7FTSJc8qoO-SkL-_ik4kB855U,1516
11
- spiral/core/metastore/__init__.pyi,sha256=pdKED91GVJ9XWxTWc9gwkHvVHV_RKxvL43Ofs5ndmew,3145
12
- spiral/types_.py,sha256=W_jyO7F6rpPiH69jhgSgV7OxQZbOlb1Ho3InpKUP6Eo,155
13
- spiral/proto/scandal/__init__.py,sha256=wAAEkPN4S4XDpGQtw1MV5zFUeHM01XSAa5tgUgcALvg,777
14
- spiral/proto/util.py,sha256=smnvVo6nYH3FfDm9jqhNLaXz4bbTBaQezHQDCTvZyiQ,1486
15
- spiral/proto/spiral/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- spiral/proto/spiral/table/__init__.py,sha256=_F1f52RMkZsXofPXpJb2KE8KR5l6zxCtrGrabR1uDxo,2816
17
- spiral/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- spiral/proto/_/scandal/__init__.py,sha256=4UV2TipI1dJWCM2gFNmZM1qbM1_C_pilO3DTI1Kqq8M,8655
19
- spiral/proto/_/arrow/flight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- spiral/proto/_/arrow/flight/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- spiral/proto/_/arrow/flight/protocol/sql/__init__.py,sha256=_xhj9QkWEW1qZ-iVxcQ8k4EjYr7KJ5ofitJGqVUGQi4,79921
22
- spiral/proto/_/arrow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- spiral/proto/_/spiral/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- spiral/proto/_/spiral/table/__init__.py,sha256=WnBvPPGHphSIa5K6ct6G3S3pK5BJtcC0EIce7r4Jam4,8818
25
- spiral/proto/_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- spiral/proto/_/spiraldb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- spiral/proto/_/spiraldb/metastore/__init__.py,sha256=40Egtg8MRYTaTTYRKOHkwuiyXEkw3Yg7ETCQskIzpIg,16873
28
- spiral/proto/_/spfs/__init__.py,sha256=9WtIXr7HGslKWRHHieFDo8N_qnGL4QQyLOCWEkOKRvk,1017
29
- spiral/proto/substrait/__init__.py,sha256=pV4-T-lwAHKkfFrNYSUGY4IkbIvuKjSo_imzF7BLj_s,126526
30
- spiral/proto/substrait/extensions/__init__.py,sha256=yD7dg0TBqn-GK_L0qeVof1GKnwSLg_kPyQSV3kcSljs,3655
31
- spiral/maintenance.py,sha256=UFvWBLf9L0qDxJa2VdyBzxQxN4W3D9F3QHFKWp1yWZU,304
32
- spiral/arrow.py,sha256=LBPwZcGkP4kXb42_kl5IUwWW3DO84CV3QJDwCHjG5Dg,7225
33
- spiral/__init__.py,sha256=Dlaha3kfmzIri8A3-d69tOC2iQrK5fXlYVSI6uU4Fus,394
34
- spiral/cli/console.py,sha256=-OP0bB_efxhWh4lZ95KRdu-SRgSUMJ47Rbi9FHv1TlY,2577
35
- spiral/cli/org.py,sha256=ezWhoGUkJQQAwI1jKvDP8uZPNlnou_hXtRDa1us5cSE,2935
36
- spiral/cli/token.py,sha256=dv30aa745bbS-c3tyzQUTSxGG_N0kCt8G4bip9fP_EM,968
37
- spiral/cli/__init__.py,sha256=CoiAJ7FDgqjG_TrU-6SpP1hyZloIlEP4wcwnrF8flHM,2237
38
- spiral/cli/types.py,sha256=4cphJs-i0vfq_CcnHxT9FpHiZdGwxME5GnOmIGB6Thw,1436
39
- spiral/cli/workload.py,sha256=-XreFPJcX7kZvcYE3oQMeGkkYoXi25R5nuktJPg-PuY,2000
40
- spiral/cli/admin.py,sha256=3pIs6PxDugMtdgzfRpn_HfBDv-cwAYtF0cJP2INB01A,579
41
- spiral/cli/fs.py,sha256=rgI3QNKg0_B-3T2d0KgjcLYQNc2M6sRK95cJ_J0nth0,1523
42
- spiral/cli/app.py,sha256=2oZfDTgj_gZ-lFMMzzJJTnvVzQhp_iedvH-FJnaaMW0,1487
43
- spiral/cli/table.py,sha256=eh2NAk0GlfvthwRNeIbcZTsRWU3ypFx_uu9OaOLHPUo,628
44
- spiral/cli/login.py,sha256=C7VpqVyYO2daUeIWHoelWnSGN7cju8YEuqOy12ImH4c,381
45
- spiral/cli/printer.py,sha256=5HD3UcszFfPk-dK8U5akuvtXqMB7PMgOB1DFYMqspG8,1625
46
- spiral/cli/__main__.py,sha256=kNaKM2xgJo7GRogf83nYldLM-RGUR6vymdGwZxywQu0,71
47
- spiral/cli/project.py,sha256=N9ocPRtjLT5lfrPMGbX_-bUXP6o5gq3RpoGYQR3CJ74,4524
48
- spiral/cli/state.py,sha256=1quvei8TnDTT6mDRo58P8FUfy4w16Z9sggBz7cFgllY,70
49
- spiral/dataset.py,sha256=53s1JGM9lQkzbZes9nXnax6XtMtCUDR0TBOeTA8wC38,14019
50
- spiral/grpc_.py,sha256=f3czdP1Mxme42Y5--a5ogYq1TTiWn-J_MlGjwJ2mWwM,1015
51
- spiral/debug.py,sha256=t590eAUtNWwMTsSdkjVNN7J1iMqY2p4PRJ3BWR_ozho,8999
52
- spiral/expressions/tiff.py,sha256=fQwIn0kLFBM2Y3YYIHmTgb_EIRHKT2fNc77nioDQQw4,8044
53
- spiral/expressions/io.py,sha256=gJ2a0FKMmdxarWKENulPRwH7KDvSJTIh_OUxX306xAM,3045
54
- spiral/expressions/__init__.py,sha256=LqvssKQTvoyX4s5UIG_HtYZbt5i5EkghkEpsE_37MWg,6118
55
- spiral/expressions/png.py,sha256=3SjreqYcnDL4_EWMENcaGpZVoXR49KAfJiERkFSdT-w,511
56
- spiral/expressions/list_.py,sha256=nbo4xQAuqBsQGajq_JgORaJl8_CDvOAv14zMbqmtZh4,1814
57
- spiral/expressions/http.py,sha256=begUydWoFHEqjeLkATvI_v66Ez6_rR-OQBWO5cHbb9c,2742
58
- spiral/expressions/refs.py,sha256=EndnGTLSA-s3hD6QOkxfUSWpw2IUm1hnKyyiPIlhZ7I,2370
59
- spiral/expressions/udf.py,sha256=vOlrdxiVpt7vdSgiTKX_XR86YKyu02Fdwb9xlINCby4,1363
60
- spiral/expressions/mp4.py,sha256=0qhTP4HGI0WL-nVhke-ykqEnOYcb64Aqryplgelv-BA,2437
61
- spiral/expressions/str_.py,sha256=tY8RXW3JWvr1-bEfCZtk5FAf11wKJnXPuA9EoeJ9tA4,1265
62
- spiral/expressions/qoi.py,sha256=FCAsoF-3ur7CMIk2Oz5Hm4qDdb901E_7_-aKT8yss6E,511
63
- spiral/expressions/base.py,sha256=mTBwS6CwdDaV8uotjZUiKi7GHQzX2TRPpnseJJUDrR0,4776
64
- spiral/expressions/struct.py,sha256=MuxoBP6ESpwmjzusG-_HxHGYKvQQz6AZWzvw7vNUHJM,2007
65
- spiral/settings.py,sha256=H0ELgA1k_YENufIWscXV7gzdU8t7JOCw0AdUDVvCFH0,4704
66
- spiral/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- spiral/api/filesystems.py,sha256=_JWFbggORWOAg0yKASsEXbu8Vk-hIMOiiVrwcEcm_RA,3424
68
- spiral/api/workloads.py,sha256=4MWs2pp9AWvx6cZhgyW-ehyCRxpHc_NAQgHaoYOEBfg,1266
69
- spiral/api/__init__.py,sha256=Wy2ZEL0balniPP7LuYLhazxI9p3ZcgZ9g_MAZLWF3Hg,6828
70
- spiral/api/tokens.py,sha256=WaSRr_l3i81t5u2qi3kWW-fySbyKFm-PQK1ZlmoSByc,1464
71
- spiral/api/admin.py,sha256=HJBrRJScbcdDuFhF_06E0EyE-_Y0osfYPxVoRAyEoTc,837
72
- spiral/api/organizations.py,sha256=-PO93HTX02IxhXM6SJpAhnAXpVW1WjthFO4-AOzZAC4,2670
73
- spiral/api/tables.py,sha256=3Kt0tPfu3jQGIIAMUSNAMRtDRGK-STRYoHHgzZ4qFNU,2532
74
- spiral/api/projects.py,sha256=-VGlu5V3TJ3XLCGu85bPHRFiktIADnAxfLWIH8Rmxug,4986
75
- spiral/txn.py,sha256=SIww2vAazE1nPxyAkeiN7NMvXmBK6jHhFM_uMkEUW0o,1508
76
- spiral/table.py,sha256=HzSRNQAI9YscdfmeHlwZcIdrK2SJ9wWq-GRRCmint_M,5541
77
- spiral/authn/modal_.py,sha256=agcnR3dYTslkH2K_a2Eis_2JWn9Ps11FVrGG_jkOdGk,472
78
- spiral/authn/device.py,sha256=ohHSVLW3a-qLNYQGN-3kXxV_836xOe0UYBN8i63cqAQ,6796
79
- spiral/authn/authn.py,sha256=OCGJAUfoKLiXw9xAcAnX6i6mBRlvsl6qEFcimqQOu7g,2555
80
- spiral/authn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
- spiral/authn/github_.py,sha256=K-0RUHDreINjnCDHyT9aeVDRk6WtNP7noBYEcwdz2W4,1313
82
- spiral/adbc.py,sha256=H_bzevPy5teyZKzjczh1gQ_zPcfk5sNASiJKQvyab9E,13830
83
- spiral/project.py,sha256=q9jHql7hz5OQzPfDfvE_hN3K9cZotD0cxkdug9EUTwM,4889
84
- spiral/_lib.abi3.so,sha256=p285aXvfZS7XzjXERyb5tsrH0fwq9rVdZ0hX4rvbkrA,64644032
85
- pyspiral-0.3.1.dist-info/RECORD,,
spiral/api/tables.py DELETED
@@ -1,91 +0,0 @@
1
- from typing import Annotated
2
-
3
- from pydantic import AfterValidator, BaseModel, StringConstraints
4
-
5
- from . import ArrowSchema, Paged, PagedRequest, PagedResponse, ProjectId, ServiceBase
6
-
7
-
8
- def _validate_root_uri(uri: str) -> str:
9
- if uri.endswith("/"):
10
- raise ValueError("Root URI must not end with a slash.")
11
- return uri
12
-
13
-
14
- RootUri = Annotated[str, AfterValidator(_validate_root_uri)]
15
- DatasetName = Annotated[str, StringConstraints(max_length=128, pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]+$")]
16
- TableName = Annotated[str, StringConstraints(max_length=128, pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]+$")]
17
-
18
-
19
- class TableMetadata(BaseModel):
20
- root_uri: RootUri
21
- spfs_mount_id: str | None = None
22
-
23
- key_schema: ArrowSchema
24
-
25
- # TODO(marko): Randomize this on creation of metadata.
26
- # Column group salt is used to compute column group IDs.
27
- # It's used to ensure that column group IDs are unique
28
- # across different tables, even if paths are the same.
29
- # It's never modified.
30
- column_group_salt: int = 0
31
-
32
-
33
- class Table(BaseModel):
34
- id: str
35
- project_id: ProjectId
36
- dataset: DatasetName
37
- table: TableName
38
- metadata: TableMetadata
39
-
40
-
41
- class CreateTable:
42
- class Request(BaseModel):
43
- project_id: ProjectId
44
- dataset: DatasetName
45
- table: TableName
46
- key_schema: ArrowSchema
47
- root_uri: RootUri | None = None
48
- exist_ok: bool = False
49
-
50
- class Response(BaseModel):
51
- table: Table
52
-
53
-
54
- class FindTable:
55
- class Request(BaseModel):
56
- project_id: ProjectId
57
- dataset: DatasetName = None
58
- table: TableName = None
59
-
60
- class Response(BaseModel):
61
- table: Table | None
62
-
63
-
64
- class GetTable:
65
- class Request(BaseModel):
66
- id: str
67
-
68
- class Response(BaseModel):
69
- table: Table
70
-
71
-
72
- class ListTables:
73
- class Request(PagedRequest):
74
- project_id: ProjectId
75
- dataset: DatasetName | None = None
76
-
77
- class Response(PagedResponse[Table]): ...
78
-
79
-
80
- class TableService(ServiceBase):
81
- def create(self, req: CreateTable.Request) -> CreateTable.Response:
82
- return self.client.post("/table/create", req, CreateTable.Response)
83
-
84
- def find(self, req: FindTable.Request) -> FindTable.Response:
85
- return self.client.put("/table/find", req, FindTable.Response)
86
-
87
- def get(self, req: GetTable.Request) -> GetTable.Response:
88
- return self.client.put(f"/table/{req.id}", GetTable.Response)
89
-
90
- def list(self, req: ListTables.Request) -> Paged[Table]:
91
- return self.client.paged("/table/list", req, ListTables.Response)
spiral/api/tokens.py DELETED
@@ -1,56 +0,0 @@
1
- from pydantic import BaseModel
2
-
3
- from . import Paged, PagedRequest, PagedResponse, ServiceBase
4
-
5
-
6
- class Token(BaseModel):
7
- id: str
8
- project_id: str
9
- on_behalf_of: str
10
-
11
-
12
- class ExchangeToken:
13
- class Request(BaseModel): ...
14
-
15
- class Response(BaseModel):
16
- token: str
17
-
18
-
19
- class IssueToken:
20
- class Request(BaseModel): ...
21
-
22
- class Response(BaseModel):
23
- token: Token
24
- token_secret: str
25
-
26
-
27
- class RevokeToken:
28
- class Request(BaseModel):
29
- token_id: str
30
-
31
- class Response(BaseModel):
32
- token: Token
33
-
34
-
35
- class ListTokens:
36
- class Request(PagedRequest):
37
- project_id: str
38
- on_behalf_of: str | None = None
39
-
40
- class Response(PagedResponse[Token]): ...
41
-
42
-
43
- class TokenService(ServiceBase):
44
- def exchange(self) -> ExchangeToken.Response:
45
- """Exchange a basic / identity token to a short-lived Spiral token."""
46
- return self.client.post("/token/exchange", ExchangeToken.Request(), ExchangeToken.Response)
47
-
48
- def issue(self) -> IssueToken.Response:
49
- """Issue an API token on behalf of a principal."""
50
- return self.client.post("/token/issue", IssueToken.Request(), IssueToken.Response)
51
-
52
- def revoke(self, request: RevokeToken.Request) -> RevokeToken.Response:
53
- return self.client.put("/token/revoke", request, RevokeToken.Response)
54
-
55
- def list(self, request: ListTokens.Request) -> Paged[Token]:
56
- return self.client.paged("/token/list", request, ListTokens.Response)
spiral/authn/authn.py DELETED
@@ -1,89 +0,0 @@
1
- import base64
2
- import logging
3
- import os
4
-
5
- from spiral.api import Authn, SpiralAPI
6
-
7
- ENV_TOKEN_ID = "SPIRAL_TOKEN_ID"
8
- ENV_TOKEN_SECRET = "SPIRAL_TOKEN_SECRET"
9
-
10
- log = logging.getLogger(__name__)
11
-
12
-
13
- class FallbackAuthn(Authn):
14
- """Credential provider that tries multiple providers in order."""
15
-
16
- def __init__(self, providers: list[Authn]):
17
- self._providers = providers
18
-
19
- def token(self) -> str | None:
20
- for provider in self._providers:
21
- token = provider.token()
22
- if token is not None:
23
- return token
24
- return None
25
-
26
-
27
- class TokenAuthn(Authn):
28
- """Credential provider that returns a fixed token."""
29
-
30
- def __init__(self, token: str):
31
- self._token = token
32
-
33
- def token(self) -> str:
34
- return self._token
35
-
36
-
37
- class EnvironmentAuthn(Authn):
38
- """Credential provider that returns a basic token from the environment.
39
-
40
- NOTE: Returns basic token. Must be exchanged.
41
- """
42
-
43
- def token(self) -> str | None:
44
- if ENV_TOKEN_ID not in os.environ:
45
- return None
46
- if ENV_TOKEN_SECRET not in os.environ:
47
- raise ValueError(f"{ENV_TOKEN_SECRET} is missing.")
48
-
49
- token_id = os.environ[ENV_TOKEN_ID]
50
- token_secret = os.environ[ENV_TOKEN_SECRET]
51
- basic_token = base64.b64encode(f"{token_id}:{token_secret}".encode()).decode("utf-8")
52
-
53
- return basic_token
54
-
55
-
56
- class DeviceAuthProvider(Authn):
57
- """Auth provider that uses the device flow to authenticate a Spiral user."""
58
-
59
- def __init__(self, device_auth):
60
- # NOTE(ngates): device_auth: spiral.auth.device_code.DeviceAuth
61
- # We don't type it to satisfy our import linter
62
- self._device_auth = device_auth
63
-
64
- def token(self) -> str | None:
65
- # TODO(ngates): only run this if we're in a notebook, CLI, or otherwise on the user's machine.
66
- return self._device_auth.authenticate().access_token
67
-
68
-
69
- class TokenExchangeProvider(Authn):
70
- """Auth provider that exchanges a basic token for a Spiral token."""
71
-
72
- def __init__(self, authn: Authn, base_url: str):
73
- self._authn = authn
74
- self._token_service = SpiralAPI(authn, base_url).token
75
-
76
- self._sp_token = None
77
-
78
- def token(self) -> str | None:
79
- if self._sp_token is not None:
80
- return self._sp_token
81
-
82
- # Don't try to exchange if token is not discovered.
83
- if self._authn.token() is None:
84
- return None
85
-
86
- log.debug("Exchanging token")
87
- self._sp_token = self._token_service.exchange().token
88
-
89
- return self._sp_token