Используйте pathlib для путей s3

Я хотел бы создать некоторую функциональность для перемещения файлов между s3 и моей локальной файловой системой, но pathlib, кажется, поглощает повторные слэши, нарушая мою функциональность aws-cli:

from pathlib import Path
p = Path('s3://loc')
str(p)
=> 's3:/loc'

Есть ли способ использовать pathlib для манипулирования путями s3?

8 ответов

Решение

Вы можете попробовать объединить urllib.parse с pathlib.

from urllib.parse import urlparse, urlunparse
from pathlib import PosixPath

s3_url = urlparse('s3://bucket/key')
s3_path = PosixPath(s3_url.path)
s3_path /= 'hello'
s3_new_url = urlunparse((s3_url.scheme, s3_url.netloc, s3_path.as_posix(), s3_url.params, s3_url.query, s3_url.fragment))
print(s3_new_url)

Это довольно громоздко, но это то, что вы просили.

С помощью s3path пакет

В s3path делает работу с путями S3 немного менее болезненной. Его можно установить из PyPI или conda-forge. ИспользоватьS3Path класс для реальных объектов в S3 и в противном случае используйте PureS3Path который на самом деле не должен обращаться к S3.

Хотя в предыдущем ответе metaperture упоминался этот пакет, он не включал синтаксис URI.

Также имейте в виду, что этот пакет имеет определенные важные недостатки, о которых сообщается в его выпусках.

>>> from s3path import PureS3Path

>>> PureS3Path.from_uri('s3://mybucket/foo/bar') / 'add/me'
PureS3Path('/mybucket/foo/bar/add/me')

>>> _.as_uri()
's3://mybucket/foo/bar/add/me'

Обратите внимание на отношения экземпляра к pathlib:

>>> from pathlib import Path, PurePath
>>> from s3path import S3Path, PureS3Path

>>> isinstance(S3Path('/my-bucket/some/prefix'), Path)
True
>>> isinstance(PureS3Path('/my-bucket/some/prefix'), PurePath)
True

С помощью pathlib.Path

Это более ленивая версия ответа Кичика, использующая только pathlib. Этот подход не обязательно рекомендуется. Просто не всегда обязательно использоватьurllib.parse.

>>> from pathlib import Path

>>> orig_s3_path = 's3://mybucket/foo/bar'
>>> orig_path = Path(*Path(orig_s3_path).parts[1:])
>>> orig_path
PosixPath('mybucket/foo/bar')

>>> new_path = orig_path / 'add/me'
>>> new_s3_path = 's3://' + str(new_path)
>>> new_s3_path
's3://mybucket/foo/bar/add/me'

С помощью os.path.join

Только для простых соединений, как насчет os.path.join?

>>> import os

>>> os.path.join('s3://mybucket/foo/bar', 'add/me')
's3://mybucket/foo/bar/add/me'
>>> os.path.join('s3://mybucket/foo/bar/', 'add/me')
's3://mybucket/foo/bar/add/me'

os.path.normpath Однако нельзя наивно использовать:

>>> os.path.normpath('s3://mybucket/foo/bar')  # Converts 's3://' to 's3:/'
's3:/mybucket/foo/bar'

С использованием cloudpathlib

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

В cloupathlibПакет предоставляет для путей S3 в дополнение к облачному хранилищу Google и хранилищу поддержку методов pathlibBLOB-объектов Azure.

Например:

      from cloudpathlib import CloudPath
from itertools import islice

ladi = CloudPath("s3://ladi/Images/FEMA_CAP/2020/70349")

ladi.parent
#> S3Path('s3://ladi/Images/FEMA_CAP/2020')

ladi.bucket
#> 'ladi'

# list first 5 images for this incident
for p in islice(ladi.iterdir(), 5):
    print(p)
#> s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0001_5a63d42e-27c6-448a-84f1-bfc632125b8e.jpg
#> s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0002_a89f1b79-786f-4dac-9dcc-609fb1a977b1.jpg
#> s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0003_02c30af6-911e-4e01-8c24-7644da2b8672.jpg
#> s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0004_d37c02b9-01a8-4672-b06f-2690d70e5e6b.jpg
#> s3://ladi/Images/FEMA_CAP/2020/70349/DSC_0005_d05609ce-1c45-4de3-b0f1-401c2bb3412c.jpg

Вот модуль, который наследует pathlib.Path для путей s3: https://pypi.org/project/s3path/

Применение:

>>> from s3path import S3Path

>>> bucket_path = S3Path('/pypi-proxy/')
>>> [path for path in bucket_path.iterdir() if path.is_dir()]
[S3Path('/pypi-proxy/requests/'),
 S3Path('/pypi-proxy/boto3/'),
 S3Path('/pypi-proxy/botocore/')]

>>> boto3_package_path = S3Path('/pypi-proxy/boto3/boto3-1.4.1.tar.gz')
>>> boto3_package_path.exists()
True
>>> boto3_package_path.is_dir()
False
>>> boto3_package_path.is_file()
True

>>> botocore_index_path = S3Path('/pypi-proxy/botocore/index.html')
>>> with botocore_index_path.open() as f:
>>>     print(f.read())
"""
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Package Index</title>
</head>
<body>
    <a href="botocore-1.4.93.tar.gz">botocore-1.4.93.tar.gz</a><br>
</body>
</html>
"""

Нет. pathlib предназначен для путей файловой системы (то есть путей к файлам на вашем компьютере), в то время как пути S3 являются URI.

Я согласен с ответом @jwodder, что pathlib предназначен только для пути fs. Однако из любопытства я немного поигрался с наследованием от pathlib.Path и получил вполне жизнеспособное решение.

      import pathlib


class S3Path(pathlib.PosixPath):
    s3_schema = "s3:/"

    def __new__(cls, *args, **kwargs):
        if args[0].startswith(cls.s3_schema):
            args = (args[0].replace(cls.s3_schema, "", 1),) + args[1:]
        return super().__new__(cls, *args, **kwargs)

    def __str__(self):
        try:
            return self.s3_schema + self._str
        except AttributeError:
            self._str = (
                self._format_parsed_parts(
                    self._drv,
                    self._root,
                    self._parts,
                )
                or "."
            )
            return self.s3_schema + self._str


def test_basic():
    s3_path_str: str = "s3://some/location"
    s3_path = S3Path(s3_path_str)
    assert str(s3_path) == s3_path_str

    hdfs_path_1 = s3_path / "here"
    assert str(hdfs_path_1) == s3_path_str + "/here"

    assert s3_path.parent == S3Path("s3://some")

Преимущество этого в том, что вам не нужна какая-либо зависимость от установки pip. Кроме того, вы можете легко адаптировать его к любым другим URI-путям, например hdfs.

Pathy отлично подходит для этого:https://github.com/justindujardin/pathy

Он использует Smart open под капотом для обеспечения доступа к файлам в хранилище ведра, и поэтому по этой причине он лучше, чем s3path.

Вы можете использовать Pathy.fluid чтобы API работали как с локальными файлами, так и с файлами в хранилище ведра

      from pathlib import BasePath
from pathy import Pathy, FluidPath

def process_f(f: Union[Union[str, Pathy, BasePath]):
    path = Pathy.fluid(f)
    # now you have a Pathlib you can process that's local or in s3/GCS


Полезно и просто расширить класс str, чтобы справиться с этим.

class URL(str):
  def __truediv__(self, val):
    return URL(self + '/' + val)

пример использования будет URL('s3://mybucket') / 'test'"s3://mybucket/test"

Другие вопросы по тегам