缓存已解析的文档

Caching parsed document

我有一组 YAML 文件。我想缓存这些文件,以便尽可能多地重复使用。

这些文件中的每一个都包含两个文档。第一份文件包含将始终以相同方式解释的“静态”信息。第二个文档包含每次使用该文件时都必须重新解释的“动态”信息。具体来说,它使用基于标签的宏系统,每次使用文件时都必须重新构建 文件。然而,文件本身不会改变,所以解析整个文件的结果可以被缓存(在相当大的资源节省下)。

在ruamel.yaml中,有没有一种简单的方法可以将整个文件解析成多个解析的文档,然后运行在每个文档上单独构建?这将允许我缓存构建第一个“静态”文档的结果并缓存第二个“动态”文档的解析以供以后构建。

示例文件:

---
default_argument: name
...
%YAML 1.2
%TAG ! tag:yaml-macros:yamlmacros.lib.extend,yamlmacros.lib.arguments:
---
!merge
name: !argument name

第一个文档包含在构建第二个文档时使用的元数据(以及来自其他地方的其他数据)。

如果您不想完全处理流中的所有 YAML 文档,则必须手动拆分流,这在通用方式下并不完全容易。

你需要知道的是what a YAML stream can consist of:

zero or more documents. Subsequent documents require some sort of separation marker line. If a document is not terminated by a document end marker line, then the following document must begin with a directives end marker line.

文档结束标记行是以 ... 开头,后跟 space/newline,指令结束标记行是 --- 后跟 space/newline。

实际的生产规则稍微复杂一些,"starts with"应该忽略你需要跳过任何mid-stream byte-order标记的事实。

如果您没有任何指令、byte-order-标记并且没有 document-end-markers(我见过的大多数 multi-document YAML 流都没有这些),那么您可以将 data = Path().read() multi-document YAML 作为字符串,使用 l = data.split('\n---') 拆分并使用 YAML().load(l[N]).

仅处理结果列表中的适当元素

我不确定以下是否正确处理了所有情况,但它确实处理了您的 multi-doc 流:

import sys
from pathlib import Path
import ruamel.yaml


docs = []
current = ""

state = "EOD"
for line in Path("example.yaml").open():
    if state in ["EOD", "DIR"]:
        if line.startswith("%"):
            state = "DIR"
        else:
            state = "BODY"
        current += line
        continue
    if line.startswith('...') and line[3].isspace():
        state = "EOD"
        docs.append(current)
        current = ""
        continue
    if state == "BODY" and current and line.startswith('---') and line[3].isspace():
        docs.append(current)
        current = ""
        continue
    current += line
if current:
   docs.append(current)

yaml = ruamel.yaml.YAML()
data = yaml.load(docs[1])
print(data['name'])

给出:

name

看起来您确实可以直接操作 ruamel.yaml 的解析器内部结构,只是没有记录。以下函数将 YAML 字符串解析为文档节点:

from ruamel.yaml import SafeLoader

def parse_documents(text):
    loader = SafeLoader(text)
    composer = loader.composer
    while composer.check_node():
        yield composer.get_node()

从那里,可以单独构建文档。为了解决我的问题,应该使用类似以下的方法:

def process_yaml(text):
    my_constructor = get_my_custom_constructor()

    parsed_documents = list(parse_documents(path.read_text()))
    metadata = my_constructor.construct_document(parsed_documents[0])
    return (metadata, document[1])

cache = {}
def do_the_thing(file_path):
    if file_path not in cache:
        cache[file_path] = process_yaml(Path(file_path).read_text())

    metadata, document = cache[file_path]

    my_constructor = get_my_custom_constructor(metadata)
    return my_constructor.construct_document(document)

这样,所有的文件IO和解析都被缓存起来,每次只需要执行最后的构建步骤