graphique 1.6__tar.gz → 1.7__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.
Files changed (28) hide show
  1. {graphique-1.6 → graphique-1.7}/PKG-INFO +6 -5
  2. {graphique-1.6 → graphique-1.7}/README.md +3 -2
  3. {graphique-1.6 → graphique-1.7}/graphique/__init__.py +0 -2
  4. {graphique-1.6 → graphique-1.7}/graphique/core.py +25 -20
  5. {graphique-1.6 → graphique-1.7}/graphique/inputs.py +2 -2
  6. {graphique-1.6 → graphique-1.7}/graphique/interface.py +2 -2
  7. {graphique-1.6 → graphique-1.7}/graphique/models.py +1 -1
  8. {graphique-1.6 → graphique-1.7}/graphique/scalars.py +26 -20
  9. {graphique-1.6 → graphique-1.7}/graphique/shell.py +11 -2
  10. {graphique-1.6 → graphique-1.7}/graphique.egg-info/PKG-INFO +6 -5
  11. {graphique-1.6 → graphique-1.7}/graphique.egg-info/SOURCES.txt +1 -0
  12. graphique-1.7/graphique.egg-info/requires.txt +8 -0
  13. {graphique-1.6 → graphique-1.7}/pyproject.toml +3 -10
  14. graphique-1.7/requirements.in +2 -0
  15. graphique-1.7/tests/test_bench.py +21 -0
  16. {graphique-1.6 → graphique-1.7}/tests/test_core.py +18 -8
  17. {graphique-1.6 → graphique-1.7}/tests/test_models.py +1 -3
  18. graphique-1.6/graphique.egg-info/requires.txt +0 -8
  19. graphique-1.6/requirements.in +0 -2
  20. {graphique-1.6 → graphique-1.7}/LICENSE.txt +0 -0
  21. {graphique-1.6 → graphique-1.7}/graphique/middleware.py +0 -0
  22. {graphique-1.6 → graphique-1.7}/graphique/py.typed +0 -0
  23. {graphique-1.6 → graphique-1.7}/graphique/service.py +0 -0
  24. {graphique-1.6 → graphique-1.7}/graphique.egg-info/dependency_links.txt +0 -0
  25. {graphique-1.6 → graphique-1.7}/graphique.egg-info/top_level.txt +0 -0
  26. {graphique-1.6 → graphique-1.7}/setup.cfg +0 -0
  27. {graphique-1.6 → graphique-1.7}/tests/test_dataset.py +0 -0
  28. {graphique-1.6 → graphique-1.7}/tests/test_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphique
3
- Version: 1.6
3
+ Version: 1.7
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
@@ -40,8 +40,8 @@ Classifier: Typing :: Typed
40
40
  Requires-Python: >=3.9
41
41
  Description-Content-Type: text/markdown
42
42
  License-File: LICENSE.txt
43
- Requires-Dist: pyarrow>=16
44
- Requires-Dist: strawberry-graphql[asgi,cli]>=0.221
43
+ Requires-Dist: pyarrow>=17
44
+ Requires-Dist: strawberry-graphql[asgi,cli]>=0.236
45
45
  Provides-Extra: server
46
46
  Requires-Dist: uvicorn[standard]; extra == "server"
47
47
  Provides-Extra: cli
@@ -51,9 +51,10 @@ Requires-Dist: tqdm; extra == "cli"
51
51
  ![image](https://img.shields.io/pypi/pyversions/graphique.svg)
52
52
  [![image](https://pepy.tech/badge/graphique)](https://pepy.tech/project/graphique)
53
53
  ![image](https://img.shields.io/pypi/status/graphique.svg)
54
- [![image](https://github.com/coady/graphique/workflows/build/badge.svg)](https://github.com/coady/graphique/actions)
54
+ [![build](https://github.com/coady/graphique/actions/workflows/build.yml/badge.svg)](https://github.com/coady/graphique/actions/workflows/build.yml)
55
55
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
56
- [![image](https://github.com/coady/graphique/workflows/codeql/badge.svg)](https://github.com/coady/graphique/security/code-scanning)
56
+ [![CodeQL](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql)
57
+ [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/coady/graphique)
57
58
  [![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
59
  [![image](https://mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
59
60
 
@@ -2,9 +2,10 @@
2
2
  ![image](https://img.shields.io/pypi/pyversions/graphique.svg)
3
3
  [![image](https://pepy.tech/badge/graphique)](https://pepy.tech/project/graphique)
4
4
  ![image](https://img.shields.io/pypi/status/graphique.svg)
5
- [![image](https://github.com/coady/graphique/workflows/build/badge.svg)](https://github.com/coady/graphique/actions)
5
+ [![build](https://github.com/coady/graphique/actions/workflows/build.yml/badge.svg)](https://github.com/coady/graphique/actions/workflows/build.yml)
6
6
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
7
- [![image](https://github.com/coady/graphique/workflows/codeql/badge.svg)](https://github.com/coady/graphique/security/code-scanning)
7
+ [![CodeQL](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql)
8
+ [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/coady/graphique)
8
9
  [![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
10
  [![image](https://mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
10
11
 
@@ -1,3 +1 @@
1
1
  from .middleware import GraphQL # noqa: F401
2
-
3
- __version__ = '1.6'
@@ -12,7 +12,7 @@ import inspect
12
12
  import itertools
13
13
  import operator
14
14
  import json
15
- from collections.abc import Callable, Iterable, Iterator, Sequence
15
+ from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
16
16
  from dataclasses import dataclass
17
17
  from typing import Optional, Union, get_type_hints
18
18
  import numpy as np
@@ -419,14 +419,16 @@ class Table(pa.Table):
419
419
  counts: alias for optional row counts
420
420
  **funcs: aggregate funcs with columns options
421
421
  """
422
- if isinstance(self, pa.Table):
423
- self = self.unify_dictionaries()
424
422
  prefix = 'hash_' if names else ''
425
423
  aggs = [agg.astuple(prefix + func) for func in funcs for agg in funcs[func]]
426
424
  columns = list(names) + [agg[0] for agg in aggs]
427
425
  if counts:
428
426
  aggs.append(([], 'hash_count_all', None, counts))
429
- return Declarations(self, columns).aggregate(aggs, names).to_table()
427
+ if isinstance(self, pa.Table):
428
+ decl = Declaration('table_source', self.unify_dictionaries())
429
+ else:
430
+ decl = Declaration.scan(self, columns=columns)
431
+ return decl.aggregate(aggs, names).to_table(use_threads=False)
430
432
 
431
433
  def aggregate(self, counts: str = '', **funcs: Sequence[Agg]) -> dict:
432
434
  """Return aggregated scalars as a row of data."""
@@ -630,8 +632,8 @@ class Table(pa.Table):
630
632
  return f'{size:n} {prefix}B'
631
633
 
632
634
 
633
- class Declarations(list):
634
- """[Acero](https://arrow.apache.org/docs/python/api/acero.html) engine declarations."""
635
+ class Declaration(ac.Declaration):
636
+ """[Acero](https://arrow.apache.org/docs/python/api/acero.html) engine declaration."""
635
637
 
636
638
  option_map = {
637
639
  'table_source': ac.TableSourceNodeOptions,
@@ -643,21 +645,24 @@ class Declarations(list):
643
645
  'hashjoin': ac.HashJoinNodeOptions,
644
646
  }
645
647
 
646
- def __init__(self, source: Union[pa.Table, ds.Dataset], columns=None):
647
- if isinstance(source, ds.Dataset):
648
- expr = source._scan_options.get('filter')
649
- self.scan(source, filter=expr, columns=columns)
650
- if expr is not None:
651
- self.filter(expr)
652
- else:
653
- self.table_source(source)
654
-
655
- def apply(self, name: str, *args, **kwargs) -> Self:
656
- self.append(ac.Declaration(name, self.option_map[name](*args, **kwargs)))
648
+ def __init__(self, name, *args, inputs=None, **options):
649
+ super().__init__(name, self.option_map[name](*args, **options), inputs)
650
+
651
+ @classmethod
652
+ def scan(cls, dataset: ds.Dataset, columns=None) -> Self:
653
+ """Return source node from a dataset."""
654
+ expr = dataset._scan_options.get('filter')
655
+ self = cls('scan', dataset, filter=expr, columns=columns)
656
+ if expr is not None:
657
+ self = self.filter(expr)
658
+ if isinstance(columns, Mapping):
659
+ self = self.project(columns.values(), columns)
660
+ elif columns is not None:
661
+ self = self.project(map(pc.field, columns))
657
662
  return self
658
663
 
664
+ def apply(self, name: str, *args, **options) -> Self:
665
+ return type(self)(name, *args, inputs=[self], **options)
666
+
659
667
  def __getattr__(self, name: str) -> Callable:
660
668
  return functools.partial(self.apply, name)
661
-
662
- def to_table(self, use_threads: bool = False) -> pa.Table:
663
- return ac.Declaration.from_sequence(self).to_table(use_threads)
@@ -15,9 +15,9 @@ import pyarrow.dataset as ds
15
15
  import strawberry
16
16
  from strawberry import UNSET
17
17
  from strawberry.annotation import StrawberryAnnotation
18
- from strawberry.arguments import StrawberryArgument
18
+ from strawberry.types.arguments import StrawberryArgument
19
19
  from strawberry.schema_directive import Location
20
- from strawberry.field import StrawberryField
20
+ from strawberry.types.field import StrawberryField
21
21
  from strawberry.scalars import JSON
22
22
  from typing_extensions import Self
23
23
  from .core import Agg
@@ -91,11 +91,11 @@ class Dataset:
91
91
 
92
92
  @classmethod
93
93
  @no_type_check
94
- def resolve_reference(cls, info, **keys) -> Self:
94
+ def resolve_reference(cls, info: Info, **keys) -> Self:
95
95
  """Return table from federated keys."""
96
96
  self = getattr(info.root_value, cls.field)
97
97
  queries = {name: Filter(eq=[keys[name]]) for name in keys}
98
- return self.filter(Info(info, None), **queries)
98
+ return self.filter(info, **queries)
99
99
 
100
100
  def columns(self, info: Info) -> dict:
101
101
  """fields for each column"""
@@ -14,7 +14,7 @@ import pyarrow as pa
14
14
  import pyarrow.compute as pc
15
15
  import strawberry
16
16
  from strawberry import Info
17
- from strawberry.field import StrawberryField
17
+ from strawberry.types.field import StrawberryField
18
18
  from .core import Column as C
19
19
  from .inputs import links
20
20
  from .scalars import Long, scalar_map, type_map
@@ -24,35 +24,41 @@ def parse_duration(value: str):
24
24
  parts = re.split(r'(-?\d+\.?\d*)', d_val.lower() + t_val)
25
25
  if parts.pop(0) != 'p':
26
26
  raise ValueError("Duration format must start with `P`")
27
+ multipliers = {'y': 12, 'w': 7, 'H': 3600, 'M': 60}
27
28
  for num, key in zip(parts[::2], parts[1::2]):
28
- if n := float(num) if '.' in num else int(num):
29
- if key in 'ym':
30
- months += n * 12 if key == 'y' else n
31
- elif key in 'wd':
32
- days += n * 7 if key == 'w' else n
33
- elif key in 'HMS':
34
- seconds += n * {'H': 3600, 'M': 60, 'S': 1}[key]
35
- else:
36
- raise ValueError(f"Invalid duration field: {key.upper()}")
37
- if months:
38
- return pa.MonthDayNano([months, days, int(seconds * 1_000_000_000)])
39
- return timedelta(days, seconds)
29
+ value = (float if '.' in num else int)(num) * multipliers.get(key, 1)
30
+ if key in 'ym':
31
+ months += value
32
+ elif key in 'wd':
33
+ days += value
34
+ elif key in 'HMS':
35
+ seconds += value
36
+ else:
37
+ raise ValueError(f"Invalid duration field: {key.upper()}")
38
+ if set(d_val).isdisjoint('YM'):
39
+ return timedelta(days, seconds)
40
+ return pa.MonthDayNano([months, days, int(seconds * 1_000_000_000)])
40
41
 
41
42
 
42
43
  @functools.singledispatch
43
- def duration_isoformat(td: timedelta) -> str:
44
- days = f'{td.days}D' if td.days else ''
45
- fraction = f'.{td.microseconds:06}' if td.microseconds else ''
46
- return f'P{days}T{td.seconds}{fraction}S'
44
+ def duration_isoformat(months: int, days: int, seconds: int, fraction: str = '.') -> str:
45
+ minutes, seconds = divmod(seconds, 60)
46
+ items = zip('YMDHM', divmod(months, 12) + (days,) + divmod(minutes, 60))
47
+ year, month, day, hour, minute = (f'{value}{key}' if value else '' for key, value in items)
48
+ fraction = fraction.rstrip('0').rstrip('.')
49
+ return f'P{year}{month}{day}T{hour}{minute}{seconds}{fraction}S'
50
+
51
+
52
+ @duration_isoformat.register
53
+ def _(td: timedelta) -> str: # type: ignore
54
+ return duration_isoformat(0, td.days, td.seconds, f'.{td.microseconds:06}')
47
55
 
48
56
 
49
57
  @duration_isoformat.register
50
58
  def _(mdn: pa.MonthDayNano) -> str:
51
- months = f'{mdn.months}M' if mdn.months else ''
52
- days = f'{mdn.days}D' if mdn.days else ''
53
59
  seconds, nanoseconds = divmod(mdn.nanoseconds, 1_000_000_000)
54
- fraction = f'.{nanoseconds:09}' if nanoseconds else ''
55
- return f'P{months}{days}T{seconds}{fraction}S'
60
+ value = duration_isoformat(mdn.months, mdn.days, seconds, f'.{nanoseconds:09}')
61
+ return value if mdn.months else value.replace('P', 'P0M')
56
62
 
57
63
 
58
64
  Long = strawberry.scalar(int, name='Long', description="64-bit int", parse_value=parse_long)
@@ -12,6 +12,8 @@ progress on the second pass.
12
12
  import shutil
13
13
  from pathlib import Path
14
14
  from typing import Annotated
15
+ import numpy as np
16
+ import pyarrow.compute as pc
15
17
  import pyarrow.dataset as ds
16
18
  import typer # type: ignore
17
19
  from tqdm import tqdm # type: ignore
@@ -22,12 +24,19 @@ def sort_key(name: str) -> tuple:
22
24
  return name.lstrip('-'), ('descending' if name.startswith('-') else 'ascending')
23
25
 
24
26
 
25
- def write_batches(scanner: ds.Scanner, base_dir: str, *partitioning: str, **options):
26
- """Partition dataset by batches."""
27
+ def write_batches(
28
+ scanner: ds.Scanner, base_dir: str, *partitioning: str, indices: str = '', **options
29
+ ):
30
+ """Partition dataset by batches.
31
+
32
+ Optionally include original indices.
33
+ """
27
34
  options.update(format='parquet', partitioning=partitioning)
28
35
  options.update(partitioning_flavor='hive', existing_data_behavior='overwrite_or_ignore')
29
36
  with tqdm(total=scanner.count_rows(), desc="Batches") as pbar:
30
37
  for index, batch in enumerate(scanner.to_batches()):
38
+ if indices:
39
+ batch = batch.append_column(indices, pc.add(np.arange(len(batch)), pbar.n))
31
40
  options['basename_template'] = f'part-{index}-{{i}}.parquet'
32
41
  ds.write_dataset(batch, base_dir, **options)
33
42
  pbar.update(len(batch))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphique
3
- Version: 1.6
3
+ Version: 1.7
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
@@ -40,8 +40,8 @@ Classifier: Typing :: Typed
40
40
  Requires-Python: >=3.9
41
41
  Description-Content-Type: text/markdown
42
42
  License-File: LICENSE.txt
43
- Requires-Dist: pyarrow>=16
44
- Requires-Dist: strawberry-graphql[asgi,cli]>=0.221
43
+ Requires-Dist: pyarrow>=17
44
+ Requires-Dist: strawberry-graphql[asgi,cli]>=0.236
45
45
  Provides-Extra: server
46
46
  Requires-Dist: uvicorn[standard]; extra == "server"
47
47
  Provides-Extra: cli
@@ -51,9 +51,10 @@ Requires-Dist: tqdm; extra == "cli"
51
51
  ![image](https://img.shields.io/pypi/pyversions/graphique.svg)
52
52
  [![image](https://pepy.tech/badge/graphique)](https://pepy.tech/project/graphique)
53
53
  ![image](https://img.shields.io/pypi/status/graphique.svg)
54
- [![image](https://github.com/coady/graphique/workflows/build/badge.svg)](https://github.com/coady/graphique/actions)
54
+ [![build](https://github.com/coady/graphique/actions/workflows/build.yml/badge.svg)](https://github.com/coady/graphique/actions/workflows/build.yml)
55
55
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
56
- [![image](https://github.com/coady/graphique/workflows/codeql/badge.svg)](https://github.com/coady/graphique/security/code-scanning)
56
+ [![CodeQL](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql)
57
+ [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/coady/graphique)
57
58
  [![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
59
  [![image](https://mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
59
60
 
@@ -17,6 +17,7 @@ graphique.egg-info/SOURCES.txt
17
17
  graphique.egg-info/dependency_links.txt
18
18
  graphique.egg-info/requires.txt
19
19
  graphique.egg-info/top_level.txt
20
+ tests/test_bench.py
20
21
  tests/test_core.py
21
22
  tests/test_dataset.py
22
23
  tests/test_models.py
@@ -0,0 +1,8 @@
1
+ pyarrow>=17
2
+ strawberry-graphql[asgi,cli]>=0.236
3
+
4
+ [cli]
5
+ tqdm
6
+
7
+ [server]
8
+ uvicorn[standard]
@@ -1,10 +1,11 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61", "wheel"]
2
+ requires = ["setuptools>=61"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graphique"
7
- dynamic = ["version", "dependencies"]
7
+ version = "1.7"
8
+ dynamic = ["dependencies"]
8
9
  description = "GraphQL service for arrow tables and parquet data sets."
9
10
  readme = "README.md"
10
11
  requires-python = ">=3.9"
@@ -39,22 +40,14 @@ Issues = "https://github.com/coady/graphique/issues"
39
40
  server = ["uvicorn[standard]"]
40
41
  cli = ["tqdm"]
41
42
 
42
- [tool.setuptools]
43
- packages = ["graphique"]
44
-
45
43
  [tool.setuptools.dynamic]
46
- version = {attr = "graphique.__version__"}
47
44
  dependencies = {file = "requirements.in"}
48
45
 
49
- [tool.setuptools.package-data]
50
- graphique = ["py.typed"]
51
-
52
46
  [tool.ruff]
53
47
  line-length = 100
54
48
  extend-include = ["*.ipynb"]
55
49
 
56
50
  [tool.ruff.format]
57
- preview = true
58
51
  quote-style = "preserve"
59
52
 
60
53
  [[tool.mypy.overrides]]
@@ -0,0 +1,2 @@
1
+ pyarrow>=17
2
+ strawberry-graphql[asgi,cli]>=0.236
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from graphique.core import Table as T
3
+
4
+
5
+ @pytest.mark.benchmark
6
+ def test_group(table):
7
+ T.group(table, 'state', 'county', 'city')
8
+ T.runs(table, 'state', 'county', 'city')
9
+
10
+
11
+ @pytest.mark.benchmark
12
+ def test_rank(table):
13
+ T.ranked(table, 1, 'state', 'county', 'city')
14
+ T.ranked(table, 10, 'state', 'county', 'city')
15
+
16
+
17
+ @pytest.mark.benchmark
18
+ def test_sort(table):
19
+ T.sort(table, 'state', 'county', 'city', length=1)
20
+ T.sort(table, 'state', 'county', 'city', length=10)
21
+ T.sort(table, 'state', 'county', 'city')
@@ -1,21 +1,24 @@
1
1
  import pyarrow as pa
2
2
  import pyarrow.compute as pc
3
+ import pyarrow.dataset as ds
3
4
  import pytest
4
- from graphique.core import Agg, ListChunk, Column as C, Table as T
5
+ from graphique.core import Agg, Declaration, ListChunk, Column as C, Table as T
5
6
  from graphique.scalars import parse_duration, duration_isoformat
6
7
 
7
8
 
8
9
  def test_duration():
9
- assert duration_isoformat(parse_duration('P1Y1M1DT1H1M1.1S')) == 'P13M1DT3661.100000000S'
10
- assert duration_isoformat(parse_duration('P1M1DT1H1M1.1S')) == 'P1M1DT3661.100000000S'
11
- assert duration_isoformat(parse_duration('P1DT1H1M1.1S')) == 'P1DT3661.100000S'
12
- assert duration_isoformat(parse_duration('PT1H1M1.1S')) == 'PT3661.100000S'
13
- assert duration_isoformat(parse_duration('PT1M1.1S')) == 'PT61.100000S'
14
- assert duration_isoformat(parse_duration('PT1.1S')) == 'PT1.100000S'
10
+ assert duration_isoformat(parse_duration('P1Y1M1DT1H1M1.1S')) == 'P1Y1M1DT1H1M1.1S'
11
+ assert duration_isoformat(parse_duration('P1M1DT1H1M1.1S')) == 'P1M1DT1H1M1.1S'
12
+ assert duration_isoformat(parse_duration('P1DT1H1M1.1S')) == 'P1DT1H1M1.1S'
13
+ assert duration_isoformat(parse_duration('PT1H1M1.1S')) == 'PT1H1M1.1S'
14
+ assert duration_isoformat(parse_duration('PT1M1.1S')) == 'PT1M1.1S'
15
+ assert duration_isoformat(parse_duration('PT1.1S')) == 'PT1.1S'
15
16
  assert duration_isoformat(parse_duration('PT1S')) == 'PT1S'
16
17
  assert duration_isoformat(parse_duration('P0D')) == 'PT0S'
17
18
  assert duration_isoformat(parse_duration('PT0S')) == 'PT0S'
18
- assert duration_isoformat(parse_duration('P-1DT-1H')) == 'P-2DT82800S'
19
+ assert duration_isoformat(parse_duration('P-1DT-1H')) == 'P-2DT23H0S'
20
+ assert duration_isoformat(parse_duration('P0MT')) == 'P0MT0S'
21
+ assert duration_isoformat(parse_duration('P0YT')) == 'P0MT0S'
19
22
  with pytest.raises(ValueError):
20
23
  duration_isoformat(parse_duration('T1H'))
21
24
  with pytest.raises(ValueError):
@@ -87,6 +90,13 @@ def test_membership():
87
90
  assert C.index(array, 1, start=2) == -1
88
91
 
89
92
 
93
+ def test_declaration(table):
94
+ dataset = ds.dataset(table).filter(pc.field('state') == 'CA')
95
+ assert Declaration.scan(dataset).to_table()['state'].unique().to_pylist() == ['CA']
96
+ (column,) = Declaration.scan(dataset, columns={'_': pc.field('state')}).to_table()
97
+ assert column.unique().to_pylist() == ['CA']
98
+
99
+
90
100
  def test_group(table):
91
101
  groups = T.group(table, 'state', list=[Agg('county'), Agg('city')])
92
102
  assert len(groups) == 52
@@ -1,4 +1,3 @@
1
- import sys
2
1
  import pytest
3
2
 
4
3
 
@@ -167,7 +166,6 @@ def test_numeric(executor):
167
166
  assert data == {'scan': {'columns': {'int64': {'values': [0, None]}}}}
168
167
 
169
168
 
170
- @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or pytz")
171
169
  def test_datetime(executor):
172
170
  for name in ('timestamp', 'date32'):
173
171
  data = executor(
@@ -230,7 +228,7 @@ def test_duration(executor):
230
228
  data = executor("""{ scan(columns: {alias: "diff", temporal:
231
229
  {monthDayNanoIntervalBetween: [{name: "timestamp"}, {name: "timestamp"}]}})
232
230
  { column(name: "diff") { ... on DurationColumn { values } } } }""")
233
- assert data == {'scan': {'column': {'values': ['PT0S', None]}}}
231
+ assert data == {'scan': {'column': {'values': ['P0MT0S', None]}}}
234
232
 
235
233
 
236
234
  def test_list(executor):
@@ -1,8 +0,0 @@
1
- pyarrow>=16
2
- strawberry-graphql[asgi,cli]>=0.221
3
-
4
- [cli]
5
- tqdm
6
-
7
- [server]
8
- uvicorn[standard]
@@ -1,2 +0,0 @@
1
- pyarrow>=16
2
- strawberry-graphql[asgi,cli]>=0.221
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes