Python configuration file format

3. Writing the Setup Configuration File¶

This document is being retained solely until the setuptools documentation at https://setuptools.readthedocs.io/en/latest/setuptools.html independently covers all of the relevant information currently included here.

Often, it’s not possible to write down everything needed to build a distribution a priori: you may need to get some information from the user, or from the user’s system, in order to proceed. As long as that information is fairly simple—a list of directories to search for C header files or libraries, for example—then providing a configuration file, setup.cfg , for users to edit is a cheap and easy way to solicit it. Configuration files also let you provide default values for any command option, which the installer can then override either on the command-line or by editing the config file.

The setup configuration file is a useful middle-ground between the setup script—which, ideally, would be opaque to installers 1—and the command-line to the setup script, which is outside of your control and entirely up to the installer. In fact, setup.cfg (and any other Distutils configuration files present on the target system) are processed after the contents of the setup script, but before the command-line. This has several useful consequences:

  • installers can override some of what you put in setup.py by editing setup.cfg
  • you can provide non-standard defaults for options that are not easily set in setup.py
  • installers can override anything in setup.cfg using the command-line options to setup.py
Читайте также:  Html style justify right

The basic syntax of the configuration file is simple:

where command is one of the Distutils commands (e.g. build_py, install), and option is one of the options that command supports. Any number of options can be supplied for each command, and any number of command sections can be included in the file. Blank lines are ignored, as are comments, which run from a ‘#’ character until the end of the line. Long option values can be split across multiple lines simply by indenting the continuation lines.

You can find out the list of options supported by a particular command with the universal —help option, e.g.

$ python setup.py --help build_ext [. ] Options for 'build_ext' command: --build-lib (-b) directory for compiled extension modules --build-temp (-t) directory for temporary files (build by-products) --inplace (-i) ignore build-lib and put compiled extensions into the source directory alongside your pure Python modules --include-dirs (-I) list of directories to search for header files --define (-D) C preprocessor macros to define --undef (-U) C preprocessor macros to undefine --swig-opts list of SWIG command line options [. ] 

Note that an option spelled —foo-bar on the command-line is spelled foo_bar in configuration files.

For example, say you want your extensions to be built “in-place”—that is, you have an extension pkg.ext , and you want the compiled extension file ( ext.so on Unix, say) to be put in the same source directory as your pure Python modules pkg.mod1 and pkg.mod2 . You can always use the —inplace option on the command-line to ensure this:

python setup.py build_ext --inplace

But this requires that you always specify the build_ext command explicitly, and remember to provide —inplace . An easier way is to “set and forget” this option, by encoding it in setup.cfg , the configuration file for this distribution:

This will affect all builds of this module distribution, whether or not you explicitly specify build_ext. If you include setup.cfg in your source distribution, it will also affect end-user builds—which is probably a bad idea for this option, since always building extensions in-place would break installation of the module distribution. In certain peculiar cases, though, modules are built right in their installation directory, so this is conceivably a useful ability. (Distributing extensions that expect to be built in their installation directory is almost always a bad idea, though.)

Another example: certain commands take a lot of options that don’t change from run to run; for example, bdist_rpm needs to know everything required to generate a “spec” file for creating an RPM distribution. Some of this information comes from the setup script, and some is automatically generated by the Distutils (such as the list of files installed). But some of it has to be supplied as options to bdist_rpm, which would be very tedious to do on the command-line for every run. Hence, here is a snippet from the Distutils’ own setup.cfg :

[bdist_rpm] release = 1 packager = Greg Ward doc_files = CHANGES.txt README.txt USAGE.txt doc/ examples/ 

Note that the doc_files option is simply a whitespace-separated string split across multiple lines for readability.

Syntax of config files in “Installing Python Modules”

More information on the configuration files is available in the manual for system administrators.

This ideal probably won’t be achieved until auto-configuration is fully supported by the Distutils.

Источник

Конфигурационные файлы в Python

Конфиги. Все хранят их по разному. Кто-то в .yaml , кто-то в .ini , а кто-то вообще в исходном коде, подумав, что «Путь Django» с его settings.py действительно хорош.

В этой статье, я хочу попробовать найти идеальный (вероятнее всего) способ хранения и использования конфигурационных файлов в Python. Ну, а также поделиться своей библиотекой для них 🙂

Попытка №1

А что насчёт того чтобы хранить конфигурацию в коде? Ну, а что, вроде удобно, да и новых языков не придётся изучать. Существует множество проектов, в которых данный способ используется, и хочу сказать, вполне успешно.

Типичный конфиг в этом стиле выглядит так:

# settings.py TWITTER_USERNAME="johndoe" TWITTER_PASSWORD="johndoespassword" TWITTER_TOKEN=". "

Выглядит неплохо. Только одно настораживает, почему секьюрные данные хранятся в коде? Как мы это коммитить будем? Загадка. Разве что вносить наш файл в .gitignore , но это, конечно, вообще не решение.

Да и вообще, почему хоть какие-то данные хранятся в коде? Как мне кажется код, он на то и код, что должен выполнять какую-то логику, а не хранить данные.

Данный подход, на самом деле используется много где. В том же Django. Все думают, что раз это самый популярный фреймворк, который используется в самом Инстаграме, то они то уж плохое советовать не будут. Жаль, что это не так.

Попытка №2

Ладно, раз уж мы решили, что хранить данные в коде — не круто, то давайте искать альтернативу. Для конфигурационных файлов изобретено немалое количество различных форматов, в последнее время набирают большую популярность toml .

Но мы начнём с того, что нам предлагает сам Python — .ini . В стандартной библиотеке имеется библиотека configparser .

Наш конфиг, который мы уже писали ранее:

# settings.ini [Twitter] username="johndoe" password="johndoespassword" token=". "

А теперь прочитаем в Python:

import configparser # импортируем библиотеку config = configparser.ConfigParser() # создаём объекта парсера config.read("settings.ini") # читаем конфиг print(config["Twitter"]["username"]) # обращаемся как к обычному словарю! # 'johndoe'

Все проблемы решены. Данные хранятся не в коде, доступ прост. Но… а если нам нужно читать другие конфиги, ну там json или yaml например, или все сразу. Конечно, есть json в стандартной библиотеке и pyyaml , но придётся написать кучу (ну, или не совсем) кода для этого.

Попытка №3

А сейчас, я хотел бы показать Вам свою библиотеку, которая призвана решить все эти проблемы (ну, или хотя бы уменьшить ваши страдания :)).

Называется она betterconf и доступна на PyPi.

Установка так же проста, как и любой другой библиотеки:

Изначально, наш конфиг представлен в виде класса с полями:

# settings.py from betterconf import Config, field class TwitterConfig(Config): # объявляем класс, который наследуется от `Config` username = field("TWITTER_USERNAME", default="johndoe") # объявляем поле `username`, если оно не найдено, выставляем стандартное password = field("TWITTER_PASSWORD", default="johndoespassword") # аналогично token = field("TWITTER_TOKEN", default=lambda: raise RuntimeError("Account's token must be defined!") # делаем тоже самое, но при отсутствии токенавозбуждаем ошибку cfg = TwitterConfig() print(cfg.username) # 'johndoe'

По умолчанию, библиотека пытается взять значения из переменных окружения, но мы также можем настроить и это:

from betterconf import Config, field from betterconf.config import AbstractProvider import json class JSONProvider(AbstractProvider): # наследуемся от абстрактного класса SETTINGS_JSON_FILE = "settings.json" # путь до файла с настройками def __init__(self): with open(self.SETTINGS_JSON_FILE, "r") as f: self._settings = json.load(f) # открываем и читаем def get(self, name): return self._settings.get(name) # если значение есть - возвращаем его, иначе - None. Библиотека будет выбрасывать свою исключением, если получит None. provider = JSONProvider() class TwitterConfig(Config): username = field("twitter_username", provider=provider) # используем наш способ получения данных # . cfg = TwitterConfig() # . 

Из этого примера следует, что мы можем применять различные провайдеры для получения данных. И это действительно иногда бывает удобно, говорю из личного опыта.

Хорошо, а что если у нас в конфигах есть булевые значения, или числа, они же в итоге будут все равно приходить в строках. И для этого есть решение:

from betterconf import Config, field # из коробки доступно всего 2 кастера from betterconf.caster import to_bool, to_int class TwitterConfig(Config): # . post_tweets = field("TWITTER_POST_TWEETS", caster=to_bool) # . 

Таким образом, все похожие на булевые типы значения (а именно true и false будут преобразованы в питоновский bool . Регистр не учитывается.

Свой кастер написать также легко:

from betterconf.caster import AbstractCaster class DashToDotCaster(AbstractCaster): def cast(self, val): return val.replace("-", ".") # заменяет тире на точки to_dot = DashToDotCaster() # . 

Итоги

Таким образом, мы пришли к выводу, что хранить настройки в исходных кодах — не есть хорошо. Для этого уже придуманы различные форматы. Ну, а вы познакомились с ещё одной полезной (как я считаю :)) библиотекой.

P.S

Да, также можно было включить и Pydantic , но я считаю, что он слишком НЕлегковесный для таких задач.

Источник

Оцените статью