Repository: chezou/tabula-py

Issue:
[BUG] column parameter of read_pdf currently needs to be list, not generic iterable
### Summary

Docs say columns parameter can be iterable, but code requires it to be list.

### Did you read the FAQ?

- [X] I have read the FAQ

### Did you search GitHub issues?

- [X] I have searched the issues

### Did you search GitHub Discussions?

- [X] I have searched the discussions

### (Optional) PDF URL

_No response_

### About your environment

```markdown
Python version:
    3.12.3 (main, Apr  9 2024, 08:09:14) [Clang 15.0.0 (clang-1500.3.9.4)]
Java version:
    openjdk version "21.0.3" 2024-04-16
OpenJDK Runtime Environment Homebrew (build 21.0.3)
OpenJDK 64-Bit Server VM Homebrew (build 21.0.3, mixed mode, sharing)
tabula-py version: 2.9.1
platform: macOS-14.5-arm64-arm-64bit
uname:
    uname_result(system='Darwin', node='Russs-MacBook-Pro-2.local', release='23.5.0', version='Darwin Kernel Version 23.5.0: Wed May  1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000', machine='arm64')
linux_distribution: ('Darwin', '23.5.0', '')
mac_ver: ('14.5', ('', '', ''), 'arm64')
```


### What did you do when you faced the problem?

Used the read_pdf function with columns parameter being a tuple.

Problem arises because of this line 238 in `util.py`:
```
            if self.columns != sorted(self.columns):
```
It should presumably say:
```
            if list(self.columns) != sorted(self.columns):
```

### Code

import tabula

# Read pdf into list of DataFrame
dfs = tabula.read_pdf("test.pdf",
                      stream=True,
                      area = (145.39, 26.05, 584.21, 584.02),
                      columns = (26.05, 142.55, 175.47, 215.61, 252.32, 385.59, 487.44, 583.15),
                      pages='all')


### Expected behavior

function executes successfully.

### Actual behavior


Relevant part of Traceback
``` 
 File "/Users/russ/project/venv/lib/python3.12/site-packages/tabula/util.py", line 239, in build_option_list
    raise ValueError("columns option should be sorted")
ValueError: columns option should be sorted
```


### Related issues

_No response_

Discussion:

Patch:
diff --git a/tabula/util.py b/tabula/util.py
index 7dc2453..2e49cf7 100644
--- a/tabula/util.py
+++ b/tabula/util.py
@@ -9,7 +9,7 @@ import platform
 import shlex
 from dataclasses import dataclass
 from logging import getLogger
-from typing import IO, Iterable, List, Optional, Union, cast
+from typing import IO, Iterable, List, Optional, Sequence, Union, cast
 
 logger = getLogger(__name__)
 
@@ -115,8 +115,9 @@ class TabulaOption:
             Password to decrypt document. Default: empty
         silent (bool, optional):
             Suppress all stderr output.
-        columns (iterable, optional):
-            X coordinates of column boundaries.
+        columns (Sequence, optional):
+            X coordinates of column boundaries. Must be sorted and of a datatype that
+            preserves order, e.g. tuple or list
 
             Example:
                 ``[10.1, 20.2, 30.3]``
@@ -147,7 +148,7 @@ class TabulaOption:
     stream: bool = False
     password: Optional[str] = None
     silent: Optional[bool] = None
-    columns: Optional[Iterable[float]] = None
+    columns: Optional[Sequence[float]] = None
     relative_columns: bool = False
     format: Optional[str] = None
     batch: Optional[str] = None
@@ -235,7 +236,7 @@ class TabulaOption:
             __options += ["--outfile", self.output_path]
 
         if self.columns:
-            if self.columns != sorted(self.columns):
+            if list(self.columns) != sorted(self.columns):
                 raise ValueError("columns option should be sorted")
 
             __columns = _format_with_relative(self.columns, self.relative_columns)


Test patch:
diff --git a/tests/test_read_pdf_table.py b/tests/test_read_pdf_table.py
index e0510a3..e906dfc 100644
--- a/tests/test_read_pdf_table.py
+++ b/tests/test_read_pdf_table.py
@@ -91,6 +91,15 @@ class TestReadPdfTable(unittest.TestCase):
             )[0].equals(pd.read_csv(expected_csv))
         )
 
+    def test_read_pdf_with_tuple_columns(self):
+        pdf_path = "tests/resources/campaign_donors.pdf"
+        expected_csv = "tests/resources/campaign_donors.csv"
+        self.assertTrue(
+            tabula.read_pdf(
+                pdf_path, columns=(47, 147, 256, 310, 375, 431, 504), guess=False
+            )[0].equals(pd.read_csv(expected_csv))
+        )
+
     def test_read_pdf_with_relative_columns(self):
         pdf_path = "tests/resources/campaign_donors.pdf"
         expected_csv = "tests/resources/campaign_donors.csv"