graphique 1.3__tar.gz → 1.5__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.3 → graphique-1.5}/PKG-INFO +11 -7
- {graphique-1.3 → graphique-1.5}/README.md +3 -3
- {graphique-1.3 → graphique-1.5}/graphique/__init__.py +1 -1
- {graphique-1.3 → graphique-1.5}/graphique/core.py +69 -67
- {graphique-1.3 → graphique-1.5}/graphique/inputs.py +90 -84
- {graphique-1.3 → graphique-1.5}/graphique/interface.py +80 -149
- {graphique-1.3 → graphique-1.5}/graphique/middleware.py +5 -2
- {graphique-1.3 → graphique-1.5}/graphique/models.py +33 -33
- {graphique-1.3 → graphique-1.5}/graphique/scalars.py +1 -0
- {graphique-1.3 → graphique-1.5}/graphique/service.py +2 -1
- {graphique-1.3 → graphique-1.5}/graphique/shell.py +23 -12
- {graphique-1.3 → graphique-1.5}/graphique.egg-info/PKG-INFO +11 -7
- {graphique-1.3 → graphique-1.5}/graphique.egg-info/SOURCES.txt +6 -1
- {graphique-1.3 → graphique-1.5}/graphique.egg-info/requires.txt +1 -1
- {graphique-1.3 → graphique-1.5}/pyproject.toml +9 -8
- graphique-1.5/requirements.in +2 -0
- graphique-1.5/tests/test_core.py +226 -0
- graphique-1.5/tests/test_dataset.py +274 -0
- graphique-1.5/tests/test_models.py +373 -0
- graphique-1.5/tests/test_service.py +454 -0
- {graphique-1.3 → graphique-1.5}/LICENSE.txt +0 -0
- {graphique-1.3 → graphique-1.5}/graphique/py.typed +0 -0
- {graphique-1.3 → graphique-1.5}/graphique.egg-info/dependency_links.txt +0 -0
- {graphique-1.3 → graphique-1.5}/graphique.egg-info/top_level.txt +0 -0
- {graphique-1.3 → graphique-1.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: graphique
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5
|
|
4
4
|
Summary: GraphQL service for arrow tables and parquet data sets.
|
|
5
5
|
Author-email: Aric Coady <aric.coady@gmail.com>
|
|
6
6
|
License: Copyright 2022 Aric Coady
|
|
@@ -28,20 +28,24 @@ Classifier: Intended Audience :: Science/Research
|
|
|
28
28
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
29
29
|
Classifier: Operating System :: OS Independent
|
|
30
30
|
Classifier: Programming Language :: Python :: 3
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
32
31
|
Classifier: Programming Language :: Python :: 3.9
|
|
33
32
|
Classifier: Programming Language :: Python :: 3.10
|
|
34
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
35
|
Classifier: Topic :: Database :: Database Engines/Servers
|
|
36
36
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
37
37
|
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
38
38
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
39
|
Classifier: Typing :: Typed
|
|
40
|
-
Requires-Python: >=3.
|
|
40
|
+
Requires-Python: >=3.9
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE.txt
|
|
43
|
+
Requires-Dist: pyarrow>=15
|
|
44
|
+
Requires-Dist: strawberry-graphql[asgi,cli]>=0.198
|
|
42
45
|
Provides-Extra: server
|
|
46
|
+
Requires-Dist: uvicorn[standard]; extra == "server"
|
|
43
47
|
Provides-Extra: cli
|
|
44
|
-
|
|
48
|
+
Requires-Dist: tqdm; extra == "cli"
|
|
45
49
|
|
|
46
50
|
[](https://pypi.org/project/graphique/)
|
|
47
51
|

|
|
@@ -50,8 +54,8 @@ License-File: LICENSE.txt
|
|
|
50
54
|
[](https://github.com/coady/graphique/actions)
|
|
51
55
|
[](https://codecov.io/gh/coady/graphique/)
|
|
52
56
|
[](https://github.com/coady/graphique/security/code-scanning)
|
|
53
|
-
[](https://github.com/astral-sh/ruff)
|
|
58
|
+
[](https://mypy-lang.org/)
|
|
55
59
|
|
|
56
60
|
[GraphQL](https://graphql.org) service for [arrow](https://arrow.apache.org) tables and [parquet](https://parquet.apache.org) data sets. The schema for a query API is derived automatically.
|
|
57
61
|
|
|
@@ -140,7 +144,7 @@ If index columns are detected in the schema metadata, then an initial `filter` w
|
|
|
140
144
|
```
|
|
141
145
|
|
|
142
146
|
## Dependencies
|
|
143
|
-
* pyarrow
|
|
147
|
+
* pyarrow
|
|
144
148
|
* strawberry-graphql[asgi,cli]
|
|
145
149
|
* uvicorn (or other [ASGI server](https://asgi.readthedocs.io/en/latest/implementations.html))
|
|
146
150
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
[](https://github.com/coady/graphique/actions)
|
|
6
6
|
[](https://codecov.io/gh/coady/graphique/)
|
|
7
7
|
[](https://github.com/coady/graphique/security/code-scanning)
|
|
8
|
-
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](https://mypy-lang.org/)
|
|
10
10
|
|
|
11
11
|
[GraphQL](https://graphql.org) service for [arrow](https://arrow.apache.org) tables and [parquet](https://parquet.apache.org) data sets. The schema for a query API is derived automatically.
|
|
12
12
|
|
|
@@ -95,7 +95,7 @@ If index columns are detected in the schema metadata, then an initial `filter` w
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
## Dependencies
|
|
98
|
-
* pyarrow
|
|
98
|
+
* pyarrow
|
|
99
99
|
* strawberry-graphql[asgi,cli]
|
|
100
100
|
* uvicorn (or other [ASGI server](https://asgi.readthedocs.io/en/latest/implementations.html))
|
|
101
101
|
|
|
@@ -4,6 +4,7 @@ Core utilities that add pandas-esque features to arrow arrays and tables.
|
|
|
4
4
|
Arrow forbids subclassing, so the classes are for logical grouping.
|
|
5
5
|
Their methods are called as functions.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import bisect
|
|
8
9
|
import contextlib
|
|
9
10
|
import functools
|
|
@@ -11,12 +12,15 @@ import inspect
|
|
|
11
12
|
import itertools
|
|
12
13
|
import operator
|
|
13
14
|
import json
|
|
15
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
14
16
|
from dataclasses import dataclass
|
|
15
|
-
from typing import
|
|
17
|
+
from typing import Optional, Union, get_type_hints
|
|
16
18
|
import numpy as np # type: ignore
|
|
17
19
|
import pyarrow as pa
|
|
20
|
+
import pyarrow.acero as ac
|
|
18
21
|
import pyarrow.compute as pc
|
|
19
22
|
import pyarrow.dataset as ds
|
|
23
|
+
from typing_extensions import Self
|
|
20
24
|
|
|
21
25
|
Array = Union[pa.Array, pa.ChunkedArray]
|
|
22
26
|
Batch = Union[pa.RecordBatch, pa.Table]
|
|
@@ -49,7 +53,7 @@ class Agg:
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
associatives = {'all', 'any', 'first', 'last', 'max', 'min', 'one', 'product', 'sum'}
|
|
52
|
-
associatives |= {'count'
|
|
56
|
+
associatives |= {'count'} # transformed to be associative
|
|
53
57
|
|
|
54
58
|
def __init__(self, name: str, alias: str = '', **options):
|
|
55
59
|
self.name = name
|
|
@@ -57,11 +61,10 @@ class Agg:
|
|
|
57
61
|
self.options = options
|
|
58
62
|
|
|
59
63
|
def astuple(self, func: str) -> tuple:
|
|
60
|
-
options = self.option_map[func](**self.options)
|
|
61
|
-
return self.name, func, options
|
|
64
|
+
options = self.option_map[func.rpartition('hash_')[-1]](**self.options)
|
|
65
|
+
return self.name, func, options, self.alias
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
last = operator.itemgetter(-1)
|
|
67
|
+
one = operator.itemgetter(0)
|
|
65
68
|
list = staticmethod(lambda array: array)
|
|
66
69
|
|
|
67
70
|
@staticmethod
|
|
@@ -91,25 +94,23 @@ def sort_key(name: str) -> tuple:
|
|
|
91
94
|
return name.lstrip('-'), ('descending' if name.startswith('-') else 'ascending')
|
|
92
95
|
|
|
93
96
|
|
|
94
|
-
def
|
|
95
|
-
"""
|
|
96
|
-
return array.dictionary_decode() if isinstance(array, pa.DictionaryArray) else array
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def register(func: Callable) -> pc.Function:
|
|
100
|
-
"""Register user defined scalar function."""
|
|
97
|
+
def register(func: Callable, kind: str = 'scalar') -> pc.Function:
|
|
98
|
+
"""Register user defined function by kind."""
|
|
101
99
|
doc = inspect.getdoc(func)
|
|
102
100
|
doc = {'summary': doc.splitlines()[0], 'description': doc} # type: ignore
|
|
103
101
|
annotations = dict(get_type_hints(func))
|
|
104
102
|
result = annotations.pop('return')
|
|
105
103
|
with contextlib.suppress(pa.ArrowKeyError): # apache/arrow#{31611,31612}
|
|
106
|
-
pc
|
|
104
|
+
getattr(pc, f'register_{kind}_function')(func, func.__name__, doc, annotations, result)
|
|
107
105
|
return pc.get_function(func.__name__)
|
|
108
106
|
|
|
109
107
|
|
|
110
108
|
@register
|
|
111
109
|
def digitize(
|
|
112
|
-
ctx,
|
|
110
|
+
ctx,
|
|
111
|
+
array: pa.float64(), # type: ignore
|
|
112
|
+
bins: pa.list_(pa.float64()), # type: ignore
|
|
113
|
+
right: pa.bool_(), # type: ignore
|
|
113
114
|
) -> pa.int64(): # type: ignore
|
|
114
115
|
"""Return the indices of the bins to which each value in input array belongs."""
|
|
115
116
|
return pa.array(np.digitize(array, bins.values, right.as_py()))
|
|
@@ -283,6 +284,13 @@ class Column(pa.ChunkedArray):
|
|
|
283
284
|
mask = predicate(Column.diff(self), *args) if args else Column.diff(self, predicate)
|
|
284
285
|
return pc.indices_nonzero(pa.chunked_array(ends + mask.chunks + ends))
|
|
285
286
|
|
|
287
|
+
def first_last(self, **options):
|
|
288
|
+
if not pa.types.is_dictionary(self.type):
|
|
289
|
+
return pc.first_last(self, **options).as_py()
|
|
290
|
+
self = self.combine_chunks()
|
|
291
|
+
indices = pc.first_last(self.indices, **options).as_py().items()
|
|
292
|
+
return {key: None if index is None else self.dictionary[index] for key, index in indices}
|
|
293
|
+
|
|
286
294
|
def min_max(self, **options):
|
|
287
295
|
if pa.types.is_dictionary(self.type):
|
|
288
296
|
self = self.unique().dictionary_decode()
|
|
@@ -343,7 +351,7 @@ class Table(pa.Table):
|
|
|
343
351
|
"""Return table with union of columns."""
|
|
344
352
|
columns: dict = {}
|
|
345
353
|
for table in tables:
|
|
346
|
-
columns
|
|
354
|
+
columns |= Table.columns(table)
|
|
347
355
|
return type(tables[0]).from_pydict(columns)
|
|
348
356
|
|
|
349
357
|
def range(self, name: str, lower=None, upper=None, **includes) -> pa.Table:
|
|
@@ -403,70 +411,31 @@ class Table(pa.Table):
|
|
|
403
411
|
table = Table.union(scalars, Table.from_offsets(lists, offsets))
|
|
404
412
|
return table, Column.diff(offsets)
|
|
405
413
|
|
|
406
|
-
def group(
|
|
407
|
-
self,
|
|
408
|
-
*names: str,
|
|
409
|
-
counts: str = '',
|
|
410
|
-
first: Sequence[Agg] = (),
|
|
411
|
-
last: Sequence[Agg] = (),
|
|
412
|
-
**funcs: Sequence[Agg],
|
|
413
|
-
) -> Union[pa.Table, pa.RecordBatch]:
|
|
414
|
+
def group(self, *names: str, counts: str = '', **funcs: Sequence[Agg]) -> pa.Table:
|
|
414
415
|
"""Group by and aggregate.
|
|
415
416
|
|
|
416
417
|
Args:
|
|
417
418
|
*names: columns to group by
|
|
418
419
|
counts: alias for optional row counts
|
|
419
|
-
first: columns to take first value
|
|
420
|
-
last: columns to take last value
|
|
421
420
|
**funcs: aggregate funcs with columns options
|
|
422
421
|
"""
|
|
423
422
|
if isinstance(self, pa.Table):
|
|
424
423
|
self = self.unify_dictionaries()
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
prefix = 'hash_' if names else ''
|
|
425
|
+
aggs = [agg.astuple(prefix + func) for func in funcs for agg in funcs[func]]
|
|
427
426
|
if counts:
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if first or last:
|
|
431
|
-
columns[''] = np.arange(len(self))
|
|
432
|
-
args.append(('', 'min_max'))
|
|
433
|
-
lists: Sequence[Agg] = []
|
|
434
|
-
list_cols = [self[agg.name] for agg in funcs.get('list', [])]
|
|
435
|
-
if any(pa.types.is_dictionary(col.type) or Column.is_list_type(col) for col in list_cols):
|
|
436
|
-
columns[''] = np.arange(len(self))
|
|
437
|
-
args.append(('', 'list'))
|
|
438
|
-
lists = funcs.pop('list')
|
|
439
|
-
for func, aggs in funcs.items():
|
|
440
|
-
aliases.update({f'{agg.name}_{func}': agg.alias for agg in aggs})
|
|
441
|
-
args += (agg.astuple(func) for agg in aggs) # type: ignore
|
|
442
|
-
table = pa.table(columns).group_by(list(names)).aggregate(args)
|
|
443
|
-
columns = {name: table[name].combine_chunks() for name in table.schema.names}
|
|
444
|
-
if first or last:
|
|
445
|
-
for aggs, indices in zip([first, last], columns.pop('_min_max').flatten()):
|
|
446
|
-
columns.update({agg.alias: self[agg.name].take(indices) for agg in aggs})
|
|
447
|
-
if lists:
|
|
448
|
-
indices = columns.pop('_list')
|
|
449
|
-
table = self.select({agg.name for agg in lists}).take(indices.values)
|
|
450
|
-
table = Table.from_offsets(table, indices.offsets)
|
|
451
|
-
columns.update({agg.alias: table[agg.name] for agg in lists})
|
|
452
|
-
columns.update({name: decode(columns[name]) for name in names})
|
|
453
|
-
columns = {aliases.get(name, name): columns[name] for name in columns}
|
|
454
|
-
return type(self).from_pydict(columns)
|
|
427
|
+
aggs.append(([], 'hash_count_all', None, counts))
|
|
428
|
+
return Declarations(self).aggregate(aggs, names).to_table()
|
|
455
429
|
|
|
456
430
|
def aggregate(self, counts: str = '', **funcs: Sequence[Agg]) -> dict:
|
|
457
431
|
"""Return aggregated scalars as a row of data."""
|
|
458
432
|
row = {counts: len(self)} if counts else {}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
func = getattr(Agg, key)
|
|
466
|
-
row.update({agg.alias: func(self[agg.name], **agg.options) for agg in funcs[key]})
|
|
467
|
-
if args:
|
|
468
|
-
table = self.group_by([]).aggregate(args)
|
|
469
|
-
row.update({aliases[name]: table[name][0] for name in table.schema.names})
|
|
433
|
+
for key in ('one', 'list', 'distinct'): # hash only functions
|
|
434
|
+
func, aggs = getattr(Agg, key), funcs.pop(key, [])
|
|
435
|
+
row |= {agg.alias: func(self[agg.name], **agg.options) for agg in aggs}
|
|
436
|
+
if funcs:
|
|
437
|
+
table = Table.group(self, **funcs) # type: ignore
|
|
438
|
+
row |= {name: table[name][0] for name in table.schema.names}
|
|
470
439
|
for name, value in row.items():
|
|
471
440
|
if isinstance(value, pa.ChunkedArray):
|
|
472
441
|
row[name] = value.combine_chunks()
|
|
@@ -649,7 +618,7 @@ class Table(pa.Table):
|
|
|
649
618
|
yield None
|
|
650
619
|
else:
|
|
651
620
|
row = {name: pa.repeat(self[name][index], count) for name in scalars}
|
|
652
|
-
row
|
|
621
|
+
row |= {name: self[name][index].values for name in lists}
|
|
653
622
|
yield pa.RecordBatch.from_pydict(row)
|
|
654
623
|
|
|
655
624
|
def size(self) -> str:
|
|
@@ -658,3 +627,36 @@ class Table(pa.Table):
|
|
|
658
627
|
for prefix in itertools.takewhile(lambda _: size >= 1e3, 'kMGT'):
|
|
659
628
|
size /= 1e3
|
|
660
629
|
return f'{size:n} {prefix}B'
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class Declarations(list):
|
|
633
|
+
"""[Acero](https://arrow.apache.org/docs/python/api/acero.html) engine declarations."""
|
|
634
|
+
|
|
635
|
+
option_map = {
|
|
636
|
+
'table_source': ac.TableSourceNodeOptions,
|
|
637
|
+
'scan': ac.ScanNodeOptions,
|
|
638
|
+
'filter': ac.FilterNodeOptions,
|
|
639
|
+
'project': ac.ProjectNodeOptions,
|
|
640
|
+
'aggregate': ac.AggregateNodeOptions,
|
|
641
|
+
'order_by': ac.OrderByNodeOptions,
|
|
642
|
+
'hashjoin': ac.HashJoinNodeOptions,
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
def __init__(self, source: Union[pa.Table, ds.Dataset]):
|
|
646
|
+
if isinstance(source, ds.Dataset):
|
|
647
|
+
expr = source._scan_options.get('filter')
|
|
648
|
+
self.scan(source, filter=expr)
|
|
649
|
+
if expr is not None:
|
|
650
|
+
self.filter(expr)
|
|
651
|
+
else:
|
|
652
|
+
self.table_source(source)
|
|
653
|
+
|
|
654
|
+
def apply(self, name: str, *args, **kwargs) -> Self:
|
|
655
|
+
self.append(ac.Declaration(name, self.option_map[name](*args, **kwargs)))
|
|
656
|
+
return self
|
|
657
|
+
|
|
658
|
+
def __getattr__(self, name: str) -> Callable:
|
|
659
|
+
return functools.partial(self.apply, name)
|
|
660
|
+
|
|
661
|
+
def to_table(self, use_threads: bool = False) -> pa.Table:
|
|
662
|
+
return ac.Declaration.from_sequence(self).to_table(use_threads)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
GraphQL input types.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import functools
|
|
5
6
|
import inspect
|
|
6
7
|
import operator
|
|
8
|
+
from collections.abc import Callable, Iterable
|
|
7
9
|
from datetime import date, datetime, time, timedelta
|
|
8
10
|
from decimal import Decimal
|
|
9
|
-
from typing import
|
|
11
|
+
from typing import Generic, Optional, TypeVar, no_type_check
|
|
10
12
|
import pyarrow as pa
|
|
11
13
|
import pyarrow.compute as pc
|
|
12
14
|
import pyarrow.dataset as ds
|
|
@@ -18,7 +20,7 @@ from strawberry.schema_directive import Location
|
|
|
18
20
|
from strawberry.field import StrawberryField
|
|
19
21
|
from strawberry.scalars import JSON
|
|
20
22
|
from typing_extensions import Self
|
|
21
|
-
from .core import Agg
|
|
23
|
+
from .core import Agg
|
|
22
24
|
from .scalars import Long
|
|
23
25
|
|
|
24
26
|
T = TypeVar('T')
|
|
@@ -81,7 +83,7 @@ def default_field(
|
|
|
81
83
|
|
|
82
84
|
@strawberry.input(description="predicates for scalars")
|
|
83
85
|
class Filter(Generic[T], Input):
|
|
84
|
-
eq: Optional[
|
|
86
|
+
eq: Optional[list[Optional[T]]] = default_field(
|
|
85
87
|
description="== or `isin`; `null` is equivalent to arrow `is_null`.", nullable=True
|
|
86
88
|
)
|
|
87
89
|
ne: Optional[T] = default_field(
|
|
@@ -143,7 +145,7 @@ class Mode(Field):
|
|
|
143
145
|
|
|
144
146
|
@strawberry.input
|
|
145
147
|
class Quantile(Field):
|
|
146
|
-
q:
|
|
148
|
+
q: list[float] = (0.5,) # type: ignore
|
|
147
149
|
interpolation: str = 'linear'
|
|
148
150
|
skip_nulls: bool = True
|
|
149
151
|
min_count: int = 1
|
|
@@ -158,13 +160,13 @@ class Rank(Field):
|
|
|
158
160
|
|
|
159
161
|
@strawberry.input
|
|
160
162
|
class Sort:
|
|
161
|
-
by:
|
|
163
|
+
by: list[str]
|
|
162
164
|
length: Optional[Long] = None
|
|
163
165
|
|
|
164
166
|
|
|
165
167
|
@strawberry.input
|
|
166
168
|
class Ranked:
|
|
167
|
-
by:
|
|
169
|
+
by: list[str]
|
|
168
170
|
max: int = 1
|
|
169
171
|
|
|
170
172
|
|
|
@@ -200,41 +202,45 @@ class VarianceAggregate(ScalarAggregate):
|
|
|
200
202
|
|
|
201
203
|
@strawberry.input(description=f"options for tdigest [aggregation]({links.compute}#aggregations)")
|
|
202
204
|
class TDigestAggregate(ScalarAggregate):
|
|
203
|
-
q:
|
|
205
|
+
q: list[float] = (0.5,) # type: ignore
|
|
204
206
|
delta: int = 100
|
|
205
207
|
buffer_size: int = 500
|
|
206
208
|
|
|
207
209
|
|
|
208
210
|
@strawberry.input
|
|
209
211
|
class ScalarAggregates(Input):
|
|
210
|
-
all:
|
|
211
|
-
any:
|
|
212
|
-
approximate_median:
|
|
213
|
-
count:
|
|
214
|
-
count_distinct:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
212
|
+
all: list[ScalarAggregate] = default_field([], func=pc.all)
|
|
213
|
+
any: list[ScalarAggregate] = default_field([], func=pc.any)
|
|
214
|
+
approximate_median: list[ScalarAggregate] = default_field([], func=pc.approximate_median)
|
|
215
|
+
count: list[CountAggregate] = default_field([], func=pc.count)
|
|
216
|
+
count_distinct: list[CountAggregate] = default_field([], func=pc.count_distinct)
|
|
217
|
+
first: list[ScalarAggregate] = default_field([], func=pc.first)
|
|
218
|
+
first_last: list[ScalarAggregate] = default_field([], func=pc.first_last)
|
|
219
|
+
last: list[ScalarAggregate] = default_field([], func=pc.last)
|
|
220
|
+
max: list[ScalarAggregate] = default_field([], func=pc.max)
|
|
221
|
+
mean: list[ScalarAggregate] = default_field([], func=pc.mean)
|
|
222
|
+
min: list[ScalarAggregate] = default_field([], func=pc.min)
|
|
223
|
+
min_max: list[ScalarAggregate] = default_field([], func=pc.min_max)
|
|
224
|
+
product: list[ScalarAggregate] = default_field([], func=pc.product)
|
|
225
|
+
stddev: list[VarianceAggregate] = default_field([], func=pc.stddev)
|
|
226
|
+
sum: list[ScalarAggregate] = default_field([], func=pc.sum)
|
|
227
|
+
tdigest: list[TDigestAggregate] = default_field([], func=pc.tdigest)
|
|
228
|
+
variance: list[VarianceAggregate] = default_field([], func=pc.variance)
|
|
224
229
|
|
|
225
230
|
def keys(self):
|
|
226
|
-
return (key for key in super().keys() if self[key])
|
|
231
|
+
return (key.rstrip('_') for key in super().keys() if self[key])
|
|
232
|
+
|
|
233
|
+
def __getitem__(self, name):
|
|
234
|
+
return super().__getitem__('list_' if name == 'list' else name)
|
|
227
235
|
|
|
228
236
|
|
|
229
237
|
@strawberry.input
|
|
230
238
|
class HashAggregates(ScalarAggregates):
|
|
231
|
-
distinct:
|
|
239
|
+
distinct: list[CountAggregate] = default_field(
|
|
232
240
|
[], description="distinct values within each scalar"
|
|
233
241
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
list: List[Field] = default_field([], description="all values within each scalar")
|
|
237
|
-
one: List[Field] = default_field([], description="arbitrary value within each scalar")
|
|
242
|
+
list_: list[Field] = default_field([], name='list', description="all values within each scalar")
|
|
243
|
+
one: list[Field] = default_field([], description="arbitrary value within each scalar")
|
|
238
244
|
|
|
239
245
|
|
|
240
246
|
@use_doc(strawberry.input)
|
|
@@ -266,7 +272,7 @@ class Expression:
|
|
|
266
272
|
* `ne` with a `null` scalar is equivalent to `is_valid`
|
|
267
273
|
"""
|
|
268
274
|
|
|
269
|
-
name:
|
|
275
|
+
name: list[str] = default_field([], description="field name(s)")
|
|
270
276
|
cast: str = strawberry.field(default='', description=f"cast as {links.type}")
|
|
271
277
|
safe: bool = strawberry.field(default=True, description="check for conversion errors on cast")
|
|
272
278
|
value: Optional[JSON] = default_field(
|
|
@@ -275,29 +281,29 @@ class Expression:
|
|
|
275
281
|
kleene: bool = strawberry.field(default=False, description="use kleene logic for booleans")
|
|
276
282
|
checked: bool = strawberry.field(default=False, description="check for overflow errors")
|
|
277
283
|
|
|
278
|
-
base64:
|
|
279
|
-
date_:
|
|
280
|
-
datetime_:
|
|
281
|
-
decimal:
|
|
282
|
-
duration:
|
|
283
|
-
time_:
|
|
284
|
-
|
|
285
|
-
eq:
|
|
286
|
-
ne:
|
|
287
|
-
lt:
|
|
288
|
-
le:
|
|
289
|
-
gt:
|
|
290
|
-
ge:
|
|
284
|
+
base64: list[bytes] = default_field([])
|
|
285
|
+
date_: list[date] = default_field([], name='date')
|
|
286
|
+
datetime_: list[datetime] = default_field([], name='datetime')
|
|
287
|
+
decimal: list[Decimal] = default_field([])
|
|
288
|
+
duration: list[timedelta] = default_field([])
|
|
289
|
+
time_: list[time] = default_field([], name='time')
|
|
290
|
+
|
|
291
|
+
eq: list['Expression'] = default_field([], description="==")
|
|
292
|
+
ne: list['Expression'] = default_field([], description="!=")
|
|
293
|
+
lt: list['Expression'] = default_field([], description="<")
|
|
294
|
+
le: list['Expression'] = default_field([], description="<=")
|
|
295
|
+
gt: list['Expression'] = default_field([], description=r"\>")
|
|
296
|
+
ge: list['Expression'] = default_field([], description=r"\>=")
|
|
291
297
|
inv: Optional['Expression'] = default_field(description="~")
|
|
292
298
|
|
|
293
299
|
abs: Optional['Expression'] = default_field(func=pc.abs)
|
|
294
|
-
add:
|
|
295
|
-
divide:
|
|
296
|
-
multiply:
|
|
300
|
+
add: list['Expression'] = default_field([], func=pc.add)
|
|
301
|
+
divide: list['Expression'] = default_field([], func=pc.divide)
|
|
302
|
+
multiply: list['Expression'] = default_field([], func=pc.multiply)
|
|
297
303
|
negate: Optional['Expression'] = default_field(func=pc.negate)
|
|
298
|
-
power:
|
|
304
|
+
power: list['Expression'] = default_field([], func=pc.power)
|
|
299
305
|
sign: Optional['Expression'] = default_field(func=pc.sign)
|
|
300
|
-
subtract:
|
|
306
|
+
subtract: list['Expression'] = default_field([], func=pc.subtract)
|
|
301
307
|
|
|
302
308
|
bit_wise: Optional['BitWise'] = default_field(description="bit-wise functions")
|
|
303
309
|
rounding: Optional['Rounding'] = default_field(description="rounding functions")
|
|
@@ -307,10 +313,10 @@ class Expression:
|
|
|
307
313
|
description="element-wise aggregate functions"
|
|
308
314
|
)
|
|
309
315
|
|
|
310
|
-
and_:
|
|
311
|
-
and_not:
|
|
312
|
-
or_:
|
|
313
|
-
xor:
|
|
316
|
+
and_: list['Expression'] = default_field([], name='and', description="&")
|
|
317
|
+
and_not: list['Expression'] = default_field([], func=pc.and_not)
|
|
318
|
+
or_: list['Expression'] = default_field([], name='or', description="|")
|
|
319
|
+
xor: list['Expression'] = default_field([], func=pc.xor)
|
|
314
320
|
|
|
315
321
|
utf8: Optional['Utf8'] = default_field(description="utf8 string functions")
|
|
316
322
|
string_is_ascii: Optional['Expression'] = default_field(func=pc.string_is_ascii)
|
|
@@ -324,14 +330,14 @@ class Expression:
|
|
|
324
330
|
is_nan: Optional['Expression'] = default_field(func=pc.is_nan)
|
|
325
331
|
true_unless_null: Optional['Expression'] = default_field(func=pc.true_unless_null)
|
|
326
332
|
|
|
327
|
-
case_when:
|
|
328
|
-
choose:
|
|
329
|
-
coalesce:
|
|
330
|
-
if_else:
|
|
333
|
+
case_when: list['Expression'] = default_field([], func=pc.case_when)
|
|
334
|
+
choose: list['Expression'] = default_field([], func=pc.choose)
|
|
335
|
+
coalesce: list['Expression'] = default_field([], func=pc.coalesce)
|
|
336
|
+
if_else: list['Expression'] = default_field([], func=pc.if_else)
|
|
331
337
|
|
|
332
338
|
temporal: Optional['Temporal'] = default_field(description="temporal functions")
|
|
333
339
|
|
|
334
|
-
replace_with_mask:
|
|
340
|
+
replace_with_mask: list['Expression'] = default_field([], func=pc.replace_with_mask)
|
|
335
341
|
|
|
336
342
|
list: Optional['Lists'] = default_field(description="list array functions")
|
|
337
343
|
|
|
@@ -438,12 +444,12 @@ class Fields:
|
|
|
438
444
|
|
|
439
445
|
@strawberry.input(description="Bit-wise functions.")
|
|
440
446
|
class BitWise(Fields):
|
|
441
|
-
and_:
|
|
442
|
-
not_:
|
|
443
|
-
or_:
|
|
444
|
-
xor:
|
|
445
|
-
shift_left:
|
|
446
|
-
shift_right:
|
|
447
|
+
and_: list[Expression] = default_field([], name='and', func=pc.bit_wise_and)
|
|
448
|
+
not_: list[Expression] = default_field([], name='not', func=pc.bit_wise_not)
|
|
449
|
+
or_: list[Expression] = default_field([], name='or', func=pc.bit_wise_or)
|
|
450
|
+
xor: list[Expression] = default_field([], func=pc.bit_wise_xor)
|
|
451
|
+
shift_left: list[Expression] = default_field([], func=pc.shift_left)
|
|
452
|
+
shift_right: list[Expression] = default_field([], func=pc.shift_right)
|
|
447
453
|
|
|
448
454
|
def getfunc(self, name):
|
|
449
455
|
return getattr(pc, name if name.startswith('shift') else 'bit_wise_' + name.rstrip('_'))
|
|
@@ -470,7 +476,7 @@ class Rounding(Fields):
|
|
|
470
476
|
class Log(Fields):
|
|
471
477
|
ln: Optional[Expression] = default_field(func=pc.ln)
|
|
472
478
|
log1p: Optional[Expression] = default_field(func=pc.log1p)
|
|
473
|
-
logb:
|
|
479
|
+
logb: list[Expression] = default_field([], func=pc.logb)
|
|
474
480
|
|
|
475
481
|
|
|
476
482
|
@strawberry.input(description="Trigonometry functions.")
|
|
@@ -480,7 +486,7 @@ class Trig(Fields):
|
|
|
480
486
|
acos: Optional[Expression] = default_field(func=pc.acos)
|
|
481
487
|
asin: Optional[Expression] = default_field(func=pc.asin)
|
|
482
488
|
atan: Optional[Expression] = default_field(func=pc.atan)
|
|
483
|
-
atan2:
|
|
489
|
+
atan2: list[Expression] = default_field([], func=pc.atan2)
|
|
484
490
|
cos: Optional[Expression] = default_field(func=pc.cos)
|
|
485
491
|
sin: Optional[Expression] = default_field(func=pc.sin)
|
|
486
492
|
tan: Optional[Expression] = default_field(func=pc.tan)
|
|
@@ -491,8 +497,8 @@ class Trig(Fields):
|
|
|
491
497
|
|
|
492
498
|
@strawberry.input(description="Element-wise aggregate functions.")
|
|
493
499
|
class ElementWise(Fields):
|
|
494
|
-
min_element_wise:
|
|
495
|
-
max_element_wise:
|
|
500
|
+
min_element_wise: list[Expression] = default_field([], name='min', func=pc.min_element_wise)
|
|
501
|
+
max_element_wise: list[Expression] = default_field([], name='max', func=pc.max_element_wise)
|
|
496
502
|
skip_nulls: bool = True
|
|
497
503
|
|
|
498
504
|
|
|
@@ -544,11 +550,11 @@ class Utf8(Fields):
|
|
|
544
550
|
@strawberry.input(description="Binary functions.")
|
|
545
551
|
class Binary(Fields):
|
|
546
552
|
length: Optional[Expression] = default_field(func=pc.binary_length)
|
|
547
|
-
repeat:
|
|
553
|
+
repeat: list[Expression] = default_field([], func=pc.binary_repeat)
|
|
548
554
|
reverse: Optional[Expression] = default_field(func=pc.binary_reverse)
|
|
549
555
|
|
|
550
|
-
join:
|
|
551
|
-
join_element_wise:
|
|
556
|
+
join: list[Expression] = default_field([], func=pc.binary_join)
|
|
557
|
+
join_element_wise: list[Expression] = default_field([], func=pc.binary_join_element_wise)
|
|
552
558
|
null_handling: str = 'emit_null'
|
|
553
559
|
null_replacement: str = ''
|
|
554
560
|
|
|
@@ -609,23 +615,23 @@ class Temporal(Fields):
|
|
|
609
615
|
year: Optional[Expression] = default_field(func=pc.year)
|
|
610
616
|
year_month_day: Optional[Expression] = default_field(func=pc.year_month_day)
|
|
611
617
|
|
|
612
|
-
day_time_interval_between:
|
|
618
|
+
day_time_interval_between: list[Expression] = default_field(
|
|
613
619
|
[], func=pc.day_time_interval_between
|
|
614
620
|
)
|
|
615
|
-
days_between:
|
|
616
|
-
hours_between:
|
|
617
|
-
microseconds_between:
|
|
618
|
-
milliseconds_between:
|
|
619
|
-
minutes_between:
|
|
620
|
-
month_day_nano_interval_between:
|
|
621
|
+
days_between: list[Expression] = default_field([], func=pc.days_between)
|
|
622
|
+
hours_between: list[Expression] = default_field([], func=pc.hours_between)
|
|
623
|
+
microseconds_between: list[Expression] = default_field([], func=pc.microseconds_between)
|
|
624
|
+
milliseconds_between: list[Expression] = default_field([], func=pc.milliseconds_between)
|
|
625
|
+
minutes_between: list[Expression] = default_field([], func=pc.minutes_between)
|
|
626
|
+
month_day_nano_interval_between: list[Expression] = default_field(
|
|
621
627
|
[], func=pc.month_day_nano_interval_between
|
|
622
628
|
)
|
|
623
|
-
month_interval_between:
|
|
624
|
-
nanoseconds_between:
|
|
625
|
-
quarters_between:
|
|
626
|
-
seconds_between:
|
|
627
|
-
weeks_between:
|
|
628
|
-
years_between:
|
|
629
|
+
month_interval_between: list[Expression] = default_field([], func=pc.month_interval_between)
|
|
630
|
+
nanoseconds_between: list[Expression] = default_field([], func=pc.nanoseconds_between)
|
|
631
|
+
quarters_between: list[Expression] = default_field([], func=pc.quarters_between)
|
|
632
|
+
seconds_between: list[Expression] = default_field([], func=pc.seconds_between)
|
|
633
|
+
weeks_between: list[Expression] = default_field([], func=pc.weeks_between)
|
|
634
|
+
years_between: list[Expression] = default_field([], func=pc.years_between)
|
|
629
635
|
|
|
630
636
|
ceil_temporal: Optional[Expression] = default_field(name='ceil', func=pc.ceil_temporal)
|
|
631
637
|
floor_temporal: Optional[Expression] = default_field(name='floor', func=pc.floor_temporal)
|
|
@@ -657,8 +663,8 @@ class Temporal(Fields):
|
|
|
657
663
|
|
|
658
664
|
@strawberry.input(description="Set lookup functions.")
|
|
659
665
|
class SetLookup(Fields):
|
|
660
|
-
index_in:
|
|
661
|
-
digitize:
|
|
666
|
+
index_in: list[Expression] = default_field([], func=pc.index_in)
|
|
667
|
+
digitize: list[Expression] = default_field(
|
|
662
668
|
[],
|
|
663
669
|
description="numpy [digitize](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html)",
|
|
664
670
|
)
|
|
@@ -677,7 +683,7 @@ class SetLookup(Fields):
|
|
|
677
683
|
|
|
678
684
|
@strawberry.input(description="List array functions.")
|
|
679
685
|
class Lists(Fields):
|
|
680
|
-
element:
|
|
686
|
+
element: list[Expression] = default_field([], func=pc.list_element)
|
|
681
687
|
value_length: Optional[Expression] = default_field(func=pc.list_value_length)
|
|
682
688
|
# user defined functions
|
|
683
689
|
all: Optional[Expression] = default_field(func=pc.all)
|