Skip to content

Commit 0639c9a

Browse files
github-actions[bot]Copilotdbrattliclaude
authored
feat(stdlib): add pathlib module bindings (#276)
* feat(stdlib): add pathlib module bindings with 36 tests Adds F# bindings for Python's pathlib.Path class: - Path construction (single string and multi-segment) - Properties: name, stem, suffix, suffixes, parent, parents, parts, root, anchor, drive - Path arithmetic: / operator (string and Path), joinpath - Pure transformations: with_name, with_stem, with_suffix, as_posix, as_uri - I/O predicates: exists, is_file, is_dir, is_symlink, is_mount, is_absolute - I/O operations: resolve, relative_to, read_text, read_bytes, write_text, write_bytes - Directory operations: mkdir, mkdir_p, rmdir, iterdir, glob, rglob - File operations: unlink, rename, replace, expanduser - Static factories: Path.cwd, Path.home - is_relative_to (3.9+) Includes 36 tests covering construction, properties, path arithmetic, transformations, predicates, file round-trips, and directory operations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(pathlib): make Path constructor emit positional args Fable emits primary-ctor parameters as Python kwargs, but pathlib.Path.__init__ does not accept `path=` as a keyword, breaking 27 tests with `TypeError: PurePath.__init__() got an unexpected keyword argument 'path'`. Switch to a no-arg primary ctor and declare the string and varargs ctors via [<Emit>] so emission stays positional. Also relax suffixes/parents/parts to `seq<_>` to match the underlying tuple/list/_PathParents return types, and wrap the byte fixture in builtins.bytes so write_bytes receives a real bytes object instead of a fable.UInt8Array. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: restore indentation on Pathlib.fs/TestPathlib.fs lines Lost during the merge conflict resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dag Brattli <dag@brattli.net> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c91f38e commit 0639c9a

4 files changed

Lines changed: 519 additions & 0 deletions

File tree

src/Fable.Python.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<Compile Include="stdlib/Itertools.fs" />
2929
<Compile Include="stdlib/Datetime.fs" />
3030
<Compile Include="stdlib/Functools.fs" />
31+
<Compile Include="stdlib/Pathlib.fs" />
3132
<Compile Include="stdlib/Regex.fs" />
3233
<Compile Include="stdlib/Queue.fs" />
3334
<Compile Include="stdlib/String.fs" />

src/stdlib/Pathlib.fs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/// Type bindings for Python pathlib module: https://docs.python.org/3/library/pathlib.html
2+
module Fable.Python.Pathlib
3+
4+
open System
5+
open Fable.Core
6+
7+
// fsharplint:disable MemberNames
8+
9+
/// Represents a filesystem path on the current OS (POSIX or Windows).
10+
/// Paths are immutable; operations return new Path instances.
11+
///
12+
/// `Path()` represents the current directory (`.`). For multi-segment construction
13+
/// use the `/` operator (`Path "a" / "b" / "c"`) or `joinpath`.
14+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path
15+
[<Import("Path", "pathlib")>]
16+
type Path() =
17+
/// Construct a Path from a single string segment.
18+
[<Emit("Path($0)")>]
19+
new(path: string) = Path()
20+
21+
/// Construct a Path by joining multiple path segments.
22+
/// Equivalent to ``Path(parts[0]) / parts[1] / …``.
23+
[<Emit("Path($0...)")>]
24+
new([<ParamArray>] paths: string[]) = Path()
25+
26+
// -------------------------------------------------------------------------
27+
// Properties
28+
// -------------------------------------------------------------------------
29+
30+
/// The final path component (file or directory name).
31+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name
32+
member _.name: string = nativeOnly
33+
34+
/// The final path component without its last suffix (file extension).
35+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.stem
36+
member _.stem: string = nativeOnly
37+
38+
/// The last file extension of the final component (e.g. ".py"), or "" if none.
39+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffix
40+
member _.suffix: string = nativeOnly
41+
42+
/// All file extensions of the final component (e.g. [".tar"; ".gz"]).
43+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes
44+
member _.suffixes: string seq = nativeOnly
45+
46+
/// The logical parent of the path (directory containing this path).
47+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent
48+
member _.parent: Path = nativeOnly
49+
50+
/// Immutable sequence of the logical ancestors of the path.
51+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parents
52+
member _.parents: Path seq = nativeOnly
53+
54+
/// The path's components as a tuple of strings.
55+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parts
56+
member _.parts: string seq = nativeOnly
57+
58+
/// The root component of the path (e.g. "/" on POSIX), or "".
59+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.root
60+
member _.root: string = nativeOnly
61+
62+
/// The concatenation of drive and root (e.g. "/" or "C:\\"), or "".
63+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.anchor
64+
member _.anchor: string = nativeOnly
65+
66+
/// The drive letter or name (relevant on Windows), or "".
67+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.drive
68+
member _.drive: string = nativeOnly
69+
70+
// -------------------------------------------------------------------------
71+
// Path arithmetic
72+
// -------------------------------------------------------------------------
73+
74+
/// Return a new Path by appending a child string segment.
75+
/// This mirrors Python's ``path / "child"`` operator.
76+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.__truediv__
77+
[<Emit("$0 / $1")>]
78+
static member (/) (left: Path, right: string) : Path = nativeOnly
79+
80+
/// Return a new Path by appending another Path.
81+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.__truediv__
82+
[<Emit("$0 / $1")>]
83+
static member (/) (left: Path, right: Path) : Path = nativeOnly
84+
85+
/// Join one or more path segments to this path and return the result.
86+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath
87+
[<Emit("$0.joinpath($1...)")>]
88+
member _.joinpath([<ParamArray>] parts: string[]) : Path = nativeOnly
89+
90+
// -------------------------------------------------------------------------
91+
// Tests (pure — no I/O)
92+
// -------------------------------------------------------------------------
93+
94+
/// Return True if the path is absolute (has both a root and, if applicable, a drive).
95+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute
96+
member _.is_absolute() : bool = nativeOnly
97+
98+
/// Return True if this path is relative to other (3.9+).
99+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to
100+
member _.is_relative_to(other: Path) : bool = nativeOnly
101+
102+
/// Return True if this path is relative to the string path other (3.9+).
103+
[<Emit("$0.is_relative_to($1)")>]
104+
member _.is_relative_to(other: string) : bool = nativeOnly
105+
106+
// -------------------------------------------------------------------------
107+
// Transformations (pure — no I/O)
108+
// -------------------------------------------------------------------------
109+
110+
/// Return a new path with the name component replaced.
111+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_name
112+
member _.with_name(name: string) : Path = nativeOnly
113+
114+
/// Return a new path with the stem component replaced (3.9+).
115+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_stem
116+
member _.with_stem(stem: string) : Path = nativeOnly
117+
118+
/// Return a new path with the suffix component replaced.
119+
/// Pass "" to remove the suffix.
120+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.with_suffix
121+
member _.with_suffix(suffix: string) : Path = nativeOnly
122+
123+
/// Return a string representation of the path.
124+
/// Equivalent to Python's ``str(path)``.
125+
[<Emit("str($0)")>]
126+
member _.str() : string = nativeOnly
127+
128+
/// Return the path as a POSIX string (forward slashes).
129+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.as_posix
130+
member _.as_posix() : string = nativeOnly
131+
132+
/// Return the path as a URI (file:// scheme). Only absolute paths are supported.
133+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.as_uri
134+
member _.as_uri() : string = nativeOnly
135+
136+
// -------------------------------------------------------------------------
137+
// I/O queries
138+
// -------------------------------------------------------------------------
139+
140+
/// Return True if the path points to an existing filesystem entry.
141+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists
142+
member _.exists() : bool = nativeOnly
143+
144+
/// Return True if the path points to a regular file (follows symlinks).
145+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file
146+
member _.is_file() : bool = nativeOnly
147+
148+
/// Return True if the path points to a directory (follows symlinks).
149+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir
150+
member _.is_dir() : bool = nativeOnly
151+
152+
/// Return True if the path points to a symbolic link.
153+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink
154+
member _.is_symlink() : bool = nativeOnly
155+
156+
/// Return True if the path is a mount point.
157+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_mount
158+
member _.is_mount() : bool = nativeOnly
159+
160+
// -------------------------------------------------------------------------
161+
// I/O operations
162+
// -------------------------------------------------------------------------
163+
164+
/// Make the path absolute and resolve any symlinks or ``..`` components.
165+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve
166+
member _.resolve() : Path = nativeOnly
167+
168+
/// Make the path relative to other, raising ValueError if impossible.
169+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.relative_to
170+
member _.relative_to(other: Path) : Path = nativeOnly
171+
172+
/// Make the path relative to a string path.
173+
[<Emit("$0.relative_to($1)")>]
174+
member _.relative_to(other: string) : Path = nativeOnly
175+
176+
/// Return the decoded contents of the file as a string (UTF-8 by default).
177+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text
178+
member _.read_text() : string = nativeOnly
179+
180+
/// Return the decoded contents of the file using the given encoding.
181+
[<Emit("$0.read_text(encoding=$1)")>]
182+
member _.read_text(encoding: string) : string = nativeOnly
183+
184+
/// Return the binary contents of the file as a bytes object.
185+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_bytes
186+
member _.read_bytes() : byte[] = nativeOnly
187+
188+
/// Write a string to the file, returning the number of characters written.
189+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text
190+
[<Emit("$0.write_text($1)")>]
191+
member _.write_text(data: string) : int = nativeOnly
192+
193+
/// Write a string to the file with the given encoding.
194+
[<Emit("$0.write_text($1, encoding=$2)")>]
195+
member _.write_text(data: string, encoding: string) : int = nativeOnly
196+
197+
/// Write binary data to the file, returning the number of bytes written.
198+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_bytes
199+
member _.write_bytes(data: byte[]) : int = nativeOnly
200+
201+
/// Iterate over the directory contents, yielding Path objects.
202+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.iterdir
203+
member _.iterdir() : Path seq = nativeOnly
204+
205+
/// Glob the given relative pattern in the directory, returning matching paths.
206+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob
207+
member _.glob(pattern: string) : Path seq = nativeOnly
208+
209+
/// Like glob() but descends into sub-directories recursively.
210+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.rglob
211+
member _.rglob(pattern: string) : Path seq = nativeOnly
212+
213+
/// Create this directory. Raises FileExistsError if it already exists.
214+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir
215+
member _.mkdir() : unit = nativeOnly
216+
217+
/// Create this directory with options.
218+
/// parents=true creates any missing parent directories.
219+
/// exist_ok=true suppresses FileExistsError.
220+
[<Emit("$0.mkdir(mode=$1, parents=$2, exist_ok=$3)")>]
221+
member _.mkdir(mode: int, parents: bool, exist_ok: bool) : unit = nativeOnly
222+
223+
/// Create this directory and all missing parents (equivalent to ``mkdir -p``).
224+
[<Emit("$0.mkdir(parents=True, exist_ok=True)")>]
225+
member _.mkdir_p() : unit = nativeOnly
226+
227+
/// Remove this directory. The directory must be empty.
228+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir
229+
member _.rmdir() : unit = nativeOnly
230+
231+
/// Remove this file (or symbolic link). Raises FileNotFoundError if missing.
232+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink
233+
member _.unlink() : unit = nativeOnly
234+
235+
/// Remove this file; if missing_ok is true, no error is raised.
236+
[<Emit("$0.unlink(missing_ok=$1)")>]
237+
member _.unlink(missing_ok: bool) : unit = nativeOnly
238+
239+
/// Rename the file or directory to target, returning the new Path.
240+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename
241+
member _.rename(target: Path) : Path = nativeOnly
242+
243+
/// Rename the file or directory to a string target, returning the new Path.
244+
[<Emit("$0.rename($1)")>]
245+
member _.rename(target: string) : Path = nativeOnly
246+
247+
/// Rename to target, overwriting any existing destination.
248+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace
249+
member _.replace(target: Path) : Path = nativeOnly
250+
251+
/// Rename to a string target, overwriting any existing destination.
252+
[<Emit("$0.replace($1)")>]
253+
member _.replace(target: string) : Path = nativeOnly
254+
255+
/// Expand the ``~`` user home directory shortcut.
256+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser
257+
member _.expanduser() : Path = nativeOnly
258+
259+
// -------------------------------------------------------------------------
260+
// Static factory methods
261+
// -------------------------------------------------------------------------
262+
263+
/// Return a new Path representing the current working directory.
264+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd
265+
static member cwd() : Path = nativeOnly
266+
267+
/// Return a new Path representing the user's home directory.
268+
/// See https://docs.python.org/3/library/pathlib.html#pathlib.Path.home
269+
static member home() : Path = nativeOnly

test/Fable.Python.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<Compile Include="TestTime.fs" />
3535
<Compile Include="TestString.fs" />
3636
<Compile Include="TestDatetime.fs" />
37+
<Compile Include="TestPathlib.fs" />
3738
<Compile Include="TestRegex.fs" />
3839
<Compile Include="TestPydantic.fs" />
3940
<Compile Include="TestFastAPI.fs" />

0 commit comments

Comments
 (0)