-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.qmd
More file actions
202 lines (154 loc) · 6.42 KB
/
Copy pathREADME.qmd
File metadata and controls
202 lines (154 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
---
title: pyrfs
format: gfm
execute:
warning: false
---
<!-- README.md is generated from README.qmd: edit that file, then render with
`uv run --group docs --extra pandas quarto render README.qmd` -->
[](https://github.com/Lightbridge-KS/pyrfs/actions/workflows/ci.yml)
[](https://github.com/Lightbridge-KS/pyrfs)
[](https://pyrfs.netlify.app)
[](https://pyrfs.netlify.app)
**pyrfs** provides a uniform, chainable interface to file system operations,
porting the UX of R's [fs](https://fs.r-lib.org) package to Python. It is
**pure Python over the standard library** — no compiled extension, no
dependencies — adding the ergonomics the stdlib leaves out: one consistent
namespace, tidy paths, typed self-describing values, and explicit failure.
📖 **Documentation: <https://pyrfs.netlify.app>**
```{python}
#| include: false
import shutil
shutil.rmtree("/tmp/pyrfs-demo", ignore_errors=True)
import pyrfs as fs
# deterministic temp names for reproducible output (fs's file_temp_push trick)
fs.file_temp_push(["/tmp/pyrfs-demo/data", "/tmp/pyrfs-demo/chain", "/tmp/pyrfs-demo/tsv"])
```
## Installation
Pre-release — install from GitHub:
```sh
pip install "pyrfs @ git+https://github.com/Lightbridge-KS/pyrfs"
# with the pandas integration:
pip install "pyrfs[pandas] @ git+https://github.com/Lightbridge-KS/pyrfs"
```
## Comparison vs the standard library
Python's file APIs accreted across `os`, `os.path`, `shutil`, `glob`,
`pathlib`, and `tempfile`. **pyrfs** smooths over the seams:
- **One namespace, predictable names.** Functions are grouped by the noun
they act on — `path_*` (pure string algebra), `file_*`, `dir_*`, `link_*` —
so `dir_` + <kbd>Tab</kbd> shows every directory operation. No more remembering
that creating is `os.makedirs` but removing is `shutil.rmtree`.
- **Predictable, path-carrying returns.** Mutating verbs return the new path
(so calls chain); queries return typed values. Compare
`os.path.getsize()` → bare `int` vs `file_size()` → `Bytes` that displays
`444.5K` and compares against `"10KB"`.
- **Explicit failure, safe defaults.** `shutil.copy2()` silently overwrites
its target; `file_copy()` raises `FileExistsError` unless you pass
`overwrite=True`. Traversals raise on unreadable entries unless you soften
them with `fail=False`.
- **Polymorphic over scalars, lists, and pandas Series.** Every path function
accepts one path or many — vectorization like fs, the Pythonic way.
- **Tidy paths.** Always `/` separators, never doubled or trailing — and in a
terminal, paths are coloured by file type via `LS_COLORS` (degrading
automatically on non-TTY or `NO_COLOR`).
## Usage
pyrfs functions are divided into four main families:
- `path_` for manipulating and constructing paths
- `file_` for files
- `dir_` for directories
- `link_` for links
Directories and links are special types of files, so `file_` functions
generally also work on them (there is deliberately no `dir_move()` — use
`file_move()`).
```{python}
import pyrfs as fs
# construct a path with path()
fs.path("foo", "bar", "a", ext="txt")
```
```{python}
# list files
fs.dir_ls("pyrfs", glob="*.py")
```
```{python}
# create a new directory
tmp = fs.dir_create(fs.file_temp())
tmp
```
```{python}
# create new files in that directory
fs.file_create(tmp / "my-file.txt")
fs.dir_ls(tmp)
```
```{python}
# remove files from the directory
fs.file_delete(tmp / "my-file.txt")
fs.dir_ls(tmp)
```
```{python}
# remove the directory
fs.dir_delete(tmp);
```
### Chaining — the Pythonic pipe
Where R pipes `file_temp() |> dir_create() |> path(letters[1:3]) |> file_create()`,
pyrfs chains: every mutating verb returns the resulting `FsPath` (which **is**
a `str`, so it drops into `open()`, `pd.read_csv()`, or any API expecting a
path).
```{python}
from pyrfs import FsPath
(FsPath(fs.file_temp())
.mkdir()
.touch_file("a").touch_file("b").touch_file("c")
.ls())
```
## pyrfs + pandas
With the `[pandas]` extra, `dir_info()` returns a DataFrame whose `path`,
`size`, and `permissions` columns are real ExtensionDtypes — so string
literals work inside `.query()`, exactly like fs's typed tibble columns:
```{python}
import pandas as pd
big = (fs.dir_info("pyrfs", recurse=True, glob="*.py")
.query("size > '4KB' and type == 'file'")
.sort_values("size", ascending=False)
.loc[:, ["path", "permissions", "size"]])
print(big.to_string(index=False))
```
The `.fs` accessor vectorizes path operations over a Series:
```{python}
paths = pd.Series(fs.dir_ls("pyrfs/_engine", glob="*ops.py"))
print(paths.fs.size())
```
And reading a collection of files into one frame — the `purrr::map_df(.id=)`
trick — is a dict comprehension away, because `dir_ls()` returns paths whose
`.name()` you can key on:
```{python}
tsv_dir = fs.dir_create(fs.file_temp())
df = pd.DataFrame({"species": ["adelie", "adelie", "gentoo"], "mass": [3800, 3250, 5000]})
for species, d in df.groupby("species"):
d.to_csv(tsv_dir / f"{species}.tsv", sep="\t", index=False)
files = fs.dir_ls(tsv_dir, glob="*.tsv")
combined = pd.concat({f.name(): pd.read_csv(f, sep="\t") for f in files}, names=["file"])
print(combined)
```
```{python}
#| include: false
shutil.rmtree("/tmp/pyrfs-demo", ignore_errors=True)
```
## Coming from R's fs?
The functional names are identical, so muscle memory transfers:
| R fs | pyrfs |
|------|-------|
| `dir_ls("d", recurse = TRUE)` | `dir_ls("d", recurse=True)` |
| `file_copy("a", "b")` | `file_copy("a", "b")` or `FsPath("a").copy_to("b")` |
| `dir_info("d") \|> filter(size > "10KB")` | `dir_info("d").query("size > '10KB'")` |
See the full **[translation guide](https://pyrfs.netlify.app/coming-from-r/)**
and the honest list of deliberate differences.
## Documentation & feedback
The **[docs site](https://pyrfs.netlify.app)** has guides, an executable tour
notebook, and the API reference — plus
[llms.txt](https://pyrfs.netlify.app/llms.txt) /
[llms-full.txt](https://pyrfs.netlify.app/llms-full.txt) for AI agents.
Bug reports and feature requests:
[GitHub issues](https://github.com/Lightbridge-KS/pyrfs/issues).
## License
MIT © Lightbridge-KS — see [LICENSE.md](LICENSE.md).
Release history: [CHANGELOG.md](CHANGELOG.md).