gs-wrap 用于封装Google Cloud Storage API,以实现多线程数据操作,包括复制、读取、写入和散列。
项目描述
gs-wrap
gs-wrap 封装了 Google Cloud Storage API,用于多线程数据操作,包括复制、读取、写入和散列。
最初,我们使用了我们的 gsutilwrap,它是gsutil命令行接口的一个薄包装,用于简化与Google Cloud Storage相关的部署和备份任务。然而,gsutilwrap 在将许多对象复制到不同目的地时速度过慢。
因此,我们开发了 gs-wrap 来加速这些操作,同时保持它在其他操作上的速度与 gsutilwrap 相当或更快。
虽然由谷歌提供的 google-cloud-storage 库具有高级功能和良好的性能,但其使用案例和行为与 gsutil 不同。由于我们希望使用 gsutil 的简洁性和使用模式,我们创建了 gs-wrap,它将 google-cloud-storage 封装在核心中,并设置了与 gsutil 相似的行为。
gs-wrap 并非第一个封装谷歌云存储 API 的 Python 库。 cloud-storage-client 采用类似的方法,旨在管理亚马逊的 S3 和谷歌云存储。其部分内容也基于 google-cloud-storage,但该库的行为与 gsutil 不同,这使得它难以作为 gsutilwrap 的替代品使用。此外,该库没有提供所有需要的操作,例如复制到多个目的地、读取、写入和散列。
gs-wrap 的主要优势是能够将许多对象从许多不同的路径复制到多个目的地,同时模仿 gsutil 接口。有关 gs-wrap 和 gsutilwrap 之间的性能直接比较,请参阅 性能比较部分。
使用方法
您需要创建一个谷歌云存储桶才能使用此客户端库。请按照 官方谷歌云存储文档 进行操作,了解如何创建存储桶。
连接到您的谷歌云存储桶
首先需要创建一个用于与谷歌云存储 API 交互的客户端。此客户端内部使用来自 google-cloud-storage 的 Storage Client。
可以向客户端传递一个参数
代表客户端操作的谷歌云存储 项目。当创建内部客户端时,它将被传递。如果没有传递,将回退到从本地认证的 谷歌云 SDK 环境中推断出的默认值。每个项目需要一个单独的客户端。不支持在不同项目之间的操作。
import gswrap
client = gswrap.Client() # project is optional
列出您存储桶中的对象
client.ls(gcs_url="gs://your-bucket/your-dir", recursive=False)
# gs://your-bucket/your-dir/your-subdir1/
# gs://your-bucket/your-dir/your-subdir2/
# gs://your-bucket/your-dir/file1
client.ls(gcs_url="gs://your-bucket/your-dir", recursive=True)
# gs://your-bucket/your-dir/your-subdir1/file1
# gs://your-bucket/your-dir/your-subdir1/file2
# gs://your-bucket/your-dir/your-subdir2/file1
# gs://your-bucket/your-dir/file1
在谷歌云存储中复制对象
如果源和目标 URL 都是来自同一提供者的云 URL,则 gsutil 将数据“在云中”复制(即不下载到并从您运行 gs-wrap 的机器上传)。
在谷歌云存储中复制文件
# your-bucket before:
# gs://your-bucket/file1
client.cp(src="gs://your-bucket/file1",
dst="gs://your-bucket/your-dir/",
recursive=True)
# your-bucket after:
# gs://your-bucket/file1
# gs://your-bucket/your-dir/file1
# your-backup-bucket before:
# "empty"
client.cp(src="gs://your-bucket/file1",
dst="gs://your-backup-bucket/backup-file1",
recursive=False)
# your-backup-bucket after:
# gs://your-backup-bucket/backup-file1
在谷歌云存储中复制目录
# your-bucket before:
# "empty"
client.cp(src="gs://your-bucket/some-dir/",
dst="gs://your-bucket/another-dir/", recursive=False)
# google.api_core.exceptions.GoogleAPIError: No URLs matched
# your-bucket before:
# gs://your-bucket/some-dir/file1
# gs://your-bucket/some-dir/dir1/file11
# Destination URL without slash
client.cp(src="gs://your-bucket/some-dir/",
dst="gs://your-bucket/another-dir", recursive=True)
# your-bucket after:
# gs://your-bucket/another-dir/file1
# gs://your-bucket/another-dir/dir1/file11
# Destination URL with slash
client.cp(src="gs://your-bucket/some-dir/",
dst="gs://your-bucket/another-dir/", recursive=True)
# your-bucket after:
# gs://your-bucket/another-dir/some-dir/file1
# gs://your-bucket/another-dir/some-dir/dir1/file11
# Choose to copy multi-threaded. (default=False)
client.cp(src="gs://your-bucket/some-dir/",
dst="gs://your-bucket/another-dir", recursive=True, multithreaded=True)
# your-bucket after:
# gs://your-bucket/another-dir/file1
# gs://your-bucket/another-dir/dir1/file11
将对象上传到谷歌云存储
# Your local directory:
# /home/user/storage/file1
# /home/user/storage/file2
# your-bucket before:
# "empty"
client.cp(src="/home/user/storage/",
dst="gs://your-bucket/local/",
recursive=True)
# your-bucket after:
# gs://your-bucket/local/storage/file1
# gs://your-bucket/local/storage/file2
从Google Cloud Storage下载对象
import os
# Current your-bucket:
# gs://your-bucket/file1
client.cp(
src="gs://your-bucket/file1",
dst="/home/user/storage/file1")
# Your local directory:
# /home/user/storage/file1
带有参数的复制、下载和上传
# Parameter: no_clobber example:
import os
# File content before: "hello"
os.stat("/home/user/storage/file1").st_mtime # 1537947563
client.cp(
src="gs://your-bucket/file1",
dst="/home/user/storage/file1",
no_clobber=True)
# no_clobber option stops from overwriting.
# File content after: "hello"
os.stat("/home/user/storage/file1").st_mtime # 1537947563
client.cp(
src="gs://your-bucket/file1",
dst="/home/user/storage/file1",
no_clobber=False)
# File content after: "hello world"
os.stat("/home/user/storage/file1").st_mtime # 1540889799
# Parameter: recursive and multi-threaded example:
# Your local directory:
# /home/user/storage/file1
# ...
# /home/user/storage/file1000
# your-bucket before:
# "empty"
# Execute normal recursive copy in multiple threads.
client.cp(src="/home/user/storage/",
dst="gs://your-bucket/local/",
recursive=True, multithreaded=True)
# your-bucket after:
# gs://your-bucket/local/storage/file1
# ...
# gs://your-bucket/local/storage/file1000
# Parameter: preserve_posix example:
# Your file before:
# /home/user/storage/file1
# e.g. file_mtime: 1547653413 equivalent to 2019-01-16 16:43:33
client.cp(src="/home/user/storage/file1",
dst="gs://your-backup-bucket/file1",
preserve_posix=False)
# your-backup-bucket after:
# gs://your-backup-bucket/file1 e.g. "no metadata file_mtime"
# Preserve the POSIX attributes. POSIX attributes are the metadata of a file.
client.cp(src="/home/user/storage/file1",
dst="gs://your-backup-bucket/file1",
preserve_posix=True)
# your-backup-bucket after:
# gs://your-backup-bucket/file1 e.g. file_mtime: 2019-01-16 16:43:33
在一个调用中执行多个复制操作
sources_destinations = [
# Copy on Google Cloud Storage
('gs://your-bucket/your-dir/file',
'gs://your-bucket/backup-dir/file'),
# Copy from gcs to local
('gs://your-bucket/your-dir/file',
pathlib.Path('/home/user/storage/backup-file')),
# Copy from local to gcs
(pathlib.Path('/home/user/storage/new-file'),
'gs://your-bucket/your-dir/new-file'),
# Copy locally
(pathlib.Path('/home/user/storage/file'),
pathlib.Path('/home/user/storage/new-file'))]
client.cp_many_to_many(srcs_dsts=sources_destinations)
从Google Cloud Storage中删除文件
# your-bucket before:
# gs://your-bucket/file
client.rm(url="gs://your-bucket/file")
# your-bucket after:
# "empty"
# your-bucket before:
# gs://your-bucket/file1
# gs://your-bucket/your-dir/file2
# gs://your-bucket/your-dir/sub-dir/file3
client.rm(url="gs://your-bucket/your-dir", recursive=True)
# your-bucket after:
# gs://your-bucket/file1
在Google Cloud Storage中读写文件
client.write_text(url="gs://your-bucket/file",
text="Hello, I'm text",
encoding='utf-8')
client.read_text(url="gs://your-bucket/file",
encoding='utf-8')
# Hello I'm text
client.write_bytes(url="gs://your-bucket/data",
data="I'm important data".encode('utf-8'))
data = client.read_bytes(url="gs://your-bucket/data")
data.decode('utf-8')
# I'm important data
复制文件的os.stat()或blob的元数据
file = pathlib.Path('/home/user/storage/file')
file.touch()
print(file.stat())
# os.stat_result(st_mode=33204, st_ino=19022665, st_dev=64769, st_nlink=1,
# st_uid=1000, st_gid=1000, st_size=0, st_atime=1544015997,
# st_mtime=1544015997, st_ctime=1544015997)
# Upload does not preserve POSIX attributes.
client.cp(src=pathlib.Path('/home/user/storage/file'),
dst="gs://your-bucket/file")
stats = client.stat(url="gs://your-bucket/file")
stats.creation_time # 2018-11-21 13:27:46.255000+00:00
stats.update_time # 2018-11-21 13:27:46.255000+00:00
stats.content_length # 1024 [bytes]
stats.storage_class # REGIONAL
stats.file_atime # None
stats.file_mtime # None
stats.posix_uid # None
stats.posix_gid # None
stats.posix_mode # None
stats.md5 # b'1B2M2Y8AsgTpgAmY7PhCfg=='
stats.crc32c # b'AAAAAA=='
# Upload with preserve_posix also copy POSIX attributes to blob.
# POSIX attributes are the metadata of a file.
# It also works for downloading.
client.cp(src=pathlib.Path('/home/user/storage/file'),
dst="gs://your-bucket/file", preserve_posix=True)
stats = client.stat(url="gs://your-bucket/file")
stats.creation_time # 2018-11-21 13:27:46.255000+00:00
stats.update_time # 2018-11-21 13:27:46.255000+00:00
stats.content_length # 1024 [bytes]
stats.storage_class # REGIONAL
stats.file_atime # 2018-11-21 13:27:46
stats.file_mtime # 2018-11-21 13:27:46
stats.posix_uid # 1000
stats.posix_gid # 1000
stats.posix_mode # 777
stats.md5 # b'1B2M2Y8AsgTpgAmY7PhCfg=='
stats.crc32c # b'AAAAAA=='
检查复制的文件是否正确
# Check modification time when copied with preserve_posix.
client.same_modtime(path='/home/user/storage/file',
url='gs://your-bucket/file')
# Check md5 hash to ensure content equality.
client.same_md5(path='/home/user/storage/file', url='gs://your-bucket/file')
# Retrieve hex digests of MD5 checksums for multiple URLs.
urls = ['gs://your-bucket/file1', 'gs://your-bucket/file2']
client.md5_hexdigests(urls=urls, multithreaded=False)
文档
文档可在readthedocs上找到。
设置
为了使用此库,您需要完成以下步骤
安装
使用pip安装gs-wrap
pip3 install gs-wrap
开发
检查存储库。
在存储库根目录中创建虚拟环境
python3 -m venv venv3
激活虚拟环境
source venv3/bin/activate
安装开发依赖项
pip3 install -e .[dev]
我们使用tox进行测试和打包发行版。假设已经激活了虚拟环境并且已经安装了开发依赖项,请运行
tox
预提交检查
我们提供了一组预提交检查,用于检查代码格式和语法。
具体来说,我们使用
yapf来检查格式。
使用pydocstyle检查文档字符串的风格。
使用mypy进行静态类型分析。
isort为您排序导入。
使用pylint进行各种检查。
使用Python doctest模块执行doctests。
pyicontract-lint检查使用icontract库定义的Python代码中的合约。
twine检查README中无效的标记,以防止它在PyPI上正确渲染。
从已激活的虚拟环境并安装了开发依赖项的地方本地运行预提交检查
./precommit.py
预提交脚本还可以自动格式化代码
./precommit.py --overwrite
基准测试
假设已经激活了虚拟环境,已经安装了开发依赖项,并且PYTHONPATH已设置为项目目录,请使用以下命令运行基准测试
./benchmark/main.py *NAME OF YOUR GCS BUCKET*
以下是我们的基准测试结果
基准测试列表10000个文件
测试 |
时间 |
加速 |
gswrap |
3.22秒 |
- |
gsutilwrap |
3.98秒 |
1.24倍 |
基准测试上传10000个文件
测试 |
时间 |
加速 |
gswrap |
45.12秒 |
- |
gsutilwrap |
34.85秒 |
0.77倍 |
基准测试上传多对多500个文件
测试 |
时间 |
加速 |
gswrap |
2.14秒 |
- |
gsutilwrap |
65.2秒 |
30.49倍 |
基准测试下载10000个文件
测试 |
时间 |
加速 |
gswrap |
43.92秒 |
- |
gsutilwrap |
43.01秒 |
0.98倍 |
基准下载多对多500个文件
测试 |
时间 |
加速 |
gswrap |
5.85秒 |
- |
gsutilwrap |
62.93秒 |
10.76倍 |
远程复制1000个文件的基准
测试 |
时间 |
加速 |
gswrap |
5.09秒 |
- |
gsutilwrap |
4.47秒 |
0.88倍 |
远程多对多复制500个文件的基准
测试 |
时间 |
加速 |
gswrap |
6.55秒 |
- |
gsutilwrap |
62.76秒 |
9.57倍 |
基准删除1000个文件
测试 |
时间 |
加速 |
gswrap |
3.16秒 |
- |
gsutilwrap |
3.66秒 |
1.16倍 |
基准读取100个文件
测试 |
时间 |
加速 |
gswrap |
16.56秒 |
- |
gsutilwrap |
64.73秒 |
3.91倍 |
基准写入30个文件
测试 |
时间 |
加速 |
gswrap |
2.67秒 |
- |
gsutilwrap |
32.55秒 |
12.17倍 |
基准统计100个文件
测试 |
时间 |
加速 |
gswrap |
6.39秒 |
- |
gsutilwrap |
48.15秒 |
7.53倍 |
我们所有的基准测试结果都可以在这里找到:这里。
版本控制
我们遵循语义版本控制。版本号X.Y.Z表示
X是主版本(不向后兼容),
Y是次版本(向后兼容),
Z是补丁版本(向后兼容的bug修复)。
项目详情
gs-wrap-1.0.5.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ab0cfa7c3a554a41847f33e0e7b1859a5e151d9014e2e84dd97db15caf54c661 |
|
MD5 | a5a9c86d665f1944676db0736c564431 |
|
BLAKE2b-256 | a791bdf2ae5eacf95ade786abf45611bd5a2dd8a7dc80d191ac40261ee4e3ece |