Julia: Feature proposal: Pathlib-like path objects

Created on 12 Nov 2020  路  4Comments  路  Source: JuliaLang/julia

Python has great, build-in, pathlib module, that enables users to dynamically manipulate system paths. Much of its functionality is already covered by Julia's base module utilities (relpath, abspath, etc.), but one of the most handy, while trivial, functionalities is not covered. Pathlib enables users to create paths dynamically, e.g. Path("foo") / "bar" creates Path("foo/bar") etc, while sticking to system convention for joinpath-like path creation.

Example

Say that you are dealing with data in nested directories, you need to loop over the directories. With the proposed functionality, you could do this with simple and readable code:

dir = Path(pwd())
for subdir in ["A", "B", "C"]
   push!(data, read(dir / subdir / "file.csv"))  # instead of using: joinpath(dir, subdir, "file.csv")
end

POC implementation

This is trivial to implement in Julia:

import Base: iterate, /, *, relpath, String

struct Path{T<:AbstractString} <: AbstractString
    path::T
end

String(path::Path) = path.path

iterate(path::Path) = iterate(String(path))
iterate(path::Path, i::Integer) = iterate(String(path), i)

macro p_str(path) Path(path) end

/(x::Path, y::Path) = Path(joinpath(String(x), String(y)))
/(x::AbstractString, y::Path) = Path(joinpath(x, String(y)))
/(x::Path, y::AbstractString) = Path(joinpath(String(x), y))

*(x::Path, y::Path) = Path(String(x) * String(y))
*(x::AbstractString, y::Path) = Path(x * String(y))
*(x::Path, y::AbstractString) = Path(String(x) * y)

relpath(path::Path, startpath::AbstractString = ".") = relpath(String(path), startpath)

and here some examples of usage:

@testset "Path objects arithmetic" begin
    @test joinpath(pwd(), "test") == Path(pwd()) / "test"
    @test joinpath("foo", "bar") == p"foo" / p"bar"
    @test joinpath("foo", "bar") == p"foo" / "bar"
    @test joinpath("foo", "bar") == "foo" / p"bar"
    @test joinpath("foo", "bar", "baz") == p"foo" / p"bar" / p"baz"
    @test joinpath("foo", "bar", "baz") == p"foo" / "bar" / "baz"
    @test joinpath("foobar", "baz") == p"foo" * p"bar" / "baz"
    @test joinpath("foobar", "baz") == p"foo" * "bar" / "baz"
    @test joinpath("foobar", "baz") == "foo" * p"bar" / "baz"
end

Since it'd inherit from AbstractString, it is compatible with other string-based methods. If this would get accepted, methods like pwd, joinpath, relpath, etc. could return Path objects instead of regular strings, so we could use them like pwd() / "foo" / "bar" to create the ./foo/bar path dynamically.

Most helpful comment

Also, a couple things to note on your proposal

  1. The path type shouldn't subtype AbstractString as many string operations are not applicable and it prevents distinguishing AbstractString and AbstractPaths during dispatch. https://github.com/rofinn/FilePathsBase.jl/issues/15
  2. Similar to pathlib, the internal representation of the path type should be a tuple of strings rather than a string that you mutate directly.
  3. You probably want an AbstractPath type to distinguish different local and remote filesystems. This has been really helpful for us when we write code that can either take a SystemPath or S3Path.

All 4 comments

Also, a couple things to note on your proposal

  1. The path type shouldn't subtype AbstractString as many string operations are not applicable and it prevents distinguishing AbstractString and AbstractPaths during dispatch. https://github.com/rofinn/FilePathsBase.jl/issues/15
  2. Similar to pathlib, the internal representation of the path type should be a tuple of strings rather than a string that you mutate directly.
  3. You probably want an AbstractPath type to distinguish different local and remote filesystems. This has been really helpful for us when we write code that can either take a SystemPath or S3Path.

@rofinn sure, as said, this is a minimalistic proof-of-concept with very limited functionality. Pros of this approach is that it minimizes the risk of any conflicts and problems with backward compatibility, since currently paths are just strings. The cons are the ones mentioned by you.

Fair enough, seems like the practicality vs purity discussion. Might be worth reviewing the design choices for pathlib, since that's what you're trying to model the behaviour after? https://snarky.ca/why-pathlib-path-doesn-t-inherit-from-str/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

m-j-w picture m-j-w  路  3Comments

yurivish picture yurivish  路  3Comments

dpsanders picture dpsanders  路  3Comments

manor picture manor  路  3Comments

tkoolen picture tkoolen  路  3Comments