pyspiral 0.3.1__cp310-abi3-macosx_11_0_arm64.whl → 0.4.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 (109) hide show
  1. {pyspiral-0.3.1.dist-info → pyspiral-0.4.0.dist-info}/METADATA +9 -13
  2. pyspiral-0.4.0.dist-info/RECORD +98 -0
  3. {pyspiral-0.3.1.dist-info → pyspiral-0.4.0.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.0.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,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
+ )
@@ -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 CHANGED
@@ -1,26 +1,18 @@
1
- from typing import TYPE_CHECKING, Any
2
-
3
- import pyarrow as pa
4
-
5
- from spiral import Table
6
- from spiral.api.tables import CreateTable, FindTable
7
- from spiral.core.core import Table as CoreTable
8
- from spiral.core.metastore import PyMetastore
9
- from spiral.core.spec import Schema
10
- from spiral.types_ import Uri
1
+ from typing import TYPE_CHECKING
11
2
 
12
3
  if TYPE_CHECKING:
13
- from spiral.catalog import Spiral
4
+ from spiral.client import Spiral
5
+ from spiral.iceberg import Iceberg
6
+ from spiral.indexes import Indexes
7
+ from spiral.tables import Tables
14
8
 
15
9
 
16
10
  class Project:
17
- def __init__(self, spiral_db: "Spiral", id: str, name: str | None = None):
18
- self._spiral_db = spiral_db
11
+ def __init__(self, spiral: "Spiral", id: str, name: str | None = None):
12
+ self._spiral = spiral
19
13
  self._id = id
20
14
  self._name = name
21
15
 
22
- self._api = self._spiral_db.config.api
23
-
24
16
  def __str__(self):
25
17
  return self._id
26
18
 
@@ -35,103 +27,20 @@ class Project:
35
27
  def name(self) -> str:
36
28
  return self._name or self._id
37
29
 
38
- def list_table_names(self) -> list[(str, str)]:
39
- """List tuples of (dataset, table) names in the project."""
40
- return [(t.dataset, t.table) for t in self._api.table.list(FindTable.Request(project_id=self.id))]
41
-
42
- def list_tables(self) -> list[Table]:
43
- """List tables in the project."""
44
- return [
45
- Table(
46
- CoreTable(
47
- PyMetastore.http(
48
- table_id=t.id,
49
- root_uri=t.metadata.root_uri,
50
- key_schema=Schema.from_arrow(t.metadata.key_schema),
51
- base_url=self._api.base_url + "/metastore/",
52
- token_provider=self._spiral_db.config.authn.token,
53
- ),
54
- ),
55
- name=f"{self.id}.{t.dataset}.{t.table}",
56
- )
57
- for t in self._api.table.list(FindTable.Request(project_id=self.id))
58
- ]
59
-
60
- def create_table(
61
- self,
62
- identifier: str,
63
- *,
64
- key_schema: pa.Schema | Any,
65
- uri: Uri | None = None,
66
- exist_ok: bool = False,
67
- ) -> Table:
68
- """Create a new table in the project."""
69
- dataset, table = self._parse_identifier(identifier)
70
-
71
- if not isinstance(key_schema, pa.Schema):
72
- key_schema = pa.schema(key_schema)
73
-
74
- res = self._api.table.create(
75
- CreateTable.Request(
76
- project_id=self.id,
77
- dataset=dataset,
78
- table=table,
79
- key_schema=key_schema,
80
- root_uri=uri,
81
- exist_ok=exist_ok,
82
- )
83
- )
84
-
85
- # Must have the same schema as provided, even if the table already exists.
86
- expected_key_schema = res.table.metadata.key_schema
87
- if key_schema != expected_key_schema:
88
- raise ValueError(f"Table already exists with different key schema: {expected_key_schema} != {key_schema}")
89
- if uri and res.table.metadata.root_uri != uri:
90
- raise ValueError(f"Table already exists with different root URI: {res.table.metadata.root_uri} != {uri}")
91
-
92
- # Set up a metastore backed by SpiralDB
93
- metastore = PyMetastore.http(
94
- table_id=res.table.id,
95
- root_uri=res.table.metadata.root_uri,
96
- key_schema=Schema.from_arrow(res.table.metadata.key_schema),
97
- base_url=self._api.base_url + "/metastore/",
98
- token_provider=self._spiral_db.config.authn.token,
99
- )
100
-
101
- return Table(CoreTable(metastore), name=f"{self.id}.{res.table.dataset}.{res.table.table}")
30
+ @property
31
+ def tables(self) -> "Tables":
32
+ from spiral.tables import Tables
102
33
 
103
- def table(self, identifier: str) -> Table:
104
- """Open a table with a `dataset.table` identifier, or `table` name using the `default` dataset."""
105
- dataset, table = self._parse_identifier(identifier)
34
+ return Tables(self._spiral._api, self._spiral._core, project_id=self.id)
106
35
 
107
- # TODO(ngates): why does the client _need_ this information? Can we defer it?
108
- res = self._api.table.find(
109
- FindTable.Request(
110
- project_id=self.id,
111
- dataset=dataset,
112
- table=table,
113
- )
114
- )
115
- if res.table is None:
116
- raise ValueError(f"Table not found: {self.id}.{dataset}.{table}")
36
+ @property
37
+ def indexes(self) -> "Indexes":
38
+ from spiral.indexes.client import Indexes
117
39
 
118
- # Set up a metastore backed by SpiralDB
119
- metastore = PyMetastore.http(
120
- table_id=res.table.id,
121
- root_uri=res.table.metadata.root_uri,
122
- key_schema=Schema.from_arrow(res.table.metadata.key_schema),
123
- base_url=self._api.base_url + "/metastore/",
124
- token_provider=self._spiral_db.config.authn.token,
125
- )
40
+ return Indexes(self._spiral._api, self._spiral._core, project_id=self._id)
126
41
 
127
- return Table(CoreTable(metastore), name=f"{self.id}.{res.table.dataset}.{res.table.table}")
42
+ @property
43
+ def iceberg(self) -> "Iceberg":
44
+ from spiral.iceberg import Iceberg
128
45
 
129
- @staticmethod
130
- def _parse_identifier(identifier: str) -> tuple[str, str]:
131
- parts = identifier.split(".")
132
- if len(parts) == 1:
133
- return "default", parts[0]
134
- elif len(parts) == 2:
135
- return parts[0], parts[1]
136
- else:
137
- raise ValueError(f"Invalid table identifier: {identifier}")
46
+ return Iceberg(self._spiral, project_id=self._id)
@@ -30,11 +30,31 @@ class Source(betterproto.Message):
30
30
  parquet: "MetadataParquet" = betterproto.message_field(10, group="metadata")
31
31
 
32
32
 
33
+ @dataclass(eq=False, repr=False)
34
+ class Metadata(betterproto.Message):
35
+ pass
36
+
37
+
38
+ @dataclass(eq=False, repr=False)
39
+ class MetadataParquet(betterproto.Message):
40
+ pass
41
+
42
+
33
43
  @dataclass(eq=False, repr=False)
34
44
  class Sink(betterproto.Message):
35
45
  url: str = betterproto.string_field(1)
36
46
 
37
47
 
48
+ @dataclass(eq=False, repr=False)
49
+ class Connectivity(betterproto.Message):
50
+ """
51
+ Information about the client's perceived connectivity to a FileSystem.
52
+ """
53
+
54
+ unreachable: bool = betterproto.bool_field(1)
55
+ round_trip_time_us: Optional[int] = betterproto.int32_field(2, optional=True)
56
+
57
+
38
58
  @dataclass(eq=False, repr=False)
39
59
  class Fetch(betterproto.Message):
40
60
  """Let's make "fetch" happen."""
@@ -52,15 +72,10 @@ class FetchRequest(betterproto.Message):
52
72
  spfs://<fsid>/path?token=<jwt&gt URI.
53
73
  """
54
74
 
55
- headers: Dict[str, str] = betterproto.map_field(
56
- 2, betterproto.TYPE_STRING, betterproto.TYPE_STRING
57
- )
58
- """Custom headers to sign into the request."""
59
-
60
- connectivity: "Connectivity" = betterproto.message_field(3)
75
+ connectivity: "Connectivity" = betterproto.message_field(2)
61
76
  """Declares whether the client has connectivity to the FileSystem."""
62
77
 
63
- accepts: List[str] = betterproto.string_field(4)
78
+ accepts: List[str] = betterproto.string_field(3)
64
79
  """Declares the MIME types the client can read directly."""
65
80
 
66
81
 
@@ -81,10 +96,7 @@ class Put(betterproto.Message):
81
96
  @dataclass(eq=False, repr=False)
82
97
  class PutRequest(betterproto.Message):
83
98
  uri: str = betterproto.string_field(1)
84
- headers: Dict[str, str] = betterproto.map_field(
85
- 2, betterproto.TYPE_STRING, betterproto.TYPE_STRING
86
- )
87
- connectivity: "Connectivity" = betterproto.message_field(3)
99
+ connectivity: "Connectivity" = betterproto.message_field(2)
88
100
 
89
101
 
90
102
  @dataclass(eq=False, repr=False)
@@ -92,64 +104,6 @@ class PutResponse(betterproto.Message):
92
104
  sinks: List["Sink"] = betterproto.message_field(1)
93
105
 
94
106
 
95
- @dataclass(eq=False, repr=False)
96
- class Head(betterproto.Message):
97
- pass
98
-
99
-
100
- @dataclass(eq=False, repr=False)
101
- class HeadRequest(betterproto.Message):
102
- uri: str = betterproto.string_field(1)
103
- headers: Dict[str, str] = betterproto.map_field(
104
- 2, betterproto.TYPE_STRING, betterproto.TYPE_STRING
105
- )
106
-
107
-
108
- @dataclass(eq=False, repr=False)
109
- class HeadResponse(betterproto.Message):
110
- url: str = betterproto.string_field(1)
111
- """Returns signed URL to head the resource."""
112
-
113
-
114
- @dataclass(eq=False, repr=False)
115
- class Delete(betterproto.Message):
116
- pass
117
-
118
-
119
- @dataclass(eq=False, repr=False)
120
- class DeleteRequest(betterproto.Message):
121
- uri: str = betterproto.string_field(1)
122
- headers: Dict[str, str] = betterproto.map_field(
123
- 2, betterproto.TYPE_STRING, betterproto.TYPE_STRING
124
- )
125
-
126
-
127
- @dataclass(eq=False, repr=False)
128
- class DeleteResponse(betterproto.Message):
129
- url: str = betterproto.string_field(1)
130
- """Returns signed URL to delete the resource."""
131
-
132
-
133
- @dataclass(eq=False, repr=False)
134
- class Connectivity(betterproto.Message):
135
- """
136
- Information about the client's perceived connectivity to a FileSystem.
137
- """
138
-
139
- unreachable: bool = betterproto.bool_field(1)
140
- round_trip_time_us: Optional[int] = betterproto.int32_field(2, optional=True)
141
-
142
-
143
- @dataclass(eq=False, repr=False)
144
- class Metadata(betterproto.Message):
145
- pass
146
-
147
-
148
- @dataclass(eq=False, repr=False)
149
- class MetadataParquet(betterproto.Message):
150
- pass
151
-
152
-
153
107
  class ScandalServiceStub(betterproto.ServiceStub):
154
108
  async def fetch(
155
109
  self,
@@ -185,40 +139,6 @@ class ScandalServiceStub(betterproto.ServiceStub):
185
139
  metadata=metadata,
186
140
  )
187
141
 
188
- async def head(
189
- self,
190
- head_request: "HeadRequest",
191
- *,
192
- timeout: Optional[float] = None,
193
- deadline: Optional["Deadline"] = None,
194
- metadata: Optional["MetadataLike"] = None
195
- ) -> "HeadResponse":
196
- return await self._unary_unary(
197
- "/scandal.ScandalService/Head",
198
- head_request,
199
- HeadResponse,
200
- timeout=timeout,
201
- deadline=deadline,
202
- metadata=metadata,
203
- )
204
-
205
- async def delete(
206
- self,
207
- delete_request: "DeleteRequest",
208
- *,
209
- timeout: Optional[float] = None,
210
- deadline: Optional["Deadline"] = None,
211
- metadata: Optional["MetadataLike"] = None
212
- ) -> "DeleteResponse":
213
- return await self._unary_unary(
214
- "/scandal.ScandalService/Delete",
215
- delete_request,
216
- DeleteResponse,
217
- timeout=timeout,
218
- deadline=deadline,
219
- metadata=metadata,
220
- )
221
-
222
142
 
223
143
  class ScandalServiceBase(ServiceBase):
224
144
  async def fetch(self, fetch_request: "FetchRequest") -> "FetchResponse":
@@ -227,12 +147,6 @@ class ScandalServiceBase(ServiceBase):
227
147
  async def put(self, put_request: "PutRequest") -> "PutResponse":
228
148
  raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
229
149
 
230
- async def head(self, head_request: "HeadRequest") -> "HeadResponse":
231
- raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
232
-
233
- async def delete(self, delete_request: "DeleteRequest") -> "DeleteResponse":
234
- raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
235
-
236
150
  async def __rpc_fetch(
237
151
  self, stream: "grpclib.server.Stream[FetchRequest, FetchResponse]"
238
152
  ) -> None:
@@ -247,20 +161,6 @@ class ScandalServiceBase(ServiceBase):
247
161
  response = await self.put(request)
248
162
  await stream.send_message(response)
249
163
 
250
- async def __rpc_head(
251
- self, stream: "grpclib.server.Stream[HeadRequest, HeadResponse]"
252
- ) -> None:
253
- request = await stream.recv_message()
254
- response = await self.head(request)
255
- await stream.send_message(response)
256
-
257
- async def __rpc_delete(
258
- self, stream: "grpclib.server.Stream[DeleteRequest, DeleteResponse]"
259
- ) -> None:
260
- request = await stream.recv_message()
261
- response = await self.delete(request)
262
- await stream.send_message(response)
263
-
264
164
  def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
265
165
  return {
266
166
  "/scandal.ScandalService/Fetch": grpclib.const.Handler(
@@ -275,16 +175,4 @@ class ScandalServiceBase(ServiceBase):
275
175
  PutRequest,
276
176
  PutResponse,
277
177
  ),
278
- "/scandal.ScandalService/Head": grpclib.const.Handler(
279
- self.__rpc_head,
280
- grpclib.const.Cardinality.UNARY_UNARY,
281
- HeadRequest,
282
- HeadResponse,
283
- ),
284
- "/scandal.ScandalService/Delete": grpclib.const.Handler(
285
- self.__rpc_delete,
286
- grpclib.const.Cardinality.UNARY_UNARY,
287
- DeleteRequest,
288
- DeleteResponse,
289
- ),
290
178
  }
@@ -0,0 +1,22 @@
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)