一个简单、Pythonic的文件格式。与
项目描述
perky
一个友好、简单、Pythonic的文本文件格式
版权所有 2018-2024 Larry Hastings
概述
Perky是一种新的简单Pythonic "rcfile"文本文件格式,用于Python程序。它解决了与"INI"文件、"TOML"文件和"JSON"文件相同的问题,但对其最佳解决方案有自己的见解。
Perky的特点
- 最小化、人性化的语法。Perky文件易于手动编写。
- 显式最小数据类型支持。Perky让您自己处理最终转换,而不是猜测数据类型。
- 轻量级、简单且快速。Perky的实现小巧且直接。忽略注释和测试代码后,大约有1k行Python代码。更少的行意味着更少的错误!(希望如此!)
- 灵活且可扩展。Perky通过"pragma"机制允许通过Perky文件扩展其语义。
- 完全用Python编写,但在现代桌面上每秒仍可解析>300k行。
- Perky支持Python 3.6+,并且通过了其单元测试套件,覆盖率100%(排除已弃用的部分)。
Perky语法
Perky配置文件看起来像JSON但没有引号。它只支持一组令人惊讶的小值类型
- 字符串,包括引号字符串和"三重引号字符串"(多行字符串),
- "映射"(字典),和
- "序列"(列表)。
Perky是面向行的;单个值在一行上。容器对象使用每行一个内部值。
您可以在内存允许的深度嵌套列表和字典。
与Python本身不同,忽略前导空白。您可以随意使用前导空白,但它不是必需的。(前导空白在三重引号字符串中保留,但具有允许实际值缩进的巧妙语法。)
空白行和注释行(以#
开头的行)将被忽略,除非在三个引号字符串内。
Perky还支持“编译指令”,这些指令是行首带有等号的行,可以执行特殊的运行时行为。默认情况下,Perky没有定义任何编译指令--它是供您使用的扩展机制。
以下是一个示例配置文件,展示了Perky能做什么
example name = value
example dict = {
name = 3
another name = 5.0
}
example list = [
a
b
c
]
nested dict = {
name = value
nesting level 2 = {
nesting level 3 = {
and = so on!
}
}
list inside the dict = [
value in the list
[
and this is in a nested list!
this is another value.
you see?
]
]
}
# lines starting with hash are comments and are ignored!
# blank lines are ignored too!
" quoted name " = " quoted value "
triple quoted string = """
indenting
is preserved
the string is automatically outdented
to the leftmost character of the *ending*
triple-quote
<-- aka here
"""
one-line empty list = []
one-line empty dict = {}
one-line empty list with whitespace = [ ]
one-line empty dict with whitespace = { }
multi-line empty list = [
]
multi-line empty dict = {
}
=pragma
=pragma with argument
显式转换优于隐式转换
Perky的一个可能令人惊讶的设计选择:Perky解析器原生支持的唯一值是字典、列表和字符串。那么整数?浮点数?日期?布尔值?NULL
?None
呢?
Perky故意将这个问题留给你。正如Python的禅所说
面对歧义,拒绝猜测的诱惑。
Perky不知道你的程序需要什么类型。所以,而不是猜测并出错,Perky使事情简单:只列表、字典和字符串。对于任何其他类型,你必须自己将其从字符串转换为你想要的类型,然后再转换回来。
注意,Perky不关心你如何或是否转换你的数据。你可以直接使用字符串,或者按照你的喜好转换它们。你可以手动转换它们,或者使用第三方数据转换库,如Marshmallow。
(Perky曾经支持一个用于自动数据转换的实验性API。但这从未完全实现,现在有更好的技术版本。我已经弃用了“转换”子模块,并将在此之前将其删除。)
编译指令
编译指令是Perky解析器的元数据指令。它是从Perky文本内部向Perky解析器发送指令的一种方式。在Perky中,编译指令是行首带有未引用等号的行。
以下是一个编译指令示例
=command argument here
等号后的第一个单词是编译指令的名称,在本例中为"command"
。编译指令名称之后的所有内容是参数,所有首尾空白都被删除,在本例中为"argument here"
。
默认情况下,Perky没有任何编译指令处理器。当Perky没有相应的处理器时调用编译指令会导致运行时错误。但你在调用load()
或loads()
时可以定义自己的编译指令处理器,使用名为pragmas
的命名参数。如果你为pragmas
传递一个值,它必须是一个字符串到函数的映射。字符串名称应该是编译指令的名称,并且必须是小写。它映射到的函数将“处理”该编译指令,应与以下原型匹配
def pragma_fn(parser, argument)
parser
是内部Perky Parser
对象。 argument
是去除首尾空白后的相关行的其余部分。(如果该行其余部分为空,则argument
为None
)。编译指令函数的返回值将被忽略。
目前只有一个预定义的编译指令处理器,一个名为pragma_include()
的函数。这为Perky添加了“包含语句”功能。如果你调用此
load(filename, pragmas={'include': pragma_include()})
那么Perky将解释filename
中开始的以=include
开头的行作为包含语句,使用行的其余部分作为文件的名称。更多信息请参见下面的pragma_include()
。
编译指令规则
- 要调用编译指令,请使用
=
作为行首的第一个非空白字符。 - 编译指令的名称必须始终为小写。
- 你可以在序列或映射上下文中调用编译指令。但你不能在三个引号字符串内部调用编译指令。
- 编译指令可以是“上下文感知的”:它们可以知道它们在文件中的运行位置,例如修改当前字典或列表。编译指令函数可以看到正在解析的整个当前嵌套字典和列表列表(通过
parser.stack
)。 - pragma名称之后的整行是pragma的参数值,如果有。这始终是一个字符串,可以是未引用的或单引号引用的;如果未引用,则不能包含任何特殊符号(
{ } = ''' """
)。 - 如果您想使一行以等号开头(一个
value
或一个name=value
),但又不想它是一个pragma,只需在它周围加上引号。同样,如果您想在pragma参数中使用特殊符号,只需在它周围加上(单引号)。
解析器对象
Pragma函数接收Perky Parser
对象作为参数。该对象封装了当前时间解析Perky文件的所有当前状态。以下是您可能希望从pragma中使用的相关属性
parser.source
包含当前Perky文本的源,要么是文件名,要么是字符串''。parser.line_number
包含当前正在解析的行的行号。Perky文本的第一行是第1行。parser.stack
是一个引用集合对象的栈--从Perky文件顶部开始的嵌套字典和列表的栈,反映我们现在的位置。parser.stack[0]
始终是根,将由load
或loads
返回的对象。parser.stack[-1]
始终是pragma运行时的当前上下文。它可以是列表或字典。您应该使用isinstance(parser.stack[-1], collections.abc.Mapping)
来确定这一点;如果是True
,当前上下文是映射上下文(一个字典),如果是False
,当前上下文是序列上下文(一个列表)。
解析错误
在解析Perky文本时,只有几种可能的错误
- 显然,语法错误,例如
- 字典中一行没有未引用的等号
- 列表中看起来像字典行(
name = value
)。(如果您想在列表中包含包含等号的值,只需将其放在引号中。) - 三引号字符串中任何行的缩进超出了结束三引号行。
- 在同一个字典中定义相同的值两次。这被标记为错误,因为它可能是一个错误,并且像Python一样,我们不想让错误无声通过。
- 使用未定义的pragma。
- 使用Perky的特殊标记作为pragma参数,如
{
、[
、'''
、"""
、[]
或{}
。
API
loads(s, *, pragmas=None, root=None)
解析Perky格式的字符串,并返回一个包含从该字符串解析出的值的容器。
如果pragmas
不是None
,它必须是一个字符串到pragma处理函数的映射。请参阅文档中的Pragmas
部分。
如果root
是None
,loads
的行为就像您传递了一个空的dict
。
如果root
不是None
,它应该是一个容器对象,要么是可变的映射(类型dict
),要么是可变的序列(类型list
)。这会影响数据的解析;如果root
是可变的映射对象,Perky文件的最高级别必须是“映射上下文”(一系列name=value
行);如果root
是可变的序列对象,Perky文件的最高级别假定是“序列上下文”(一系列value
行)。
load(filename, *, pragmas=None, root=None)
加载包含Perky文件格式设置的文件。返回一个字典。
文件中的文本必须使用UTF-8编码。
如果root
是None
,loads
的行为就像您传递了一个空的dict
。
如果root
不是None
,它应该是一个容器,要么是可变的映射(dict
),要么是可变的序列(list
)。这会影响数据的解析;如果root
是可变的映射,Perky文件的最高级别必须是“映射上下文”(一系列name=value
行);如果root
是可变的序列,Perky文件的最高级别假定是“序列上下文”(一系列value
行)。
如果pragmas
不是None
,它必须是一个字符串到pragma处理函数的映射。请参阅文档中的Pragmas
部分。
dumps(d)
将字典转换为Perky文件格式的字符串。字典中的键必须是字符串。非字典、列表或字符串的值将使用str转换为字符串。返回一个字符串。
dump(filename, d)
使用dump
将字典转换为Perky文件格式的字符串,然后将其写入filename。
文件中的文本将使用UTF-8编码。
pragma_include(include_path=(".",), jail=False)
此函数创建并返回一个实现Perky "include"功能的pragma处理器。包含文件意味着将一个Perky文件在另一个文件中按词法插入,在调用pragma的位置上下文中。
例如,如果您运行以下代码
d = loads(
"""
a=3
=include data.pky
c=5
""",
pragmas={"include": pragma_include()},
)
并且当前目录中的data.pky可读,并包含以下文本
b=4
则d
将被设置为以下字典
{'a': '3', 'b': '4', 'c': '5'}
pragma_include
不是pragma处理器本身;它返回一个函数(闭包),该函数记住其配置。
include_path
参数允许您指定要搜索包含文件的有序目录列表。它必须是一个可迭代的(要么是tuple
或list
),包含str
或pathlib.Path
对象。默认情况下,它只包含一个条目'.'
,这意味着它只会包含当前目录路径下的文件。
jail
参数允许激活安全预防措施。默认情况下,jail
为false,这意味着pragma_include
将愉快地读取您磁盘上的任何文件。
=include ../../../../../secretfile
=include ~/.history
=include /etc/passwd
如果jail
为true,则pragma_include
只允许读取include_path
中指定的目录(或目录)内的文件。如果pragma调用尝试读取这些目录之外的文件,则pragma_include
将引发PermissionError
。
您可以在同一个load
或loads
调用中使用多个pragma_include
处理器,以允许从不同的路径包含,例如。
include_dirs = [appdirs.user_data_dir(myapp_name)]
config_dirs = [appdirs.user_config_dir(myapp_name)]
pragmas = {
'include': pragma_include(include_dirs),
'config': pragma_include(config_dirs),
}
d = load(path, pragmas=pragmas)
注意
-
此pragma处理器是上下文相关的;包含的文件将表现得像被复制粘贴,替换pragma行。在许多方面,这意味着如果pragma在序列上下文中调用,则包含的文件必须开始在序列上下文中。
-
在加载包含的文件时,pragma处理器将当前pragma处理器传递给
load()
。在许多方面,这允许递归包含。 -
当在字典上下文中包含时,您明确允许重新定义在另一个文件中先前定义的现有键。这是双向的;内部(包含)文件可以覆盖外部文件(包含它的文件)中定义的键,外部文件也可以覆盖包含文件中的键。然而,Perky仍然强制执行每个上下文中只能定义一次键的规则。
-
include_path
的默认值仅搜索当前目录("."
)。如果您覆盖默认值并传递自己的包含路径,则pragma处理器不会搜索当前目录,除非您自己显式地将"."
添加到包含路径中。 -
如果
pragma_include
在其搜索路径中找不到请求的文件,它将引发FileNotFoundError
。
已弃用的API
Perky有一个“转换”子模块。想法是,您加载一个Perky文件,然后在那个字典上运行transform
以将字符串转换为原生值。
这些函数不再维护或支持,已从coverage测试中排除,并将在新版本1.0之前移除。为什么?Perky的这个部分始终是一个实验……而这个实验从未真正成功。有更好的实现这个想法的方法,比如Marshmallow——你应该使用那些。如果你依赖于Perky中的这段代码,我鼓励你复制一份并自行维护。但我不认为有人会这么做。
为了未来,以下是现在已弃用的API的文档。
map(d, fn) -> o
遍历字典。返回一个新的字典,其中对于每个值
- 如果是字典,则替换为新的字典。
- 如果是列表,则替换为新的列表。
- 如果不是字典或列表,则替换为
fn(value)
。
传入的函数称为转换函数。
transform(d, schema, default=None) -> o
递归地将Perky字典转换为另一个对象(通常是字典),使用提供的模式。返回一个新的字典。
模式是一个与d的一般预期形状相匹配的数据结构,其中值是字典、列表和可调用对象。转换类似于map()
,但每个值将具有单独的转换函数。此外,可以为d中的任何值指定模式转换函数,甚至是字典或列表。
default是一个默认转换函数。如果d中存在一个值v,它没有在schema中找到等效条目,并且v既不是列表也不是字典,并且default是一个可调用对象,则将v替换为default(v)
在输出中。
必需
实验性的。
nullable(fn) -> fn
实验性的。
const(fn) -> o
实验性的。
待办事项
- 反斜杠引号目前做“你Python版本所做的任何事情”。这可能应该是明确的,并由Perky本身解析?
变更日志
0.9.3 2024/09/18
两个新功能,都是针对pragma_include
。
pragma_include
现在接受一个新的jail
参数,一个布尔值。默认情况下,jail
为false。如果jail
为true,则包含文件的路径必须在include_paths
的路径中或其下。pragma_include
现在允许在include_paths
参数中使用pathlib.Path
对象。当然,它仍然支持str
对象。
此外,还有一些常规的文档改进,并将版权通知更新至2024年。
0.9.2 2023/07/22
这是一个极其小的版本。没有新功能或错误修复。
- 添加了GitHub Actions集成。在每次提交后,在云端运行测试和覆盖率。感谢Dan Pope耐心地引导我完成这个过程!
- 修复了
pyproject.toml
文件中的元数据。 - 放弃了Python 3.5的支持。(我假设我已经放弃了,但它仍然列在项目元数据中作为支持版本。)
- 添加了测试、覆盖率和支持的Python版本徽章。
0.9.1 2023/07/03
- API更改:将
Parser
属性breadcrumbs
重命名为stack
。它以前未记录,尽管从这个版本开始,它现在是记录的并且是官方支持的。之前的(未记录,未支持)名称breadcrumbs
目前保留为别名,但将在1.0之前删除。 - 为
Parser
对象添加了line_number
和source
属性,以便于pragma处理器。 - 对
parser_include
进行了轻微的重构。没有更改功能或行为,只是进行了小的代码清理。 - 在基准程序中添加了“每秒行数”输出度量。
0.9 2023/07/02
破坏性API更改:完全移除了encoding
参数。
-
从现在开始,Perky 仅支持以 UTF-8 编码读取和写入文件。如果您需要使用不同的编码,您必须自行处理加载和保存到磁盘的操作。您需要使用
loads
和dumps
来处理 Perky 字符串格式与原生 Python 对象之间的转换。 -
进一步优化了 Perky。与 0.8.1 版本相比,大约快了 11%。
0.8.2 2023/06/30
-
API 略有改动
- 现在您可以将
encoding
关键字参数传递给pragma_include
。这是指定通过pragma_include
加载的文件的编码的唯一方式。 - 移除了 Perky 的
Parser
对象的(未记录的)encoding
属性。 - 移除了
loads
的encoding
参数。 load
的encoding
参数现在仅由load
本身用于加载顶层 Perky 文件。
- 现在您可以将
0.8.1 2023/06/26
- 哎呀!一个严重的回归:我不小心将非字符串值的默认转换从
str
改为了repr
。这是一个糟糕的改动!str
要好得多。添加了一个测试,以免再次发生这种情况。
0.8 2023/06/25
-
Perky 现在明确使用
collections.abc.MutableMapping
和collections.abc.MutableSequence
进行isinstance
检查,而不是dict
和list
。这允许您使用自己的映射和序列对象,这些对象 不 继承自dict
和list
。 -
将
PerkyFormatError
重命名为FormatError
。旧名称目前仍然支持,但请过渡到新名称。旧名称将在 1.0 版本之前被移除。 -
"transformation" 子模块现在已弃用且不再受支持。请停止使用它,或者分叉并自行维护它。这包括
map
、transform
、Required
、nullable
和const
。 -
Perky 现在有了一个适当的单元测试套件,它以 100% 的覆盖率通过测试——除了不受支持的
transform
子模块。 -
在追求 100% 覆盖率的同时,也对代码进行了一些清理。
-
重制了
LineTokenizer
- 将其名称从
LineParser
改为LineTokenizer
。它从不进行解析,只是进行标记化。 - 使 API 更加一致:现在,唯一会抛出
StopIteration
的函数是__next__
。 - 之前可能抛出
StopIteration
的其他函数现在在迭代器为空时返回一个包含None
值的元组。这意味着您可以安全地编写for a, b, c in line_tokenizer:
。 bool(lt)
现在是准确的;如果它返回True
,您可以调用next(lt)
或lt.next_line()
或lt.tokens()
并确信您会得到一个值。
- 将其名称从
-
将
RuntimeError
异常替换为更合适的异常(ValueError
、TypeError
)。
-
项目详情
下载文件
下载适用于您平台的应用程序。如果您不确定要选择哪一个,请了解有关 安装包 的更多信息。
源代码分发
构建的发行版
perky-0.9.3.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ae847c4ff1f5e2bde74097eb7a29a1411bc22912142ba4c8cf08d77789823f08 |
|
MD5 | 3c01f37ec69f85fd40a24ed55ac74012 |
|
BLAKE2b-256 | 73c9c67fc465afa7990cb74c0044039c2f3a1992b8ef6ae1d2c69b7c12613a9b |
perky-0.9.3-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ebe0c58ee35951b1b878d5acb98bda4aa3e2067cd397baf1a3721f7f7da5acd4 |
|
MD5 | 7a54c717cd7931b4de7ab27101949e31 |
|
BLAKE2b-256 | ffc61c55fd512e9d613ec849a36662eae3758b8a4269771667f9c7a13f25a538 |