跳转到主要内容

适用于Pluto风格ini文件的I/O库。

项目描述

inifix

PyPI pre-commit.ci status Ruff

inifix 是一个小的Python库,提供类似于标准库模块 jsontomllib(与 tomli_w 一起)的 load/dump 接口,用于Pluto风格和 Idefix 风格的ini配置文件。

虽然其主要目标是遵循Idefix的'ini'格式规范,但它支持其一小部分。

主要区别在于

  • inifix 支持无节定义。这意味着也支持来自 FARGO 3D 的配置文件。
  • inifix 中,字符串可以使用 '" 转义。这允许在字符串值中有空格,并强制字符串类型解码,其中数值和布尔类型可以工作。

在极少数Idefix的'ini'格式与Pluto不匹配的情况下,inifix采取最简单的路径来支持两者。

已知差异是

  • Idefix 允许将布尔值写成 yesno,inifix 也可以这样做,但在 Pluto 中(截至版本 4.4)这些是不合法的。请注意,与 Idefix 不同,Idefix 对这些特殊字符串真正不区分大小写,而从版本 5.0.0 开始的 inifix 只解析一组受限制的未转义字符串作为布尔值,如 trueTRUETrueyesYesYES,但如 TruEyES 这样的字符串将被解析为字符串。
  • Idefix 允许使用十进制表示法来编写整数(例如 1.01e3)。在反序列化这样的字符串时,这会产生一些歧义,因为期望的类型(intfloat)不能被明确猜测。默认情况下,从版本 5.0 开始的 inifix 将这些解析为 float,允许进行 1:1 的往返。从版本 2.1 开始的 Idefix 对以十进制形式编写的整数也有抵抗力,因此 inifix 不会通过加载/修补/转储例程破坏任何有效的 inifile。有关更多信息,请参阅 读取选项

文件格式规范详情

展开 !- 参数名称是字母数字字符串 - 名称和值由非换行空白字符分隔 - 值以 Unicode 字符表示 - 所有的值如果可能都视为数字(例如,`1e3` 被读取为 `1000`) - 数字值如果不会丢失精度,则读取为整数,否则为浮点数 - 未转义的字符串 `true`、`false`、`yes` 和 `no` 转换为布尔值,以及它们相应的 uppercase 和 "title" 变体(例如 `TRUE` 或 `True`)。 - 不能读取为数字或布尔值的值被读取为字符串。 - 字符串分隔符 `"` 和 `'` 可用于包含空白的字符串,或用于强制将值作为字符串读取,否则这些值会被读取为数字和布尔值。 - 一个参数可以关联到单个值或由空白字符分隔的值列表 - 部分标题以 `[` 开始,以 `]` 结束 - 注释以 `#` 开始,将被忽略

如果调用 inifix.load(<filename>) 不引发错误,则认为文件是有效的。

示例

以下内容被认为是有效的

# My awesome experiment
[Grid]
x   1 2 u 10    # a comment
y   4 5 l 100
[Time Integrator]
CFL  1e-3
tstop 1E3

并映射到

{
    "Grid": {
        "x": [1, 2, "u", 10],
        "y": [4, 5, "l", 100]
    },
    "Time Integrator": {
        "CFL": 0.001,
        "tstop": 1000.0
    }
}

以下无标题的格式不符合 Pluto/Idefix 的规范,但在 inifix 中也是有效的

mode   fargo

# Time integrator
CFL    1e-3
tstop  1e3

并映射到

{
    "mode": "fargo",
    "CFL": 0.001,
    "tstop": 1000.0
}

请注意,使用 e-表示法(例如 1e-31E3)的字符串被解码为浮点数。相反,在写入文件时,如果使用 e-表示法会导致更紧凑的表示,则浮点数将以 e-表示法重新编码。例如,100000.0 被编码为 1e5,但 189.0 保持不变,因为 1.89e2 需要多一个字符。在两种表示法同样紧凑的情况下(例如 1.01e0),在编码时优先使用十进制。

在解码时,`e` 可以是大写或小写,但它们在编码时始终以小写形式表示。

安装

python -m pip install inifix

用法

公共 API 模仿 Python 标准库 json,并包含四个主要功能

  • inifix.loadinifix.dump 分别从文件读取和写入
  • inifix.loadsstr 读取并返回一个 dict,而 inifix.dumps 执行相反的操作。

读取数据

inifix.load 从文件读取并返回一个 dict

import inifix

with open("pluto.ini", "rb") as fh:
    conf = inifix.load(fh)

# or equivalently
conf = inifix.load("pluto.ini")

假设文件以 UTF-8 编码。

读取选项

inifix.loadinifix.loads 接受一个可选的布尔标志 parse_scalars_as_lists(从 inifix v4.0.0 开始),这对于简化处理未知数据很有用:所有值都可以安全地处理为数组,并且可以迭代,即使在存在标量字符串的情况下也是如此。例如

>>> import inifix
>>> from pprint import pprint
>>> pprint(inifix.load("example.ini"))
{'Grid': {'x': [1, 2, 'u', 10], 'y': [4, 5, 'l', 100]},
 'Time Integrator': {'CFL': 0.001, 'tstop': 1000.0}}
>>> pprint(inifix.load("example.ini", parse_scalars_as_lists=True))
{'Grid': {'x': [1, 2, 'u', 10], 'y': [4, 5, 'l', 100]},
 'Time Integrator': {'CFL': [0.001], 'tstop': [1000.0]}}

inifix.loadinifix.loads 也接受一个 integer_casting 参数(自 inifix v5.0.0 新增),该参数可以设置为决定十进制表示法中恰好具有整数值的数字(例如 1e230000.)应该如何解析。此参数接受两个值:'stable'(默认值)提供 float 而且提供 'aggressive' 提供 int,与 inifix v4.5.0 的行为相匹配。

主要区别在于默认策略在类型上是往返稳定的,而积极模式则不是

>>> import inifix
>>> data = {'option_a': [0, 1., 2e3, 4.5]}
>>> data
{'option_a': [0, 1.0, 2000.0, 4.5]}
>>> inifix.loads(inifix.dumps(data))
{'option_a': [0, 1.0, 2000.0, 4.5]}
>>> inifix.loads(inifix.dumps(data), integer_casting='aggressive')
{'option_a': [0, 1, 2000, 4.5]}

积极转换也可能导致超出一定范围的精度丢失

>>> import inifix
>>> data = {'option_b': 9_007_199_254_740_993}
>>> inifix.loads(inifix.dumps(data))
{'option_b': 9007199254740993}
>>> inifix.loads(inifix.dumps(data), integer_casting='aggressive')
{'option_b': 9007199254740992}

默认情况下,inifix.loadinifix.loads 验证输入数据。可以通过指定 skip_validation=True 跳过此步骤。

写入文件或字符串

inifix.dump 将数据写入文件。

一个典型用例是将 inifix.loadinifix.dump 结合起来,通过加载/修补/转储例程在运行时程序化地更新现有配置文件。

>>> import inifix
>>> with open("pluto.ini", "rb") as fr:
...    inifix.load(fr)
>>> conf["Time"]["CFL"] = 0.1
>>> with open("pluto-mod.ini", "wb") as fw:
...    inifix.dump(conf, fw)

或者,等价地

>>> import inifix
>>> inifix.load("pluto.ini")
>>> conf["Time"]["CFL"] = 0.1
>>> inifix.dump(conf, "pluto-mod.ini")

在写入时,数据将根据 inifix 的格式规范进行验证。文件始终以 UTF-8 编码。

inifix.dumpsinifix.dump 相同,不同之处在于它返回一个字符串而不是写入文件。

>>> import inifix
>>> data = {"option_a": 1, "option_b": True}
>>> print(inifix.dumps(data))
option_a 1
option_b True

默认情况下,inifix.dumpinifix.dumps 验证输入数据。可以通过指定 skip_validation=True 跳过此步骤。

模式验证

inifix.validate_inifile_schema 可以用来验证任意字典是否符合 inifile 的格式,遵循库的格式。如果字典 data 无效,将引发异常(ValueError)。

inifix.validate_inifile_schema(data)

运行时格式化

inifix.format_string 格式化表示 ini 文件内容的字符串。有关如何大规模使用此功能,请参阅 格式化 CLI

编写类型安全的 inifix.load(s) 应用程序

inifix.load 对任何特定参数的类型没有内置期望;相反,所有类型都在运行时推断,这使得该函数能够无缝地与任意参数文件一起工作。

但这也意味着输出不是(也不能是)类型安全的。换句话说,类型检查器(例如 mypy)无法推断输出的确切类型,这在依赖类型检查的应用程序中是一个问题。

此问题的解决方案实际上不仅限于 inifix.load,并且与任何任意形成的 dict 都兼容,即创建一个管道,其中实现可类型检查的代码,其中数据也进行运行时验证。

我们将通过一个来自 nonos 的真实生活示例来展示这一点。例如,如果我们只关心 idefix.ini[Output][Hydro] 部分的几个参数。让我们围绕这些参数构建一个类型安全的 read_parameter_file 函数。

class IdefixIni:
    def __init__(self, *, Hydro, Output, **kwargs):
        self.hydro = IdefixIniHydro(**Hydro)
        self.output = IdefixIniOutput(**Output)

class IdefixIniHydro:
    def __init__(self, **kwargs):
        if "rotation" in kwargs:
            self.frame = "COROT"
            self.rotation = float(kwargs["rotation"])
        else:
            self.frame = "UNSET"
            self.rotation = 0.0

class IdefixIniOutput:
    def __init__(self, *, vtk, **kwargs):
        self.vtk = float(vtk)

def read_parameter_file(file) -> IdefixIni:
    return IdefixIni(**inifix.load(file))

ini = read_parameter_file("idefix.ini")

类型检查器现在可以安全地假设 ini.hydro.frameini.hydro.rotationini.output.vtk 都存在,并且分别为 strfloatfloat 类型。如果在运行时不验证此假设,将引发 TypeError

请注意,我们已使用通配符 **kwargs 构造来捕获可选参数以及任何其他我们不在乎的参数(可能是没有)。

命令行界面

包附带命令行工具,用于验证或格式化兼容的 inifiles。

验证 CLI

此命令行工具检查您的 inifiles 是否可以使用 inifix.load 从命令行加载。

$ inifix-validate pluto.ini
Validated pluto.ini

此 CLI 也可以作为 python -m inifix.validate 调用。

格式化 CLI

要就地格式化文件,请使用

$ inifix-format pluto.ini

inifix格式保证保留注释,并且只编辑(添加或删除)空白字符。

文件始终以UTF-8编码。

要打印到标准输出而不是编辑文件,请使用--diff标志

$ inifix-format pluto.ini --diff

此CLI也可以作为python -m inifix.format调用。

默认情况下,inifix-format也会验证输入数据。可以通过--skip-validation标志跳过此步骤

pre-commit钩子

inifix-validateinifix-format可以作为以下配置的pre-commit钩子使用(添加到.pre-commit-config.yaml

  - repo: https://github.com/neutrinoceros/inifix.git
    rev: v5.0.2
    hooks:
      - id: inifix-validate

或者

  - repo: https://github.com/neutrinoceros/inifix.git
    rev: v5.0.2
    hooks:
      - id: inifix-format

请注意,inifix-format默认也会验证数据,因此同时使用两个钩子是多余的。验证和格式化可以解耦如下

  - repo: https://github.com/neutrinoceros/inifix.git
    rev: v5.0.2
    hooks:
    - id: inifix-validate
    - id: inifix-format
+     args: [--skip-validation]

默认情况下,这两个钩子针对匹配正则表达式(\.ini)$的文件。可以覆盖此表达式,例如

   hooks:
   - id: inifix-format
+    files: (\.ini|\.par)$

测试

我们使用pytest框架来测试inifix。可以通过简单的pytest调用从顶级运行测试套件。

$ python -m pip install --requirement requirements/tests.txt
$ pytest

项目详情


下载文件

下载您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分布

inifix-5.0.2.tar.gz (32.3 kB 查看散列)

构建分布

inifix-5.0.2-py3-none-any.whl (29.6 kB 查看散列)

Python 3

由以下支持