Skip to content

Support preserving path meaning in os.path.normpath() and abspath() #88482

Description

@barneygale
mannequin
BPO 44316
Nosy @terryjreedy, @eryksun, @barneygale
PRs
  • bpo-44316: add keep_curdir and keep_pardir arguments to os.path.normpath() #26694
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2021-06-04.21:21:19.230>
    labels = ['type-feature', 'library', '3.11']
    title = 'Support preserving path meaning in os.path.normpath() and abspath()'
    updated_at = <Date 2021-06-14.03:35:12.692>
    user = 'https://github.com/barneygale'

    bugs.python.org fields:

    activity = <Date 2021-06-14.03:35:12.692>
    actor = 'eryksun'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2021-06-04.21:21:19.230>
    creator = 'barneygale'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 44316
    keywords = ['patch']
    message_count = 7.0
    messages = ['395122', '395672', '395697', '395698', '395714', '395715', '395770']
    nosy_count = 3.0
    nosy_names = ['terry.reedy', 'eryksun', 'barneygale']
    pr_nums = ['26694']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue44316'
    versions = ['Python 3.11']

    Activity

    1. barneygale commented on Jun 4, 2021

      barneygalemannequin
      MannequinAuthor
      >>> os.path.normpath('a/./b/../c//.')
      'a/c'
      >>> pathlib.Path('a/./b/../c//.')
      PosixPath('a/b/../c')

      pathlib takes care not to change the meaning of the path when normalising. That means preserving '..' entries, as these can't be simplified without resolving symlinks etc.

      normpath(), on the other handle, /always/ eliminates '..' entries, which can change the meaning of the path.

      We could add a new argument to normpath() and abspath() that leaves '..' entries intact. This was closed as "won't fix" back in bpo-2289, but I think it's worth re-considering.

      This enhancement would be helpful for my longer-term work to make pathlib an OOP wrapper of os + os.path, rather than a parallel implementation.

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancement
      on Jun 4, 2021
    3. terryjreedy commented on Jun 12, 2021

      @terryjreedy
      Member

      I think you should propose this for discussion on python-ideas list to try for more support. If you can, say more about why reconsider.

    4. barneygale commented on Jun 12, 2021

      barneygalemannequin
      MannequinAuthor

      Thanks Terry, I've added a topic here: https://discuss.python.org/t/pathlib-and-os-path-code-duplication-and-feature-parity/9239

      The bit about normpath() is towards the middle of the post.

    5. barneygale commented on Jun 12, 2021

      barneygalemannequin
      MannequinAuthor

      For this bug specifically, the pathlib docs describe the desirable behaviour:

      <quote>

      Spurious slashes and single dots are collapsed, but double dots ('..') are not, since this would change the meaning of a path in the face of symbolic links:

      >>> PurePath('foo//bar')
      PurePosixPath('foo/bar')
      >>> PurePath('foo/./bar')
      PurePosixPath('foo/bar')
      >>> PurePath('foo/../bar')
      PurePosixPath('foo/../bar')

      (a naïve approach would make PurePosixPath('foo/../bar') equivalent to PurePosixPath('bar'), which is wrong if foo is a symbolic link to another directory)

      </quote>

    6. eryksun commented on Jun 12, 2021

      @eryksun
      Contributor

      single dots are collapsed

      For pathlib, I've previously discussed a desire to retain a leading dot component from the initializing path. This could be implemented in strict mode for normpath().

      A leading dot is significant in the path of an executable in a search context, such as the first item in the args sequence of subprocess.Popen(). For example, if "./spam" is normalized as "spam", then the system will search the PATH directories instead of the current working directory. It's also import to note that in Windows a leading dot component is required in order to preclude searching even if the path contains slashes. For example, CreateProcessW() and SearchPathW() will try to resolve r"spam\eggs.exe" against every directory in the search path, whereas r".\spam\eggs.exe" is explicitly relative to just the current working directory.

      Retaining a leading dot component can also be important in Windows in order to disambiguate a drive-relative path from a named data stream of a file that has a single-letter filename. For example, "C:spam" is a file named "spam" in the working path on drive "C:" (e.g. "C:spam" -> r"C:\working\path\spam"), but r".\C:spam" is a data stream named "spam" in a file named "C" in the current working directory.

    7. barneygale commented on Jun 12, 2021

      barneygalemannequin
      MannequinAuthor

      I think I agree

      How would you feel about two new arguments? Following os.curdir and os.pardir names:

      def normpath(path, *, keep_curdir=False, keep_pardir=False)
          ...
    8. eryksun commented on Jun 14, 2021

      @eryksun
      Contributor

      I think separate keep_curdir and keep_pardir options is over-complicating the signature. Also, I'd prefer to remove a dot component if it's not the first component since there's no reason to keep it.

      If you plan to use normpath() in pathlib, then the case for special_prefixes in ntpath.normpath() should be removed. Actually, it never should have been added. IIRC it was added as a workaround for a buggy implementation that's no longer an issue. Only \\?\ is special, and that's only when opening/accessing a path. It's not special in GetFullPathNameW(), as is called by ntpath.abspath() in Windows. This needlessly introduces inconsistency for ntpath.abspath() calls in Windows vs Unix.

    9. transferred this issue fromon Apr 10, 2022
    10. barneygale commented on Jan 13, 2024

      @barneygale
      ContributorAuthor

      It's reasonable/safe for abspath() to eliminate leading ../ segments from the given path, because os.getcwd() is guaranteed to return a path without symlinks.

      >>> os.getcwd()
      '/home/barney'
      >>> os.path.abspath('../foo')
      '/home/foo'  # ok
      >>> os.path.abspath('foo/..')
      '/home/barney'  # wrong if `foo/` is a symlink
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.11only security fixesstdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

      Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions