pyspiral 0.5.0__cp310-abi3-macosx_11_0_arm64.whl → 0.6.0__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 (86) hide show
  1. {pyspiral-0.5.0.dist-info → pyspiral-0.6.0.dist-info}/METADATA +7 -3
  2. pyspiral-0.6.0.dist-info/RECORD +99 -0
  3. {pyspiral-0.5.0.dist-info → pyspiral-0.6.0.dist-info}/WHEEL +1 -1
  4. spiral/__init__.py +10 -3
  5. spiral/_lib.abi3.so +0 -0
  6. spiral/adbc.py +6 -6
  7. spiral/api/__init__.py +8 -2
  8. spiral/api/client.py +1 -1
  9. spiral/api/key_space_indexes.py +23 -0
  10. spiral/api/projects.py +15 -0
  11. spiral/api/text_indexes.py +1 -1
  12. spiral/cli/__init__.py +15 -6
  13. spiral/cli/admin.py +2 -4
  14. spiral/cli/app.py +4 -2
  15. spiral/cli/fs.py +5 -6
  16. spiral/cli/iceberg.py +97 -0
  17. spiral/cli/key_spaces.py +68 -0
  18. spiral/cli/login.py +6 -7
  19. spiral/cli/orgs.py +7 -8
  20. spiral/cli/printer.py +3 -3
  21. spiral/cli/projects.py +5 -6
  22. spiral/cli/tables.py +131 -0
  23. spiral/cli/telemetry.py +3 -4
  24. spiral/cli/text.py +115 -0
  25. spiral/cli/types.py +3 -4
  26. spiral/cli/workloads.py +7 -8
  27. spiral/client.py +111 -8
  28. spiral/core/authn/__init__.pyi +27 -0
  29. spiral/core/client/__init__.pyi +135 -63
  30. spiral/core/table/__init__.pyi +34 -24
  31. spiral/core/table/metastore/__init__.pyi +0 -4
  32. spiral/core/table/spec/__init__.pyi +0 -2
  33. spiral/{tables/dataset.py → dataset.py} +13 -7
  34. spiral/{tables/debug → debug}/manifests.py +17 -6
  35. spiral/{tables/debug → debug}/scan.py +3 -3
  36. spiral/expressions/base.py +3 -3
  37. spiral/expressions/udf.py +1 -1
  38. spiral/{iceberg/client.py → iceberg.py} +1 -3
  39. spiral/key_space_index.py +44 -0
  40. spiral/project.py +171 -18
  41. spiral/protogen/_/arrow/flight/protocol/sql/__init__.py +1668 -1110
  42. spiral/protogen/_/google/protobuf/__init__.py +2190 -0
  43. spiral/protogen/_/message_pool.py +3 -0
  44. spiral/protogen/_/py.typed +0 -0
  45. spiral/protogen/_/scandal/__init__.py +138 -126
  46. spiral/protogen/_/spfs/__init__.py +72 -0
  47. spiral/protogen/_/spql/__init__.py +61 -0
  48. spiral/protogen/_/substrait/__init__.py +5256 -2459
  49. spiral/protogen/_/substrait/extensions/__init__.py +103 -49
  50. spiral/{tables/scan.py → scan.py} +37 -44
  51. spiral/settings.py +14 -3
  52. spiral/snapshot.py +55 -0
  53. spiral/streaming_/__init__.py +3 -0
  54. spiral/streaming_/reader.py +117 -0
  55. spiral/streaming_/stream.py +146 -0
  56. spiral/substrait_.py +9 -9
  57. spiral/table.py +257 -0
  58. spiral/text_index.py +17 -0
  59. spiral/{tables/transaction.py → transaction.py} +11 -15
  60. pyspiral-0.5.0.dist-info/RECORD +0 -103
  61. spiral/cli/iceberg/__init__.py +0 -7
  62. spiral/cli/iceberg/namespaces.py +0 -47
  63. spiral/cli/iceberg/tables.py +0 -60
  64. spiral/cli/indexes/__init__.py +0 -40
  65. spiral/cli/indexes/args.py +0 -39
  66. spiral/cli/indexes/workers.py +0 -59
  67. spiral/cli/tables/__init__.py +0 -88
  68. spiral/cli/tables/args.py +0 -42
  69. spiral/core/index/__init__.pyi +0 -7
  70. spiral/iceberg/__init__.py +0 -3
  71. spiral/indexes/__init__.py +0 -5
  72. spiral/indexes/client.py +0 -137
  73. spiral/indexes/index.py +0 -28
  74. spiral/indexes/scan.py +0 -22
  75. spiral/protogen/_/spiral/table/__init__.py +0 -22
  76. spiral/protogen/substrait/__init__.py +0 -3399
  77. spiral/protogen/substrait/extensions/__init__.py +0 -115
  78. spiral/tables/__init__.py +0 -12
  79. spiral/tables/client.py +0 -133
  80. spiral/tables/maintenance.py +0 -12
  81. spiral/tables/snapshot.py +0 -78
  82. spiral/tables/table.py +0 -145
  83. {pyspiral-0.5.0.dist-info → pyspiral-0.6.0.dist-info}/entry_points.txt +0 -0
  84. /spiral/{protogen/_/spiral → debug}/__init__.py +0 -0
  85. /spiral/{tables/debug → debug}/metrics.py +0 -0
  86. /spiral/{tables/debug → protogen/_/google}/__init__.py +0 -0
@@ -1,47 +0,0 @@
1
- import sys
2
- from typing import Annotated
3
-
4
- import pyiceberg.exceptions
5
- import rich
6
- import typer
7
- from typer import Argument
8
-
9
- from spiral.cli import AsyncTyper, state
10
- from spiral.cli.types import ProjectArg
11
-
12
- app = AsyncTyper(short_help="Apache Iceberg Namespaces.")
13
-
14
-
15
- @app.command(help="List namespaces.")
16
- def ls(
17
- project: ProjectArg,
18
- namespace: Annotated[str | None, Argument(help="List only namespaces under this namespace.")] = None,
19
- ):
20
- """List Iceberg namespaces."""
21
- catalog = state.spiral.iceberg.catalog()
22
-
23
- if namespace is None:
24
- try:
25
- namespaces = catalog.list_namespaces(project)
26
- except pyiceberg.exceptions.ForbiddenError:
27
- print(
28
- f"The project, {repr(project)}, does not exist or you lack the "
29
- f"`iceberg:view` permission to list namespaces in it.",
30
- file=sys.stderr,
31
- )
32
- raise typer.Exit(code=1)
33
- else:
34
- try:
35
- namespaces = catalog.list_namespaces((project, namespace))
36
- except pyiceberg.exceptions.ForbiddenError:
37
- print(
38
- f"The namespace, {repr(project)}.{repr(namespace)}, does not exist or you lack the "
39
- f"`iceberg:view` permission to list namespaces in it.",
40
- file=sys.stderr,
41
- )
42
- raise typer.Exit(code=1)
43
-
44
- table = rich.table.Table("Namespace ID", title="Iceberg namespaces")
45
- for ns in namespaces:
46
- table.add_row(".".join(ns))
47
- rich.print(table)
@@ -1,60 +0,0 @@
1
- import sys
2
- from typing import Annotated
3
-
4
- import pyiceberg.exceptions
5
- import rich
6
- import typer
7
- from typer import Argument
8
-
9
- from spiral.cli import AsyncTyper, state
10
- from spiral.cli.types import ProjectArg
11
-
12
- app = AsyncTyper(short_help="Apache Iceberg Tables.")
13
-
14
-
15
- @app.command(help="List tables.")
16
- def ls(
17
- project: ProjectArg,
18
- namespace: Annotated[str | None, Argument(help="Show only tables in the given namespace.")] = None,
19
- ):
20
- catalog = state.spiral.iceberg.catalog()
21
-
22
- try:
23
- if namespace is None:
24
- tables = catalog.list_tables(project)
25
- else:
26
- tables = catalog.list_tables((project, namespace))
27
- except pyiceberg.exceptions.ForbiddenError:
28
- print(
29
- f"The namespace, {repr(project)}.{repr(namespace)}, does not exist or you lack the "
30
- f"`iceberg:view` permission to list tables in it.",
31
- file=sys.stderr,
32
- )
33
- raise typer.Exit(code=1)
34
-
35
- rich_table = rich.table.Table("table id", title="Iceberg tables")
36
- for table in tables:
37
- rich_table.add_row(".".join(table))
38
- rich.print(rich_table)
39
-
40
-
41
- @app.command(help="Show the table schema.")
42
- def schema(
43
- project: ProjectArg,
44
- namespace: Annotated[str, Argument(help="Table namespace.")],
45
- table: Annotated[str, Argument(help="Table name.")],
46
- ):
47
- catalog = state.spiral.iceberg.catalog()
48
-
49
- try:
50
- tbl = catalog.load_table((project, namespace, table))
51
- except pyiceberg.exceptions.NoSuchTableError:
52
- print(f"No table {repr(table)} found in {repr(project)}.{repr(namespace)}", file=sys.stderr)
53
- raise typer.Exit(code=1)
54
-
55
- rich_table = rich.table.Table(
56
- "Field ID", "Field name", "Type", "Required", "Doc", title=f"{project}.{namespace}.{table}"
57
- )
58
- for col in tbl.schema().columns:
59
- rich_table.add_row(str(col.field_id), col.name, str(col.field_type), str(col.required), col.doc)
60
- rich.print(rich_table)
@@ -1,40 +0,0 @@
1
- from typing import Annotated
2
-
3
- import rich
4
- from typer import Option
5
-
6
- from spiral.api.text_indexes import SyncIndexRequest
7
- from spiral.cli import AsyncTyper, state
8
- from spiral.cli.indexes.args import get_text_index_id
9
- from spiral.cli.types import ProjectArg
10
-
11
- from ...api.workers import ResourceClass
12
- from . import workers
13
-
14
- app = AsyncTyper(short_help="Indexes.")
15
- app.add_typer(workers.app, name="workers")
16
-
17
-
18
- @app.command(help="List indexes.")
19
- def ls(
20
- project: ProjectArg,
21
- ):
22
- """List indexes."""
23
- indexes = state.spiral.project(project).indexes.list_indexes()
24
-
25
- rich_table = rich.table.Table("id", "name", title="Indexes")
26
- for index in indexes:
27
- rich_table.add_row(index.id, index.name)
28
- rich.print(rich_table)
29
-
30
-
31
- @app.command(help="Trigger a sync job for the index.")
32
- def sync(
33
- project: ProjectArg,
34
- name: Annotated[str | None, Option(help="Index name.")] = None,
35
- resources: Annotated[ResourceClass, Option(help="Resources to use for the sync job.")] = ResourceClass.SMALL,
36
- ):
37
- """Trigger a sync job for the index."""
38
- index_id = get_text_index_id(project, name)
39
- response = state.spiral.api.text_indexes.sync_index(index_id, SyncIndexRequest(resources=resources))
40
- rich.print(f"Triggered sync job {response.worker_id} for index {index_id}.")
@@ -1,39 +0,0 @@
1
- from typing import Annotated
2
-
3
- import questionary
4
- import rich
5
- import typer
6
- from questionary import Choice
7
- from typer import Option
8
-
9
- from spiral.api.projects import TextIndexResource
10
- from spiral.api.types import IndexId
11
- from spiral.cli import state
12
- from spiral.cli.types import ProjectArg
13
-
14
-
15
- def ask_index(project_id, title="Select an index"):
16
- indexes: list[TextIndexResource] = list(state.spiral.project(project_id).indexes.list_indexes())
17
-
18
- if not indexes:
19
- rich.print("[red]No indexes found[/red]")
20
- raise typer.Exit(1)
21
-
22
- return questionary.select(
23
- title,
24
- choices=[Choice(title=index.name, value=index.id) for index in sorted(indexes, key=lambda t: (t.name, t.id))],
25
- ).ask()
26
-
27
-
28
- def get_text_index_id(
29
- project: ProjectArg,
30
- name: Annotated[str | None, Option(help="Index name.")] = None,
31
- ) -> IndexId:
32
- if name is None:
33
- return ask_index(project)
34
-
35
- indexes: list[TextIndexResource] = list(state.spiral.project(project).indexes.list_indexes())
36
- for index in indexes:
37
- if index.name == name:
38
- return index.id
39
- raise ValueError(f"Index not found: {name}")
@@ -1,59 +0,0 @@
1
- from typing import Annotated
2
-
3
- import rich
4
- from typer import Option
5
-
6
- from spiral.api.text_indexes import CreateWorkerRequest
7
- from spiral.api.workers import CPU, GcpRegion, Memory
8
- from spiral.cli import AsyncTyper, state
9
- from spiral.cli.indexes.args import get_text_index_id
10
- from spiral.cli.types import ProjectArg
11
-
12
- app = AsyncTyper(short_help="Text Search Workers.")
13
-
14
-
15
- @app.command(name="serve", help="Create a search worker.")
16
- def serve(
17
- project: ProjectArg,
18
- index: Annotated[str | None, Option(help="Index name.")] = None,
19
- region: Annotated[GcpRegion, Option(help="GCP region for the worker.")] = GcpRegion.US_EAST4,
20
- cpu: Annotated[CPU, Option(help="CPU resources for the worker.")] = CPU.ONE,
21
- memory: Annotated[Memory, Option(help="Memory resources for the worker in MB.")] = Memory.MB_512,
22
- ):
23
- """Create a new text search worker."""
24
- index_id = get_text_index_id(project, index)
25
- request = CreateWorkerRequest(cpu=cpu, memory=memory, region=region)
26
- response = state.spiral.api.text_indexes.create_worker(index_id, request)
27
- rich.print(f"Created worker {response.worker_id} for {index_id}.")
28
-
29
-
30
- @app.command(name="shutdown", help="Shutdown a search worker.")
31
- def shutdown(worker_id: str):
32
- """Shutdown a worker."""
33
- state.spiral.api.text_indexes.shutdown_worker(worker_id)
34
- rich.print(f"Requested worker {worker_id} to shutdown.")
35
-
36
-
37
- @app.command(name="ls", help="List search workers.")
38
- def ls(
39
- project: ProjectArg,
40
- index: Annotated[str | None, Option(help="Index name.")] = None,
41
- ):
42
- """List text search workers."""
43
- index_id = get_text_index_id(project, index)
44
- worker_ids = state.spiral.api.text_indexes.list_workers(index_id)
45
-
46
- rich_table = rich.table.Table("Worker ID", "URL", title=f"Text Search Workers for {index_id}")
47
- for worker_id in worker_ids:
48
- try:
49
- worker = state.spiral.api.text_indexes.get_worker(worker_id)
50
- rich_table.add_row(
51
- worker_id,
52
- worker.url,
53
- )
54
- except Exception:
55
- rich_table.add_row(
56
- worker_id,
57
- "Unavailable",
58
- )
59
- rich.print(rich_table)
@@ -1,88 +0,0 @@
1
- from typing import Annotated
2
-
3
- import rich
4
- from typer import Argument, Option
5
-
6
- from spiral import Spiral
7
- from spiral.cli import AsyncTyper
8
- from spiral.cli.tables.args import get_table
9
- from spiral.cli.types import ProjectArg
10
-
11
- app = AsyncTyper(short_help="Spiral Tables.")
12
-
13
-
14
- @app.command(help="List tables.")
15
- def ls(
16
- project: ProjectArg,
17
- ):
18
- tables = Spiral().project(project).tables.list_tables()
19
-
20
- rich_table = rich.table.Table("id", "dataset", "name", title="Spiral tables")
21
- for table in tables:
22
- rich_table.add_row(table.id, table.dataset, table.table)
23
- rich.print(rich_table)
24
-
25
-
26
- @app.command(help="Show the table key schema.")
27
- def key_schema(
28
- project: ProjectArg,
29
- table: Annotated[str | None, Option(help="Table name.")] = None,
30
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
31
- ):
32
- _, table = get_table(project, table, dataset)
33
- rich.print(table.key_schema)
34
-
35
-
36
- @app.command(help="Compute the full table schema.")
37
- def schema(
38
- project: ProjectArg,
39
- table: Annotated[str | None, Option(help="Table name.")] = None,
40
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
41
- ):
42
- _, table = get_table(project, table, dataset)
43
- rich.print(table.schema)
44
-
45
-
46
- @app.command(help="Flush Write-Ahead-Log.")
47
- def flush(
48
- project: ProjectArg,
49
- table: Annotated[str | None, Option(help="Table name.")] = None,
50
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
51
- ):
52
- identifier, table = get_table(project, table, dataset)
53
- table.maintenance().flush_wal()
54
- print(f"Flushed WAL for table {identifier} in project {project}.")
55
-
56
-
57
- @app.command(help="Display scan.")
58
- def debug(
59
- project: ProjectArg,
60
- table: Annotated[str | None, Option(help="Table name.")] = None,
61
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
62
- column_group: Annotated[str, Argument(help="Dot-separated column group path.")] = ".",
63
- ):
64
- _, table = get_table(project, table, dataset)
65
- if column_group != ".":
66
- projection = table[column_group]
67
- else:
68
- projection = table
69
- scan = table.scan(projection)
70
-
71
- scan._debug()
72
-
73
-
74
- @app.command(help="Display manifests.")
75
- def manifests(
76
- project: ProjectArg,
77
- table: Annotated[str | None, Option(help="Table name.")] = None,
78
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
79
- column_group: Annotated[str, Argument(help="Dot-separated column group path.")] = ".",
80
- ):
81
- _, table = get_table(project, table, dataset)
82
- if column_group != ".":
83
- projection = table[column_group]
84
- else:
85
- projection = table
86
- scan = projection.scan()
87
-
88
- scan._dump_manifests()
spiral/cli/tables/args.py DELETED
@@ -1,42 +0,0 @@
1
- from typing import Annotated
2
-
3
- import questionary
4
- import rich
5
- import typer
6
- from questionary import Choice
7
- from typer import Option
8
-
9
- from spiral.api.projects import TableResource
10
- from spiral.cli import state
11
- from spiral.cli.types import ProjectArg
12
- from spiral.tables import Table
13
-
14
-
15
- def ask_table(project_id, title="Select a table"):
16
- tables: list[TableResource] = list(state.spiral.project(project_id).tables.list_tables())
17
-
18
- if not tables:
19
- rich.print("[red]No tables found[/red]")
20
- raise typer.Exit(1)
21
-
22
- return questionary.select(
23
- title,
24
- choices=[
25
- Choice(title=f"{table.dataset}.{table.table}", value=f"{table.project_id}.{table.dataset}.{table.table}")
26
- for table in sorted(tables, key=lambda t: (t.dataset, t.table))
27
- ],
28
- ).ask()
29
-
30
-
31
- def get_table(
32
- project: ProjectArg,
33
- table: Annotated[str | None, Option(help="Table name.")] = None,
34
- dataset: Annotated[str | None, Option(help="Dataset name.")] = None,
35
- ) -> (str, Table):
36
- if table is None:
37
- identifier = ask_table(project)
38
- else:
39
- identifier = table
40
- if dataset is not None:
41
- identifier = f"{dataset}.{table}"
42
- return identifier, state.spiral.project(project).tables.table(identifier)
@@ -1,7 +0,0 @@
1
- import pyarrow as pa
2
-
3
- class TextIndex:
4
- id: str
5
-
6
- class SearchScan:
7
- def to_record_batches(self) -> pa.RecordBatchReader: ...
@@ -1,3 +0,0 @@
1
- from spiral.iceberg.client import Iceberg
2
-
3
- __all__ = ["Iceberg"]
@@ -1,5 +0,0 @@
1
- from spiral.indexes.client import Indexes
2
- from spiral.indexes.index import TextIndex
3
- from spiral.indexes.scan import SearchScan
4
-
5
- __all__ = ["Indexes", "SearchScan", "TextIndex"]
spiral/indexes/client.py DELETED
@@ -1,137 +0,0 @@
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 DELETED
@@ -1,28 +0,0 @@
1
- from typing import TYPE_CHECKING
2
-
3
- from spiral.core.index import TextIndex as CoreTextIndex
4
- from spiral.expressions import Expr
5
-
6
- if TYPE_CHECKING:
7
- from spiral.indexes import Indexes
8
-
9
-
10
- class TextIndex(Expr):
11
- def __init__(self, indexes: "Indexes", index: CoreTextIndex, name: str):
12
- super().__init__(index.__expr__)
13
-
14
- self._indexes = indexes
15
- self._index = index
16
- self._name = name
17
-
18
- @property
19
- def client(self) -> "Indexes":
20
- return self._indexes
21
-
22
- @property
23
- def index_id(self) -> str:
24
- return self._index.id
25
-
26
- @property
27
- def name(self) -> str:
28
- return self._name
spiral/indexes/scan.py DELETED
@@ -1,22 +0,0 @@
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()
@@ -1,22 +0,0 @@
1
- # Generated by the protocol buffer compiler. DO NOT EDIT!
2
- # sources: spiral/table/statistics.proto
3
- # plugin: python-betterproto
4
- # This file has been @generated
5
-
6
- from dataclasses import dataclass
7
-
8
- import betterproto
9
-
10
-
11
- @dataclass(eq=False, repr=False)
12
- class ApproximateSetMembership(betterproto.Message):
13
- bloom_filter: "BloomFilter" = betterproto.message_field(
14
- 2, group="membership_strategy"
15
- )
16
-
17
-
18
- @dataclass(eq=False, repr=False)
19
- class BloomFilter(betterproto.Message):
20
- bit_vec: bytes = betterproto.bytes_field(1)
21
- bitmap_bits: int = betterproto.uint64_field(2)
22
- k_num: int = betterproto.uint32_field(3)