graphique 1.8__tar.gz → 1.9__tar.gz
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.
- {graphique-1.8 → graphique-1.9}/PKG-INFO +6 -20
- {graphique-1.8 → graphique-1.9}/graphique/core.py +9 -10
- {graphique-1.8 → graphique-1.9}/graphique/inputs.py +166 -165
- {graphique-1.8 → graphique-1.9}/graphique/interface.py +16 -14
- {graphique-1.8 → graphique-1.9}/graphique/middleware.py +4 -5
- {graphique-1.8 → graphique-1.9}/graphique/models.py +26 -27
- {graphique-1.8 → graphique-1.9}/graphique/scalars.py +3 -2
- {graphique-1.8 → graphique-1.9}/graphique/shell.py +2 -2
- {graphique-1.8 → graphique-1.9}/graphique.egg-info/PKG-INFO +6 -20
- {graphique-1.8 → graphique-1.9}/graphique.egg-info/requires.txt +1 -1
- {graphique-1.8 → graphique-1.9}/pyproject.toml +3 -5
- {graphique-1.8 → graphique-1.9}/requirements.in +1 -1
- {graphique-1.8 → graphique-1.9}/tests/test_core.py +2 -0
- {graphique-1.8 → graphique-1.9}/LICENSE.txt +0 -0
- {graphique-1.8 → graphique-1.9}/README.md +0 -0
- {graphique-1.8 → graphique-1.9}/graphique/__init__.py +0 -0
- {graphique-1.8 → graphique-1.9}/graphique/py.typed +0 -0
- {graphique-1.8 → graphique-1.9}/graphique/service.py +0 -0
- {graphique-1.8 → graphique-1.9}/graphique.egg-info/SOURCES.txt +0 -0
- {graphique-1.8 → graphique-1.9}/graphique.egg-info/dependency_links.txt +0 -0
- {graphique-1.8 → graphique-1.9}/graphique.egg-info/top_level.txt +0 -0
- {graphique-1.8 → graphique-1.9}/setup.cfg +0 -0
- {graphique-1.8 → graphique-1.9}/tests/test_bench.py +0 -0
- {graphique-1.8 → graphique-1.9}/tests/test_dataset.py +0 -0
- {graphique-1.8 → graphique-1.9}/tests/test_models.py +0 -0
- {graphique-1.8 → graphique-1.9}/tests/test_service.py +0 -0
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: graphique
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9
|
|
4
4
|
Summary: GraphQL service for arrow tables and parquet data sets.
|
|
5
5
|
Author-email: Aric Coady <aric.coady@gmail.com>
|
|
6
|
-
License:
|
|
7
|
-
|
|
8
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
-
you may not use this file except in compliance with the License.
|
|
10
|
-
You may obtain a copy of the License at
|
|
11
|
-
|
|
12
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
-
|
|
14
|
-
Unless required by applicable law or agreed to in writing, software
|
|
15
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
-
See the License for the specific language governing permissions and
|
|
18
|
-
limitations under the License.
|
|
19
|
-
|
|
6
|
+
License: Apache-2.0
|
|
20
7
|
Project-URL: Homepage, https://github.com/coady/graphique
|
|
21
8
|
Project-URL: Documentation, https://coady.github.io/graphique
|
|
22
9
|
Project-URL: Changelog, https://github.com/coady/graphique/blob/main/CHANGELOG.md
|
|
@@ -25,10 +12,8 @@ Keywords: graphql,arrow,parquet
|
|
|
25
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
26
13
|
Classifier: Intended Audience :: Developers
|
|
27
14
|
Classifier: Intended Audience :: Science/Research
|
|
28
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
29
15
|
Classifier: Operating System :: OS Independent
|
|
30
16
|
Classifier: Programming Language :: Python :: 3
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
32
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
33
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -38,10 +23,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
|
38
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
39
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
25
|
Classifier: Typing :: Typed
|
|
41
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.10
|
|
42
27
|
Description-Content-Type: text/markdown
|
|
43
28
|
License-File: LICENSE.txt
|
|
44
|
-
Requires-Dist: pyarrow>=
|
|
29
|
+
Requires-Dist: pyarrow>=20
|
|
45
30
|
Requires-Dist: strawberry-graphql[asgi,cli]>=0.236
|
|
46
31
|
Requires-Dist: numpy
|
|
47
32
|
Requires-Dist: isodate>=0.7
|
|
@@ -49,6 +34,7 @@ Provides-Extra: server
|
|
|
49
34
|
Requires-Dist: uvicorn[standard]; extra == "server"
|
|
50
35
|
Provides-Extra: cli
|
|
51
36
|
Requires-Dist: tqdm; extra == "cli"
|
|
37
|
+
Dynamic: license-file
|
|
52
38
|
|
|
53
39
|
[](https://pypi.org/project/graphique/)
|
|
54
40
|

|
|
@@ -14,7 +14,7 @@ import operator
|
|
|
14
14
|
import json
|
|
15
15
|
from collections.abc import Callable, Iterable, Iterator, Mapping
|
|
16
16
|
from dataclasses import dataclass
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import TypeAlias, get_type_hints
|
|
18
18
|
import numpy as np
|
|
19
19
|
import pyarrow as pa
|
|
20
20
|
import pyarrow.acero as ac
|
|
@@ -22,8 +22,8 @@ import pyarrow.compute as pc
|
|
|
22
22
|
import pyarrow.dataset as ds
|
|
23
23
|
from typing_extensions import Self
|
|
24
24
|
|
|
25
|
-
Array =
|
|
26
|
-
Batch =
|
|
25
|
+
Array: TypeAlias = pa.Array | pa.ChunkedArray
|
|
26
|
+
Batch: TypeAlias = pa.RecordBatch | pa.Table
|
|
27
27
|
bit_any = functools.partial(functools.reduce, operator.or_)
|
|
28
28
|
bit_all = functools.partial(functools.reduce, operator.and_)
|
|
29
29
|
|
|
@@ -64,12 +64,11 @@ class Agg:
|
|
|
64
64
|
return self.option_map[func.removeprefix('hash_')](**self.options)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
@dataclass(frozen=True)
|
|
67
|
+
@dataclass(frozen=True, slots=True)
|
|
68
68
|
class Compare:
|
|
69
69
|
"""Comparable wrapper for bisection search."""
|
|
70
70
|
|
|
71
71
|
value: object
|
|
72
|
-
__slots__ = ('value',) # slots keyword in Python >=3.10
|
|
73
72
|
|
|
74
73
|
def __lt__(self, other):
|
|
75
74
|
return self.value < other.as_py()
|
|
@@ -152,7 +151,7 @@ class ListChunk(pa.lib.BaseListArray):
|
|
|
152
151
|
offsets = self.values.offsets.take(self.offsets)
|
|
153
152
|
return type(self).from_arrays(offsets, self.values.values)
|
|
154
153
|
|
|
155
|
-
def aggregate(self, **funcs:
|
|
154
|
+
def aggregate(self, **funcs: pc.FunctionOptions | None) -> pa.RecordBatch:
|
|
156
155
|
"""Return aggregated scalars by grouping each hash function on the parent indices.
|
|
157
156
|
|
|
158
157
|
If there are empty or null scalars, then the result must be padded with null defaults and
|
|
@@ -241,7 +240,7 @@ class Column(pa.ChunkedArray):
|
|
|
241
240
|
if not pa.types.is_dictionary(self.type):
|
|
242
241
|
return self
|
|
243
242
|
array = self if isinstance(self, pa.Array) else self.combine_chunks()
|
|
244
|
-
return pc.rank(array.dictionary
|
|
243
|
+
return pc.rank(array.dictionary).take(array.indices)
|
|
245
244
|
|
|
246
245
|
def pairwise_diff(self, period: int = 1) -> Array:
|
|
247
246
|
"""`pairwise_diff` with chunked array support."""
|
|
@@ -391,7 +390,7 @@ class Table(pa.Table):
|
|
|
391
390
|
return Table.union(self, Table.from_counts(table, counts))
|
|
392
391
|
|
|
393
392
|
def sort_indices(
|
|
394
|
-
self, *names: str, length:
|
|
393
|
+
self, *names: str, length: int | None = None, null_placement: str = 'at_end'
|
|
395
394
|
) -> pa.Array:
|
|
396
395
|
"""Return indices which would sort the table by columns, optimized for fixed length."""
|
|
397
396
|
func = functools.partial(pc.sort_indices, null_placement=null_placement)
|
|
@@ -404,7 +403,7 @@ class Table(pa.Table):
|
|
|
404
403
|
def sort(
|
|
405
404
|
self,
|
|
406
405
|
*names: str,
|
|
407
|
-
length:
|
|
406
|
+
length: int | None = None,
|
|
408
407
|
indices: str = '',
|
|
409
408
|
null_placement: str = 'at_end',
|
|
410
409
|
) -> Batch:
|
|
@@ -521,7 +520,7 @@ class Table(pa.Table):
|
|
|
521
520
|
offset += len(batch)
|
|
522
521
|
yield pa.RecordBatch.from_pydict(columns)
|
|
523
522
|
|
|
524
|
-
def split(self) -> Iterator[
|
|
523
|
+
def split(self) -> Iterator[pa.RecordBatch | None]:
|
|
525
524
|
"""Generate tables from splitting list scalars."""
|
|
526
525
|
lists = Table.list_fields(self)
|
|
527
526
|
scalars = set(self.schema.names) - lists
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
GraphQL input types.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
5
6
|
import functools
|
|
6
7
|
import inspect
|
|
7
8
|
import operator
|
|
8
9
|
from collections.abc import Callable, Iterable
|
|
9
10
|
from datetime import date, datetime, time, timedelta
|
|
10
11
|
from decimal import Decimal
|
|
11
|
-
from typing import Generic,
|
|
12
|
+
from typing import Generic, TypeVar, no_type_check
|
|
12
13
|
import pyarrow as pa
|
|
13
14
|
import pyarrow.compute as pc
|
|
14
15
|
import pyarrow.dataset as ds
|
|
@@ -71,7 +72,7 @@ class provisional:
|
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def default_field(
|
|
74
|
-
default=UNSET, func:
|
|
75
|
+
default=UNSET, func: Callable | None = None, nullable: bool = False, **kwargs
|
|
75
76
|
) -> StrawberryField:
|
|
76
77
|
"""Use dataclass `default_factory` for `UNSET` or mutables."""
|
|
77
78
|
if func is not None:
|
|
@@ -83,16 +84,16 @@ def default_field(
|
|
|
83
84
|
|
|
84
85
|
@strawberry.input(description="predicates for scalars")
|
|
85
86
|
class Filter(Generic[T], Input):
|
|
86
|
-
eq:
|
|
87
|
+
eq: list[T | None] | None = default_field(
|
|
87
88
|
description="== or `isin`; `null` is equivalent to arrow `is_null`.", nullable=True
|
|
88
89
|
)
|
|
89
|
-
ne:
|
|
90
|
+
ne: T | None = default_field(
|
|
90
91
|
description="!=; `null` is equivalent to arrow `is_valid`.", nullable=True
|
|
91
92
|
)
|
|
92
|
-
lt:
|
|
93
|
-
le:
|
|
94
|
-
gt:
|
|
95
|
-
ge:
|
|
93
|
+
lt: T | None = default_field(description="<")
|
|
94
|
+
le: T | None = default_field(description="<=")
|
|
95
|
+
gt: T | None = default_field(description=r"\>")
|
|
96
|
+
ge: T | None = default_field(description=r"\>=")
|
|
96
97
|
|
|
97
98
|
nullables = {'eq', 'ne'}
|
|
98
99
|
|
|
@@ -133,7 +134,7 @@ class Pairwise(Field):
|
|
|
133
134
|
class Index(Field):
|
|
134
135
|
value: JSON
|
|
135
136
|
start: Long = 0
|
|
136
|
-
end:
|
|
137
|
+
end: Long | None = None
|
|
137
138
|
|
|
138
139
|
|
|
139
140
|
@strawberry.input
|
|
@@ -152,16 +153,20 @@ class Quantile(Field):
|
|
|
152
153
|
|
|
153
154
|
|
|
154
155
|
@strawberry.input
|
|
155
|
-
class
|
|
156
|
+
class RankQuantile(Field):
|
|
156
157
|
sort_keys: str = 'ascending'
|
|
157
158
|
null_placement: str = 'at_end'
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@strawberry.input
|
|
162
|
+
class Rank(RankQuantile):
|
|
158
163
|
tiebreaker: str = 'first'
|
|
159
164
|
|
|
160
165
|
|
|
161
166
|
@strawberry.input
|
|
162
167
|
class Sort:
|
|
163
168
|
by: list[str]
|
|
164
|
-
length:
|
|
169
|
+
length: Long | None = None
|
|
165
170
|
|
|
166
171
|
|
|
167
172
|
@strawberry.input
|
|
@@ -174,12 +179,12 @@ class Ranked:
|
|
|
174
179
|
class ListFunction(Input):
|
|
175
180
|
deprecation = "List scalar functions will be moved to `scan(...: {list: ...})`"
|
|
176
181
|
|
|
177
|
-
filter:
|
|
178
|
-
sort:
|
|
179
|
-
rank:
|
|
180
|
-
index:
|
|
181
|
-
mode:
|
|
182
|
-
quantile:
|
|
182
|
+
filter: Expression = default_field({}, description="filter within list scalars")
|
|
183
|
+
sort: Sort | None = default_field(description="sort within list scalars")
|
|
184
|
+
rank: Ranked | None = default_field(description="select by dense rank within list scalars")
|
|
185
|
+
index: Index | None = default_field(func=pc.index, deprecation_reason=deprecation)
|
|
186
|
+
mode: Mode | None = default_field(func=pc.mode, deprecation_reason=deprecation)
|
|
187
|
+
quantile: Quantile | None = default_field(func=pc.quantile, deprecation_reason=deprecation)
|
|
183
188
|
|
|
184
189
|
def keys(self):
|
|
185
190
|
return set(super().keys()) - {'filter', 'sort', 'rank'}
|
|
@@ -252,10 +257,10 @@ class Diff(Input):
|
|
|
252
257
|
"""
|
|
253
258
|
|
|
254
259
|
name: str
|
|
255
|
-
less:
|
|
256
|
-
less_equal:
|
|
257
|
-
greater:
|
|
258
|
-
greater_equal:
|
|
260
|
+
less: float | None = default_field(name='lt', description="<", nullable=True)
|
|
261
|
+
less_equal: float | None = default_field(name='le', description="<=", nullable=True)
|
|
262
|
+
greater: float | None = default_field(name='gt', description=r"\>", nullable=True)
|
|
263
|
+
greater_equal: float | None = default_field(name='ge', description=r"\>=", nullable=True)
|
|
259
264
|
|
|
260
265
|
nullables = {'less', 'less_equal', 'greater', 'greater_equal'}
|
|
261
266
|
|
|
@@ -275,7 +280,7 @@ class Expression:
|
|
|
275
280
|
name: list[str] = default_field([], description="field name(s)")
|
|
276
281
|
cast: str = strawberry.field(default='', description=f"cast as {links.type}")
|
|
277
282
|
safe: bool = strawberry.field(default=True, description="check for conversion errors on cast")
|
|
278
|
-
value:
|
|
283
|
+
value: JSON | None = default_field(
|
|
279
284
|
description="JSON scalar; also see typed scalars", nullable=True
|
|
280
285
|
)
|
|
281
286
|
kleene: bool = strawberry.field(default=False, description="use kleene logic for booleans")
|
|
@@ -288,58 +293,56 @@ class Expression:
|
|
|
288
293
|
duration: list[timedelta] = default_field([])
|
|
289
294
|
time_: list[time] = default_field([], name='time')
|
|
290
295
|
|
|
291
|
-
eq: list[
|
|
292
|
-
ne: list[
|
|
293
|
-
lt: list[
|
|
294
|
-
le: list[
|
|
295
|
-
gt: list[
|
|
296
|
-
ge: list[
|
|
297
|
-
inv:
|
|
298
|
-
|
|
299
|
-
abs:
|
|
300
|
-
add: list[
|
|
301
|
-
divide: list[
|
|
302
|
-
multiply: list[
|
|
303
|
-
negate:
|
|
304
|
-
power: list[
|
|
305
|
-
sign:
|
|
306
|
-
subtract: list[
|
|
307
|
-
|
|
308
|
-
bit_wise:
|
|
309
|
-
rounding:
|
|
310
|
-
log:
|
|
311
|
-
trig:
|
|
312
|
-
element_wise:
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
list: Optional['Lists'] = default_field(description="list array functions")
|
|
296
|
+
eq: list[Expression] = default_field([], description="==")
|
|
297
|
+
ne: list[Expression] = default_field([], description="!=")
|
|
298
|
+
lt: list[Expression] = default_field([], description="<")
|
|
299
|
+
le: list[Expression] = default_field([], description="<=")
|
|
300
|
+
gt: list[Expression] = default_field([], description=r"\>")
|
|
301
|
+
ge: list[Expression] = default_field([], description=r"\>=")
|
|
302
|
+
inv: Expression | None = default_field(description="~")
|
|
303
|
+
|
|
304
|
+
abs: Expression | None = default_field(func=pc.abs)
|
|
305
|
+
add: list[Expression] = default_field([], func=pc.add)
|
|
306
|
+
divide: list[Expression] = default_field([], func=pc.divide)
|
|
307
|
+
multiply: list[Expression] = default_field([], func=pc.multiply)
|
|
308
|
+
negate: Expression | None = default_field(func=pc.negate)
|
|
309
|
+
power: list[Expression] = default_field([], func=pc.power)
|
|
310
|
+
sign: Expression | None = default_field(func=pc.sign)
|
|
311
|
+
subtract: list[Expression] = default_field([], func=pc.subtract)
|
|
312
|
+
|
|
313
|
+
bit_wise: BitWise | None = default_field(description="bit-wise functions")
|
|
314
|
+
rounding: Rounding | None = default_field(description="rounding functions")
|
|
315
|
+
log: Log | None = default_field(description="logarithmic functions")
|
|
316
|
+
trig: Trig | None = default_field(description="trigonometry functions")
|
|
317
|
+
element_wise: ElementWise | None = default_field(description="element-wise aggregate functions")
|
|
318
|
+
|
|
319
|
+
and_: list[Expression] = default_field([], name='and', description="&")
|
|
320
|
+
and_not: list[Expression] = default_field([], func=pc.and_not)
|
|
321
|
+
or_: list[Expression] = default_field([], name='or', description="|")
|
|
322
|
+
xor: list[Expression] = default_field([], func=pc.xor)
|
|
323
|
+
|
|
324
|
+
utf8: Utf8 | None = default_field(description="utf8 string functions")
|
|
325
|
+
string_is_ascii: Expression | None = default_field(func=pc.string_is_ascii)
|
|
326
|
+
substring: MatchSubstring | None = default_field(description="match substring functions")
|
|
327
|
+
|
|
328
|
+
binary: Binary | None = default_field(description="binary functions")
|
|
329
|
+
set_lookup: SetLookup | None = default_field(description="set lookup functions")
|
|
330
|
+
|
|
331
|
+
is_finite: Expression | None = default_field(func=pc.is_finite)
|
|
332
|
+
is_inf: Expression | None = default_field(func=pc.is_inf)
|
|
333
|
+
is_nan: Expression | None = default_field(func=pc.is_nan)
|
|
334
|
+
true_unless_null: Expression | None = default_field(func=pc.true_unless_null)
|
|
335
|
+
|
|
336
|
+
case_when: list[Expression] = default_field([], func=pc.case_when)
|
|
337
|
+
choose: list[Expression] = default_field([], func=pc.choose)
|
|
338
|
+
coalesce: list[Expression] = default_field([], func=pc.coalesce)
|
|
339
|
+
if_else: list[Expression] = default_field([], func=pc.if_else)
|
|
340
|
+
|
|
341
|
+
temporal: Temporal | None = default_field(description="temporal functions")
|
|
342
|
+
|
|
343
|
+
replace_with_mask: list[Expression] = default_field([], func=pc.replace_with_mask)
|
|
344
|
+
|
|
345
|
+
list: Lists | None = default_field(description="list array functions")
|
|
343
346
|
|
|
344
347
|
unaries = ('inv', 'abs', 'negate', 'sign', 'string_is_ascii', 'is_finite', 'is_inf', 'is_nan')
|
|
345
348
|
associatives = ('add', 'multiply', 'and_', 'or_', 'xor')
|
|
@@ -349,7 +352,7 @@ class Expression:
|
|
|
349
352
|
groups = ('bit_wise', 'rounding', 'log', 'trig', 'element_wise', 'utf8', 'substring', 'binary')
|
|
350
353
|
groups += ('set_lookup', 'temporal', 'list') # type: ignore
|
|
351
354
|
|
|
352
|
-
def to_arrow(self) ->
|
|
355
|
+
def to_arrow(self) -> ds.Expression | None:
|
|
353
356
|
"""Transform GraphQL expression into a dataset expression."""
|
|
354
357
|
fields = []
|
|
355
358
|
if self.name:
|
|
@@ -457,11 +460,11 @@ class BitWise(Fields):
|
|
|
457
460
|
|
|
458
461
|
@strawberry.input(description="Rounding functions.")
|
|
459
462
|
class Rounding(Fields):
|
|
460
|
-
ceil:
|
|
461
|
-
floor:
|
|
462
|
-
trunc:
|
|
463
|
+
ceil: Expression | None = default_field(func=pc.ceil)
|
|
464
|
+
floor: Expression | None = default_field(func=pc.floor)
|
|
465
|
+
trunc: Expression | None = default_field(func=pc.trunc)
|
|
463
466
|
|
|
464
|
-
round:
|
|
467
|
+
round: Expression | None = default_field(func=pc.round)
|
|
465
468
|
ndigits: int = 0
|
|
466
469
|
round_mode: str = 'half_to_even'
|
|
467
470
|
multiple: float = 1.0
|
|
@@ -474,8 +477,8 @@ class Rounding(Fields):
|
|
|
474
477
|
|
|
475
478
|
@strawberry.input(description="Logarithmic functions.")
|
|
476
479
|
class Log(Fields):
|
|
477
|
-
ln:
|
|
478
|
-
log1p:
|
|
480
|
+
ln: Expression | None = default_field(func=pc.ln)
|
|
481
|
+
log1p: Expression | None = default_field(func=pc.log1p)
|
|
479
482
|
logb: list[Expression] = default_field([], func=pc.logb)
|
|
480
483
|
|
|
481
484
|
|
|
@@ -483,13 +486,13 @@ class Log(Fields):
|
|
|
483
486
|
class Trig(Fields):
|
|
484
487
|
checked: bool = strawberry.field(default=False, description="check for overflow errors")
|
|
485
488
|
|
|
486
|
-
acos:
|
|
487
|
-
asin:
|
|
488
|
-
atan:
|
|
489
|
+
acos: Expression | None = default_field(func=pc.acos)
|
|
490
|
+
asin: Expression | None = default_field(func=pc.asin)
|
|
491
|
+
atan: Expression | None = default_field(func=pc.atan)
|
|
489
492
|
atan2: list[Expression] = default_field([], func=pc.atan2)
|
|
490
|
-
cos:
|
|
491
|
-
sin:
|
|
492
|
-
tan:
|
|
493
|
+
cos: Expression | None = default_field(func=pc.cos)
|
|
494
|
+
sin: Expression | None = default_field(func=pc.sin)
|
|
495
|
+
tan: Expression | None = default_field(func=pc.tan)
|
|
493
496
|
|
|
494
497
|
def getfunc(self, name):
|
|
495
498
|
return getattr(pc, name + ('_checked' * self.checked))
|
|
@@ -504,40 +507,40 @@ class ElementWise(Fields):
|
|
|
504
507
|
|
|
505
508
|
@strawberry.input(description="Utf8 string functions.")
|
|
506
509
|
class Utf8(Fields):
|
|
507
|
-
is_alnum:
|
|
508
|
-
is_alpha:
|
|
509
|
-
is_decimal:
|
|
510
|
-
is_digit:
|
|
511
|
-
is_lower:
|
|
512
|
-
is_numeric:
|
|
513
|
-
is_printable:
|
|
514
|
-
is_space:
|
|
515
|
-
is_title:
|
|
516
|
-
is_upper:
|
|
517
|
-
|
|
518
|
-
capitalize:
|
|
519
|
-
length:
|
|
520
|
-
lower:
|
|
521
|
-
reverse:
|
|
522
|
-
swapcase:
|
|
523
|
-
title:
|
|
524
|
-
upper:
|
|
525
|
-
|
|
526
|
-
ltrim:
|
|
527
|
-
rtrim:
|
|
528
|
-
trim:
|
|
510
|
+
is_alnum: Expression | None = default_field(func=pc.utf8_is_alnum)
|
|
511
|
+
is_alpha: Expression | None = default_field(func=pc.utf8_is_alpha)
|
|
512
|
+
is_decimal: Expression | None = default_field(func=pc.utf8_is_decimal)
|
|
513
|
+
is_digit: Expression | None = default_field(func=pc.utf8_is_digit)
|
|
514
|
+
is_lower: Expression | None = default_field(func=pc.utf8_is_lower)
|
|
515
|
+
is_numeric: Expression | None = default_field(func=pc.utf8_is_numeric)
|
|
516
|
+
is_printable: Expression | None = default_field(func=pc.utf8_is_printable)
|
|
517
|
+
is_space: Expression | None = default_field(func=pc.utf8_is_space)
|
|
518
|
+
is_title: Expression | None = default_field(func=pc.utf8_is_title)
|
|
519
|
+
is_upper: Expression | None = default_field(func=pc.utf8_is_upper)
|
|
520
|
+
|
|
521
|
+
capitalize: Expression | None = default_field(func=pc.utf8_capitalize)
|
|
522
|
+
length: Expression | None = default_field(func=pc.utf8_length)
|
|
523
|
+
lower: Expression | None = default_field(func=pc.utf8_lower)
|
|
524
|
+
reverse: Expression | None = default_field(func=pc.utf8_reverse)
|
|
525
|
+
swapcase: Expression | None = default_field(func=pc.utf8_swapcase)
|
|
526
|
+
title: Expression | None = default_field(func=pc.utf8_title)
|
|
527
|
+
upper: Expression | None = default_field(func=pc.utf8_upper)
|
|
528
|
+
|
|
529
|
+
ltrim: Expression | None = default_field(func=pc.utf8_ltrim)
|
|
530
|
+
rtrim: Expression | None = default_field(func=pc.utf8_rtrim)
|
|
531
|
+
trim: Expression | None = default_field(func=pc.utf8_trim)
|
|
529
532
|
characters: str = default_field('', description="trim options; by default trims whitespace")
|
|
530
533
|
|
|
531
|
-
replace_slice:
|
|
532
|
-
slice_codeunits:
|
|
534
|
+
replace_slice: Expression | None = default_field(func=pc.utf8_replace_slice)
|
|
535
|
+
slice_codeunits: Expression | None = default_field(func=pc.utf8_slice_codeunits)
|
|
533
536
|
start: int = 0
|
|
534
|
-
stop:
|
|
537
|
+
stop: int | None = UNSET
|
|
535
538
|
step: int = 1
|
|
536
539
|
replacement: str = ''
|
|
537
540
|
|
|
538
|
-
center:
|
|
539
|
-
lpad:
|
|
540
|
-
rpad:
|
|
541
|
+
center: Expression | None = default_field(func=pc.utf8_center)
|
|
542
|
+
lpad: Expression | None = default_field(func=pc.utf8_lpad)
|
|
543
|
+
rpad: Expression | None = default_field(func=pc.utf8_rpad)
|
|
541
544
|
width: int = 0
|
|
542
545
|
padding: str = ''
|
|
543
546
|
|
|
@@ -549,16 +552,16 @@ class Utf8(Fields):
|
|
|
549
552
|
|
|
550
553
|
@strawberry.input(description="Binary functions.")
|
|
551
554
|
class Binary(Fields):
|
|
552
|
-
length:
|
|
555
|
+
length: Expression | None = default_field(func=pc.binary_length)
|
|
553
556
|
repeat: list[Expression] = default_field([], func=pc.binary_repeat)
|
|
554
|
-
reverse:
|
|
557
|
+
reverse: Expression | None = default_field(func=pc.binary_reverse)
|
|
555
558
|
|
|
556
559
|
join: list[Expression] = default_field([], func=pc.binary_join)
|
|
557
560
|
join_element_wise: list[Expression] = default_field([], func=pc.binary_join_element_wise)
|
|
558
561
|
null_handling: str = 'emit_null'
|
|
559
562
|
null_replacement: str = ''
|
|
560
563
|
|
|
561
|
-
replace_slice:
|
|
564
|
+
replace_slice: Expression | None = default_field(func=pc.binary_replace_slice)
|
|
562
565
|
start: int = 0
|
|
563
566
|
stop: int = 0
|
|
564
567
|
replacement: bytes = b''
|
|
@@ -568,22 +571,20 @@ class Binary(Fields):
|
|
|
568
571
|
|
|
569
572
|
@strawberry.input(description="Match substring functions.")
|
|
570
573
|
class MatchSubstring(Fields):
|
|
571
|
-
count_substring:
|
|
572
|
-
ends_with:
|
|
573
|
-
find_substring:
|
|
574
|
-
match_substring:
|
|
575
|
-
starts_with:
|
|
576
|
-
replace_substring:
|
|
577
|
-
|
|
578
|
-
)
|
|
579
|
-
split_pattern: Optional[Expression] = default_field(name='split', func=pc.split_pattern)
|
|
580
|
-
extract: Optional[Expression] = default_field(func=pc.extract_regex)
|
|
574
|
+
count_substring: Expression | None = default_field(name='count', func=pc.count_substring)
|
|
575
|
+
ends_with: Expression | None = default_field(func=pc.ends_with)
|
|
576
|
+
find_substring: Expression | None = default_field(name='find', func=pc.find_substring)
|
|
577
|
+
match_substring: Expression | None = default_field(name='match', func=pc.match_substring)
|
|
578
|
+
starts_with: Expression | None = default_field(func=pc.starts_with)
|
|
579
|
+
replace_substring: Expression | None = default_field(name='replace', func=pc.replace_substring)
|
|
580
|
+
split_pattern: Expression | None = default_field(name='split', func=pc.split_pattern)
|
|
581
|
+
extract: Expression | None = default_field(func=pc.extract_regex)
|
|
581
582
|
pattern: str = ''
|
|
582
583
|
ignore_case: bool = False
|
|
583
584
|
regex: bool = False
|
|
584
585
|
replacement: str = ''
|
|
585
|
-
max_replacements:
|
|
586
|
-
max_splits:
|
|
586
|
+
max_replacements: int | None = None
|
|
587
|
+
max_splits: int | None = None
|
|
587
588
|
reverse: bool = False
|
|
588
589
|
|
|
589
590
|
def getfunc(self, name):
|
|
@@ -594,26 +595,26 @@ class MatchSubstring(Fields):
|
|
|
594
595
|
|
|
595
596
|
@strawberry.input(description="Temporal functions.")
|
|
596
597
|
class Temporal(Fields):
|
|
597
|
-
day:
|
|
598
|
-
day_of_year:
|
|
599
|
-
hour:
|
|
600
|
-
iso_week:
|
|
601
|
-
iso_year:
|
|
602
|
-
iso_calendar:
|
|
603
|
-
is_leap_year:
|
|
604
|
-
|
|
605
|
-
microsecond:
|
|
606
|
-
millisecond:
|
|
607
|
-
minute:
|
|
608
|
-
month:
|
|
609
|
-
nanosecond:
|
|
610
|
-
quarter:
|
|
611
|
-
second:
|
|
612
|
-
subsecond:
|
|
613
|
-
us_week:
|
|
614
|
-
us_year:
|
|
615
|
-
year:
|
|
616
|
-
year_month_day:
|
|
598
|
+
day: Expression | None = default_field(func=pc.day)
|
|
599
|
+
day_of_year: Expression | None = default_field(func=pc.day_of_year)
|
|
600
|
+
hour: Expression | None = default_field(func=pc.hour)
|
|
601
|
+
iso_week: Expression | None = default_field(func=pc.iso_week)
|
|
602
|
+
iso_year: Expression | None = default_field(func=pc.iso_year)
|
|
603
|
+
iso_calendar: Expression | None = default_field(func=pc.iso_calendar)
|
|
604
|
+
is_leap_year: Expression | None = default_field(func=pc.is_leap_year)
|
|
605
|
+
|
|
606
|
+
microsecond: Expression | None = default_field(func=pc.microsecond)
|
|
607
|
+
millisecond: Expression | None = default_field(func=pc.millisecond)
|
|
608
|
+
minute: Expression | None = default_field(func=pc.minute)
|
|
609
|
+
month: Expression | None = default_field(func=pc.month)
|
|
610
|
+
nanosecond: Expression | None = default_field(func=pc.nanosecond)
|
|
611
|
+
quarter: Expression | None = default_field(func=pc.quarter)
|
|
612
|
+
second: Expression | None = default_field(func=pc.second)
|
|
613
|
+
subsecond: Expression | None = default_field(func=pc.subsecond)
|
|
614
|
+
us_week: Expression | None = default_field(func=pc.us_week)
|
|
615
|
+
us_year: Expression | None = default_field(func=pc.us_year)
|
|
616
|
+
year: Expression | None = default_field(func=pc.year)
|
|
617
|
+
year_month_day: Expression | None = default_field(func=pc.year_month_day)
|
|
617
618
|
|
|
618
619
|
day_time_interval_between: list[Expression] = default_field(
|
|
619
620
|
[], func=pc.day_time_interval_between
|
|
@@ -633,29 +634,29 @@ class Temporal(Fields):
|
|
|
633
634
|
weeks_between: list[Expression] = default_field([], func=pc.weeks_between)
|
|
634
635
|
years_between: list[Expression] = default_field([], func=pc.years_between)
|
|
635
636
|
|
|
636
|
-
ceil_temporal:
|
|
637
|
-
floor_temporal:
|
|
638
|
-
round_temporal:
|
|
637
|
+
ceil_temporal: Expression | None = default_field(name='ceil', func=pc.ceil_temporal)
|
|
638
|
+
floor_temporal: Expression | None = default_field(name='floor', func=pc.floor_temporal)
|
|
639
|
+
round_temporal: Expression | None = default_field(name='round', func=pc.round_temporal)
|
|
639
640
|
multiple: int = 1
|
|
640
641
|
unit: str = 'day'
|
|
641
642
|
week_starts_monday: bool = True
|
|
642
643
|
ceil_is_strictly_greater: bool = False
|
|
643
644
|
calendar_based_origin: bool = False
|
|
644
645
|
|
|
645
|
-
week:
|
|
646
|
-
count_from_zero:
|
|
646
|
+
week: Expression | None = default_field(func=pc.week)
|
|
647
|
+
count_from_zero: bool | None = UNSET
|
|
647
648
|
first_week_is_fully_in_year: bool = False
|
|
648
649
|
|
|
649
|
-
day_of_week:
|
|
650
|
+
day_of_week: Expression | None = default_field(func=pc.day_of_week)
|
|
650
651
|
week_start: int = 1
|
|
651
652
|
|
|
652
|
-
strftime:
|
|
653
|
-
strptime:
|
|
653
|
+
strftime: Expression | None = default_field(func=pc.strftime)
|
|
654
|
+
strptime: Expression | None = default_field(func=pc.strptime)
|
|
654
655
|
format: str = '%Y-%m-%dT%H:%M:%S'
|
|
655
656
|
locale: str = 'C'
|
|
656
657
|
error_is_null: bool = False
|
|
657
658
|
|
|
658
|
-
assume_timezone:
|
|
659
|
+
assume_timezone: Expression | None = default_field(func=pc.assume_timezone)
|
|
659
660
|
timezone: str = ''
|
|
660
661
|
ambiguous: str = 'raise'
|
|
661
662
|
nonexistent: str = 'raise'
|
|
@@ -684,16 +685,16 @@ class SetLookup(Fields):
|
|
|
684
685
|
@strawberry.input(description="List array functions.")
|
|
685
686
|
class Lists(Fields):
|
|
686
687
|
element: list[Expression] = default_field([], func=pc.list_element)
|
|
687
|
-
value_length:
|
|
688
|
+
value_length: Expression | None = default_field(func=pc.list_value_length)
|
|
688
689
|
# user defined functions
|
|
689
|
-
all:
|
|
690
|
-
any:
|
|
690
|
+
all: Expression | None = default_field(func=pc.all)
|
|
691
|
+
any: Expression | None = default_field(func=pc.any)
|
|
691
692
|
|
|
692
|
-
slice:
|
|
693
|
+
slice: Expression | None = default_field(func=pc.list_slice)
|
|
693
694
|
start: int = 0
|
|
694
|
-
stop:
|
|
695
|
+
stop: int | None = None
|
|
695
696
|
step: int = 1
|
|
696
|
-
return_fixed_size_list:
|
|
697
|
+
return_fixed_size_list: bool | None = None
|
|
697
698
|
|
|
698
699
|
prefix = 'list_'
|
|
699
700
|
|
|
@@ -10,7 +10,7 @@ import inspect
|
|
|
10
10
|
import itertools
|
|
11
11
|
from collections.abc import Callable, Iterable, Iterator, Mapping, Sized
|
|
12
12
|
from datetime import timedelta
|
|
13
|
-
from typing import Annotated,
|
|
13
|
+
from typing import Annotated, TypeAlias, no_type_check
|
|
14
14
|
import pyarrow as pa
|
|
15
15
|
import pyarrow.compute as pc
|
|
16
16
|
import pyarrow.dataset as ds
|
|
@@ -20,12 +20,12 @@ from strawberry.extensions.utils import get_path_from_info
|
|
|
20
20
|
from typing_extensions import Self
|
|
21
21
|
from .core import Agg, Batch, Column as C, ListChunk, Nodes, Table as T
|
|
22
22
|
from .inputs import CountAggregate, Cumulative, Diff, Expression, Field, Filter
|
|
23
|
-
from .inputs import HashAggregates, ListFunction, Pairwise, Projection, Rank
|
|
23
|
+
from .inputs import HashAggregates, ListFunction, Pairwise, Projection, Rank, RankQuantile
|
|
24
24
|
from .inputs import ScalarAggregate, TDigestAggregate, VarianceAggregate, links, provisional
|
|
25
25
|
from .models import Column, doc_field
|
|
26
26
|
from .scalars import Long
|
|
27
27
|
|
|
28
|
-
Source =
|
|
28
|
+
Source: TypeAlias = ds.Dataset | Nodes | ds.Scanner | pa.Table
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def references(field) -> Iterator:
|
|
@@ -83,7 +83,7 @@ class Dataset:
|
|
|
83
83
|
return self.source.select(names)
|
|
84
84
|
return Nodes.scan(self.source, names)
|
|
85
85
|
|
|
86
|
-
def to_table(self, info: Info, length:
|
|
86
|
+
def to_table(self, info: Info, length: int | None = None) -> pa.Table:
|
|
87
87
|
"""Return table with only the rows and columns necessary to proceed."""
|
|
88
88
|
source = self.select(info)
|
|
89
89
|
if isinstance(source, pa.Table):
|
|
@@ -163,7 +163,7 @@ class Dataset:
|
|
|
163
163
|
) # type: ignore
|
|
164
164
|
|
|
165
165
|
@doc_field
|
|
166
|
-
def optional(self) ->
|
|
166
|
+
def optional(self) -> Self | None:
|
|
167
167
|
"""Nullable field to stop error propagation, enabling partial query results.
|
|
168
168
|
|
|
169
169
|
Will be replaced by client controlled nullability.
|
|
@@ -192,7 +192,7 @@ class Dataset:
|
|
|
192
192
|
return len(table) >= length
|
|
193
193
|
|
|
194
194
|
@doc_field
|
|
195
|
-
def size(self) ->
|
|
195
|
+
def size(self) -> Long | None:
|
|
196
196
|
"""buffer size in bytes; null if table is not loaded"""
|
|
197
197
|
return getattr(self.source, 'nbytes', None)
|
|
198
198
|
|
|
@@ -203,7 +203,7 @@ class Dataset:
|
|
|
203
203
|
)
|
|
204
204
|
def column(
|
|
205
205
|
self, info: Info, name: list[str], cast: str = '', safe: bool = True
|
|
206
|
-
) ->
|
|
206
|
+
) -> Column | None:
|
|
207
207
|
"""Return column of any type by name.
|
|
208
208
|
|
|
209
209
|
This is typically only needed for aliased or casted columns.
|
|
@@ -222,14 +222,14 @@ class Dataset:
|
|
|
222
222
|
reverse="reverse order after slicing; forces a copy",
|
|
223
223
|
)
|
|
224
224
|
def slice(
|
|
225
|
-
self, info: Info, offset: Long = 0, length:
|
|
225
|
+
self, info: Info, offset: Long = 0, length: Long | None = None, reverse: bool = False
|
|
226
226
|
) -> Self:
|
|
227
227
|
"""Return zero-copy slice of table.
|
|
228
228
|
|
|
229
|
-
Can also be
|
|
229
|
+
Can also be used to force loading a dataset.
|
|
230
230
|
"""
|
|
231
231
|
table = self.to_table(info, length and (offset + length if offset >= 0 else None))
|
|
232
|
-
table = table[offset:][:length] # `slice` bug:
|
|
232
|
+
table = table[offset:][:length] # `slice` bug: apache/arrow#30894
|
|
233
233
|
return type(self)(table[::-1] if reverse else table)
|
|
234
234
|
|
|
235
235
|
@doc_field(
|
|
@@ -301,7 +301,7 @@ class Dataset:
|
|
|
301
301
|
self,
|
|
302
302
|
info: Info,
|
|
303
303
|
by: list[str],
|
|
304
|
-
length:
|
|
304
|
+
length: Long | None = None,
|
|
305
305
|
null_placement: str = 'at_end',
|
|
306
306
|
) -> Self:
|
|
307
307
|
"""Return table slice sorted by specified columns.
|
|
@@ -362,6 +362,8 @@ class Dataset:
|
|
|
362
362
|
fill_null_forward: doc_argument(list[Field], func=pc.fill_null_forward) = [],
|
|
363
363
|
pairwise_diff: doc_argument(list[Pairwise], func=pc.pairwise_diff) = [],
|
|
364
364
|
rank: doc_argument(list[Rank], func=pc.rank) = [],
|
|
365
|
+
rank_quantile: doc_argument(list[RankQuantile], func=pc.rank_quantile) = [],
|
|
366
|
+
rank_normal: doc_argument(list[RankQuantile], func=pc.rank_normal) = [],
|
|
365
367
|
list_: Annotated[
|
|
366
368
|
ListFunction,
|
|
367
369
|
strawberry.argument(name='list', description="functions for list arrays."),
|
|
@@ -377,7 +379,7 @@ class Dataset:
|
|
|
377
379
|
columns = {}
|
|
378
380
|
funcs = pc.cumulative_max, pc.cumulative_mean, pc.cumulative_min, pc.cumulative_prod
|
|
379
381
|
funcs += pc.cumulative_sum, C.fill_null_backward, C.fill_null_forward, C.pairwise_diff
|
|
380
|
-
funcs +=
|
|
382
|
+
funcs += pc.rank, pc.rank_quantile, pc.rank_normal
|
|
381
383
|
for func in funcs:
|
|
382
384
|
for field in locals()[func.__name__]:
|
|
383
385
|
callable = func
|
|
@@ -396,7 +398,7 @@ class Dataset:
|
|
|
396
398
|
return type(self)(self.add_metric(info, table, mode='batch'))
|
|
397
399
|
|
|
398
400
|
@doc_field
|
|
399
|
-
def tables(self, info: Info) -> list[
|
|
401
|
+
def tables(self, info: Info) -> list[Self | None]: # type: ignore
|
|
400
402
|
"""Return a list of tables by splitting list columns.
|
|
401
403
|
|
|
402
404
|
At least one list column must be referenced, and all list columns must have the same lengths.
|
|
@@ -477,7 +479,7 @@ class Dataset:
|
|
|
477
479
|
info: Info,
|
|
478
480
|
right: str,
|
|
479
481
|
keys: list[str],
|
|
480
|
-
right_keys:
|
|
482
|
+
right_keys: list[str] | None = None,
|
|
481
483
|
join_type: str = 'left outer',
|
|
482
484
|
left_suffix: str = '',
|
|
483
485
|
right_suffix: str = '',
|
|
@@ -6,7 +6,6 @@ import warnings
|
|
|
6
6
|
from collections.abc import Iterable, Mapping
|
|
7
7
|
from datetime import timedelta
|
|
8
8
|
from keyword import iskeyword
|
|
9
|
-
from typing import Optional
|
|
10
9
|
import pyarrow.dataset as ds
|
|
11
10
|
import strawberry.asgi
|
|
12
11
|
from strawberry import Info, UNSET
|
|
@@ -33,7 +32,7 @@ class MetricsExtension(tracing.ApolloTracingExtension):
|
|
|
33
32
|
return {'metrics': metrics}
|
|
34
33
|
|
|
35
34
|
@staticmethod
|
|
36
|
-
def duration(data: dict) ->
|
|
35
|
+
def duration(data: dict) -> str | None:
|
|
37
36
|
return data['duration'] and str(timedelta(microseconds=data['duration'] / 1e3))
|
|
38
37
|
|
|
39
38
|
|
|
@@ -87,12 +86,12 @@ def implemented(root: Source, name: str = '', keys: Iterable = ()):
|
|
|
87
86
|
prefix = to_camel_case(name.title())
|
|
88
87
|
|
|
89
88
|
namespace = {name: strawberry.field(default=UNSET, name=name) for name in types}
|
|
90
|
-
annotations = {name:
|
|
89
|
+
annotations = {name: Column.registry[types[name]] | None for name in types}
|
|
91
90
|
cls = type(prefix + 'Columns', (), dict(namespace, __annotations__=annotations))
|
|
92
91
|
Columns = strawberry.type(cls, description="fields for each column")
|
|
93
92
|
|
|
94
93
|
namespace = {name: strawberry.field(default=UNSET, name=name) for name in types}
|
|
95
|
-
annotations = {name:
|
|
94
|
+
annotations = {name: (Column if cls is list else cls) | None for name, cls in types.items()}
|
|
96
95
|
cls = type(prefix + 'Row', (), dict(namespace, __annotations__=annotations))
|
|
97
96
|
Row = strawberry.type(cls, description="scalar fields")
|
|
98
97
|
|
|
@@ -104,7 +103,7 @@ def implemented(root: Source, name: str = '', keys: Iterable = ()):
|
|
|
104
103
|
"""fields for each column"""
|
|
105
104
|
return Columns(**super().columns(info))
|
|
106
105
|
|
|
107
|
-
def row(self, info: Info, index: Long = 0) ->
|
|
106
|
+
def row(self, info: Info, index: Long = 0) -> Row | None: # type: ignore
|
|
108
107
|
"""Return scalar values at index."""
|
|
109
108
|
row = super().row(info, index)
|
|
110
109
|
for name, value in row.items():
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
GraphQL output types and resolvers.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
5
6
|
import functools
|
|
6
7
|
import inspect
|
|
7
8
|
from collections.abc import Callable
|
|
8
9
|
from datetime import date, datetime, time, timedelta
|
|
9
10
|
from decimal import Decimal
|
|
10
|
-
from typing import Annotated, Generic,
|
|
11
|
+
from typing import Annotated, Generic, TypeVar, TYPE_CHECKING, get_args
|
|
11
12
|
import pyarrow as pa
|
|
12
13
|
import pyarrow.compute as pc
|
|
13
14
|
import strawberry
|
|
@@ -27,7 +28,7 @@ def selections(*fields) -> set:
|
|
|
27
28
|
return {selection.name for field in fields for selection in field.selections}
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
def doc_field(func:
|
|
31
|
+
def doc_field(func: Callable | None = None, **kwargs: str) -> StrawberryField:
|
|
31
32
|
"""Return strawberry field with argument and docstring descriptions."""
|
|
32
33
|
if func is None:
|
|
33
34
|
return functools.partial(doc_field, **kwargs) # type: ignore
|
|
@@ -77,12 +78,12 @@ class Column:
|
|
|
77
78
|
return self.array.nbytes
|
|
78
79
|
|
|
79
80
|
@classmethod
|
|
80
|
-
def cast(cls, array: pa.ChunkedArray) ->
|
|
81
|
+
def cast(cls, array: pa.ChunkedArray) -> Column:
|
|
81
82
|
"""Return typed column based on array type."""
|
|
82
83
|
return cls.registry[py_type(array.type)](array)
|
|
83
84
|
|
|
84
85
|
@classmethod
|
|
85
|
-
def fromscalar(cls, scalar: pa.ListScalar) ->
|
|
86
|
+
def fromscalar(cls, scalar: pa.ListScalar) -> Column | None:
|
|
86
87
|
return None if scalar.values is None else cls.cast(pa.chunked_array([scalar.values]))
|
|
87
88
|
|
|
88
89
|
@compute_field
|
|
@@ -105,7 +106,7 @@ class Set(Generic[T]):
|
|
|
105
106
|
self.array, self.counts = array, counts.to_pylist()
|
|
106
107
|
|
|
107
108
|
@doc_field
|
|
108
|
-
def values(self) -> list[
|
|
109
|
+
def values(self) -> list[T | None]:
|
|
109
110
|
"""list of values"""
|
|
110
111
|
return self.array.to_pylist()
|
|
111
112
|
|
|
@@ -126,7 +127,7 @@ class NominalColumn(Generic[T], Column):
|
|
|
126
127
|
return Set(self.array.unique())
|
|
127
128
|
|
|
128
129
|
@doc_field
|
|
129
|
-
def value(self, index: Long = 0) ->
|
|
130
|
+
def value(self, index: Long = 0) -> T | None:
|
|
130
131
|
"""scalar value at index"""
|
|
131
132
|
return self.array[index].as_py()
|
|
132
133
|
|
|
@@ -139,23 +140,23 @@ class NominalColumn(Generic[T], Column):
|
|
|
139
140
|
@strawberry.type(name='Column', description="column of ordinal values")
|
|
140
141
|
class OrdinalColumn(NominalColumn[T]):
|
|
141
142
|
@compute_field
|
|
142
|
-
def first(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
143
|
+
def first(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
143
144
|
return pc.first(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
144
145
|
|
|
145
146
|
@compute_field
|
|
146
|
-
def last(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
147
|
+
def last(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
147
148
|
return pc.last(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
148
149
|
|
|
149
150
|
@compute_field
|
|
150
|
-
def min(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
151
|
+
def min(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
151
152
|
return pc.min(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
152
153
|
|
|
153
154
|
@compute_field
|
|
154
|
-
def max(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
155
|
+
def max(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
155
156
|
return pc.max(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
156
157
|
|
|
157
158
|
@compute_field
|
|
158
|
-
def index(self, value: T, start: Long = 0, end:
|
|
159
|
+
def index(self, value: T, start: Long = 0, end: Long | None = None) -> Long:
|
|
159
160
|
return C.index(self.array, value, start, end)
|
|
160
161
|
|
|
161
162
|
@compute_field
|
|
@@ -175,15 +176,15 @@ class IntervalColumn(OrdinalColumn[T]):
|
|
|
175
176
|
return Set(*pc.mode(self.array, n, skip_nulls=skip_nulls, min_count=min_count).flatten())
|
|
176
177
|
|
|
177
178
|
@compute_field
|
|
178
|
-
def sum(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
179
|
+
def sum(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
179
180
|
return pc.sum(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
180
181
|
|
|
181
182
|
@compute_field
|
|
182
|
-
def product(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
183
|
+
def product(self, skip_nulls: bool = True, min_count: int = 0) -> T | None:
|
|
183
184
|
return pc.product(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
184
185
|
|
|
185
186
|
@compute_field
|
|
186
|
-
def mean(self, skip_nulls: bool = True, min_count: int = 0) ->
|
|
187
|
+
def mean(self, skip_nulls: bool = True, min_count: int = 0) -> float | None:
|
|
187
188
|
return pc.mean(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
188
189
|
|
|
189
190
|
@compute_field
|
|
@@ -195,13 +196,11 @@ class IntervalColumn(OrdinalColumn[T]):
|
|
|
195
196
|
@strawberry.type(name='Column', description="column of floats or decimals")
|
|
196
197
|
class RatioColumn(IntervalColumn[T]):
|
|
197
198
|
@compute_field
|
|
198
|
-
def stddev(self, ddof: int = 0, skip_nulls: bool = True, min_count: int = 0) ->
|
|
199
|
+
def stddev(self, ddof: int = 0, skip_nulls: bool = True, min_count: int = 0) -> float | None:
|
|
199
200
|
return pc.stddev(self.array, ddof=ddof, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
200
201
|
|
|
201
202
|
@compute_field
|
|
202
|
-
def variance(
|
|
203
|
-
self, ddof: int = 0, skip_nulls: bool = True, min_count: int = 0
|
|
204
|
-
) -> Optional[float]:
|
|
203
|
+
def variance(self, ddof: int = 0, skip_nulls: bool = True, min_count: int = 0) -> float | None:
|
|
205
204
|
options = {'skip_nulls': skip_nulls, 'min_count': min_count}
|
|
206
205
|
return pc.variance(self.array, ddof=ddof, **options).as_py()
|
|
207
206
|
|
|
@@ -212,7 +211,7 @@ class RatioColumn(IntervalColumn[T]):
|
|
|
212
211
|
interpolation: str = 'linear',
|
|
213
212
|
skip_nulls: bool = True,
|
|
214
213
|
min_count: int = 0,
|
|
215
|
-
) -> list[
|
|
214
|
+
) -> list[float | None]:
|
|
216
215
|
options = {'skip_nulls': skip_nulls, 'min_count': min_count}
|
|
217
216
|
return pc.quantile(self.array, q=q, interpolation=interpolation, **options).to_pylist()
|
|
218
217
|
|
|
@@ -224,7 +223,7 @@ class RatioColumn(IntervalColumn[T]):
|
|
|
224
223
|
buffer_size: int = 500,
|
|
225
224
|
skip_nulls: bool = True,
|
|
226
225
|
min_count: int = 0,
|
|
227
|
-
) -> list[
|
|
226
|
+
) -> list[float | None]:
|
|
228
227
|
options = {'buffer_size': buffer_size, 'skip_nulls': skip_nulls, 'min_count': min_count}
|
|
229
228
|
return pc.tdigest(self.array, q=q, delta=delta, **options).to_pylist()
|
|
230
229
|
|
|
@@ -233,11 +232,11 @@ class RatioColumn(IntervalColumn[T]):
|
|
|
233
232
|
@strawberry.type(name='eanColumn', description="column of booleans")
|
|
234
233
|
class BooleanColumn(IntervalColumn[T]):
|
|
235
234
|
@compute_field
|
|
236
|
-
def any(self, skip_nulls: bool = True, min_count: int = 1) ->
|
|
235
|
+
def any(self, skip_nulls: bool = True, min_count: int = 1) -> bool | None:
|
|
237
236
|
return pc.any(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
238
237
|
|
|
239
238
|
@compute_field
|
|
240
|
-
def all(self, skip_nulls: bool = True, min_count: int = 1) ->
|
|
239
|
+
def all(self, skip_nulls: bool = True, min_count: int = 1) -> bool | None:
|
|
241
240
|
return pc.all(self.array, skip_nulls=skip_nulls, min_count=min_count).as_py()
|
|
242
241
|
|
|
243
242
|
|
|
@@ -247,7 +246,7 @@ class IntColumn(RatioColumn[T]):
|
|
|
247
246
|
@doc_field
|
|
248
247
|
def take_from(
|
|
249
248
|
self, info: Info, field: str
|
|
250
|
-
) ->
|
|
249
|
+
) -> Annotated['Dataset', strawberry.lazy('.interface')] | None:
|
|
251
250
|
"""Select indices from a table on the root Query type."""
|
|
252
251
|
root = getattr(info.root_value, field)
|
|
253
252
|
return type(root)(root.select(info).take(self.array.combine_chunks()))
|
|
@@ -257,12 +256,12 @@ class IntColumn(RatioColumn[T]):
|
|
|
257
256
|
@strawberry.type(description="column of lists")
|
|
258
257
|
class ListColumn(Column):
|
|
259
258
|
@doc_field
|
|
260
|
-
def value(self, index: Long = 0) ->
|
|
259
|
+
def value(self, index: Long = 0) -> Column | None:
|
|
261
260
|
"""scalar column at index"""
|
|
262
261
|
return self.fromscalar(self.array[index])
|
|
263
262
|
|
|
264
263
|
@doc_field
|
|
265
|
-
def values(self) -> list[
|
|
264
|
+
def values(self) -> list[Column | None]:
|
|
266
265
|
"""list of columns"""
|
|
267
266
|
return list(map(self.fromscalar, self.array))
|
|
268
267
|
|
|
@@ -280,7 +279,7 @@ class ListColumn(Column):
|
|
|
280
279
|
@strawberry.type(description="column of structs")
|
|
281
280
|
class StructColumn(Column):
|
|
282
281
|
@doc_field
|
|
283
|
-
def value(self, index: Long = 0) ->
|
|
282
|
+
def value(self, index: Long = 0) -> dict | None:
|
|
284
283
|
"""scalar json object at index"""
|
|
285
284
|
return self.array[index].as_py()
|
|
286
285
|
|
|
@@ -290,6 +289,6 @@ class StructColumn(Column):
|
|
|
290
289
|
return [field.name for field in self.array.type]
|
|
291
290
|
|
|
292
291
|
@doc_field(name="field name(s); multiple names access nested fields")
|
|
293
|
-
def column(self, name: list[str]) ->
|
|
292
|
+
def column(self, name: list[str]) -> Column | None:
|
|
294
293
|
"""Return struct field as a column."""
|
|
295
294
|
return self.cast(pc.struct_field(self.array, name))
|
|
@@ -5,7 +5,6 @@ GraphQL scalars.
|
|
|
5
5
|
import functools
|
|
6
6
|
from datetime import date, datetime, time, timedelta
|
|
7
7
|
from decimal import Decimal
|
|
8
|
-
from typing import Union
|
|
9
8
|
import isodate
|
|
10
9
|
import pyarrow as pa
|
|
11
10
|
import strawberry
|
|
@@ -39,7 +38,7 @@ def _(mdn: pa.MonthDayNano) -> str:
|
|
|
39
38
|
|
|
40
39
|
Long = strawberry.scalar(int, name='Long', description="64-bit int", parse_value=parse_long)
|
|
41
40
|
Duration = strawberry.scalar(
|
|
42
|
-
|
|
41
|
+
timedelta | pa.MonthDayNano,
|
|
43
42
|
name='Duration',
|
|
44
43
|
description="Duration (isoformat)",
|
|
45
44
|
specified_by_url="https://en.wikipedia.org/wiki/ISO_8601#Durations",
|
|
@@ -66,6 +65,8 @@ type_map = {
|
|
|
66
65
|
pa.lib.Type_HALF_FLOAT: float,
|
|
67
66
|
pa.lib.Type_FLOAT: float,
|
|
68
67
|
pa.lib.Type_DOUBLE: float,
|
|
68
|
+
pa.lib.Type_DECIMAL32: Decimal,
|
|
69
|
+
pa.lib.Type_DECIMAL64: Decimal,
|
|
69
70
|
pa.lib.Type_DECIMAL128: Decimal,
|
|
70
71
|
pa.lib.Type_DECIMAL256: Decimal,
|
|
71
72
|
pa.lib.Type_DATE32: date,
|
|
@@ -12,7 +12,7 @@ progress on the second pass.
|
|
|
12
12
|
import operator
|
|
13
13
|
import shutil
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Annotated, Callable
|
|
15
|
+
from typing import Annotated, Callable
|
|
16
16
|
import numpy as np
|
|
17
17
|
import pyarrow.compute as pc
|
|
18
18
|
import pyarrow.dataset as ds
|
|
@@ -43,7 +43,7 @@ def write_batches(
|
|
|
43
43
|
pbar.update(len(batch))
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def write_fragments(dataset: ds.Dataset, base_dir: str, func:
|
|
46
|
+
def write_fragments(dataset: ds.Dataset, base_dir: str, func: Callable | None = None, **options):
|
|
47
47
|
"""Rewrite partition files by fragment to consolidate, optionally transforming."""
|
|
48
48
|
options['format'] = 'parquet'
|
|
49
49
|
exprs = {Path(frag.path).parent: frag.partition_expression for frag in dataset.get_fragments()}
|
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: graphique
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9
|
|
4
4
|
Summary: GraphQL service for arrow tables and parquet data sets.
|
|
5
5
|
Author-email: Aric Coady <aric.coady@gmail.com>
|
|
6
|
-
License:
|
|
7
|
-
|
|
8
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
-
you may not use this file except in compliance with the License.
|
|
10
|
-
You may obtain a copy of the License at
|
|
11
|
-
|
|
12
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
-
|
|
14
|
-
Unless required by applicable law or agreed to in writing, software
|
|
15
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
-
See the License for the specific language governing permissions and
|
|
18
|
-
limitations under the License.
|
|
19
|
-
|
|
6
|
+
License: Apache-2.0
|
|
20
7
|
Project-URL: Homepage, https://github.com/coady/graphique
|
|
21
8
|
Project-URL: Documentation, https://coady.github.io/graphique
|
|
22
9
|
Project-URL: Changelog, https://github.com/coady/graphique/blob/main/CHANGELOG.md
|
|
@@ -25,10 +12,8 @@ Keywords: graphql,arrow,parquet
|
|
|
25
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
26
13
|
Classifier: Intended Audience :: Developers
|
|
27
14
|
Classifier: Intended Audience :: Science/Research
|
|
28
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
29
15
|
Classifier: Operating System :: OS Independent
|
|
30
16
|
Classifier: Programming Language :: Python :: 3
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
32
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
33
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -38,10 +23,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
|
38
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
39
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
25
|
Classifier: Typing :: Typed
|
|
41
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.10
|
|
42
27
|
Description-Content-Type: text/markdown
|
|
43
28
|
License-File: LICENSE.txt
|
|
44
|
-
Requires-Dist: pyarrow>=
|
|
29
|
+
Requires-Dist: pyarrow>=20
|
|
45
30
|
Requires-Dist: strawberry-graphql[asgi,cli]>=0.236
|
|
46
31
|
Requires-Dist: numpy
|
|
47
32
|
Requires-Dist: isodate>=0.7
|
|
@@ -49,6 +34,7 @@ Provides-Extra: server
|
|
|
49
34
|
Requires-Dist: uvicorn[standard]; extra == "server"
|
|
50
35
|
Provides-Extra: cli
|
|
51
36
|
Requires-Dist: tqdm; extra == "cli"
|
|
37
|
+
Dynamic: license-file
|
|
52
38
|
|
|
53
39
|
[](https://pypi.org/project/graphique/)
|
|
54
40
|

|
|
@@ -4,22 +4,20 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graphique"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.9"
|
|
8
8
|
dynamic = ["dependencies"]
|
|
9
9
|
description = "GraphQL service for arrow tables and parquet data sets."
|
|
10
10
|
readme = "README.md"
|
|
11
|
-
requires-python = ">=3.
|
|
12
|
-
license = {
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = {text = "Apache-2.0"}
|
|
13
13
|
authors = [{name = "Aric Coady", email = "aric.coady@gmail.com"}]
|
|
14
14
|
keywords = ["graphql", "arrow", "parquet"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 5 - Production/Stable",
|
|
17
17
|
"Intended Audience :: Developers",
|
|
18
18
|
"Intended Audience :: Science/Research",
|
|
19
|
-
"License :: OSI Approved :: Apache Software License",
|
|
20
19
|
"Operating System :: OS Independent",
|
|
21
20
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.9",
|
|
23
21
|
"Programming Language :: Python :: 3.10",
|
|
24
22
|
"Programming Language :: Python :: 3.11",
|
|
25
23
|
"Programming Language :: Python :: 3.12",
|
|
@@ -187,3 +187,5 @@ def test_not_implemented():
|
|
|
187
187
|
pa.table({'': list('aba')}).group_by([]).aggregate([('', 'first'), ('', 'last')])
|
|
188
188
|
with pytest.raises(ValueError):
|
|
189
189
|
pc.pairwise_diff(pa.chunked_array([[0]]))
|
|
190
|
+
with pytest.raises(NotImplementedError):
|
|
191
|
+
pc.rank(pa.table({'': array}), [('', 'ascending')])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|