Python 包层次结构

Python package hierarchy

我开发了一个Python包。我的结构如下:

- my_package
  - conf
  ...
  - src
    - my_package
       - main.py
         ...

为了执行包,我需要从conf 目录中读取一些配置文件。最初,我考虑将 conf 目录放在包外,以便在用户克隆包存储库时将其暴露给用户,以便用户可以轻松查看其中找到的文件并根据需要进行修改。

当然,用户可以更改配置文件的路径,使其与其位置相匹配。

BUT 我还希望安装包 运行 开箱即用。因此,在代码内部,默认值是相对于main.py设置的。这适用于开发模式。

但是,安装包时,结构发生变化,变为:

- /some/path/to/env/Lib/site-packages/my_package.egg
  - conf
  - my_package
    main.py

并且我在结构中丢失了一层,从而破坏了我最初设置的相对路径。难道我做错了什么 ?我遵循 these 包装原则。我当然可以很容易地添加一个开发标志,它改变了在开发模式和生产模式下处理路径的方式。但我觉得我在包装规则中遗漏了一些更基本的东西,我想确定一下。

谢谢!

用户配置通常应保存在应用程序配置的 os 特定位置。作为包作者通常的处理方式是

  • 编写默认配置文件并将其放入包中,例如在 src/my_package/default.ini - 这比将配置写成代码要好,就像你的 main.py 可能做的那样,因为你可以重新使用你的解析步骤,可以复制默认配置以获得有效的用户配置,并且只需要维护一个文件类型
  • 将默认配置复制到用户特定的目录中,使用 appdirs 找出每个 os 上哪个是正确的
    • 您可以询问您的用户是否可以创建新配置,例如/home/user/.configs/my_app 并在确认后执行,或者只是执行并告诉他们在哪里可以找到它,以防他们想要自定义某些东西
  • 在应用程序启动时,首先解析默认配置,然后解析用户配置并覆盖不同的设置
  • 如果您还允许通过环境变量进行配置,ose 将在之后进行解析,并且应该覆盖默认配置和用户配置

作为起点,我的配置解析通常看起来像这样:

config.py,在你包裹的某处

from configparser import ConfigParser
from pathlib import Path
import os

# 2nd party in python3.8+ only, pip-install and use importlib_resources on <3.7 
import importlib.resources  

# 3rd party, needs needs to be pip-installed
from appdirs import user_config_dir  

# a ConfigParser instance behaves like a dictionary, but has some nice utility
# like parsing .ini files, a common config file format. 
#: import this object to access the package's configuration
config = ConfigParser(interpolation=None)


def load_default_config():
    # this is how to access data files within a package 
    with importlib.resources.path("my_package", "default.ini") as default_config:
        with default_config.open() as f:
            config.read_file(f, source="default")

def load_user_config():
    user_config = Path(appdirs.user_config_dir("my_package")) / "user.ini"
    try:
        with user_config.open() as f:
            config.read_file(f, source="user")
    except FileNotFoundError:
        print(f"User config expected at '{user_config}', but not found.")


load_default_config()
load_user_config()
load_env()  # in case you need it

要填充初始用户配置,您可以 运行 像这样一次:

import importlib.resources
import pathlib
import os

import appdirs


with importlib.resources.path("my_package", "default.ini") as config:
    with config.open() as f:
        content = f.read()
target = pathlib.Path(appdirs.user_config_dir("my_package")) / "user.ini"
os.makedirs(target.parent)
with open(target, "w") as f:
    f.write(content)
print(f"Created new user config at {target}.")