pyfilechoose 0.1.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.
- pyfilechoose-0.1.0/LICENSE +21 -0
- pyfilechoose-0.1.0/PKG-INFO +162 -0
- pyfilechoose-0.1.0/README.md +132 -0
- pyfilechoose-0.1.0/pyproject.toml +45 -0
- pyfilechoose-0.1.0/setup.cfg +4 -0
- pyfilechoose-0.1.0/src/pyfilechoose/__init__.py +12 -0
- pyfilechoose-0.1.0/src/pyfilechoose/core.py +149 -0
- pyfilechoose-0.1.0/src/pyfilechoose/py.typed +0 -0
- pyfilechoose-0.1.0/src/pyfilechoose.egg-info/PKG-INFO +162 -0
- pyfilechoose-0.1.0/src/pyfilechoose.egg-info/SOURCES.txt +12 -0
- pyfilechoose-0.1.0/src/pyfilechoose.egg-info/dependency_links.txt +1 -0
- pyfilechoose-0.1.0/src/pyfilechoose.egg-info/requires.txt +3 -0
- pyfilechoose-0.1.0/src/pyfilechoose.egg-info/top_level.txt +1 -0
- pyfilechoose-0.1.0/tests/test_core.py +68 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan Katende Pinto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfilechoose
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple replication of R's file.choose() for Python.
|
|
5
|
+
Author-email: Jonathan Katende Pinto <katendepinto@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pinto-Katende-Jonathan/pyfilechoose
|
|
8
|
+
Project-URL: Repository, https://github.com/Pinto-Katende-Jonathan/pyfilechoose
|
|
9
|
+
Project-URL: Issues, https://github.com/Pinto-Katende-Jonathan/pyfilechoose/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Pinto-Katende-Jonathan/pyfilechoose/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: file,dialog,tkinter,file.choose,R,filedialog
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# pyfilechoose
|
|
32
|
+
|
|
33
|
+
R's `file.choose()`, reimplemented for Python.
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
36
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
In R, `file.choose()` opens a file picker and returns the path you select.
|
|
40
|
+
`pyfilechoose` gives you the same one-liner in Python, with no GUI boilerplate
|
|
41
|
+
and no leftover Tk windows.
|
|
42
|
+
|
|
43
|
+
It uses `tkinter` from the standard library, so it has no third-party
|
|
44
|
+
dependencies.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
With pip:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pyfilechoose
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Add it to your project
|
|
58
|
+
uv add pyfilechoose
|
|
59
|
+
|
|
60
|
+
# ...or install it into the current environment
|
|
61
|
+
uv pip install pyfilechoose
|
|
62
|
+
|
|
63
|
+
# ...or run a one-off script that uses it, no install needed
|
|
64
|
+
uv run --with pyfilechoose your_script.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
On Linux, `tkinter` ships with most Python builds but may need to be installed
|
|
68
|
+
separately, e.g. `sudo apt install python3-tk`.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Pick a single file
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pyfilechoose import file_choose
|
|
76
|
+
|
|
77
|
+
path = file_choose()
|
|
78
|
+
print(path) # absolute path of the file you selected
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With pandas (the classic R workflow)
|
|
82
|
+
|
|
83
|
+
`file_choose()` returns a path string, so it works with any function that takes
|
|
84
|
+
a file path, including `pd.read_csv`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import pandas as pd
|
|
88
|
+
from pyfilechoose import file_choose
|
|
89
|
+
|
|
90
|
+
# Just like df <- read.csv(file.choose()) in R
|
|
91
|
+
df = pd.read_csv(file_choose())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You can still pass the usual pandas options, and filter the picker to CSVs:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
df = pd.read_csv(file_choose(filetypes=[("CSV files", "*.csv")]), sep=";")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Filter file types and set a starting directory
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
path = file_choose(
|
|
104
|
+
title="Pick your dataset",
|
|
105
|
+
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
|
|
106
|
+
initialdir="~/Documents",
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pick several files at once
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from pyfilechoose import files_choose
|
|
114
|
+
|
|
115
|
+
paths = files_choose(filetypes=[("Images", "*.png *.jpg")])
|
|
116
|
+
for p in paths:
|
|
117
|
+
print(p)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `file_choose(*, title="Select a file", filetypes=None, initialdir=None) -> str`
|
|
123
|
+
|
|
124
|
+
Opens a dialog and returns the absolute path of the chosen file.
|
|
125
|
+
Raises `FileNotFoundError` if the user cancels.
|
|
126
|
+
|
|
127
|
+
### `files_choose(*, title="Select one or more files", filetypes=None, initialdir=None) -> list[str]`
|
|
128
|
+
|
|
129
|
+
Same as above but allows multiple selections; returns a list of absolute paths.
|
|
130
|
+
Raises `FileNotFoundError` if nothing is selected.
|
|
131
|
+
|
|
132
|
+
| Argument | Type | Description |
|
|
133
|
+
| ------------ | ----------------------------- | --------------------------------------------------------- |
|
|
134
|
+
| `title` | `str` | Title of the dialog window. |
|
|
135
|
+
| `filetypes` | `list[tuple[str, str]]` \| `None` | `(label, pattern)` pairs, e.g. `[("CSV", "*.csv")]`. |
|
|
136
|
+
| `initialdir` | `str` \| `None` | Directory the dialog opens in. |
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/Pinto-Katende-Jonathan/pyfilechoose.git
|
|
142
|
+
cd pyfilechoose
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
With uv, which creates and manages the virtualenv for you:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv sync --extra dev # set up the environment with dev dependencies
|
|
149
|
+
uv run pytest # run the test suite
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With pip:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
156
|
+
pip install -e ".[dev]"
|
|
157
|
+
pytest
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
[MIT](LICENSE) © Jonathan Katende Pinto
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# pyfilechoose
|
|
2
|
+
|
|
3
|
+
R's `file.choose()`, reimplemented for Python.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
6
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
In R, `file.choose()` opens a file picker and returns the path you select.
|
|
10
|
+
`pyfilechoose` gives you the same one-liner in Python, with no GUI boilerplate
|
|
11
|
+
and no leftover Tk windows.
|
|
12
|
+
|
|
13
|
+
It uses `tkinter` from the standard library, so it has no third-party
|
|
14
|
+
dependencies.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
With pip:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install pyfilechoose
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Add it to your project
|
|
28
|
+
uv add pyfilechoose
|
|
29
|
+
|
|
30
|
+
# ...or install it into the current environment
|
|
31
|
+
uv pip install pyfilechoose
|
|
32
|
+
|
|
33
|
+
# ...or run a one-off script that uses it, no install needed
|
|
34
|
+
uv run --with pyfilechoose your_script.py
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On Linux, `tkinter` ships with most Python builds but may need to be installed
|
|
38
|
+
separately, e.g. `sudo apt install python3-tk`.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Pick a single file
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pyfilechoose import file_choose
|
|
46
|
+
|
|
47
|
+
path = file_choose()
|
|
48
|
+
print(path) # absolute path of the file you selected
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### With pandas (the classic R workflow)
|
|
52
|
+
|
|
53
|
+
`file_choose()` returns a path string, so it works with any function that takes
|
|
54
|
+
a file path, including `pd.read_csv`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import pandas as pd
|
|
58
|
+
from pyfilechoose import file_choose
|
|
59
|
+
|
|
60
|
+
# Just like df <- read.csv(file.choose()) in R
|
|
61
|
+
df = pd.read_csv(file_choose())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
You can still pass the usual pandas options, and filter the picker to CSVs:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
df = pd.read_csv(file_choose(filetypes=[("CSV files", "*.csv")]), sep=";")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Filter file types and set a starting directory
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
path = file_choose(
|
|
74
|
+
title="Pick your dataset",
|
|
75
|
+
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
|
|
76
|
+
initialdir="~/Documents",
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Pick several files at once
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pyfilechoose import files_choose
|
|
84
|
+
|
|
85
|
+
paths = files_choose(filetypes=[("Images", "*.png *.jpg")])
|
|
86
|
+
for p in paths:
|
|
87
|
+
print(p)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API
|
|
91
|
+
|
|
92
|
+
### `file_choose(*, title="Select a file", filetypes=None, initialdir=None) -> str`
|
|
93
|
+
|
|
94
|
+
Opens a dialog and returns the absolute path of the chosen file.
|
|
95
|
+
Raises `FileNotFoundError` if the user cancels.
|
|
96
|
+
|
|
97
|
+
### `files_choose(*, title="Select one or more files", filetypes=None, initialdir=None) -> list[str]`
|
|
98
|
+
|
|
99
|
+
Same as above but allows multiple selections; returns a list of absolute paths.
|
|
100
|
+
Raises `FileNotFoundError` if nothing is selected.
|
|
101
|
+
|
|
102
|
+
| Argument | Type | Description |
|
|
103
|
+
| ------------ | ----------------------------- | --------------------------------------------------------- |
|
|
104
|
+
| `title` | `str` | Title of the dialog window. |
|
|
105
|
+
| `filetypes` | `list[tuple[str, str]]` \| `None` | `(label, pattern)` pairs, e.g. `[("CSV", "*.csv")]`. |
|
|
106
|
+
| `initialdir` | `str` \| `None` | Directory the dialog opens in. |
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/Pinto-Katende-Jonathan/pyfilechoose.git
|
|
112
|
+
cd pyfilechoose
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With uv, which creates and manages the virtualenv for you:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv sync --extra dev # set up the environment with dev dependencies
|
|
119
|
+
uv run pytest # run the test suite
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
With pip:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
126
|
+
pip install -e ".[dev]"
|
|
127
|
+
pytest
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
[MIT](LICENSE) © Jonathan Katende Pinto
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyfilechoose"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A simple replication of R's file.choose() for Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jonathan Katende Pinto", email = "katendepinto@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["file", "dialog", "tkinter", "file.choose", "R", "filedialog"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Topic :: Software Development :: User Interfaces",
|
|
29
|
+
]
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=7.0"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/Pinto-Katende-Jonathan/pyfilechoose"
|
|
37
|
+
Repository = "https://github.com/Pinto-Katende-Jonathan/pyfilechoose"
|
|
38
|
+
Issues = "https://github.com/Pinto-Katende-Jonathan/pyfilechoose/issues"
|
|
39
|
+
Changelog = "https://github.com/Pinto-Katende-Jonathan/pyfilechoose/blob/main/CHANGELOG.md"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
pyfilechoose = ["py.typed"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""pyfilechoose - R's ``file.choose()``, reimplemented for Python.
|
|
2
|
+
|
|
3
|
+
Open a native file-selection dialog and get back an absolute path:
|
|
4
|
+
|
|
5
|
+
>>> from pyfilechoose import file_choose
|
|
6
|
+
>>> path = file_choose()
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .core import file_choose, files_choose
|
|
10
|
+
|
|
11
|
+
__all__ = ["file_choose", "files_choose"]
|
|
12
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Core implementation of :func:`file_choose` and :func:`files_choose`.
|
|
2
|
+
|
|
3
|
+
These functions open a native "Open file" dialog using :mod:`tkinter`
|
|
4
|
+
(part of the Python standard library) and return the selected path(s).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Iterable, Optional, Sequence, Tuple
|
|
11
|
+
|
|
12
|
+
__all__ = ["file_choose", "files_choose"]
|
|
13
|
+
|
|
14
|
+
# Type alias for the ``filetypes`` argument, e.g. ``[("CSV files", "*.csv")]``.
|
|
15
|
+
FileTypes = Sequence[Tuple[str, str]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _open_dialog(
|
|
19
|
+
*,
|
|
20
|
+
multiple: bool,
|
|
21
|
+
title: str,
|
|
22
|
+
filetypes: Optional[FileTypes],
|
|
23
|
+
initialdir: Optional[str],
|
|
24
|
+
):
|
|
25
|
+
"""Open a Tkinter file dialog and return the raw selection.
|
|
26
|
+
|
|
27
|
+
A hidden root window is created, forced to the foreground, used for a
|
|
28
|
+
single dialog, then destroyed so no resources leak.
|
|
29
|
+
"""
|
|
30
|
+
# Imported lazily so that merely importing the package does not require
|
|
31
|
+
# a working Tk installation (e.g. on a headless server).
|
|
32
|
+
try:
|
|
33
|
+
import tkinter as tk
|
|
34
|
+
from tkinter import filedialog
|
|
35
|
+
except ImportError as exc: # pragma: no cover - platform dependent
|
|
36
|
+
raise RuntimeError(
|
|
37
|
+
"tkinter is not available in this Python installation. "
|
|
38
|
+
"On Linux install it with your package manager, e.g. "
|
|
39
|
+
"'sudo apt install python3-tk'."
|
|
40
|
+
) from exc
|
|
41
|
+
|
|
42
|
+
root = tk.Tk()
|
|
43
|
+
root.withdraw()
|
|
44
|
+
# Force the dialog to appear on top of other windows.
|
|
45
|
+
root.wm_attributes("-topmost", 1)
|
|
46
|
+
|
|
47
|
+
options = {"title": title}
|
|
48
|
+
if filetypes is not None:
|
|
49
|
+
options["filetypes"] = list(filetypes)
|
|
50
|
+
if initialdir is not None:
|
|
51
|
+
options["initialdir"] = initialdir
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if multiple:
|
|
55
|
+
selection = filedialog.askopenfilenames(**options)
|
|
56
|
+
else:
|
|
57
|
+
selection = filedialog.askopenfilename(**options)
|
|
58
|
+
finally:
|
|
59
|
+
# Always release the Tk resources, even if the dialog raises.
|
|
60
|
+
root.destroy()
|
|
61
|
+
|
|
62
|
+
return selection
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def file_choose(
|
|
66
|
+
*,
|
|
67
|
+
title: str = "Select a file",
|
|
68
|
+
filetypes: Optional[FileTypes] = None,
|
|
69
|
+
initialdir: Optional[str] = None,
|
|
70
|
+
) -> str:
|
|
71
|
+
"""Open a dialog and return the absolute path of the chosen file.
|
|
72
|
+
|
|
73
|
+
A small replication of R's ``file.choose()`` for Python.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
title:
|
|
78
|
+
Title shown in the dialog window.
|
|
79
|
+
filetypes:
|
|
80
|
+
Optional sequence of ``(label, pattern)`` pairs used to filter the
|
|
81
|
+
files shown, e.g. ``[("CSV files", "*.csv"), ("All files", "*.*")]``.
|
|
82
|
+
initialdir:
|
|
83
|
+
Directory the dialog should open in. Defaults to the platform's
|
|
84
|
+
last-used directory.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
str
|
|
89
|
+
The absolute path of the selected file.
|
|
90
|
+
|
|
91
|
+
Raises
|
|
92
|
+
------
|
|
93
|
+
FileNotFoundError
|
|
94
|
+
If the user cancels the dialog without selecting a file.
|
|
95
|
+
|
|
96
|
+
Examples
|
|
97
|
+
--------
|
|
98
|
+
>>> import pandas as pd
|
|
99
|
+
>>> df = pd.read_csv(file_choose(filetypes=[("CSV files", "*.csv")]))
|
|
100
|
+
"""
|
|
101
|
+
file_path = _open_dialog(
|
|
102
|
+
multiple=False,
|
|
103
|
+
title=title,
|
|
104
|
+
filetypes=filetypes,
|
|
105
|
+
initialdir=initialdir,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# askopenfilename returns an empty string when the user cancels.
|
|
109
|
+
if not file_path:
|
|
110
|
+
raise FileNotFoundError("No file was selected.")
|
|
111
|
+
|
|
112
|
+
return os.path.abspath(file_path)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def files_choose(
|
|
116
|
+
*,
|
|
117
|
+
title: str = "Select one or more files",
|
|
118
|
+
filetypes: Optional[FileTypes] = None,
|
|
119
|
+
initialdir: Optional[str] = None,
|
|
120
|
+
) -> list[str]:
|
|
121
|
+
"""Open a dialog allowing multiple selections and return absolute paths.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
title, filetypes, initialdir:
|
|
126
|
+
See :func:`file_choose`.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
list of str
|
|
131
|
+
Absolute paths of every selected file, in selection order.
|
|
132
|
+
|
|
133
|
+
Raises
|
|
134
|
+
------
|
|
135
|
+
FileNotFoundError
|
|
136
|
+
If the user cancels the dialog without selecting any file.
|
|
137
|
+
"""
|
|
138
|
+
selection: Iterable[str] = _open_dialog(
|
|
139
|
+
multiple=True,
|
|
140
|
+
title=title,
|
|
141
|
+
filetypes=filetypes,
|
|
142
|
+
initialdir=initialdir,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
paths = [os.path.abspath(p) for p in selection]
|
|
146
|
+
if not paths:
|
|
147
|
+
raise FileNotFoundError("No file was selected.")
|
|
148
|
+
|
|
149
|
+
return paths
|
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfilechoose
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple replication of R's file.choose() for Python.
|
|
5
|
+
Author-email: Jonathan Katende Pinto <katendepinto@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pinto-Katende-Jonathan/pyfilechoose
|
|
8
|
+
Project-URL: Repository, https://github.com/Pinto-Katende-Jonathan/pyfilechoose
|
|
9
|
+
Project-URL: Issues, https://github.com/Pinto-Katende-Jonathan/pyfilechoose/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Pinto-Katende-Jonathan/pyfilechoose/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: file,dialog,tkinter,file.choose,R,filedialog
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# pyfilechoose
|
|
32
|
+
|
|
33
|
+
R's `file.choose()`, reimplemented for Python.
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
36
|
+
[](https://pypi.org/project/pyfilechoose/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
In R, `file.choose()` opens a file picker and returns the path you select.
|
|
40
|
+
`pyfilechoose` gives you the same one-liner in Python, with no GUI boilerplate
|
|
41
|
+
and no leftover Tk windows.
|
|
42
|
+
|
|
43
|
+
It uses `tkinter` from the standard library, so it has no third-party
|
|
44
|
+
dependencies.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
With pip:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pyfilechoose
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Add it to your project
|
|
58
|
+
uv add pyfilechoose
|
|
59
|
+
|
|
60
|
+
# ...or install it into the current environment
|
|
61
|
+
uv pip install pyfilechoose
|
|
62
|
+
|
|
63
|
+
# ...or run a one-off script that uses it, no install needed
|
|
64
|
+
uv run --with pyfilechoose your_script.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
On Linux, `tkinter` ships with most Python builds but may need to be installed
|
|
68
|
+
separately, e.g. `sudo apt install python3-tk`.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Pick a single file
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pyfilechoose import file_choose
|
|
76
|
+
|
|
77
|
+
path = file_choose()
|
|
78
|
+
print(path) # absolute path of the file you selected
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With pandas (the classic R workflow)
|
|
82
|
+
|
|
83
|
+
`file_choose()` returns a path string, so it works with any function that takes
|
|
84
|
+
a file path, including `pd.read_csv`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import pandas as pd
|
|
88
|
+
from pyfilechoose import file_choose
|
|
89
|
+
|
|
90
|
+
# Just like df <- read.csv(file.choose()) in R
|
|
91
|
+
df = pd.read_csv(file_choose())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You can still pass the usual pandas options, and filter the picker to CSVs:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
df = pd.read_csv(file_choose(filetypes=[("CSV files", "*.csv")]), sep=";")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Filter file types and set a starting directory
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
path = file_choose(
|
|
104
|
+
title="Pick your dataset",
|
|
105
|
+
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
|
|
106
|
+
initialdir="~/Documents",
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pick several files at once
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from pyfilechoose import files_choose
|
|
114
|
+
|
|
115
|
+
paths = files_choose(filetypes=[("Images", "*.png *.jpg")])
|
|
116
|
+
for p in paths:
|
|
117
|
+
print(p)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `file_choose(*, title="Select a file", filetypes=None, initialdir=None) -> str`
|
|
123
|
+
|
|
124
|
+
Opens a dialog and returns the absolute path of the chosen file.
|
|
125
|
+
Raises `FileNotFoundError` if the user cancels.
|
|
126
|
+
|
|
127
|
+
### `files_choose(*, title="Select one or more files", filetypes=None, initialdir=None) -> list[str]`
|
|
128
|
+
|
|
129
|
+
Same as above but allows multiple selections; returns a list of absolute paths.
|
|
130
|
+
Raises `FileNotFoundError` if nothing is selected.
|
|
131
|
+
|
|
132
|
+
| Argument | Type | Description |
|
|
133
|
+
| ------------ | ----------------------------- | --------------------------------------------------------- |
|
|
134
|
+
| `title` | `str` | Title of the dialog window. |
|
|
135
|
+
| `filetypes` | `list[tuple[str, str]]` \| `None` | `(label, pattern)` pairs, e.g. `[("CSV", "*.csv")]`. |
|
|
136
|
+
| `initialdir` | `str` \| `None` | Directory the dialog opens in. |
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/Pinto-Katende-Jonathan/pyfilechoose.git
|
|
142
|
+
cd pyfilechoose
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
With uv, which creates and manages the virtualenv for you:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv sync --extra dev # set up the environment with dev dependencies
|
|
149
|
+
uv run pytest # run the test suite
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With pip:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
156
|
+
pip install -e ".[dev]"
|
|
157
|
+
pytest
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
[MIT](LICENSE) © Jonathan Katende Pinto
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/pyfilechoose/__init__.py
|
|
5
|
+
src/pyfilechoose/core.py
|
|
6
|
+
src/pyfilechoose/py.typed
|
|
7
|
+
src/pyfilechoose.egg-info/PKG-INFO
|
|
8
|
+
src/pyfilechoose.egg-info/SOURCES.txt
|
|
9
|
+
src/pyfilechoose.egg-info/dependency_links.txt
|
|
10
|
+
src/pyfilechoose.egg-info/requires.txt
|
|
11
|
+
src/pyfilechoose.egg-info/top_level.txt
|
|
12
|
+
tests/test_core.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyfilechoose
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Tests for pyfilechoose.
|
|
2
|
+
|
|
3
|
+
The Tk dialog itself is replaced with a fake so the suite can run headless
|
|
4
|
+
(in CI, without a display). We only verify the logic around the dialog:
|
|
5
|
+
absolute-path conversion, cancellation handling, and argument forwarding.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from pyfilechoose import core, file_choose, files_choose
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_file_choose_returns_absolute_path(tmp_path, monkeypatch):
|
|
16
|
+
target = tmp_path / "data.csv"
|
|
17
|
+
target.write_text("a,b\n1,2\n")
|
|
18
|
+
|
|
19
|
+
monkeypatch.setattr(core, "_open_dialog", lambda **kwargs: str(target))
|
|
20
|
+
|
|
21
|
+
result = file_choose()
|
|
22
|
+
assert result == os.path.abspath(str(target))
|
|
23
|
+
assert os.path.isabs(result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_file_choose_raises_on_cancel(monkeypatch):
|
|
27
|
+
# An empty string is what Tk returns when the user cancels.
|
|
28
|
+
monkeypatch.setattr(core, "_open_dialog", lambda **kwargs: "")
|
|
29
|
+
|
|
30
|
+
with pytest.raises(FileNotFoundError):
|
|
31
|
+
file_choose()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_file_choose_forwards_arguments(monkeypatch):
|
|
35
|
+
captured = {}
|
|
36
|
+
|
|
37
|
+
def fake(**kwargs):
|
|
38
|
+
captured.update(kwargs)
|
|
39
|
+
return "/some/file.csv"
|
|
40
|
+
|
|
41
|
+
monkeypatch.setattr(core, "_open_dialog", fake)
|
|
42
|
+
|
|
43
|
+
file_choose(
|
|
44
|
+
title="Pick one",
|
|
45
|
+
filetypes=[("CSV", "*.csv")],
|
|
46
|
+
initialdir="/tmp",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
assert captured["multiple"] is False
|
|
50
|
+
assert captured["title"] == "Pick one"
|
|
51
|
+
assert captured["filetypes"] == [("CSV", "*.csv")]
|
|
52
|
+
assert captured["initialdir"] == "/tmp"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_files_choose_returns_list(monkeypatch):
|
|
56
|
+
monkeypatch.setattr(
|
|
57
|
+
core, "_open_dialog", lambda **kwargs: ("/a/one.txt", "/b/two.txt")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = files_choose()
|
|
61
|
+
assert result == [os.path.abspath("/a/one.txt"), os.path.abspath("/b/two.txt")]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_files_choose_raises_on_empty(monkeypatch):
|
|
65
|
+
monkeypatch.setattr(core, "_open_dialog", lambda **kwargs: ())
|
|
66
|
+
|
|
67
|
+
with pytest.raises(FileNotFoundError):
|
|
68
|
+
files_choose()
|