Как максимально просто хранить/загружать настройки вашего python-приложения ?

Если вы часто пишете что-то на python — необязательно даже какие-то большие проекты, просто какие админские утилиты под себя, — то рано или поздно придется задуматься о хранении настроек вашего приложения/скрипта.Конечно для совсем небольшого скрипта многие просто делают кортеж типа «опция»-«значение», сбрасывают его в простой текстовый файл и потом с ним как-то работают.Очевидно, что это подходит для скриптов, которые будут поддерживаться со временем.

Кроме того в силу своей гибкости python позволяет создать файл вроде settings.py и исполнять его на уровне кода.
То есть просто сделать settings.py таким :

import os.path as path
conf_path = path.join(path.dirname(__file__), "config")
my_settings = {
    'item 1' : "value 1",
    'item 2' : "value 2",
     ...
}

А потом его импортировать и как-то работать :

import settings
...
def main():
    ...
    print settings.conf_path
    ...

Но какждый раз лезть в settings.py и менять там какие-то опции это явно не комильфо.Следует ,как мне кажется, максмимально разделять логику и настройки.

Человечество уже давно придумало такую штуку, как XML. Существует огромное число библиотек для работы с XML и python-биндингов к ним. Несложно сериализовать обьекты в XML. Очень много софта используют XML для хранения настроек. Самый близкий пример — plist файлы в Mac OS/iOS и т.д.

Но мне кажется есть как минимум один недостаток (кроме производительности) — такой конфиг как неудобно редактировать руками человеку.Смотреть на портянку тегов как-то не очень.

Поэтому есть компромисс между XML и простыми текстовыми настройками — это так называемые .conf файлы, использумые широко в *nix/linux софте. Например, smb.conf для настроек сервера samba :

Синтаксис такого файла прост : он состоит из одного или более именованных разделов, каждый из которых может содержать отдельные параметры с именами и значениями.Разделом являются строки, начинающиеся с «[» и заканчивающиеся на  «]«. Значение в квадратных скобках —  название раздела, и может содержать любые символы, кроме квадратных скобок.

Простой пример :

[database_connection]
dbtype = mysql
url = http://localhost:3306
username = admin
password = admin_password

Сохраним на вымышленный конфиг подключения к серверу БД как dbconnect.conf.

Теперь каким образом без ручного считывания и парсинга этого файла сделать класс аля Settings? Да все просто. Уже написан отличный модуль ConfigParser, о котором собственно весь пост. Ставим этот модуль при помощи easy_install :

sudo easy_install ConfigParser

Или при помощи pip. В моей K/Ubuntu 11.10 для python 2.7 он уже установлен.И так, пилим класс (пусть будет в отдельном файле MyBDConfig.py):

from ConfigParser import SafeConfigParser

class BDProperties:
        def __init__(self, fname):
                self.filename = fname;
                self.cfgParser = SafeConfigParser();
        def load(self):
                self.cfgParser.read(self.filename);
        def getDBType(self):
                return self.cfgParser.get('database_connection','dbtype');
        def getURL(self):
                return self.cfgParser.get('database_connection','url');
        def getLogin(self):
                return self.cfgParser.get('database_connection','username');
        def getPass(self):
                return self.cfgParser.get('database_connection','password');

Итак, здесь мы импортировали необходимый нам модуль ConfigParser и использовали класс SafeConfigParser.Собственно после инициализации мы считываем наш конфиг-файл методом read(), который может принимать несколько путей к файлам и для каждого из них создать объект типа SafeConfigParser.
Для доступа к опции значению опции «op» секции «section» используем вызов метода get(«section»,»op»), как здесь :

...
        def getDBType(self):
                return self.cfgParser.get('database_connection','dbtype');
...

Проверяем — я для этого использую чумовую штуку под названием ipython :

Всё ок.Вот таким нехитрым способом можно считывать конфиги.read () возвращает список, содержащий имена файлов, успешно загруженных , чтобы ваше приложение могло определить, каких файлов конфигурации не хватает, и решить, следует ли игнорировать их.

Модуль легко подружить с юникодовыми конфигами, используя codecs.

С помощью этого модуля можно лекго пробежаться по структуре всего конфига, например так :

from ConfigParser import SafeConfigParser

parser = SafeConfigParser()
parser.read('/home/teddy/config/dbconnect.conf')

for section_name in parser.sections():
        print 'Section:', section_name
        print '  Options:', parser.options(section_name)
        for name, value in parser.items(section_name):
                print '  %s = %s' % (name, value)

Что даст :

teddy@teddy-System-Product-Name:~/config$ python explore.py
Section: database_connection
  Options: ['dbtype', 'url', 'username', 'password']
  dbtype = mysql
  url = http://localhost:3306
  username = admin
  password = admin_password
teddy@teddy-System-Product-Name:~/config$

В качестве разделителя опции и её значения можно использовать как «=«, так и «:«.
Остается лишь понять, как модифицировать наш конфиг. Для того чтобы модифицировать значение «value» опции «option» в секции «section» нужно сделать так :

...
ConfigParser.set('section','option','value');
...

Создать новый файл конфигурации что называется «from scratch» тоже легко :

import sys,ConfigParser

parser = ConfigParser.SafeConfigParser()

config_file = open("/home/teddy/config/new_config.conf","w")

parser.add_section('section 1')
parser.set('section 1', 'option 1', 'val 1')
parser.set('section 1', 'option 2', 'val 2')

for section in parser.sections():
    print section
    for name, value in parser.items(section):
        print '  %s = %r' % (name, value)

parser.write(config_file)

Как видим, был создан требуемый файл конфигурации :

Собственно, для того чтобы начать правильно хранить конфиги ваших приложений на python, этого достаточно.За бортом остались вопросы валидации, интерполяции значений и DEFAULT секции.Все это есть здесь :

Как работать с буфером обмена KDE?

Одной из самых продвинутых сред рабочего стола вообще, и по-моему мнению лучшей для настольной машины под Linux(почему для именно для настольной — см. нынешнее состояние Gnome 3).

Не в последнюю очередь это обусловлено это тем, что у KDE помимо единого целостного API(+ никто не запрещает пользоваться Qt в чистом виде), у пользователя всегда есть возможность автоматизировать часть рутинной работы.

Подобная задача возникла и у меня — нужно было иметь возможность получать содержимое буфера обмена, пробегать по нему парой регулярок, и опять сохранить результат в буфер.
Стоит сразу сказать, что речь не идет о использовании python скриптов или c++ — об этом позже.

Содержимым буфера обмена удобнее всего управлять при помощи утилиты Klipper.Вообще это довольно мощная утилита, среди фич которой стоит выделить :

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

Применений этому можно найти массу : так например, можно при копировании в буфер определенного доменного именни можно вызвать утилиту dig, получать от нее вывод, обработать c помощью sed/awk и получить список IP-адресов, связанных с этим именем.
Подробнее эта возможность рассмотрена на Хабре

Так что будем рулить буфером обмена klipper, не трогая сам klipper мышкой, из консоли.
А для этого нам понадобится D-Bus.Вообще D-Bus можно посвятить целую серию постов, однако пост и так выходит большим, поэтому ограничюсь цитатой из русской Википедии :

D-Bus — система межпроцессного взаимодействия, которая позволяет приложениям в операционной системе общаться друг с другом…
D-Bus предоставляет системе несколько шин:

  1. Системная шина. Создаётся при старте демона D-Bus. С её помощью происходит общение различных демонов, она практически недоступна для пользовательских приложений
  2. Сессионная шина. Создаётся для пользователя, авторизировавшегося в системе. Для каждой такой шины запускается отдельная копия демона, посредством неё будут общаться приложения, с которыми работает пользователь.

Каждое сообщение D-Bus, передаваемое по шине, имеет своего отправителя и своего получателя, их адреса называются путями объектов, поскольку D-Bus предполагает, что каждое приложение состоит из набора объектов, а сообщения пересылаются не между приложениями, а между объектами этих самых приложений.
Каждый объект может поддерживать один или более интерфейсов, которые представлены здесь в виде именованных групп методов и сигналов…
D-Bus также предусматривает концепцию сервисов. Сервис — уникальное местоположение приложения на шине. При запуске приложение регистрирует один или несколько сервисов, которыми оно будет владеть до тех пор, пока самостоятельно не освободит, до этого момента никакое другое приложение, претендующее на тот же сервис, занять его не сможет.
Сервисы делают доступной ещё одну функцию — запуск необходимых приложений в случае поступления сообщений для них. Для этого должна быть включена автоактивация, а в конфигурации D-Bus за этим сервисом должно быть закреплено одно приложение. Тогда D-Bus сможет его запустить при появлении сообщения

Подробно о D-Bus можно узнать здесь.Возможно позже сделаю ряд постов поглубже о этой технологии, а пока вставлю сюда общую схему работы :

Для работы с D-Bus существует целый ряд утилит, главная из которых — консольная dbus, или её реализация для KDE — qdbus.

Из man страницы узнаем общий синтаксис утилиты qdbus :

qdbus [--system] [--literal] [servicename] [path] [method] [args]

Рассмотрим аргументы запуска :

  • —system — подключение к системной шине
  • —literal   — вывод в литеральном виде

Рассмотрим параметры

  • servicename — имя сервиса, реализуемого приложением
  • path — путь к сервису в UNIX — формате, корень — «/«
  • method — вызов метода данного сервиса
  • args — аргументы вызова метода

Вообщем похоже на обычную работу с классами в современных языках программирования , только здесь у нас вместо классов — интерфейсы, имеющие методы и свойства (поля).В этом то и есть одна из целей проекта D-Bus.

Список всех доступных сервисов можно получить, вызвав утилиту без параметров.При помощи  grep можно легко выяснить что среди таковых есть и сервис

teddy@teddy-System-Product-Name:~$ qdbus | grep klipper
-> org.kde.klipper

Забегая вперёд скажу, что в данном случае корень для параметра path будет «org.kde» в имени сервиса. То есть для того чтобы составить правильно путь к сервису klipper, нужно заменить «org.kde.klipper» на «/klipper» . Пользуемся клавишей Tab чтобы узнать, чтомы можем сделать с этим интерфейсом :

Видим список методов и свойств этого интерфейса.Сразу бросаются в глаза три группы :

  • с префиксом org.freedesktop.Dbus  — методы, реализующие непосредственно D-Bus.О них — отдельный разговор, ибо это сердце этой технологии (интроспекция, маршаллинг и прочее)
  • с префиксом org.kde.klipper.klipper — легко догадаться это и есть нужные методы klipper’а
  • QString/QVariantMap/Q* — структуры данных Qt, ибо под капотом у KDE — Qt.

Видим метод

org.kde.klipper.klipper.getClipboardContents

Который как-бэ намекает своим названием, что должен на вернуть содержимое буфера обмена.Проверим :

Так оно и есть.Мы получили строку (QString) с содержимым вершины стека буфера обмена(если у нас клиппер настроен на сохранение истории буфера обмена) или просто содержимым этого буфера обмена.

Если нам нужен не последний элемент буфера в истории, то берем в руки метод

org.kde.klipper.klipper.getClipboardHistoryItem

Передаем ему номер элемента от вершины стека истории (то есть последний скопированный элемент будет иметь индекс 0 и находиться на вершине стека)

Очистить буфер обмена можно используя методы :

org.kde.klipper.klipper.clearClipboardHistory
org.kde.klipper.klipper.clearClipboardContents

Наконец, для того чтобы записать в вершину стека истории буфера обмена, можно использовать метод :

org.kde.klipper.klipper.setClipboardContents

Проверим :

Остальные методы связанна с кастомными действиями над содержимым буфера, вызовом меню и прочим.

Вот таким простым способом можно манипулировать буфером обмена KDE.Учитывая, что биндиги к D-Bus есть как в Qt, так и в виде модуля для Python, применений этой штуке можно найти массу, все ограниченно лишь Вашей фантазией.

Ссылки :