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.
- {graphique-1.9 → graphique-2.0}/PKG-INFO +49 -39
- graphique-2.0/README.md +119 -0
- graphique-2.0/graphique/__init__.py +3 -0
- graphique-2.0/graphique/core.py +83 -0
- graphique-2.0/graphique/inputs.py +472 -0
- graphique-2.0/graphique/interface.py +384 -0
- {graphique-1.9 → graphique-2.0}/graphique/middleware.py +16 -12
- graphique-2.0/graphique/models.py +260 -0
- {graphique-1.9 → graphique-2.0}/graphique/scalars.py +35 -40
- graphique-2.0/graphique/service.py +44 -0
- {graphique-1.9 → graphique-2.0}/graphique/shell.py +4 -2
- {graphique-1.9 → graphique-2.0}/graphique.egg-info/PKG-INFO +49 -39
- {graphique-1.9 → graphique-2.0}/graphique.egg-info/SOURCES.txt +0 -2
- {graphique-1.9 → graphique-2.0}/graphique.egg-info/requires.txt +3 -1
- {graphique-1.9 → graphique-2.0}/pyproject.toml +25 -13
- graphique-2.0/tests/test_core.py +44 -0
- graphique-2.0/tests/test_dataset.py +167 -0
- graphique-2.0/tests/test_models.py +186 -0
- graphique-2.0/tests/test_service.py +342 -0
- graphique-1.9/README.md +0 -111
- graphique-1.9/graphique/__init__.py +0 -1
- graphique-1.9/graphique/core.py +0 -619
- graphique-1.9/graphique/inputs.py +0 -704
- graphique-1.9/graphique/interface.py +0 -518
- graphique-1.9/graphique/models.py +0 -294
- graphique-1.9/graphique/service.py +0 -40
- graphique-1.9/requirements.in +0 -4
- graphique-1.9/tests/test_bench.py +0 -21
- graphique-1.9/tests/test_core.py +0 -191
- graphique-1.9/tests/test_dataset.py +0 -278
- graphique-1.9/tests/test_models.py +0 -390
- graphique-1.9/tests/test_service.py +0 -483
- {graphique-1.9 → graphique-2.0}/LICENSE.txt +0 -0
- {graphique-1.9 → graphique-2.0}/graphique/py.typed +0 -0
- {graphique-1.9 → graphique-2.0}/graphique.egg-info/dependency_links.txt +0 -0
- {graphique-1.9 → graphique-2.0}/graphique.egg-info/top_level.txt +0 -0
- {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:
|
|
4
|
-
Summary: GraphQL service for
|
|
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:
|
|
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
|
[](https://github.com/coady/graphique/actions/workflows/build.yml)
|
|
44
46
|
[](https://codecov.io/gh/coady/graphique/)
|
|
45
47
|
[](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql)
|
|
46
|
-
[](https://codspeed.io/coady/graphique)
|
|
47
48
|
[](https://github.com/astral-sh/ruff)
|
|
48
49
|
[](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)
|
|
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
|
-
|
|
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
|
-
|
|
69
|
+
env PARQUET_PATH=... strawberry export-schema graphique.service:app.schema
|
|
61
70
|
```
|
|
62
|
-
outputs the graphql schema
|
|
71
|
+
outputs the graphql schema.
|
|
63
72
|
|
|
64
73
|
### Configuration
|
|
65
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
89
|
+
import ibis
|
|
79
90
|
from graphique import GraphQL
|
|
80
91
|
|
|
81
|
-
source =
|
|
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
|
|
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
|
|
99
|
-
* `Row`: scalar fields.
|
|
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
|
|
104
|
-
* `
|
|
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
|
-
* `
|
|
111
|
-
* `
|
|
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
|
-
* `
|
|
116
|
-
* `
|
|
117
|
-
* `
|
|
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
|
-
* `
|
|
121
|
-
* `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
+
pip install graphique[server]
|
|
135
145
|
```
|
|
136
146
|
|
|
137
147
|
## Dependencies
|
|
138
|
-
*
|
|
148
|
+
* ibis-framework (with duckdb or other backend)
|
|
139
149
|
* strawberry-graphql[asgi,cli]
|
|
140
|
-
*
|
|
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
|
-
|
|
158
|
+
pytest [--cov]
|
|
149
159
|
```
|
graphique-2.0/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
[](https://pypi.org/project/graphique/)
|
|
2
|
+

|
|
3
|
+
[](https://pepy.tech/project/graphique)
|
|
4
|
+

|
|
5
|
+
[](https://github.com/coady/graphique/actions/workflows/build.yml)
|
|
6
|
+
[](https://codecov.io/gh/coady/graphique/)
|
|
7
|
+
[](https://github.com/coady/graphique/actions/workflows/github-code-scanning/codeql)
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
[](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,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)
|