Skip to content

os.path.isabs change in behavior in Python 3.13 on MS-Windows #125283

Closed as not planned

Description

@ikappaki

Bug report

Bug description:

I'm not sure if this is a fix or a regression issue?

os.path.isabs use to return True when a windows path started with /.

In python 3.13

Python 3.13.0 (tags/v3.13.0:60403a5, Oct  7 2024, 09:38:07) [MSC v.1941 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os.path
>>> os.path.isabs("/c:")
False

In python 3.12

Python 3.12.7 (tags/v3.12.7:0b05ead, Oct  1 2024, 03:06:41) [MSC v.1941 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os.path
>>> os.path.isabs("/c:")
True

CPython versions tested on:

3.13

Operating systems tested on:

Windows

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    3.13only security fixes
    on Oct 11, 2024
  2. nineteendo commented on Oct 11, 2024

    @nineteendo
    Contributor

    Works as intended: #113829, maybe a new function could be considered to check if a path is rooted?

  3. ikappaki commented on Oct 11, 2024

    @ikappaki
    Author

    Works as intended: #113829, maybe a new function could be considered to check if a path is rooted?

    This behavior changes how os.path.isabs interacts with urllib.parse.urlparse when dealing with absolute paths in Windows URIs. In Python 3.12, paths parsed from local file URIs (drive and network paths) were recognized as absolute, but in Python 3.13, they no longer are

    import os.path
    import pathlib
    import urllib.parse
    
    >>> urllib.parse.urlparse(pathlib.Path('c:\\xyz').as_uri())
    ParseResult(scheme='file', netloc='', path='/c:/xyz', params='', query='', fragment='')
    >>> urllib.parse.urlparse(pathlib.Path('\\\\machine\\xyz').as_uri())
    ParseResult(scheme='file', netloc='machine', path='/xyz/', params='', query='', fragment='')

    In Python 3.12, os.path.isabs would correctly identify these paths as absolute, including network paths:

    >>> os.path.isabs(urllib.parse.urlparse(pathlib.Path('c:\\xyz').as_uri()).path)
    True
    >>> os.path.isabs(urllib.parse.urlparse(pathlib.Path('\\\\machine\\xyz').as_uri()).path)
    True

    but not any more in 3.13

    >>> os.path.isabs(urllib.parse.urlparse(pathlib.Path('c:\\xyz').as_uri()).path)
    False
    >>> os.path.isabs(urllib.parse.urlparse(pathlib.Path('\\\\machine\\xyz').as_uri()).path)
    False

    It seems as if os.path.isabs was never intended to be used with ParseResult paths. I suppose a more appropriate solution would be to directly check if the path starts with '/', which would indicate that it's absolute.

  4. nineteendo commented on Oct 11, 2024

    @nineteendo
    Contributor

    You can use pathlib.Path.from_uri() starting from 3.13:

    >>> import pathlib
    >>> pathlib.Path.from_uri(pathlib.Path('c:/xyz').as_uri())
    WindowsPath('c:/xyz')
    >>> pathlib.Path.from_uri(pathlib.Path('//server/drive/xyz').as_uri())
    WindowsPath('//server/drive/xyz')

    This doesn't roundtrip:

    >>> import pathlib
    >>> import urllib.parse
    >>> pathlib.Path(urllib.parse.urlparse(pathlib.Path('c:/xyz').as_uri()).path)
    WindowsPath('/c:/xyz')
    >>> pathlib.Path(urllib.parse.urlparse(pathlib.Path('//server/drive/xyz').as_uri()).path)
    WindowsPath('/drive/xyz')

    Also, aren't uri's always absolute?

  5. marc-hb commented on Oct 11, 2024

    @marc-hb

    Also, aren't uri's always absolute?

    Relative URLs work perfectly fine.

    EDIT: I meant in general in life, not related to Python.

  6. nineteendo commented on Oct 11, 2024

    @nineteendo
    Contributor

    Well, at least pathlib doesn't allow creating them.

  7. barneygale commented on Oct 11, 2024

    @barneygale
    Contributor

    It seems as if os.path.isabs was never intended to be used with ParseResult paths. I suppose a more appropriate solution would be to directly check if the path starts with '/', which would indicate that it's absolute.

    Checking whether it starts with a / makes most sense to me. Another option is posixpath.isabs().

  8. nineteendo commented on Oct 11, 2024

    @nineteendo
    Contributor

    Use p.scheme and p.netloc and p.path.startswith("/") to check if an uri is absolute, an absolute path doesn't change when joined with another path:

    >>> import urllib.parse
    >>> urllib.parse.urljoin("file://netloc/foo/", "bar")
    'file://netloc/foo/bar'
    >>> urllib.parse.urljoin("file://netloc/foo/", "/bar")
    'file://netloc/bar'
    >>> urllib.parse.urljoin("file://netloc/foo/", "//netloc2/bar")
    'file://netloc2/bar'
    >>> urllib.parse.urljoin("file://netloc/foo/", "file://netloc2/bar")
    'file://netloc2/bar'

    That's also why we changed the behaviour of os.path.isabs():

    >>> import os.path
    >>> os.path.join(r"c:\foo", "bar")
    'c:\\foo\\bar'
    >>> os.path.join(r"c:\foo", r"\bar")
    'c:\\bar'
    >>> os.path.join(r"c:\foo", r"d:\bar")
    'd:\\bar'
  9. nineteendo commented on Oct 11, 2024

    @nineteendo
    Contributor

    I think this can be closed.

  10. barneygale commented on Oct 11, 2024

    @barneygale
    Contributor

    Thanks @nineteendo! Closing.

  11. kayychou commented on Dec 3, 2024

    @kayychou

    Works as intended: #113829, maybe a new function could be considered to check if a path is rooted?

    hi @nineteendo, @barneygale,

    the doc updated in #113829 still indicates the following behavior:

    Return ``True`` if *path* is an absolute pathname.  On Unix, that means it
    begins with a slash, on Windows that it begins with two (back)slashes, or a
    drive letter, colon, and (back)slash together.
    

    whereas the intended behavior, as illustrated in Lib/test/test_ntpath.py, appears to be now :

    >>> os.path.isabs("/foo/bar") # path beginning with a slash on windows
    False
    >>> os.path.isabs("\\foo\\bar") # path beginning with two backslashes on windows
    False
    

    maybe I'm missing something under the hood

    Thanks for your help!

  12. barneygale commented on Dec 3, 2024

    @barneygale
    Contributor

    Hey @kayychou - "\\foo\\bar" expresses a string that begins with a single backslash. If you want two backslashes, you could use a raw string (r"\\foo\bar") or double the backslashes ("\\\\foo\\bar")

    For more info: https://docs.python.org/3/reference/lexical_analysis.html#escape-sequences

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.13only security fixesOS-windowsstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions