Python Forum
PurePath.relative_to() fails in 3.6
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
PurePath.relative_to() fails in 3.6
#1
i was trying to eliminate some messy code that was making some relative symlinks by using PurePath.relative_to() to create the relative path. but it is failing with ValueError. the documentation says this exception happens when the relative path is impossible. it gives an example of this exception which my messy code would have created a valid relative path. that example does:
    p = PurePosixPath('/etc/passwd')
    p.relative_to('/usr')
it describes this as a failure case. a valid relative path is '../etc/passwd' but they don't get it. here is test output i get:
Output:
>>> import pathlib >>> p = pathlib.PurePosixPath('/etc/passwd') >>> p.relative_to('/usr') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.6/pathlib.py", line 874, in relative_to .format(str(self), str(formatted))) ValueError: '/etc/passwd' does not start with '/usr' >>>
it should give '../etc/passwd'.

it looks like this method is only intend for "inline" relative paths, not "common parent" relative paths. it can't even do "inline reverse" relative paths:
Output:
>>> p.relative_to('/etc/foo/bar') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.6/pathlib.py", line 874, in relative_to .format(str(self), str(formatted))) ValueError: '/etc/passwd' does not start with '/etc/foo/bar' >>>
it should give '../../passwd'.

is the a real relative path function/method around somewhere?
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#2
I know this. This is s****

You have to use os.path.relpath.

Type 'copyright', 'credits' or 'license' for more information
IPython 7.8.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import os                                                                                                                       

In [2]: os.path.relpath('/home/andre/test', '/etc/systemd/system/')                                                                     
Out[2]: '../../../home/andre/test'

In [3]:                                                                                                                                 

In [3]: os.path.relpath('/etc/systemd/system/', '/home/andre/test')                                                                     
Out[3]: '../../../etc/systemd/system'

In [4]: os.path.relpath?                                                                                                                
Signature: os.path.relpath(path, start=None)
Docstring: Return a relative version of a path
File:      ~/.pyenv/versions/3.7.4/lib/python3.7/posixpath.py
Type:      function

In [5]:  
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#3
I developed a stable path assignment a few years back.
The following file is always kept in the topmost source directory of a project, and symbolic links
are created in sub-source directories that point to this file.
Every module in the project imports this pathfile, thus allowing changes to be made in one place and immediately broadcast to every source file in the project.
I have never had an issue with relative files since I started using this method.
An additional benefit is that when a project is copied to a new computer, and I need to create the directory structure (with empty directories), I just run ProjectPaths.py and the directory structure is created
import os
from pathlib import Path

class ProjectPaths():
    '''
    This file contains paths used throughout the system.
    ** WARNING ** -- Never modify this file without keeping a backup.
                     Never modify this file if you're not absolutely sure how it works!
                     After modification: **TEST**, **TEST**, and **TEST AGAIN**
    '''
    def __init__(self):
        # Anchor Base Path -- Assures relative stability
        os.chdir(os.path.abspath(os.path.dirname(__file__)))

        self.homepath = Path('.')
        self.rootpath = self.homepath / '..' / '..'

        self.datapath = self.rootpath / 'data'
        self.datapath.mkdir(exist_ok=True)

# ========================== National ==========================

        self.national_datapath = self.datapath / 'National'
        self.national_datapath.mkdir(exist_ok=True)

        self.natlgeopath = self.national_datapath / 'Geo'
        self.natlgeopath.mkdir(exist_ok=True)

        self.natlhtmlpath = self.national_datapath / 'html'
        self.natlhtmlpath.mkdir(exist_ok=True)

        self.natlbackuppath = self.national_datapath / 'backup'
        self.natlbackuppath.mkdir(exist_ok=True)

        self.natlcsvpath = self.national_datapath / 'csv'
        self.natlcsvpath.mkdir(exist_ok=True)

        self.natldatabasepath = self.national_datapath / 'database'
        self.natldatabasepath.mkdir(exist_ok=True)

        self.natlexcelpath = self.national_datapath / 'excel'
        self.natlexcelpath.mkdir(exist_ok=True)

        self.natlhtmlpath = self.national_datapath / 'html'        
        self.natlhtmlpath.mkdir(exist_ok=True)

        self.natljsonpath = self.national_datapath / 'json'
        self.natljsonpath.mkdir(exist_ok=True)

        self.natllogpath = self.national_datapath / 'logs'
        self.natllogpath.mkdir(exist_ok=True)

        self.natlpdfpath = self.national_datapath / 'pdf'
        self.natlpdfpath.mkdir(exist_ok=True)

        self.natlprettypath = self.national_datapath / 'pretty'
        self.natlprettypath.mkdir(exist_ok=True)

        self.natlreportpath = self.national_datapath / 'reports'
        self.natlreportpath.mkdir(exist_ok=True)

        self.natltextpath = self.national_datapath / 'text'
        self.natltextpath.mkdir(exist_ok = True)

        self.natltmppath = self.national_datapath / 'tmp'
        self.natltmppath.mkdir(exist_ok=True)

if __name__ == '__main__':
    ProjectPaths()
Reply
#4
creating a new set of paths should be straight forward because you start at the project apex, and probably stay right there or descend in destructible units (or at least save the apex path). but when doing system automation, relative paths can often be between two almost random points, like how to access a systems "/etc/passwd" file while in its "/usr/local" directory, when the whole system ha been replicated to /backups/vol6/2019/maxwell/root. given the 2 system paths, you can come up with the correct relative path, easy enough. but teaching it to code that is going to process a sequence of tuples with paths to transition is another whole matter.
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#5
there needs to be added PurePath.relpath() ... or at the very least, a note in the documentation where one might expect to find PurePath.relpath() that os.path.relpath() should be used. there is a note in the documentation for os.path.relpath() that it accepts Path-like object since 3.6, but nothing in documentation for pathlib that would refer readers over to os.path.relpath().
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#6
From docs:
Quote:Note

Although os.path.relpath() and PurePath.relative_to() have some overlapping use-cases, their semantics differ enough to warrant not considering them equivalent.

You can use monkey patching.

from pathlib import Path
from os.path import relpath

Path.relpath = lambda self, start=None: Path(relpath(self, start))

# or define a function
def rp(self, start=None):
    return Path(relpath(self, start))
# Path.relpath = rp

my_path = Path.home() / '.local' / 'bin'
rel = my_path.relpath('/var/log/nginx')
print(my_path)
print(rel)
Output:
/home/deadeye/.local/bin ../../../home/deadeye/.local/bin
Windows:
from pathlib import Path
from os.path import relpath
from os import environ


Path.relpath = lambda self, start=None: Path(relpath(self, start))
 
# or define a function
def rp(self, start=None):
    return Path(relpath(self, start))
# Path.relpath = rp

# Windows has some special Paths stored in environment variables
PROGRAMFILES = Path(environ['PROGRAMFILES'])
LOCALAPPDATA = Path(environ['LOCALAPPDATA'])


rel_prog_path = 'Testprogramm/data'
curdir = PROGRAMFILES / rel_prog_path 
target = LOCALAPPDATA / rel_prog_path 

print(curdir)
print(target)
print(target.relpath(curdir))
Maybe someone can try this on Mac. I don't have Mac.
I guess it's similar to UNIX.
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Path or PurePath Skaperen 2 6,712 Jul-06-2018, 02:06 AM
Last Post: Skaperen
  Path.relative_to() and symlinks Skaperen 0 2,372 Jun-07-2018, 03:19 AM
Last Post: Skaperen
  getting a full path string from a pathlib.PurePath object Skaperen 14 141,347 Mar-24-2018, 03:55 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020