Есть ли удобный способ сопоставить файл uri с os.path?

Подсистема, которую я не могу контролировать, настаивает на предоставлении путей файловой системы в виде uri. Есть ли модуль/функция python, которая может преобразовать этот путь в соответствующую форму, ожидаемую файловой системой, независимо от платформы?


person Gearoid Murphy    schedule 12.05.2011    source источник
comment
Собираетесь ли вы делать больше, чем просто читать из него?   -  person Ignacio Vazquez-Abrams    schedule 12.05.2011
comment
Нет, я хочу передать этот uri или эквивалентную форму в модули Python для манипулирования путями.   -  person Gearoid Murphy    schedule 12.05.2011


Ответы (5)


Используйте urllib.parse.urlparse, чтобы получить путь из URI:

import os
from urllib.parse import urlparse
p = urlparse('file://C:/test/doc.txt')
final_path = os.path.abspath(os.path.join(p.netloc, p.path))
person Jakob Bowyer    schedule 12.05.2011
comment
@JakobBowyer - .path во второй строке должен быть удален, иначе вы просто возвращаете строку в переменную p вместо кортежа, который вам нужно обработать в третьей строке. - person fossfreedom; 26.11.2012
comment
Допустимым URI файла для C:\test\doc.txt является file:///C:/test/doc.txt, а не file://C:/test/doc.txt — см. IETF RFC 8089: Схема URI файла / 2. Синтаксис и запустите его в последнем python 3 import pathlib; print(pathlib.PureWindowsPath("C:\\test\\doc.txt").as_uri()), поэтому этот ответ не точен. - person Iwan Aucamp; 12.08.2019

Решение @Jakob Bowyer не преобразует закодированные символы URL в обычные символы UTF-8. . Для этого вам нужно использовать urllib.parse.unquote.

>>> from urllib.parse import unquote, urlparse
>>> unquote(urlparse('file:///home/user/some%20file.txt').path)
'/home/user/some file.txt'
person colton7909    schedule 24.02.2013
comment
@IwanAucamp, можете ли вы объяснить, почему? - person Boris; 11.12.2019
comment
Попробуйте использовать urllib.parse.unquote_plus, который похож на unquote(), но также замените плюсики пробелами. - person Boris; 11.12.2019
comment
@Boris, потому что путь, возвращаемый для URI файлов Windows, начинается с косой черты unquote(urlparse('file:///C:/Program Files/Steam/').path) -> '/C:/Program Files/Steam/' - person Iwan Aucamp; 12.12.2019
comment
Идеально подходит для моей среды Linux - person fsevenm; 03.08.2020

Из всех ответов до сих пор я не нашел ни одного, который бы улавливал крайние случаи, не требовал ветвления, был бы на 2/3 совместим, и кроссплатформенным.

Короче говоря, это делает работу, используя только встроенные функции:

try:
    from urllib.parse import urlparse, unquote
    from urllib.request import url2pathname
except ImportError:
    # backwards compatability
    from urlparse import urlparse
    from urllib import unquote, url2pathname


def uri_to_path(uri):
    parsed = urlparse(uri)
    host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc)
    return os.path.normpath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )

Сложный момент (я обнаружил) был при работе в Windows с путями, указывающими хост. Это не проблема за пределами Windows: доступ к сетевым расположениям в *NIX возможен только по путям после монтирования в корень файловой системы.

Из Википедии: URI файла принимает форму file://host/path , где host – это полное доменное имя система, в которой путь доступен [...]. Если host не указан, он считается «localhost».

Имея это в виду, я взял за правило ВСЕГДА добавлять к пути префикс netloc, предоставленный urlparse, прежде чем передать его os.path.abspath, что необходимо, так как оно удаляет все результирующие лишние косые черты (os.path.normpath, которые также претендует на исправление косых черт, в Windows может быть немного переусердствовать, отсюда и использование abspath).

Другим важным компонентом преобразования является использование unquote для экранирования/декодирования процентного кодирования URL, которое иначе ваша файловая система не поймет. Опять же, это может быть более серьезной проблемой в Windows, которая допускает такие вещи, как $ и пробелы в путях, которые будут закодированы в URI файла.

Для демонстрации:

import os
from pathlib import Path   # This demo requires pip install for Python < 3.4
import sys
try:
    from urllib.parse import urlparse, unquote
    from urllib.request import url2pathname
except ImportError:  # backwards compatability:
    from urlparse import urlparse
    from urllib import unquote, url2pathname

DIVIDER = "-" * 30

if sys.platform == "win32":  # WINDOWS
    filepaths = [
        r"C:\Python27\Scripts\pip.exe",
        r"C:\yikes\paths with spaces.txt",
        r"\\localhost\c$\WINDOWS\clock.avi",
        r"\\networkstorage\homes\rdekleer",
    ]
else:  # *NIX
    filepaths = [
        os.path.expanduser("~/.profile"),
        "/usr/share/python3/py3versions.py",
    ]

for path in filepaths:
    uri = Path(path).as_uri()
    parsed = urlparse(uri)
    host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc)
    normpath = os.path.normpath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )
    absolutized = os.path.abspath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )
    result = ("{DIVIDER}"
              "\norig path:       \t{path}"
              "\nconverted to URI:\t{uri}"
              "\nrebuilt normpath:\t{normpath}"
              "\nrebuilt abspath:\t{absolutized}").format(**locals())
    print(result)
    assert path == absolutized

Результаты (WINDOWS):

------------------------------
orig path:              C:\Python27\Scripts\pip.exe
converted to URI:       file:///C:/Python27/Scripts/pip.exe
rebuilt normpath:       C:\Python27\Scripts\pip.exe
rebuilt abspath:        C:\Python27\Scripts\pip.exe
------------------------------
orig path:              C:\yikes\paths with spaces.txt
converted to URI:       file:///C:/yikes/paths%20with%20spaces.txt
rebuilt normpath:       C:\yikes\paths with spaces.txt
rebuilt abspath:        C:\yikes\paths with spaces.txt
------------------------------
orig path:              \\localhost\c$\WINDOWS\clock.avi
converted to URI:       file://localhost/c%24/WINDOWS/clock.avi
rebuilt normpath:       \localhost\c$\WINDOWS\clock.avi
rebuilt abspath:        \\localhost\c$\WINDOWS\clock.avi
------------------------------
orig path:              \\networkstorage\homes\rdekleer
converted to URI:       file://networkstorage/homes/rdekleer
rebuilt normpath:       \networkstorage\homes\rdekleer
rebuilt abspath:        \\networkstorage\homes\rdekleer

Результаты (*NIX):

------------------------------
orig path:              /home/rdekleer/.profile
converted to URI:       file:///home/rdekleer/.profile
rebuilt normpath:       /home/rdekleer/.profile
rebuilt abspath:        /home/rdekleer/.profile
------------------------------
orig path:              /usr/share/python3/py3versions.py
converted to URI:       file:///usr/share/python3/py3versions.py
rebuilt normpath:       /usr/share/python3/py3versions.py
rebuilt abspath:        /usr/share/python3/py3versions.py
person Ryan de Kleer    schedule 20.05.2020
comment
согласно документации url2pathname использует unquote поэтому url2pathname(parsed.path) должно быть достаточно - person dshanahan; 05.01.2021
comment
Ваше решение не работает, когда закодированное имя пути включает символы, подобные urlencode. Например. имя файла foo%20bar.baz будет правильно закодировано вашим решением для foo%2520bar.baz, но неправильно декодировано до foo bar.baz. Это происходит из-за непревзойденного unquote внутри url2pathname, как указал @dshanahan. - person Vader B; 29.05.2021

Чтобы преобразовать файл uri в путь с помощью python (конкретно для 3, я могу сделать для python 2, если кто-то действительно этого хочет):

  1. Разобрать uri с помощью urllib.parse.urlparse
  2. Уберите из кавычек компонент пути проанализированного uri с помощью urllib.parse.unquote

  3. тогда ...

    а. Если путь является путем Windows и начинается с /: удалите первый символ компонента пути без кавычек (компонент пути file:///C:/some/file.txt равен /C:/some/file.txt, который не интерпретируется как эквивалентный C:\some\file.txt по pathlib.PureWindowsPath)

    б. В противном случае просто используйте компонент пути без кавычек как есть.

Вот функция, которая делает это:

import urllib
import pathlib

def file_uri_to_path(file_uri, path_class=pathlib.PurePath):
    """
    This function returns a pathlib.PurePath object for the supplied file URI.

    :param str file_uri: The file URI ...
    :param class path_class: The type of path in the file_uri. By default it uses
        the system specific path pathlib.PurePath, to force a specific type of path
        pass pathlib.PureWindowsPath or pathlib.PurePosixPath
    :returns: the pathlib.PurePath object
    :rtype: pathlib.PurePath
    """
    windows_path = isinstance(path_class(),pathlib.PureWindowsPath)
    file_uri_parsed = urllib.parse.urlparse(file_uri)
    file_uri_path_unquoted = urllib.parse.unquote(file_uri_parsed.path)
    if windows_path and file_uri_path_unquoted.startswith("/"):
        result = path_class(file_uri_path_unquoted[1:])
    else:
        result = path_class(file_uri_path_unquoted)
    if result.is_absolute() == False:
        raise ValueError("Invalid file uri {} : resulting path {} not absolute".format(
            file_uri, result))
    return result

Примеры использования (работает в Linux):

>>> file_uri_to_path("file:///etc/hosts")
PurePosixPath('/etc/hosts')

>>> file_uri_to_path("file:///etc/hosts", pathlib.PurePosixPath)
PurePosixPath('/etc/hosts')

>>> file_uri_to_path("file:///C:/Program Files/Steam/", pathlib.PureWindowsPath)
PureWindowsPath('C:/Program Files/Steam')

>>> file_uri_to_path("file:/proc/cpuinfo", pathlib.PurePosixPath)
PurePosixPath('/proc/cpuinfo')

>>> file_uri_to_path("file:c:/system32/etc/hosts", pathlib.PureWindowsPath)
PureWindowsPath('c:/system32/etc/hosts')

Эта функция работает с URI файлов Windows и posix и будет обрабатывать URI файлов без раздела полномочий. Однако он НЕ будет проверять полномочия URI, поэтому это не будет соблюдаться:

IETF RFC 8089: Схема URI "файл" / 2. Синтаксис

«Хост» — это полное доменное имя системы, в которой доступен файл. Это позволяет клиенту в другой системе знать, что он не может получить доступ к файловой системе или, возможно, что ему нужно использовать какой-то другой локальный механизм для доступа к файлу.

Проверка (pytest) для функции:

import os
import pytest

def validate(file_uri, expected_windows_path, expected_posix_path):
    if expected_windows_path is not None:
        expected_windows_path_object = pathlib.PureWindowsPath(expected_windows_path)
    if expected_posix_path is not None:
        expected_posix_path_object = pathlib.PurePosixPath(expected_posix_path)

    if expected_windows_path is not None:
        if os.name == "nt":
            assert file_uri_to_path(file_uri) == expected_windows_path_object
        assert file_uri_to_path(file_uri, pathlib.PureWindowsPath) == expected_windows_path_object

    if expected_posix_path is not None:
        if os.name != "nt":
            assert file_uri_to_path(file_uri) == expected_posix_path_object
        assert file_uri_to_path(file_uri, pathlib.PurePosixPath) == expected_posix_path_object


def test_some_paths():
    validate(pathlib.PureWindowsPath(r"C:\Windows\System32\Drivers\etc\hosts").as_uri(),
        expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
        expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")

    validate(pathlib.PurePosixPath(r"/C:/Windows/System32/Drivers/etc/hosts").as_uri(),
        expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
        expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")

    validate(pathlib.PureWindowsPath(r"C:\some dir\some file").as_uri(),
        expected_windows_path=r"C:\some dir\some file",
        expected_posix_path=r"/C:/some dir/some file")

    validate(pathlib.PurePosixPath(r"/C:/some dir/some file").as_uri(),
        expected_windows_path=r"C:\some dir\some file",
        expected_posix_path=r"/C:/some dir/some file")

def test_invalid_url():
    with pytest.raises(ValueError) as excinfo:
        validate(r"file://C:/test/doc.txt",
            expected_windows_path=r"test\doc.txt",
            expected_posix_path=r"/test/doc.txt")
        assert "is not absolute" in str(excinfo.value)

def test_escaped():
    validate(r"file:///home/user/some%20file.txt",
        expected_windows_path=None,
        expected_posix_path=r"/home/user/some file.txt")
    validate(r"file:///C:/some%20dir/some%20file.txt",
        expected_windows_path="C:\some dir\some file.txt",
        expected_posix_path=r"/C:/some dir/some file.txt")

def test_no_authority():
    validate(r"file:c:/path/to/file",
        expected_windows_path=r"c:\path\to\file",
        expected_posix_path=None)
    validate(r"file:/path/to/file",
        expected_windows_path=None,
        expected_posix_path=r"/path/to/file")

Этот вклад распространяется под лицензией (в дополнение к любым другим лицензиям, которые могут применяться) в соответствии с Лицензией BSD с нулевым пунктом (0BSD)< /а> лицензия

Настоящим дается разрешение на использование, копирование, изменение и/или распространение этого программного обеспечения для любых целей за плату или бесплатно.

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», И АВТОР ОТКАЗЫВАЕТСЯ ОТ ВСЕХ ГАРАНТИЙ В ОТНОШЕНИИ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ВКЛЮЧАЯ ВСЕ ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ КОММЕРЧЕСКОЙ ПРИГОДНОСТИ И ПРИГОДНОСТИ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ АВТОР НЕ НЕСЕТ ОТВЕТСТВЕННОСТИ ЗА ЛЮБОЙ ОСОБЫЙ, ПРЯМОЙ, КОСВЕННЫЙ ИЛИ КОСВЕННЫЙ УЩЕРБ ИЛИ ЛЮБОЙ УЩЕРБ, ВОЗНИКШИЙ В РЕЗУЛЬТАТЕ ПОТЕРИ ИСПОЛЬЗОВАНИЯ, ДАННЫХ ИЛИ ПРИБЫЛИ, БУДЬ ТО В ДЕЙСТВИИ ДОГОВОРА, НЕБРЕЖНОСТИ ИЛИ ДРУГОМ СЛУЧАЙНОМ ДЕЙСТВИИ, ВОЗНИКШЕМОМ ИЗ ИЛИ В СВЯЗИ С ИСПОЛЬЗОВАНИЕМ ИЛИ ЭКСПЛУАТАЦИЕЙ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ.


Общественное достояние

Насколько это возможно по закону, Иван Аукамп отказался от всех авторских и смежных прав на этот вклад в обмен стеками. Эта работа опубликована из: Норвегия.

person Iwan Aucamp    schedule 12.08.2019

Решение от @colton7909 в основном правильное и помогло мне получить этот ответ, но имеет некоторые ошибки импорта с Python 3. Это, и я думаю, что это лучший способ справиться с частью 'file://' URL-адреса, чем просто отрезать первые 7 персонажи. Поэтому я считаю, что это самый идиоматический способ сделать это с помощью стандартной библиотеки:

import urllib.parse
url_data = urllib.parse.urlparse('file:///home/user/some%20file.txt')
path = urllib.parse.unquote(url_data.path)

Этот пример должен создать строку '/home/user/some file.txt'

person Mike    schedule 04.05.2019