Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature proposal: Pathlib-like path objects #38415

Closed
twolodzko opened this issue Nov 12, 2020 · 5 comments
Closed

Feature proposal: Pathlib-like path objects #38415

twolodzko opened this issue Nov 12, 2020 · 5 comments
Labels
julep Julia Enhancement Proposal

Comments

@twolodzko
Copy link

twolodzko commented Nov 12, 2020

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.

@fredrikekre
Copy link
Member

See JuliaLang/Juleps#26, https://github.com/rofinn/FilePathsBase.jl

@rofinn
Copy link
Contributor

rofinn commented Nov 13, 2020

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. Drop AbstractString subtyping rofinn/FilePathsBase.jl#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.

@twolodzko
Copy link
Author

@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.

@rofinn
Copy link
Contributor

rofinn commented Nov 13, 2020

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/

@vtjnash
Copy link
Member

vtjnash commented Aug 21, 2024

Implemented in https://github.com/rofinn/FilePathsBase.jl. However, we are not going to suddenly deprecate using Strings as Paths in Base.

@vtjnash vtjnash closed this as not planned Won't fix, can't repro, duplicate, stale Aug 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
julep Julia Enhancement Proposal
Projects
None yet
Development

No branches or pull requests

5 participants