Подсистема, которую я не могу контролировать, настаивает на предоставлении путей файловой системы в виде uri. Есть ли модуль/функция python, которая может преобразовать этот путь в соответствующую форму, ожидаемую файловой системой, независимо от платформы?
Есть ли удобный способ сопоставить файл uri с os.path?
Ответы (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))
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'
urllib.parse.unquote_plus
, который похож на unquote()
, но также замените плюсики пробелами.
- person Boris; 11.12.2019
unquote(urlparse('file:///C:/Program Files/Steam/').path)
-> '/C:/Program Files/Steam/'
- person Iwan Aucamp; 12.12.2019
Из всех ответов до сих пор я не нашел ни одного, который бы улавливал крайние случаи, не требовал ветвления, был бы на 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
url2pathname
использует unquote
поэтому url2pathname(parsed.path)
должно быть достаточно
- person dshanahan; 05.01.2021
foo%20bar.baz
будет правильно закодировано вашим решением для foo%2520bar.baz
, но неправильно декодировано до foo bar.baz
. Это происходит из-за непревзойденного unquote
внутри url2pathname
, как указал @dshanahan.
- person Vader B; 29.05.2021
Чтобы преобразовать файл uri в путь с помощью python (конкретно для 3, я могу сделать для python 2, если кто-то действительно этого хочет):
- Разобрать uri с помощью
urllib.parse.urlparse
Уберите из кавычек компонент пути проанализированного uri с помощью
urllib.parse.unquote
тогда ...
а. Если путь является путем 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)< /а> лицензия
Настоящим дается разрешение на использование, копирование, изменение и/или распространение этого программного обеспечения для любых целей за плату или бесплатно.
ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», И АВТОР ОТКАЗЫВАЕТСЯ ОТ ВСЕХ ГАРАНТИЙ В ОТНОШЕНИИ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ВКЛЮЧАЯ ВСЕ ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ КОММЕРЧЕСКОЙ ПРИГОДНОСТИ И ПРИГОДНОСТИ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ АВТОР НЕ НЕСЕТ ОТВЕТСТВЕННОСТИ ЗА ЛЮБОЙ ОСОБЫЙ, ПРЯМОЙ, КОСВЕННЫЙ ИЛИ КОСВЕННЫЙ УЩЕРБ ИЛИ ЛЮБОЙ УЩЕРБ, ВОЗНИКШИЙ В РЕЗУЛЬТАТЕ ПОТЕРИ ИСПОЛЬЗОВАНИЯ, ДАННЫХ ИЛИ ПРИБЫЛИ, БУДЬ ТО В ДЕЙСТВИИ ДОГОВОРА, НЕБРЕЖНОСТИ ИЛИ ДРУГОМ СЛУЧАЙНОМ ДЕЙСТВИИ, ВОЗНИКШЕМОМ ИЗ ИЛИ В СВЯЗИ С ИСПОЛЬЗОВАНИЕМ ИЛИ ЭКСПЛУАТАЦИЕЙ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ.
Насколько это возможно по закону, Иван Аукамп отказался от всех авторских и смежных прав на этот вклад в обмен стеками. Эта работа опубликована из: Норвегия.
Решение от @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'