Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.3k
GH-33986: [Python] Add a minimal protocol for datasets#35568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,124 @@ | ||||||||||||||||||||
| .. Licensed to the Apache Software Foundation (ASF) under one | ||||||||||||||||||||
| .. or more contributor license agreements. See the NOTICE file | ||||||||||||||||||||
| .. distributed with this work for additional information | ||||||||||||||||||||
| .. regarding copyright ownership. The ASF licenses this file | ||||||||||||||||||||
| .. to you under the Apache License, Version 2.0 (the | ||||||||||||||||||||
| .. "License"); you may not use this file except in compliance | ||||||||||||||||||||
| .. with the License. You may obtain a copy of the License at | ||||||||||||||||||||
| .. http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||
| .. Unless required by applicable law or agreed to in writing, | ||||||||||||||||||||
| .. software distributed under the License is distributed on an | ||||||||||||||||||||
| .. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||||||||||||||||||
| .. KIND, either express or implied. See the License for the | ||||||||||||||||||||
| .. specific language governing permissions and limitations | ||||||||||||||||||||
| .. under the License. | ||||||||||||||||||||
| Extending PyArrow Datasets | ||||||||||||||||||||
| ========================== | ||||||||||||||||||||
| .. warn:: | ||||||||||||||||||||
| This protocol is currently experimental. | ||||||||||||||||||||
| PyArrow provides a core protocol for datasets, so third-party libraries can both | ||||||||||||||||||||
| produce and consume classes that conform to useful subset of the PyArrow dataset | ||||||||||||||||||||
| API. This subset provides enough functionality to provide projection | ||||||||||||||||||||
| pushdown. The subset of the API is contained in ``pyarrow.dataset.protocol``. | ||||||||||||||||||||
Comment on lines
28
to
29
| ||||||||||||||||||||
| pushdown. The subset of the API is contained in ``pyarrow.dataset.protocol``. | |
| pushdown. The subset of the API is contained in ``pyarrow.dataset.protocol``. | |
| Producers are scanner implementations. For example, table formats like Delta | |
| Lake and Iceberg might provide their own dataset implementations. Consumers | |
| are typically query engines, such as DuckDB, DataFusion, Polars, and Dask. | |
| Providing a common API avoids a situation where supporting ``N`` dataset | |
| formats in ``M`` query engines requires ``N * M`` integrations. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is "ok" but the definition of producer and consumer here are reversed from what they are in Substrait which confused me for a while. Maybe we can go with "Data producer" and "Data consumer"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha it finally clicked why I myself find these terms confusing 🤣. It feels backwards! I'll think of new names.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,158 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| # Licensed to the Apache Software Foundation (ASF) under one | ||||||||||||||||||||||||||||||||||||||||||||||
| # or more contributor license agreements. See the NOTICE file | ||||||||||||||||||||||||||||||||||||||||||||||
| # distributed with this work for additional information | ||||||||||||||||||||||||||||||||||||||||||||||
| # regarding copyright ownership. The ASF licenses this file | ||||||||||||||||||||||||||||||||||||||||||||||
| # to you under the Apache License, Version 2.0 (the | ||||||||||||||||||||||||||||||||||||||||||||||
| # "License"); you may not use this file except in compliance | ||||||||||||||||||||||||||||||||||||||||||||||
| # with the License. You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # Unless required by applicable law or agreed to in writing, | ||||||||||||||||||||||||||||||||||||||||||||||
| # software distributed under the License is distributed on an | ||||||||||||||||||||||||||||||||||||||||||||||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||||||||||||||||||||||||||||||||||||||||||||
| # KIND, either express or implied. See the License for the | ||||||||||||||||||||||||||||||||||||||||||||||
| # specific language governing permissions and limitations | ||||||||||||||||||||||||||||||||||||||||||||||
| # under the License. | ||||||||||||||||||||||||||||||||||||||||||||||
| """Protocol definitions for pyarrow.dataset | ||||||||||||||||||||||||||||||||||||||||||||||
| These provide the abstract interface for a dataset. Other libraries may implement | ||||||||||||||||||||||||||||||||||||||||||||||
| this interface to expose their data, without having to extend PyArrow's classes. | ||||||||||||||||||||||||||||||||||||||||||||||
| Applications and libraries that want to consume datasets should accept datasets | ||||||||||||||||||||||||||||||||||||||||||||||
| that implement these protocols, rather than requiring the specific | ||||||||||||||||||||||||||||||||||||||||||||||
| PyArrow classes. | ||||||||||||||||||||||||||||||||||||||||||||||
| The pyarrow.dataset.Dataset class itself implements this protocol. | ||||||||||||||||||||||||||||||||||||||||||||||
| See Extending PyArrow Datasets for more information: | ||||||||||||||||||||||||||||||||||||||||||||||
| https://arrow.apache.org/docs/python/integration/dataset.html | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| from abc import abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Iterator, List, Optional | ||||||||||||||||||||||||||||||||||||||||||||||
| # TODO: remove once we drop support for Python 3.7 | ||||||||||||||||||||||||||||||||||||||||||||||
| if sys.version_info >= (3, 8): | ||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Protocol, runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||
| from typing_extensions import Protocol, runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| from pyarrow.dataset import Expression | ||||||||||||||||||||||||||||||||||||||||||||||
| from pyarrow import Table, RecordBatchReader, Schema | ||||||||||||||||||||||||||||||||||||||||||||||
| @runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| class Scanner(Protocol): | ||||||||||||||||||||||||||||||||||||||||||||||
wjones127 marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| A scanner implementation for a dataset. | ||||||||||||||||||||||||||||||||||||||||||||||
| This may be a scan of a whole dataset, or a scan of a single fragment. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def count_rows(self) -> int: | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| Count the number of rows in this dataset or fragment. | ||||||||||||||||||||||||||||||||||||||||||||||
| Implementors may provide optimized code paths that compute this from metadata. | ||||||||||||||||||||||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||||||||||||||||||||||
| int | ||||||||||||||||||||||||||||||||||||||||||||||
| The number of rows in the dataset or fragment. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def head(self, num_rows: int) -> Table: | ||||||||||||||||||||||||||||||||||||||||||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For head and count_rows, should we say that not all producers will support them and that they may raise NotImplementedError? MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's reasonable. They aren't that important to the protocol's goals; I almost considered removing them. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would leave them in. In Iceberg we can do a count without touching the actual data files (if you don't use a filter). MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving them in. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| Get the first ``num_rows`` rows of the dataset or fragment. | ||||||||||||||||||||||||||||||||||||||||||||||
| Parameters | ||||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||||
| num_rows : int | ||||||||||||||||||||||||||||||||||||||||||||||
| The number of rows to return. | ||||||||||||||||||||||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||||||||||||||||||||||
| Table | ||||||||||||||||||||||||||||||||||||||||||||||
| A table containing the first ``num_rows`` rows of the dataset or fragment. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def to_reader(self) -> RecordBatchReader: | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| Create a Record Batch Reader for this scan. | ||||||||||||||||||||||||||||||||||||||||||||||
| This is used to read the data in chunks. | ||||||||||||||||||||||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||||||||||||||||||||||
| RecordBatchReader | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| class Scannable(Protocol): | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def scanner(self, columns: Optional[List[str]] = None, | ||||||||||||||||||||||||||||||||||||||||||||||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nit, I prefer to use Tuples over Lists because:
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't disagree in principle, but also trying to keep this somewhat compatible. Though maybe we can loosen to Actually, it currently supports arrow/python/pyarrow/_dataset.pyx Lines 3182 to 3201 in af38263
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference between MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to #35568 (comment), we might want to only allow | ||||||||||||||||||||||||||||||||||||||||||||||
| batch_size: Optional[int] = None, | ||||||||||||||||||||||||||||||||||||||||||||||
| use_threads: bool = True, | ||||||||||||||||||||||||||||||||||||||||||||||
| **kwargs) -> Scanner: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Create a scanner for this dataset. | ||||||||||||||||||||||||||||||||||||||||||||||
| Parameters | ||||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||||
| columns : List[str], optional | ||||||||||||||||||||||||||||||||||||||||||||||
| Names of columns to include in the scan. If None, all columns are | ||||||||||||||||||||||||||||||||||||||||||||||
| included. | ||||||||||||||||||||||||||||||||||||||||||||||
| batch_size : int, optional | ||||||||||||||||||||||||||||||||||||||||||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm open to that. But I don't want to add that to the protocol without also implementing it in PyArrow Datasets. So if we think this is important, I'll remove this for now. | ||||||||||||||||||||||||||||||||||||||||||||||
| The number of rows to include in each batch. If None, the default | ||||||||||||||||||||||||||||||||||||||||||||||
| value is used. The default value is implementation specific. | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
111
to
113
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe expand that the default value is not only implementation specific but might not even be consistent between batches? Also, if this is a min/max or is it a maximum-only? In other words, if MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good detail to think about. I don't think we should require it to be exact; for example, if the reader can't read in exactly that batch size I don't think it should error. But I do think readers should make their best effort to be close the batch size as possible, even if that means splitting row groups into chunks, for example. Though you might have more informed opinions here; what do you think is reasonable to expect here? Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This parameter has lost importance in arrow-c++ datasets. It used to be an important tuning parameter that affected the size of the batches used internally by the C++ implementation. However, it didn't make sense for the user to pick the correct value (and there are multiple batch sizes in the C++ and the right value might even depend on the schema and be quite difficult to calculate). I think it still has value, especially "max batch size". The user needs someway to say "don't give me 20GB of data all at once". So I think it needs to be a hard upper limit but it can be a soft lower limit. We could either call it | ||||||||||||||||||||||||||||||||||||||||||||||
| use_threads : bool, default True | ||||||||||||||||||||||||||||||||||||||||||||||
| Whether to use multiple threads to read the rows. Often consumers | ||||||||||||||||||||||||||||||||||||||||||||||
| reading a whole dataset in one scanner will keep this | ||||||||||||||||||||||||||||||||||||||||||||||
| as True, while consumers reading a single fragment per worker will | ||||||||||||||||||||||||||||||||||||||||||||||
| set this to False. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| class Fragment(Scannable, Protocol): | ||||||||||||||||||||||||||||||||||||||||||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a some things I would like to have here, as a user, but I understand we are just getting started and trying to be minimal. So take these as suggestions:
| ||||||||||||||||||||||||||||||||||||||||||||||
| """A fragment of a dataset. | ||||||||||||||||||||||||||||||||||||||||||||||
| This might be a partition, a file, a file chunk, etc. | ||||||||||||||||||||||||||||||||||||||||||||||
| This class should be pickleable so that it can be used in a distributed scan.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @runtime_checkable | ||||||||||||||||||||||||||||||||||||||||||||||
| class Dataset(Scannable, Protocol): | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def get_fragments( | ||||||||||||||||||||||||||||||||||||||||||||||
| self, **kwargs | ||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Iterator[Fragment]: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Get the fragments of this dataset. | ||||||||||||||||||||||||||||||||||||||||||||||
| Parameters | ||||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||||
| **kwargs : dict | ||||||||||||||||||||||||||||||||||||||||||||||
| Additional arguments to pass to underlying implementation. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||||
| @abstractmethod | ||||||||||||||||||||||||||||||||||||||||||||||
| def schema(self) -> Schema: | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| Get the schema of this dataset. | ||||||||||||||||||||||||||||||||||||||||||||||
| Returns | ||||||||||||||||||||||||||||||||||||||||||||||
| ------- | ||||||||||||||||||||||||||||||||||||||||||||||
| Schema | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, | ||
| # software distributed under the License is distributed on an | ||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| # KIND, either express or implied. See the License for the | ||
| # specific language governing permissions and limitations | ||
| # under the License. | ||
| """Test that PyArrow datasets conform to the protocol.""" | ||
| import pyarrow.dataset.protocol as protocol | ||
| import pyarrow.dataset as ds | ||
| def test_dataset_protocol(): | ||
| assert isinstance(ds.Dataset, protocol.Dataset) | ||
| assert isinstance(ds.Fragment, protocol.Fragment) | ||
| assert isinstance(ds.Dataset, protocol.Scannable) | ||
| assert isinstance(ds.Fragment, protocol.Scannable) | ||
| assert isinstance(ds.Scanner, protocol.Scanner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we say that this is currently experimental, and list the things that we know are on the roadmap?