Skip to content

Improve file URI ergonomics in urllib.request #125866

Description

@barneygale

Feature or enhancement

I request that we make pathname2url and url2pathname easier to use:

  • pathname2url() is made to accept an optional include_scheme argument that sticks file: on the front when true
  • url2pathname() is made to strip any file: prefix from its argument.

I think this would go a long way towards making these functions usable, and allow us to remove the scary "This does not accept/produce a complete URL" warnings from the docs.

Linked PRs

Activity

  1. added 4 commits that reference this issue on Oct 25, 2024
  2. added 2 commits that reference this issue on Oct 29, 2024
  3. added 3 commits that reference this issue on Oct 29, 2024
  4. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 30, 2024
  5. added 3 commits that reference this issue on Nov 19, 2024
  6. 7 remaining items

  7. barneygale commented on Nov 25, 2024

    @barneygale
    ContributorAuthor

    @serhiy-storchaka I've revised the description as follows:

    I request that we make pathname2url and url2pathname easier to use:

    • pathname2url() is made to accept an optional include_scheme argument that sticks file: on the front when true
    • url2pathname() is made to strip any file: prefix from its argument.

    Does that seem like a good idea to you?

    If we go for these changes, and the user provides a URL with a non-file: scheme to url2pathname(), what should happen in your opinion? (I'm erring towards raising URLError.)

    Relatedly, what do you think should happen if the user provides a query or fragment in the URL? Currently it's returned as part of the file path. (This seems under-specified to me, but I'm leaning towards raising URLError here too.)

    Thank you

  8. serhiy-storchaka commented on Dec 2, 2024

    @serhiy-storchaka
    Member

    I wonder -- would not be better to add such functions in os.path? The implementation is clearly platform depending, and if we add support for platform with different path format (like old macpath), it will need a completely different implementation. In custom build for exotic platform it is easier to provide a separate os.path implementation than patch urllib.request.

  9. barneygale commented on Dec 3, 2024

    @barneygale
    ContributorAuthor

    Good question! It's something that's come up before:

    The current Windows + POSIX implementations don't have much in common, but I think that will change when I address this issue and #123599. To solve them, I'm planning to use urlsplit() to parse the URL, replacing parts of the janky implementation in url2pathname(). Here's a rough draft of where I want to end up:

    def url2pathname(url):
        scheme, authority, path, query, fragment = urlsplit(url, scheme='file')
        if scheme != 'file':
            raise URLError(f'URL uses non-file scheme: {url!r}')
        if query:
            raise URLError(f'file URL has query: {url!r}')
        if fragment:
            raise URLError(f'file URL has fragment: {url!r}')
    
        if os.name == 'nt':
            # Windows-specific file URI quirks.
            if authority and authority != 'localhost':
                # e.g. file://server/share/file.txt
                path = '//' + authority + path
            elif path[:3] == '///':
                # e.g. file://///server/share/file.txt
                path = path[1:]
            else:
                if path[:1] == '/' and path[2:3] in ':|':
                    # Skip past extra slash before DOS drive in URL path.
                    path = path[1:]
                if path[1:2] == '|':
                    # Older URLs use a pipe after a drive letter
                    path = path.replace('|', ':', 1)
            path = path.replace('/', '\\')
        elif not _is_local_authority(authority):
            # POSIX only: reject URL if authority doesn't resolve to localhost.
            raise URLError(f'file URL has non-local authority: {url!r}')
    
        encoding = sys.getfilesystemencoding()
        errors = sys.getfilesystemencodeerrors()
        return unquote(path, encoding=encoding, errors=errors)
    
    
    def pathname2url(pathname, include_scheme=False):
        if os.name == 'nt':
            pathname = pathname.replace('\\', '/')
        encoding = sys.getfilesystemencoding()
        errors = sys.getfilesystemencodeerrors()
        prefix = 'file:' if include_scheme else ''
        drive, root, tail = os.path.splitroot(pathname)
    
        if drive:
            if drive[:4] == '//?/':
                drive = drive[4:]
                if drive[:4].upper() == 'UNC/':
                    drive = '//' + drive[4:]
            if drive[1:] == ':':
                prefix += '///'
            drive = quote(drive, encoding=encoding, errors=errors, safe='/:')
        elif root:
            prefix += '//'
    
        tail = quote(tail, encoding=encoding, errors=errors)
        return prefix + drive + root + tail

    There's clearly some OS-specific code in there, but there's also a fair amount of shared and URL-specific code which might feel out-of-place in os.path.

    Besides, if we added functions in os.path we'd need to either live with near-duplicate functionality in urllib, or deprecate the urllib functions (which isn't much fun for their users). I'm not ruling that out, but it seems unnecessarily if there's a reasonable path towards making pathname2url() and url2pathname() a bit more usable.

  10. barneygale commented on Dec 3, 2024

    @barneygale
    ContributorAuthor

    FWIW, if we go with the above merger, then we can deprecate the oddball nturl2path module.

  11. added a commit that references this issue on Dec 8, 2024
  12. added 2 commits that reference this issue on Jan 12, 2025
  13. added 3 commits that reference this issue on Mar 18, 2025
  14. added 4 commits that reference this issue on Apr 10, 2025
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

    stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions