cas-manifest 允许开发者使用自描述清单在内容寻址存储中存储工件
项目描述
cas-manifest 提供了一种存储数据的方法,这些数据始终是不可变的,当需要时是稳定的,并且当需要时是灵活的。
cas-manifest 通过内容寻址存储存储数据工件。它促进了使用具有与数据共存并支持数据的标准可序列化包装的CAS。
为什么选择CAS?
简而言之,CAS强制执行不可变性。在使用CAS时,密钥的内容永远不会更改。以下内容随之而来:
- 不再有
data_final__2_new
文件 - 所有对象都是唯一指定的 - 不再需要缓存失效 - 自由地缓存对象,知道它们的内容永远不会在上游更改
- 不再有来源问题 - 模型可以稳健地链接到用于训练它们的数据集
在CAS存储中,您不是在Key处 put 一个Value,而是 put 一个Value,并返回由该值唯一确定的Key。
为什么选择清单?
将一些数据放入二进制工件并保留一个引用它的键是非常好的。难点在于,当你得到一个键时,你怎么知道那里存储了什么?如果你确定了一个标准的序列化方法,你怎么让它进化同时保持向后兼容性?
cas-manifest鼓励使用清单类来应对这些挑战。这些清单类包括序列化和反序列化工件所需的代码。它们提供了一个存储关于工件元数据的地方——这可以用于反序列化,用来指示加载的数据应如何使用,或者可能仅仅是信息性的。最后,清单类中的字段可以引用CAS中的其他对象,从而使对象可以组合和重用。与CAS结合使用,您可以确保您的元数据和底层数据始终保持同步,因为您的元数据将引用底层数据的不可变引用。
示例
实现序列化
假设我们希望存储我们在内存中以pandas Dataframe表示的数据集。我们将创建一个cas_manifest.Serializable
的子类来表示我们希望存储的内容
class CSVSerializable(Serializable[pd.DataFrame]):
column_names: List[str]
path: Ref
@classmethod
def pack(cls, inst: pd.DataFrame, fs: HashFS) -> CSVSerializable:
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir) / 'tmp.csv'
with open(tmp_path, mode='w') as f:
inst.to_csv(f, header=False, index=False)
csv_addr = fs.put(tmp_path)
return CSVSerializable(path=Ref(csv_addr.id), column_names=inst.columns.to_list())
def unpack(self, fs: HashFS) -> pd.DataFrame:
addr = fs.get(self.path.hash_str)
df = pd.read_csv(addr.abspath, names=self.column_names)
return df
让我们逐一分析这些项
class CSVSerializable(Serializable[pd.DataFrame]):
Serializable
的类型参数是我们内存中使用的类型。无论您在应用程序代码中使用什么,您都将其放在这里。
column_names: List[str]
path: Ref
cas-manifest使用pydantic来定义其清单类。这允许您在顶层指定类字段。为了确保它们可以被序列化,这些字段必须是简单类型或其他pydantic类。Ref
是一个特殊包装类,用于在cas中引用其他对象。
@classmethod
def pack(cls, inst: pd.DataFrame, fs: HashFS) -> CSVSerializable:
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir) / 'tmp.csv'
with open(tmp_path, mode='w') as f:
inst.to_csv(f, header=False, index=False)
csv_addr = fs.put(tmp_path)
return CSVSerializable(path=Ref(csv_addr.id), column_names=inst.columns.to_list())
pack
是一个必需的方法;它指定了您的数据应如何被序列化。注意参数的类型。
在此方法体内,我们将我们的dataframe保存为csv,不带标题行。然后我们将它放入pack
方法作为参数接收的HashFS
实例中。HashFS
提供了我们使用的CAS
的实现。那次put
操作返回了我们的csv的地址(或键)。然后我们可以构建一个我们的包装类实例,它包含一个指向csv文件的Ref
,以及清单类中的字段作为列名。
def unpack(self, fs: HashFS) -> pd.DataFrame:
addr = fs.get(self.path.hash_str)
df = pd.read_csv(addr.abspath, names=self.column_names)
return df
unpack
是另一个必需的方法;它表明您的序列化数据应如何被反序列化。同样,注意参数的类型和返回类型。在这种情况下,我们得到保存的csv文件在文件系统中的位置。然后我们调用pandas.read_csv
来读取它,并传递清单类中存储的列名。
请注意,在现实世界中遵循此模式时,通常会难以跟踪列名是作为标题行存储还是保存在其他地方,是否应有索引列等。这听起来很愚蠢,但它确实是一个问题——尤其是如果您想改变主意的话!cas-manifest通过在清单类中嵌入逻辑来标准化这些决策。
存储和检索数据
现在我们已经实现了所有这些,我们该如何使用它呢?
首先,我们需要将某些内容放入cas。假设我们有一个名为df
的DataFrame。我们还需要从hashfs
包中获取一个HashFS
实例。AWS用户可能希望使用此包中的S3HashFS
,它提供了由S3支持的HashFS
的实现。然后我们可以做以下操作
import pandas as pd
from hashfs import HashFS, HashAddress
df: pd.DataFrame = ...
fs_instance: HashFS = ...
addr: HashAddress = CSVSerializable.dump(df, fs_instance)
给定一个HashFS
实例,我们可以对CSVSerializable
调用dump
来序列化我们的DataFrame并将其存储在fs_instance
中。返回的对象是一个HashAddress
,它包括序列化对象的不可变哈希,以及诸如其磁盘上的路径等辅助信息。如果我们想访问序列化表示,我们也可以调用CSVSerializable.pack(df, fs_instance)
来获取一个CSVSerializable
实例。
现在,我们如何从存储中检索我们的序列化对象呢?同样,使用我们的HashFS
实例,我们将做以下操作
hash_str = addr.id
# We create a Registry that knows what classes to expect
registry: SerializableRegistry[pd.DataFrame] = \
SerializableRegistry(fs=fs_instance, classes=[CSVSerializable])
# We can `open` a hash address to get access to the dataframe
with registry.open(hash_str) as df:
pass # df is the DataFrame that we saved before
# Or we can get the serialized form directly
serialized: CSVSerializable = registry.load(addr.id)
为什么open
是一个上下文管理器?一些Serializable
的实现可能创建需要清理的临时资源,所以我们把open
当作打开和关闭文件一样处理。
序列化模式的演变
现在,让我们假设我们决定想要更改我们的序列化格式。也许我们想利用numpy的序列化方法来存储我们的dataframe中的数据。我们可以创建另一个Serializable
的子类,像之前一样实现pack
和unpack
class NPYSerializable(Serializable[pd.DataFrame]):
...
这里为了简洁省略了实现细节,但实现可以在tests/dataset.py
中找到。
我们可以用这种新格式序列化dataframe,就像我们用CSVSerializable
做的那样。当我们想要加载数据时,事情变得有趣起来。
registry_2: SerializableRegistry[pd.DataFrame] = \
SerializableRegistry(fs=fs_instance, classes=[CSVSerializable, NPYSerializable])
registry_2
现在知道如何反序列化存储在两种格式中的数据。你可以传递一个与任何格式相对应的哈希字符串,它将正确地将它反序列化为DataFrame。
这意味着你不需要在代码库中搜索数据是如何存储在磁盘上的,并四处散布代码。你可以在类中整合你的序列化/反序列化逻辑,让cas-manifest处理后续的处理。
注意事项
- 关于可移植性和模式演变:请记住,你的代码没有被序列化。所以,为了加载类型为
X
的对象,你仍然需要在代码库中提供X
。创建你的注册表应该会清楚地说明这一点。 - 与上述内容相关,如果你对类进行了更改,你必须确保它们是向后兼容的(例如,添加可选字段),以便能够加载旧数据。
- 类型:我已经尽我所能提供正确的类型注解,但mypy在推断某些泛型函数的返回类型时很困难。显式类型注解可能很有帮助。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。
源分发
构建分发
cas-manifest-0.4.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6847fb2511c7768f92cd6fbbf842e0fc8ca0e794952364db1fcc518c2cafdb4b |
|
MD5 | a67657ef06d5fd2ac19f114eb6d2104e |
|
BLAKE2b-256 | 6192f7078bc77c5d2c0e2aad0adeec1db81dbd817bfb8a758425483fa0ac0ef1 |
cas_manifest-0.4.1-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 4fb6c720f2be2b57207c5cf7d0ebc8be7eb309fd63d29d51d6702e7b79c591f3 |
|
MD5 | 72bb890c6bfd76c955d91fbc32670acd |
|
BLAKE2b-256 | 47a1abadfc9dbcd7c461d5fedd4b1dcb79295848d478042fcb5eedb428aa6dfc |