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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphique
3
- Version: 1.3
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.8
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
- License-File: LICENSE.txt
48
+ Requires-Dist: tqdm; extra == "cli"
45
49
 
46
50
  [![image](https://img.shields.io/pypi/v/graphique.svg)](https://pypi.org/project/graphique/)
47
51
  ![image](https://img.shields.io/pypi/pyversions/graphique.svg)
@@ -50,8 +54,8 @@ License-File: LICENSE.txt
50
54
  [![image](https://github.com/coady/graphique/workflows/build/badge.svg)](https://github.com/coady/graphique/actions)
51
55
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
52
56
  [![image](https://github.com/coady/graphique/workflows/codeql/badge.svg)](https://github.com/coady/graphique/security/code-scanning)
53
- [![image](https://img.shields.io/badge/code%20style-black-000000.svg)](https://pypi.org/project/black/)
54
- [![image](http://mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
57
+ [![image](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
58
+ [![image](https://mypy-lang.org/static/mypy_badge.svg)](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 >=13
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
  [![image](https://github.com/coady/graphique/workflows/build/badge.svg)](https://github.com/coady/graphique/actions)
6
6
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
7
7
  [![image](https://github.com/coady/graphique/workflows/codeql/badge.svg)](https://github.com/coady/graphique/security/code-scanning)
8
- [![image](https://img.shields.io/badge/code%20style-black-000000.svg)](https://pypi.org/project/black/)
9
- [![image](http://mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
8
+ [![image](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
+ [![image](https://mypy-lang.org/static/mypy_badge.svg)](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 >=13
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
 
@@ -1,3 +1,3 @@
1
1
  from .middleware import GraphQL # noqa: F401
2
2
 
3
- __version__ = '1.3'
3
+ __version__ = '1.5'
@@ -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 Callable, Iterable, Iterator, Optional, Sequence, Union, get_type_hints
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', 'distinct', 'list'} # transformed to be associative
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
- first = one = operator.itemgetter(0)
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 decode(array: pa.Array) -> pa.Array:
95
- """Decode dictionary array."""
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.register_scalar_function(func, func.__name__, doc, annotations, result)
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, array: pa.float64(), bins: pa.list_(pa.float64()), right: pa.bool_() # type: ignore
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.update(Table.columns(table))
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
- columns = Table.columns(self)
426
- aliases, args = {}, [] # type: ignore
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
- aliases['count_all'] = counts
429
- args.append(([], 'count_all'))
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
- aliases, args = {}, [] # type: ignore
460
- for key in funcs:
461
- if hasattr(pc, key) and key not in ('first', 'last'):
462
- aliases.update({f'{agg.name}_{key}': agg.alias for agg in funcs[key]})
463
- args += (agg.astuple(key) for agg in funcs[key])
464
- else:
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.update({name: self[name][index].values for name in lists})
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 Callable, Generic, Iterable, List, Optional, TypeVar, no_type_check
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, ListChunk
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[List[Optional[T]]] = default_field(
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: List[float] = (0.5,) # type: ignore
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: List[str]
163
+ by: list[str]
162
164
  length: Optional[Long] = None
163
165
 
164
166
 
165
167
  @strawberry.input
166
168
  class Ranked:
167
- by: List[str]
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: List[float] = (0.5,) # type: ignore
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: List[ScalarAggregate] = default_field([], func=pc.all)
211
- any: List[ScalarAggregate] = default_field([], func=pc.any)
212
- approximate_median: List[ScalarAggregate] = default_field([], func=pc.approximate_median)
213
- count: List[CountAggregate] = default_field([], func=pc.count)
214
- count_distinct: List[CountAggregate] = default_field([], func=pc.count_distinct)
215
- max: List[ScalarAggregate] = default_field([], func=pc.max)
216
- mean: List[ScalarAggregate] = default_field([], func=pc.mean)
217
- min: List[ScalarAggregate] = default_field([], func=pc.min)
218
- min_max: List[ScalarAggregate] = default_field([], func=pc.min_max)
219
- product: List[ScalarAggregate] = default_field([], func=pc.product)
220
- stddev: List[VarianceAggregate] = default_field([], func=pc.stddev)
221
- sum: List[ScalarAggregate] = default_field([], func=pc.sum)
222
- tdigest: List[TDigestAggregate] = default_field([], func=pc.tdigest)
223
- variance: List[VarianceAggregate] = default_field([], func=pc.variance)
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: List[CountAggregate] = default_field(
239
+ distinct: list[CountAggregate] = default_field(
232
240
  [], description="distinct values within each scalar"
233
241
  )
234
- first: List[Field] = default_field([], func=ListChunk.first)
235
- last: List[Field] = default_field([], func=ListChunk.last)
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: List[str] = default_field([], description="field name(s)")
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: List[bytes] = default_field([])
279
- date_: List[date] = default_field([], name='date')
280
- datetime_: List[datetime] = default_field([], name='datetime')
281
- decimal: List[Decimal] = default_field([])
282
- duration: List[timedelta] = default_field([])
283
- time_: List[time] = default_field([], name='time')
284
-
285
- eq: List['Expression'] = default_field([], description="==")
286
- ne: List['Expression'] = default_field([], description="!=")
287
- lt: List['Expression'] = default_field([], description="<")
288
- le: List['Expression'] = default_field([], description="<=")
289
- gt: List['Expression'] = default_field([], description=r"\>")
290
- ge: List['Expression'] = default_field([], description=r"\>=")
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: List['Expression'] = default_field([], func=pc.add)
295
- divide: List['Expression'] = default_field([], func=pc.divide)
296
- multiply: List['Expression'] = default_field([], func=pc.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: List['Expression'] = default_field([], func=pc.power)
304
+ power: list['Expression'] = default_field([], func=pc.power)
299
305
  sign: Optional['Expression'] = default_field(func=pc.sign)
300
- subtract: List['Expression'] = default_field([], func=pc.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_: List['Expression'] = default_field([], name='and', description="&")
311
- and_not: List['Expression'] = default_field([], func=pc.and_not)
312
- or_: List['Expression'] = default_field([], name='or', description="|")
313
- xor: List['Expression'] = default_field([], func=pc.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: List['Expression'] = default_field([], func=pc.case_when)
328
- choose: List['Expression'] = default_field([], func=pc.choose)
329
- coalesce: List['Expression'] = default_field([], func=pc.coalesce)
330
- if_else: List['Expression'] = default_field([], func=pc.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: List['Expression'] = default_field([], func=pc.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_: List[Expression] = default_field([], name='and', func=pc.bit_wise_and)
442
- not_: List[Expression] = default_field([], name='not', func=pc.bit_wise_not)
443
- or_: List[Expression] = default_field([], name='or', func=pc.bit_wise_or)
444
- xor: List[Expression] = default_field([], func=pc.bit_wise_xor)
445
- shift_left: List[Expression] = default_field([], func=pc.shift_left)
446
- shift_right: List[Expression] = default_field([], func=pc.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: List[Expression] = default_field([], func=pc.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: List[Expression] = default_field([], func=pc.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: List[Expression] = default_field([], name='min', func=pc.min_element_wise)
495
- max_element_wise: List[Expression] = default_field([], name='max', func=pc.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: List[Expression] = default_field([], func=pc.binary_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: List[Expression] = default_field([], func=pc.binary_join)
551
- join_element_wise: List[Expression] = default_field([], func=pc.binary_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: List[Expression] = default_field(
618
+ day_time_interval_between: list[Expression] = default_field(
613
619
  [], func=pc.day_time_interval_between
614
620
  )
615
- days_between: List[Expression] = default_field([], func=pc.days_between)
616
- hours_between: List[Expression] = default_field([], func=pc.hours_between)
617
- microseconds_between: List[Expression] = default_field([], func=pc.microseconds_between)
618
- milliseconds_between: List[Expression] = default_field([], func=pc.milliseconds_between)
619
- minutes_between: List[Expression] = default_field([], func=pc.minutes_between)
620
- month_day_nano_interval_between: List[Expression] = default_field(
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: List[Expression] = default_field([], func=pc.month_interval_between)
624
- nanoseconds_between: List[Expression] = default_field([], func=pc.nanoseconds_between)
625
- quarters_between: List[Expression] = default_field([], func=pc.quarters_between)
626
- seconds_between: List[Expression] = default_field([], func=pc.seconds_between)
627
- weeks_between: List[Expression] = default_field([], func=pc.weeks_between)
628
- years_between: List[Expression] = default_field([], func=pc.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: List[Expression] = default_field([], func=pc.index_in)
661
- digitize: List[Expression] = default_field(
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: List[Expression] = default_field([], func=pc.list_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)