Would there be any interest in adding a counterpart for `os.path.expanduser` like:
def unexpanduser(path):
home_directory = os.path.expanduser("~")
if os.path.isabs(home_directory) and os.path.isabs(path) and os.path.commonpath((home_directory, path)) == home_directory:
relative_path = os.path.relpath(path, start=home_directory)
return "~" if relative_path == "." else os.path.join("~", relative_path)
else:
return path
I need this from time to time in order to make pathnames more pretty and thought it might be of general use.
The os.path.isabs(home_directory) is needed in case the os.path.expanduser("~") doesn’t find the homedir and returns "~" again (and for weird cases where the homedir would be relative).
The os.path.isabs(path) is needed if the input path is relative.
Other than that, pretty straight forward and I could try make a PR (and perhaps even add tests ) if there’d be some consensus that this should be added to os.path.
Are these cases even a problem?
I mean we (un)resolve to ~ (not to ~user) which can obviously only ever be valid or the respective current user – if another user uses the resulting path, the results are undefined, just as everywhere with these pathnames.
And it wouldn’t surprise me if it’s on PyPI… the question here was really whether it would be beneficial to have it in the standard library – I’m totally fine if the consensus is “no”.
Maybe I’m misunderstanding what you’re proposing. Could you specify the behavior, without relying on code? I would expect such a function, given “/user/foo” to return “~foo”, assuming user foo’s home directory is “/home/foo”, irrespective of what user calls it.
I think all are more or less straight forward, except perhaps the last one, which is in the current implementation ignored because it’s a relative link.
I would tend to say that this is the behaviour one mostly wants from such a function, which is really intended for making the strings “prettier”.
That’s also the reason why the path tmp from a CWD that is the homedir is not changed to ~/.
I shall also note, that I think all of the functions I use operate purely on the strings and never look anything up in the filesystem and I’d say this is also desired, consider e.g.
where IMO we do not want to have a replacement, because the .. could give different results when symlinks are used.
Now even if anther UID also has the same homedir, I think nothing changes, or does it?
Same if mine is beneath another homedir or another homedir is in-between.
I find a function telling within whose home directory is a pathname located more general and thus more useful. unexpand and other related helpers could be implemented on top of that. Anyway, still in category niche usage.
Working with just the current user will be sufficient for a lot of situations. TBH I have never thought that I wanted this, but in hindsight, it would make a lot of output tidier. I don’t need to see the full explicit path in things, it’s more interesting to see that it’s somewhere off my home directory.
+1 for the original simple version that just handles one’s own home; -0 for extending it to other users.
One could perhaps even do the same in case of nested homedirs, but then – in order to reflect all possibilities – the result would probably need to be a list of lists, with the outer list giving corresponding to the nesting levels and the inner list corresponding to multiple users using the same homedir.
Or maybe even a dict, where the ke<a are the pathnames, and the values the usernames who claim the respective pathname as their homedir.
But not sure whether it makes all to much sense for unexpand to build on that?
In general, I don’t see a way how one could ever perfectly resolve the ambiguity of nested and/or shared homedirs except for the current user, cause if e.g. /rootis used by both root and toor (and assuming these have different UIDs)… which would you choose?
I mean one could go for the owning UID, but what if it’s neither user’s?
Throw an exception.
One could just let such pathnames through as is. Perhaps with an option that controls whether the function should assume the current user, if it works with that.
I guess I’d prefer the latter.
expanduser expands all users (i.e. not just ~ but also ~foo), while the function I proposed above only looks at it from the current user’s PoV… so perhaps one indeed needs to extend it… or rather rename it.
I implemented similar feature several times. Not only in Python. But it not always was relative to the current user home dir. It could be relative to the project root dir, or relative to the root of all projects, using different sympols instead of a tilda. And it could be a URI, not a path. The behavior also evolved with time. So a single function in the stdlib would not help at all.
Hmm, support for other users would indeed be nice… but
It opens that can of worms with the ambiguities.
But one could just add a flag that allows to control the behaviour like:
return pathname unchanged
prefer current user
prefer user out of list and return unchanged otherwise
I guess it makes unexpansion much more complicated, cause effectively one needs to go through the whole user DB, which I guess can easily become quite expensive. And if one wants the above control for the ambiguities, one really needs to go completely through it, and can’t stop on the first match.
I mean if you have some university computer with thousands of usernames (via sss or is nis still alive )
So perhaps this should then only be done if some flag is given.
But with that argument you could also throw out expanduser. Plus, none of these alternative usages is by any means standardised (of course ~ is in principle also only defined for POSIX Shell Command Language, but it’s still any idiom that is widely recognised and supported).
That said, it’s not that I’d desperately want such function in the stdlib
Personally, I wouldn’t want unexpanduser("/bin/sh") to return "~proxy/sh" just because my /etc/passwd happens to have a proxy system user with the home directory set to /bin. (There are three users with that home directory – bin, sync, and proxy – on my Ubuntu 24.04 system, and I think this extends to any Debian derivative. There are others with / as the home directory, and again I don’t want every absolute path like /usr/bin/python3 unexpanded to e.g. ~systemd-network/usr/bin/python3.)
Meanwhile a pathlib.Path.unexpand() that just handles the current user would be useful. I think my every script that prints paths has grown a