pyspiral 0.4.0__pp310-pypy310_pp73-macosx_10_12_x86_64.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.4.0.dist-info/METADATA +46 -0
- pyspiral-0.4.0.dist-info/RECORD +98 -0
- pyspiral-0.4.0.dist-info/WHEEL +4 -0
- pyspiral-0.4.0.dist-info/entry_points.txt +2 -0
- spiral/__init__.py +10 -0
- spiral/_lib.pypy310-pp73-darwin.so +0 -0
- spiral/adbc.py +393 -0
- spiral/api/__init__.py +64 -0
- spiral/api/admin.py +15 -0
- spiral/api/client.py +160 -0
- spiral/api/filesystems.py +153 -0
- spiral/api/organizations.py +77 -0
- spiral/api/projects.py +197 -0
- spiral/api/telemetry.py +19 -0
- spiral/api/types.py +20 -0
- spiral/api/workloads.py +52 -0
- spiral/arrow_.py +221 -0
- spiral/cli/__init__.py +79 -0
- spiral/cli/__main__.py +4 -0
- spiral/cli/admin.py +16 -0
- spiral/cli/app.py +65 -0
- spiral/cli/console.py +95 -0
- spiral/cli/fs.py +112 -0
- spiral/cli/iceberg/__init__.py +7 -0
- spiral/cli/iceberg/namespaces.py +47 -0
- spiral/cli/iceberg/tables.py +60 -0
- spiral/cli/indexes/__init__.py +19 -0
- spiral/cli/login.py +22 -0
- spiral/cli/orgs.py +90 -0
- spiral/cli/printer.py +53 -0
- spiral/cli/projects.py +136 -0
- spiral/cli/state.py +5 -0
- spiral/cli/tables/__init__.py +121 -0
- spiral/cli/telemetry.py +18 -0
- spiral/cli/types.py +51 -0
- spiral/cli/workloads.py +59 -0
- spiral/client.py +79 -0
- spiral/core/__init__.pyi +0 -0
- spiral/core/client/__init__.pyi +117 -0
- spiral/core/index/__init__.pyi +15 -0
- spiral/core/table/__init__.pyi +108 -0
- spiral/core/table/manifests/__init__.pyi +35 -0
- spiral/core/table/metastore/__init__.pyi +62 -0
- spiral/core/table/spec/__init__.pyi +214 -0
- spiral/datetime_.py +27 -0
- spiral/expressions/__init__.py +245 -0
- spiral/expressions/base.py +149 -0
- spiral/expressions/http.py +86 -0
- spiral/expressions/io.py +100 -0
- spiral/expressions/list_.py +68 -0
- spiral/expressions/mp4.py +62 -0
- spiral/expressions/png.py +18 -0
- spiral/expressions/qoi.py +18 -0
- spiral/expressions/refs.py +58 -0
- spiral/expressions/str_.py +39 -0
- spiral/expressions/struct.py +59 -0
- spiral/expressions/text.py +62 -0
- spiral/expressions/tiff.py +223 -0
- spiral/expressions/udf.py +46 -0
- spiral/grpc_.py +32 -0
- spiral/iceberg/__init__.py +3 -0
- spiral/iceberg/client.py +33 -0
- spiral/indexes/__init__.py +5 -0
- spiral/indexes/client.py +137 -0
- spiral/indexes/index.py +34 -0
- spiral/indexes/scan.py +22 -0
- spiral/project.py +46 -0
- spiral/protogen/_/__init__.py +0 -0
- spiral/protogen/_/arrow/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/protocol/__init__.py +0 -0
- spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +1990 -0
- spiral/protogen/_/scandal/__init__.py +178 -0
- spiral/protogen/_/spiral/__init__.py +0 -0
- spiral/protogen/_/spiral/table/__init__.py +22 -0
- spiral/protogen/_/substrait/__init__.py +3399 -0
- spiral/protogen/_/substrait/extensions/__init__.py +115 -0
- spiral/protogen/__init__.py +0 -0
- spiral/protogen/substrait/__init__.py +3399 -0
- spiral/protogen/substrait/extensions/__init__.py +115 -0
- spiral/protogen/util.py +41 -0
- spiral/py.typed +0 -0
- spiral/server.py +17 -0
- spiral/settings.py +101 -0
- spiral/substrait_.py +279 -0
- spiral/tables/__init__.py +12 -0
- spiral/tables/client.py +130 -0
- spiral/tables/dataset.py +250 -0
- spiral/tables/debug/__init__.py +0 -0
- spiral/tables/debug/manifests.py +70 -0
- spiral/tables/debug/metrics.py +56 -0
- spiral/tables/debug/scan.py +248 -0
- spiral/tables/maintenance.py +12 -0
- spiral/tables/scan.py +193 -0
- spiral/tables/snapshot.py +78 -0
- spiral/tables/table.py +157 -0
- spiral/tables/transaction.py +52 -0
- spiral/types_.py +6 -0
spiral/indexes/client.py
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
from spiral.api import SpiralAPI
|
4
|
+
from spiral.api.projects import TextIndexResource
|
5
|
+
from spiral.core.client import Spiral as CoreSpiral
|
6
|
+
from spiral.expressions.base import ExprLike
|
7
|
+
from spiral.indexes.index import TextIndex
|
8
|
+
from spiral.indexes.scan import SearchScan
|
9
|
+
from spiral.types_ import Uri
|
10
|
+
|
11
|
+
|
12
|
+
class Indexes:
|
13
|
+
def __init__(self, api: SpiralAPI, spiral: CoreSpiral, *, project_id: str | None = None):
|
14
|
+
self._api = api
|
15
|
+
self._spiral = spiral
|
16
|
+
self._project_id = project_id
|
17
|
+
|
18
|
+
def index(self, identifier: str) -> TextIndex:
|
19
|
+
"""Returns the index with the given identifier."""
|
20
|
+
project_id, index_name = self._parse_identifier(identifier)
|
21
|
+
if project_id is None:
|
22
|
+
raise ValueError("Must provide a fully qualified index identifier.")
|
23
|
+
|
24
|
+
res = list(self._api.project.list_text_indexes(project_id, name=index_name))
|
25
|
+
if len(res) == 0:
|
26
|
+
raise ValueError(f"Index not found: {project_id}.{index_name}")
|
27
|
+
res = res[0]
|
28
|
+
|
29
|
+
return TextIndex(self, self._spiral.get_text_index(res.id), index_name)
|
30
|
+
|
31
|
+
def list_indexes(self) -> list[TextIndexResource]:
|
32
|
+
project_id = self._project_id
|
33
|
+
if project_id is None:
|
34
|
+
raise ValueError("Must provide a project ID to list indexes.")
|
35
|
+
return list(self._api.project.list_text_indexes(project_id))
|
36
|
+
|
37
|
+
def create_text_index(
|
38
|
+
self,
|
39
|
+
identifier: str,
|
40
|
+
# At least one projection is required. All projections must reference the same table!
|
41
|
+
# NOTE(marko): Indexes are currently independent of tables.
|
42
|
+
# That will likely change with the new root resource such as documents.
|
43
|
+
*projections: ExprLike,
|
44
|
+
where: ExprLike | None = None,
|
45
|
+
root_uri: Uri | None = None,
|
46
|
+
exist_ok: bool = False,
|
47
|
+
) -> TextIndex:
|
48
|
+
"""Creates a text index over the table projection.
|
49
|
+
|
50
|
+
See `se.text.field` for how to create and configure indexable fields.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
identifier: The index identifier, in the form `project.index` or `index`.
|
54
|
+
projections: At least one projection expression is required.
|
55
|
+
All projections must reference the same table.
|
56
|
+
where: An optional filter expression to apply to the index.
|
57
|
+
root_uri: The root URI for the index.
|
58
|
+
exist_ok: If True, do not raise an error if the index already exists.
|
59
|
+
"""
|
60
|
+
from spiral import expressions as se
|
61
|
+
|
62
|
+
project_id, index_name = self._parse_identifier(identifier)
|
63
|
+
if project_id is None:
|
64
|
+
raise ValueError("Must provide a fully qualified index identifier.")
|
65
|
+
|
66
|
+
if not projections:
|
67
|
+
raise ValueError("At least one projection is required.")
|
68
|
+
projection = se.merge(*projections)
|
69
|
+
if where is not None:
|
70
|
+
where = se.lift(where)
|
71
|
+
|
72
|
+
core_index = self._spiral.create_text_index(
|
73
|
+
project_id,
|
74
|
+
index_name,
|
75
|
+
projection.__expr__,
|
76
|
+
where.__expr__ if where else None,
|
77
|
+
root_uri=root_uri,
|
78
|
+
# TODO(marko): Validate that if an index exists, it's the same?
|
79
|
+
exist_ok=exist_ok,
|
80
|
+
)
|
81
|
+
|
82
|
+
return TextIndex(self, core_index, index_name)
|
83
|
+
|
84
|
+
def _parse_identifier(self, identifier: str) -> tuple[str | None, str]:
|
85
|
+
parts = identifier.split(".")
|
86
|
+
if len(parts) == 1:
|
87
|
+
return self._project_id, parts[0]
|
88
|
+
elif len(parts) == 2:
|
89
|
+
return parts[0], parts[1]
|
90
|
+
else:
|
91
|
+
raise ValueError(f"Invalid index identifier: {identifier}")
|
92
|
+
|
93
|
+
def search(
|
94
|
+
self,
|
95
|
+
*rank_by: ExprLike,
|
96
|
+
where: ExprLike | None = None,
|
97
|
+
top_k: int = 10,
|
98
|
+
# Do not refresh the index if freshness does not exceed the freshness window.
|
99
|
+
# NOTE(marko): The current implementation fails the query if the index is stale.
|
100
|
+
freshness_window: datetime.timedelta | None = None,
|
101
|
+
) -> SearchScan:
|
102
|
+
"""Queries the index with the given rank by and where clauses.
|
103
|
+
|
104
|
+
Rank by expressions are combined for scoring.
|
105
|
+
See `se.text.find` and `se.text.boost` for scoring expressions.
|
106
|
+
The `where` expression is used to filter the results.
|
107
|
+
It must return a boolean value and use only conjunctions (ANDs). Expressions in where statement
|
108
|
+
are considered either a `must` or `must_not` clause in search terminology.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
rank_by: At least one rank by expression is required.
|
112
|
+
These expressions are used to score the results.
|
113
|
+
where: An optional filter expression to apply to the index.
|
114
|
+
It must return a boolean value and use only conjunctions (ANDs).
|
115
|
+
top_k: The number of top results to return.
|
116
|
+
freshness_window: If provided, the index will not be refreshed if its freshness does not exceed this window.
|
117
|
+
"""
|
118
|
+
from spiral import expressions as se
|
119
|
+
|
120
|
+
if not rank_by:
|
121
|
+
raise ValueError("At least one rank by expression is required.")
|
122
|
+
rank_by = se.or_(*rank_by)
|
123
|
+
if where is not None:
|
124
|
+
where = se.lift(where)
|
125
|
+
|
126
|
+
if freshness_window is None:
|
127
|
+
freshness_window = datetime.timedelta(seconds=0)
|
128
|
+
freshness_window_s = int(freshness_window.total_seconds())
|
129
|
+
|
130
|
+
return SearchScan(
|
131
|
+
self._spiral.open_search_scan(
|
132
|
+
rank_by.__expr__,
|
133
|
+
top_k=top_k,
|
134
|
+
freshness_window_s=freshness_window_s,
|
135
|
+
filter=where.__expr__ if where else None,
|
136
|
+
)
|
137
|
+
)
|
spiral/indexes/index.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
import datetime
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from spiral.core.index import TextIndex as CoreTextIndex
|
5
|
+
from spiral.expressions import Expr
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from spiral.indexes import Indexes
|
9
|
+
|
10
|
+
|
11
|
+
class TextIndex(Expr):
|
12
|
+
def __init__(self, indexes: "Indexes", index: CoreTextIndex, name: str):
|
13
|
+
super().__init__(index.__expr__)
|
14
|
+
|
15
|
+
self._indexes = indexes
|
16
|
+
self._index = index
|
17
|
+
self._name = name
|
18
|
+
|
19
|
+
@property
|
20
|
+
def client(self) -> "Indexes":
|
21
|
+
return self._indexes
|
22
|
+
|
23
|
+
@property
|
24
|
+
def index_id(self) -> str:
|
25
|
+
return self._index.id
|
26
|
+
|
27
|
+
@property
|
28
|
+
def name(self) -> str:
|
29
|
+
return self._name
|
30
|
+
|
31
|
+
def status(self) -> (str, datetime.timedelta | None):
|
32
|
+
"""Fetch the status of the index. If status is ready, returns the staleness of the index."""
|
33
|
+
status = self._index.status()
|
34
|
+
return status.status, datetime.timedelta(seconds=status.staleness_s) if status.staleness_s is not None else None
|
spiral/indexes/scan.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
import pyarrow as pa
|
2
|
+
|
3
|
+
from spiral.core.index import SearchScan as CoreSearchScan
|
4
|
+
from spiral.settings import CI, DEV
|
5
|
+
|
6
|
+
|
7
|
+
class SearchScan:
|
8
|
+
def __init__(self, scan: CoreSearchScan):
|
9
|
+
self._scan = scan
|
10
|
+
|
11
|
+
def to_record_batches(self) -> pa.RecordBatchReader:
|
12
|
+
"""Read all results as a record batch reader."""
|
13
|
+
return self._scan.to_record_batches()
|
14
|
+
|
15
|
+
def to_table(self) -> pa.Table:
|
16
|
+
"""Read all results as a table."""
|
17
|
+
# NOTE: Evaluates fully on Rust side which improved debuggability.
|
18
|
+
if DEV and not CI:
|
19
|
+
rb = self._scan.to_record_batch()
|
20
|
+
return pa.Table.from_batches([rb])
|
21
|
+
|
22
|
+
return self.to_record_batches().read_all()
|
spiral/project.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
if TYPE_CHECKING:
|
4
|
+
from spiral.client import Spiral
|
5
|
+
from spiral.iceberg import Iceberg
|
6
|
+
from spiral.indexes import Indexes
|
7
|
+
from spiral.tables import Tables
|
8
|
+
|
9
|
+
|
10
|
+
class Project:
|
11
|
+
def __init__(self, spiral: "Spiral", id: str, name: str | None = None):
|
12
|
+
self._spiral = spiral
|
13
|
+
self._id = id
|
14
|
+
self._name = name
|
15
|
+
|
16
|
+
def __str__(self):
|
17
|
+
return self._id
|
18
|
+
|
19
|
+
def __repr__(self):
|
20
|
+
return f"Project(id={self._id}{', name=' + self._name if self._name else ''})"
|
21
|
+
|
22
|
+
@property
|
23
|
+
def id(self) -> str:
|
24
|
+
return self._id
|
25
|
+
|
26
|
+
@property
|
27
|
+
def name(self) -> str:
|
28
|
+
return self._name or self._id
|
29
|
+
|
30
|
+
@property
|
31
|
+
def tables(self) -> "Tables":
|
32
|
+
from spiral.tables import Tables
|
33
|
+
|
34
|
+
return Tables(self._spiral._api, self._spiral._core, project_id=self.id)
|
35
|
+
|
36
|
+
@property
|
37
|
+
def indexes(self) -> "Indexes":
|
38
|
+
from spiral.indexes.client import Indexes
|
39
|
+
|
40
|
+
return Indexes(self._spiral._api, self._spiral._core, project_id=self._id)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def iceberg(self) -> "Iceberg":
|
44
|
+
from spiral.iceberg import Iceberg
|
45
|
+
|
46
|
+
return Iceberg(self._spiral, project_id=self._id)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|