graphique 1.9__tar.gz → 2.0__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 (37) hide show
  1. {graphique-1.9 → graphique-2.0}/PKG-INFO +49 -39
  2. graphique-2.0/README.md +119 -0
  3. graphique-2.0/graphique/__init__.py +3 -0
  4. graphique-2.0/graphique/core.py +83 -0
  5. graphique-2.0/graphique/inputs.py +472 -0
  6. graphique-2.0/graphique/interface.py +384 -0
  7. {graphique-1.9 → graphique-2.0}/graphique/middleware.py +16 -12
  8. graphique-2.0/graphique/models.py +260 -0
  9. {graphique-1.9 → graphique-2.0}/graphique/scalars.py +35 -40
  10. graphique-2.0/graphique/service.py +44 -0
  11. {graphique-1.9 → graphique-2.0}/graphique/shell.py +4 -2
  12. {graphique-1.9 → graphique-2.0}/graphique.egg-info/PKG-INFO +49 -39
  13. {graphique-1.9 → graphique-2.0}/graphique.egg-info/SOURCES.txt +0 -2
  14. {graphique-1.9 → graphique-2.0}/graphique.egg-info/requires.txt +3 -1
  15. {graphique-1.9 → graphique-2.0}/pyproject.toml +25 -13
  16. graphique-2.0/tests/test_core.py +44 -0
  17. graphique-2.0/tests/test_dataset.py +167 -0
  18. graphique-2.0/tests/test_models.py +186 -0
  19. graphique-2.0/tests/test_service.py +342 -0
  20. graphique-1.9/README.md +0 -111
  21. graphique-1.9/graphique/__init__.py +0 -1
  22. graphique-1.9/graphique/core.py +0 -619
  23. graphique-1.9/graphique/inputs.py +0 -704
  24. graphique-1.9/graphique/interface.py +0 -518
  25. graphique-1.9/graphique/models.py +0 -294
  26. graphique-1.9/graphique/service.py +0 -40
  27. graphique-1.9/requirements.in +0 -4
  28. graphique-1.9/tests/test_bench.py +0 -21
  29. graphique-1.9/tests/test_core.py +0 -191
  30. graphique-1.9/tests/test_dataset.py +0 -278
  31. graphique-1.9/tests/test_models.py +0 -390
  32. graphique-1.9/tests/test_service.py +0 -483
  33. {graphique-1.9 → graphique-2.0}/LICENSE.txt +0 -0
  34. {graphique-1.9 → graphique-2.0}/graphique/py.typed +0 -0
  35. {graphique-1.9 → graphique-2.0}/graphique.egg-info/dependency_links.txt +0 -0
  36. {graphique-1.9 → graphique-2.0}/graphique.egg-info/top_level.txt +0 -0
  37. {graphique-1.9 → graphique-2.0}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphique
3
- Version: 1.9
4
- Summary: GraphQL service for arrow tables and parquet data sets.
3
+ Version: 2.0
4
+ Summary: GraphQL service for python dataframes and parquet datasets.
5
5
  Author-email: Aric Coady <aric.coady@gmail.com>
6
- License: Apache-2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/coady/graphique
8
8
  Project-URL: Documentation, https://coady.github.io/graphique
9
9
  Project-URL: Changelog, https://github.com/coady/graphique/blob/main/CHANGELOG.md
@@ -28,10 +28,12 @@ Description-Content-Type: text/markdown
28
28
  License-File: LICENSE.txt
29
29
  Requires-Dist: pyarrow>=20
30
30
  Requires-Dist: strawberry-graphql[asgi,cli]>=0.236
31
- Requires-Dist: numpy
31
+ Requires-Dist: ibis-framework>=10.7
32
+ Requires-Dist: pyarrow-hotfix
32
33
  Requires-Dist: isodate>=0.7
33
34
  Provides-Extra: server
34
35
  Requires-Dist: uvicorn[standard]; extra == "server"
36
+ Requires-Dist: duckdb; extra == "server"
35
37
  Provides-Extra: cli
36
38
  Requires-Dist: tqdm; extra == "cli"
37
39
  Dynamic: license-file
@@ -43,42 +45,52 @@ Dynamic: license-file
43
45
  [![build](https://github.com/coady/graphique/actions/workflows/build.yml/badge.svg)](https://github.com/coady/graphique/actions/workflows/build.yml)
44
46
  [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
45
47
  [![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)
46
- [![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/coady/graphique)
47
48
  [![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)
48
49
  [![image](https://mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
49
50
 
50
- [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.
51
+ [GraphQL](https://graphql.org) service for [ibis](https://ibis-project.org) dataframes, [arrow](https://arrow.apache.org) tables, and [parquet](https://parquet.apache.org) datasets. The schema for a query API is derived automatically.
52
+
53
+ ## Version 2
54
+ When this project started, there was no out-of-core execution engine with performance comparable to [PyArrow](https://arrow.apache.org/docs/python/index.html). So it effectively included one, based on datasets and [Acero](https://arrow.apache.org/docs/python/api/acero.html).
55
+
56
+ Since then the ecosystem has grown considerably: [DuckDB](https://duckdb.org), [DataFusion](https://datafusion.apache.org), and [Ibis](https://ibis-project.org). As of version 2, graphique is based on `ibis`. It provides a common dataframe API for multiple backends, enabling graphique to also have a default but configurable backend.
57
+
58
+ Being a major version upgrade, there are incompatible changes from version 1. However the overall API remains largely the same.
51
59
 
52
60
  ## Usage
61
+ There is an example app which reads a parquet dataset.
53
62
  ```console
54
- % env PARQUET_PATH=... uvicorn graphique.service:app
63
+ env PARQUET_PATH=... uvicorn graphique.service:app
55
64
  ```
56
65
 
57
66
  Open http://localhost:8000/ to try out the API in [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#readme). There is a test fixture at `./tests/fixtures/zipcodes.parquet`.
58
67
 
59
68
  ```console
60
- % env PARQUET_PATH=... strawberry export-schema graphique.service:app.schema
69
+ env PARQUET_PATH=... strawberry export-schema graphique.service:app.schema
61
70
  ```
62
- outputs the graphql schema for a parquet data set.
71
+ outputs the graphql schema.
63
72
 
64
73
  ### Configuration
65
- Graphique uses [Starlette's config](https://www.starlette.io/config/): in environment variables or a `.env` file. Config variables are used as input to a [parquet dataset](https://arrow.apache.org/docs/python/dataset.html).
74
+ The example app uses [Starlette's config](https://www.starlette.io/config/): in environment variables or a `.env` file.
66
75
 
67
76
  * PARQUET_PATH: path to the parquet directory or file
68
- * FEDERATED = '': field name to extend type `Query` with a federated `Table`
69
- * DEBUG = False: run service in debug mode, which includes metrics
77
+ * FEDERATED = '': field name to extend type `Query` with a federated `Table`
78
+ * METRICS = False: include timings from apollo tracing extension
70
79
  * COLUMNS = None: list of names, or mapping of aliases, of columns to select
71
80
  * FILTERS = None: json `filter` query for which rows to read at startup
72
81
 
73
- For more options create a custom [ASGI](https://asgi.readthedocs.io/en/latest/index.html) app. Call graphique's `GraphQL` on an arrow [Dataset](https://arrow.apache.org/docs/python/api/dataset.html), [Scanner](https://arrow.apache.org/docs/python/generated/pyarrow.dataset.Scanner.html), or [Table](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html). The GraphQL `Table` type will be the root Query type.
82
+ Configuration options exist to provide a convenient no-code solution, but are subject to change in the future. Using a custom app is recommended for production usage.
74
83
 
84
+ ### App
85
+ For more options create a custom [ASGI](https://asgi.readthedocs.io/en/latest/index.html) app. Call graphique's `GraphQL` on an ibis [Table](https://ibis-project.org/reference/expression-tables) or arrow [Dataset](https://arrow.apache.org/docs/python/api/dataset.html).
75
86
  Supply a mapping of names to datasets for multiple roots, and to enable federation.
76
87
 
77
88
  ```python
78
- import pyarrow.dataset as ds
89
+ import ibis
79
90
  from graphique import GraphQL
80
91
 
81
- source = ds.dataset(...)
92
+ source = ibis.read_*(...) # or ibis.connect(...).table(...) or pyarrow.dataset.dataset(...)
93
+ # apply initial projections or filters to `source`
82
94
  app = GraphQL(source) # Table is root query type
83
95
  app = GraphQL.federated({<name>: source, ...}, keys={<name>: [], ...}) # Tables on federated fields
84
96
  ```
@@ -89,55 +101,53 @@ Start like any ASGI app.
89
101
  uvicorn <module>:app
90
102
  ```
91
103
 
92
- Configuration options exist to provide a convenient no-code solution, but are subject to change in the future. Using a custom app is recommended for production usage.
93
-
94
104
  ### API
95
105
  #### types
96
- * `Dataset`: interface for an arrow dataset, scanner, or table.
106
+ * `Dataset`: interface for an ibis table or arrow dataset.
97
107
  * `Table`: implements the `Dataset` interface. Adds typed `row`, `columns`, and `filter` fields from introspecting the schema.
98
- * `Column`: interface for an arrow column (a.k.a. ChunkedArray). Each arrow data type has a corresponding column implementation: Boolean, Int, Long, Float, Decimal, Date, Datetime, Time, Duration, Base64, String, List, Struct. All columns have a `values` field for their list of scalars. Additional fields vary by type.
99
- * `Row`: scalar fields. Arrow tables are column-oriented, and graphique encourages that usage for performance. A single `row` field is provided for convenience, but a field for a list of rows is not. Requesting parallel columns is far more efficient.
108
+ * `Column`: interface for an ibis column. Each data type has a corresponding column implementation: Boolean, Int, BigInt, Float, Decimal, Date, Datetime, Time, Duration, Base64, String, Array, Struct. All columns have a `values` field for their list of scalars. Additional fields vary by type.
109
+ * `Row`: scalar fields. Tables are column-oriented, and graphique encourages that usage for performance. A single `row` field is provided for convenience, but a field for a list of rows is not. Requesting parallel columns is far more efficient.
100
110
 
101
111
  #### selection
102
112
  * `slice`: contiguous selection of rows
103
- * `filter`: select rows with simple predicates
104
- * `scan`: select rows and project columns with expressions
113
+ * `filter`: select rows by predicates
114
+ * `join`: join tables by key columns
115
+ * `take`: rows by index
116
+ * `dropNull`: remove rows with nulls
105
117
 
106
118
  #### projection
119
+ * `project`: project columns with expressions
107
120
  * `columns`: provides a field for every `Column` in the schema
108
121
  * `column`: access a column of any type by name
109
122
  * `row`: provides a field for each scalar of a single row
110
- * `apply`: transform columns by applying a function
111
- * `join`: join tables by key columns
123
+ * `cast`: cast column types
124
+ * `fillNull`: fill null values
112
125
 
113
126
  #### aggregation
114
127
  * `group`: group by given columns, and aggregate the others
115
- * `runs`: partition on adjacent values in given columns, transforming the others into list columns
116
- * `tables`: return a list of tables by splitting on the scalars in list columns
117
- * `flatten`: flatten list columns with repeated scalars
128
+ * `distinct`: group with all columns
129
+ * `runs`: provisionally group by adjacency
130
+ * `unnest`: unnest an array column
131
+ * `count`: number of rows
118
132
 
119
133
  #### ordering
120
- * `sort`: sort table by given columns
121
- * `rank`: select rows with smallest or largest values
134
+ * `order`: sort table by given columns
135
+ * options `limit` and `dense`: select rows with smallest or largest values
122
136
 
123
137
  ### Performance
124
- Graphique relies on native [PyArrow](https://arrow.apache.org/docs/python/index.html) routines wherever possible. Otherwise it falls back to using [NumPy](https://numpy.org/doc/stable/) or custom optimizations.
125
-
126
- By default, datasets are read on-demand, with only the necessary rows and columns scanned. Although graphique is a running service, [parquet is performant](https://arrow.apache.org/docs/python/generated/pyarrow.dataset.Dataset.html) at reading a subset of data. Optionally specify `FILTERS` in the json `filter` format to read a subset of rows at startup, trading-off memory for latency. An empty filter (`{}`) will read the whole table.
138
+ Performance is dependent on the [ibis backend](https://ibis-project.org/backends/duckdb), which defaults to [duckdb](https://duckdb.org/). There are no internal Python loops. Scalars do not become Python types until serialized.
127
139
 
128
- Specifying `COLUMNS` will limit memory usage when reading at startup (`FILTERS`). There is little speed difference as unused columns are inherently ignored. Optional aliasing can also be used for camel casing.
129
-
130
- If index columns are detected in the schema metadata, then an initial `filter` will also attempt a binary search on tables.
140
+ [PyArrow](https://arrow.apache.org/docs/python/) is also used for partitioned dataset optimizations, and for any feature which ibis does not support. Table fields are lazily evaluated up until scalars are reached, and automatically cached as needed for multiple fields.
131
141
 
132
142
  ## Installation
133
143
  ```console
134
- % pip install graphique[server]
144
+ pip install graphique[server]
135
145
  ```
136
146
 
137
147
  ## Dependencies
138
- * pyarrow
148
+ * ibis-framework (with duckdb or other backend)
139
149
  * strawberry-graphql[asgi,cli]
140
- * numpy
150
+ * pyarrow
141
151
  * isodate
142
152
  * uvicorn (or other [ASGI server](https://asgi.readthedocs.io/en/latest/implementations.html))
143
153
 
@@ -145,5 +155,5 @@ If index columns are detected in the schema metadata, then an initial `filter` w
145
155
  100% branch coverage.
146
156
 
147
157
  ```console
148
- % pytest [--cov]
158
+ pytest [--cov]
149
159
  ```
@@ -0,0 +1,119 @@
1
+ [![image](https://img.shields.io/pypi/v/graphique.svg)](https://pypi.org/project/graphique/)
2
+ ![image](https://img.shields.io/pypi/pyversions/graphique.svg)
3
+ [![image](https://pepy.tech/badge/graphique)](https://pepy.tech/project/graphique)
4
+ ![image](https://img.shields.io/pypi/status/graphique.svg)
5
+ [![build](https://github.com/coady/graphique/actions/workflows/build.yml/badge.svg)](https://github.com/coady/graphique/actions/workflows/build.yml)
6
+ [![image](https://codecov.io/gh/coady/graphique/branch/main/graph/badge.svg)](https://codecov.io/gh/coady/graphique/)
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
+ [![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
+
11
+ [GraphQL](https://graphql.org) service for [ibis](https://ibis-project.org) dataframes, [arrow](https://arrow.apache.org) tables, and [parquet](https://parquet.apache.org) datasets. The schema for a query API is derived automatically.
12
+
13
+ ## Version 2
14
+ When this project started, there was no out-of-core execution engine with performance comparable to [PyArrow](https://arrow.apache.org/docs/python/index.html). So it effectively included one, based on datasets and [Acero](https://arrow.apache.org/docs/python/api/acero.html).
15
+
16
+ Since then the ecosystem has grown considerably: [DuckDB](https://duckdb.org), [DataFusion](https://datafusion.apache.org), and [Ibis](https://ibis-project.org). As of version 2, graphique is based on `ibis`. It provides a common dataframe API for multiple backends, enabling graphique to also have a default but configurable backend.
17
+
18
+ Being a major version upgrade, there are incompatible changes from version 1. However the overall API remains largely the same.
19
+
20
+ ## Usage
21
+ There is an example app which reads a parquet dataset.
22
+ ```console
23
+ env PARQUET_PATH=... uvicorn graphique.service:app
24
+ ```
25
+
26
+ Open http://localhost:8000/ to try out the API in [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#readme). There is a test fixture at `./tests/fixtures/zipcodes.parquet`.
27
+
28
+ ```console
29
+ env PARQUET_PATH=... strawberry export-schema graphique.service:app.schema
30
+ ```
31
+ outputs the graphql schema.
32
+
33
+ ### Configuration
34
+ The example app uses [Starlette's config](https://www.starlette.io/config/): in environment variables or a `.env` file.
35
+
36
+ * PARQUET_PATH: path to the parquet directory or file
37
+ * FEDERATED = '': field name to extend type `Query` with a federated `Table`
38
+ * METRICS = False: include timings from apollo tracing extension
39
+ * COLUMNS = None: list of names, or mapping of aliases, of columns to select
40
+ * FILTERS = None: json `filter` query for which rows to read at startup
41
+
42
+ Configuration options exist to provide a convenient no-code solution, but are subject to change in the future. Using a custom app is recommended for production usage.
43
+
44
+ ### App
45
+ For more options create a custom [ASGI](https://asgi.readthedocs.io/en/latest/index.html) app. Call graphique's `GraphQL` on an ibis [Table](https://ibis-project.org/reference/expression-tables) or arrow [Dataset](https://arrow.apache.org/docs/python/api/dataset.html).
46
+ Supply a mapping of names to datasets for multiple roots, and to enable federation.
47
+
48
+ ```python
49
+ import ibis
50
+ from graphique import GraphQL
51
+
52
+ source = ibis.read_*(...) # or ibis.connect(...).table(...) or pyarrow.dataset.dataset(...)
53
+ # apply initial projections or filters to `source`
54
+ app = GraphQL(source) # Table is root query type
55
+ app = GraphQL.federated({<name>: source, ...}, keys={<name>: [], ...}) # Tables on federated fields
56
+ ```
57
+
58
+ Start like any ASGI app.
59
+
60
+ ```console
61
+ uvicorn <module>:app
62
+ ```
63
+
64
+ ### API
65
+ #### types
66
+ * `Dataset`: interface for an ibis table or arrow dataset.
67
+ * `Table`: implements the `Dataset` interface. Adds typed `row`, `columns`, and `filter` fields from introspecting the schema.
68
+ * `Column`: interface for an ibis column. Each data type has a corresponding column implementation: Boolean, Int, BigInt, Float, Decimal, Date, Datetime, Time, Duration, Base64, String, Array, Struct. All columns have a `values` field for their list of scalars. Additional fields vary by type.
69
+ * `Row`: scalar fields. Tables are column-oriented, and graphique encourages that usage for performance. A single `row` field is provided for convenience, but a field for a list of rows is not. Requesting parallel columns is far more efficient.
70
+
71
+ #### selection
72
+ * `slice`: contiguous selection of rows
73
+ * `filter`: select rows by predicates
74
+ * `join`: join tables by key columns
75
+ * `take`: rows by index
76
+ * `dropNull`: remove rows with nulls
77
+
78
+ #### projection
79
+ * `project`: project columns with expressions
80
+ * `columns`: provides a field for every `Column` in the schema
81
+ * `column`: access a column of any type by name
82
+ * `row`: provides a field for each scalar of a single row
83
+ * `cast`: cast column types
84
+ * `fillNull`: fill null values
85
+
86
+ #### aggregation
87
+ * `group`: group by given columns, and aggregate the others
88
+ * `distinct`: group with all columns
89
+ * `runs`: provisionally group by adjacency
90
+ * `unnest`: unnest an array column
91
+ * `count`: number of rows
92
+
93
+ #### ordering
94
+ * `order`: sort table by given columns
95
+ * options `limit` and `dense`: select rows with smallest or largest values
96
+
97
+ ### Performance
98
+ Performance is dependent on the [ibis backend](https://ibis-project.org/backends/duckdb), which defaults to [duckdb](https://duckdb.org/). There are no internal Python loops. Scalars do not become Python types until serialized.
99
+
100
+ [PyArrow](https://arrow.apache.org/docs/python/) is also used for partitioned dataset optimizations, and for any feature which ibis does not support. Table fields are lazily evaluated up until scalars are reached, and automatically cached as needed for multiple fields.
101
+
102
+ ## Installation
103
+ ```console
104
+ pip install graphique[server]
105
+ ```
106
+
107
+ ## Dependencies
108
+ * ibis-framework (with duckdb or other backend)
109
+ * strawberry-graphql[asgi,cli]
110
+ * pyarrow
111
+ * isodate
112
+ * uvicorn (or other [ASGI server](https://asgi.readthedocs.io/en/latest/implementations.html))
113
+
114
+ ## Tests
115
+ 100% branch coverage.
116
+
117
+ ```console
118
+ pytest [--cov]
119
+ ```
@@ -0,0 +1,3 @@
1
+ # ruff: noqa: F401
2
+ from .interface import Dataset
3
+ from .middleware import GraphQL
@@ -0,0 +1,83 @@
1
+ """
2
+ Core arrow utilities.
3
+ """
4
+
5
+ import itertools
6
+
7
+ import ibis
8
+ import pyarrow as pa
9
+ import pyarrow.dataset as ds
10
+
11
+
12
+ class links:
13
+ ref = "https://ibis-project.org/reference"
14
+ types = f"[data type]({ref}/datatypes)"
15
+ schema = f"[table schema]({ref}/schemas#ibis.expr.schema.Schema)"
16
+
17
+
18
+ def getitems(obj, *keys):
19
+ """Nested `getitem`."""
20
+ for key in keys:
21
+ obj = obj[key]
22
+ return obj
23
+
24
+
25
+ def order_key(name: str) -> ibis.Deferred:
26
+ """Parse sort order."""
27
+ return (ibis.desc if name.startswith('-') else ibis.asc)(ibis._[name.lstrip('-')])
28
+
29
+
30
+ class Parquet(ds.Dataset):
31
+ """Partitioned parquet dataset."""
32
+
33
+ def schema(self) -> pa.Schema:
34
+ """partition schema"""
35
+ return self.partitioning.schema if hasattr(self, 'partitioning') else pa.schema([])
36
+
37
+ def keys(self, *names) -> list:
38
+ """Return prefix of matching partition keys."""
39
+ keys = set(Parquet.schema(self).names)
40
+ return list(itertools.takewhile(lambda name: name.lstrip('-') in keys, names))
41
+
42
+ def fragments(self, counts: str = '') -> ibis.Table:
43
+ """Return partition fragments as a table."""
44
+ parts = []
45
+ for frag in self._get_fragments(self._scan_options.get('filter')):
46
+ parts.append(ds.get_partition_keys(frag.partition_expression))
47
+ parts[-1]['__path__'] = frag.path
48
+ if counts:
49
+ parts[-1][counts] = frag.count_rows()
50
+ return ibis.memtable(pa.Table.from_pylist(parts))
51
+
52
+ def group(self, *names, counts: str = '') -> ibis.Table:
53
+ """Return grouped partitions as a table."""
54
+ table = Parquet.fragments(self, counts)
55
+ agg = {counts: table[counts].sum()} if counts else {}
56
+ return table.aggregate(agg, by=names).order_by(*names)
57
+
58
+ def filter(self, expr: ds.Expression) -> ds.Dataset | None:
59
+ """Attempt to apply filter to partition keys."""
60
+ try: # raises ValueError if filter references non-partition keys
61
+ ds.dataset([], schema=self.partitioning.schema).scanner(filter=expr)
62
+ except (AttributeError, ValueError):
63
+ return None
64
+ return self if expr is None else self.filter(expr)
65
+
66
+ def to_table(self) -> ibis.Table:
67
+ """Return ibis `Table` from filtered dataset."""
68
+ paths = [frag.path for frag in self._get_fragments(self._scan_options.get('filter'))]
69
+ hive = isinstance(self.partitioning, ds.HivePartitioning)
70
+ return ibis.read_parquet(paths, hive_partitioning=hive)
71
+
72
+ def rank(self, limit: int, *names: str, dense: bool = False) -> ibis.Table:
73
+ """Return ordered limited partitions as a table."""
74
+ keys = {name.strip('-'): order_key(name) for name in names}
75
+ table = Parquet.fragments(self, counts='_').order_by(*keys.values()).cache()
76
+ groups = table.aggregate(count=ibis._.count(), total=table['_'].sum(), by=list(keys))
77
+ groups = groups.order_by(*keys.values()).cache()
78
+ if not dense:
79
+ totals = itertools.accumulate(groups['total'].to_list())
80
+ limit = next((index for index, total in enumerate(totals, 1) if total >= limit), None) # type: ignore
81
+ limit = groups[:limit]['count'].sum().to_pyarrow().as_py()
82
+ hive = isinstance(self.partitioning, ds.HivePartitioning)
83
+ return ibis.read_parquet(table[:limit]['__path__'].to_list(), hive_partitioning=hive)