Release 0.4.0 (2016-11-24)¶
Developer Changes¶
Getting rid of Facade
¶
В предыдущих версиях фреймворка ram функции для взаимодействия с пользователем были доступны как методы класса Facade
.
Разработчику требовалось создать объект этого класса в коде юнита {unit},
а при необходимости взаимодействия с пользователем из вызываемых в коде функций - передать созданный экземпляр Facade
{instance of Facade} в качестве аргумента.
Помимо функций для взаимодействия с пользователем, Facade
также предоставлял архаичные методы для вызова процессов:
RunCommand
для запуска процесса в приоритетном режиме {in foreground} и ExecTask
для запуска процесса в фоновом режиме {in background}.
В версии 0.4.0 фреймворка ram проведен рефакторинг целью устранения объектов класса Facade
. Методы для взаимодействия
с пользователем вынесены в модуль {module} ram.widgets
и доступны из любого места программы как функции этого модуля. Методы
для вызова внешних процессов расширены и вынесены в модуль ram.process
.
Например, код для отображения диалогового окна с вопросом к пользователю и вызовом процесса в предыдущих версиях фреймворка мог выглядеть так:
import ram.unitlib
def Run(facade):
if facade.RunCommand('rm -rf /'):
facade.ShowMessage("Success", "All files removed!")
else:
facade.ShowError("Failure", "Failed to remove all files!")
if __name__ == '__main__':
facade = ram.unitlib.Facade()
if facade.AskViaButtons(
"Remove all files",
"Would you like to remove all files?"
):
Run(facade)
В результате изменений, доступных в новой версии фреймворка, этот код будет выглядеть так:
import ram.widgets
import ram.process
def Run():
if ram.process.launch('rm -rf /'):
ram.widgets.ShowMessage("Success", "All files removed!")
else:
ram.widgets.ShowError("Failure", "Failed to remove all files!")
if __name__ == '__main__':
if ram.widgets.AskViaButtons(
"Remove all files",
"Would yo like to remove all files?
):
Run()
Process management¶
Все функции для работы с внешними процессами теперь сосредоточены в модуле ram.process
.
В предыдущих релизах фреймворка ram они были не систематизированы.
Часть из этих функций была доступна в качестве методов упразденного класса Facade
. Другая часть - в виде статических функций класса Process
из уже упомянутого модуля ram.process
(класс Process
также был упразднен).
Работа с процессами реализована в виде функций верхнего уровня.
Ниже представлено несколько примеров того, как выглядела работа с процессами при использовании предыдущих версий фреймворка:
import ram.unitlib
from ram.process import Process
facade = Facade()
# run process and get it's exit code
pinged = facade.RunCommand('ping -c1 localhost')
# same but using Process
pinged = Process.launch('ping -c1 localhost')
# get process output or errors
try:
output = Process.iopipe('ls -1 /tmp')
except RuntimeError as err:
facade.ShowMessage("errors", err)
При использовании версии фреймворка ram 0.4.0 эти команды выглядят так:
import ram.process
import ram.widgets
# run process and get it's exit code
pinged = ram.process.launch('ping -c1 localhost')
# get process output or errors
try:
output = ram.process.output('ls -1 /tmp')
except RuntimeError as err:
ram.widgets.ShowMessage("errors", err)
# get process exit code, output and errors
status, output, errors = ram.process.run('rm -rf /')
На момент релиза версии 0.4.0 разработчикам доступны следующие основные функции:
-
ram.process.
launch
(command) Вызывает внешний процесс, заданный параметром
command
, в приоритетном режиме.Вызов этой функции идентичен вызову
os.system
, но по умолчанию функция использует реализацию на основе библиотекиsubprocess
. Для отладочных целей можно переключить реализацию этого вызова на использованиеos.system
. Для этого необходимо включить опцию shell:# ram tweak shell on
Parameters: command – Команда, которая должна быть выполнена при вызове функции. Может быть задана строкой или списком строк.
Если команда задана в виде строки, то параметр в неизменном виде передается в качестве аргумента вызываемому shell-у.
Если команда задана в виде списка строк, то элементы списка при необходимости заключаются в кавычки {quoted}, а затем объединяются в строку. Далее функция выполняется так же, как с изначально полученной строкой.
Returns: Код возврата {exit code} завершенного процесса.
-
ram.process.
output
(command, input=None) Вызывает внешний процесс, заданный параметром
command
, и возвращает его вывод, направленный в поток stdout.В случае неуспешного {failure} завершения процесса функция порождает исключение {raises exception} c текстом вывода, направленного в поток stderr.
Parameters: - command –
Команда, которая должна быть выполнена при вызове функции. Может быть задана строкой или списком строк.
Если команда задана в виде строки, то параметр в неизменном виде передается в качестве аргумента вызываемому shell-у.
Если команда задана в виде списка строк, то элементы списка при необходимости заключаются в кавычки {quoted}, а затем объединяются в строку. Далее функция выполняется так же, как с изначально полученной строкой.
- input – Текст, который направляется в поток stdin процесса.
Returns: Текст, полученный из потока stdout процесса.
Raises: RuntimeError – Исключение с текстом из потока stderr процесса, порождаемое в случае неудачного завершения процесса.
- command –
-
ram.process.
run
(command, input=None) Вызывает внешний процесс, заданный параметром
command
. Возвращает кортеж {tuple} из трех элементов - код возврата, вывод в поток stdout и вывод в поток stderr.Parameters: - command –
Команда, которая должна быть выполнена при вызове функции. Может быть задана строкой или списком строк.
Если команда задана в виде строки, то параметр в неизменном виде передается в качестве аргумента вызываемому shell-у.
Если команда задана в виде списка строк, то элементы списка при необходимости заключаются в кавычки {quoted}, а затем объединяются в строку. Далее функция выполняется так же, как с изначально полученной строкой.
- input – Текст, который выводится в поток stdin процесса.
Returns: (exit code, output, errors)
- command –
Watching process events¶
Функции, описанные в предыдущем разделе, предназначены для синхронной работы с процессами. Это значит, что во время запуска внешних процессов выполнение вызывающего кода останавливается. Такой подход не всегда удобен для отображения псевдографического пользовательского интерфейса. Например, если нужно запустить внешний процесс и отобразить результат его выполнения, вместо блокирующего запуска внешнего процесса в приоритетном режиме лучше запустить процесс в фоновом режиме, а когда он завершится - перерисовать интерфейс.
Для асинхронной работы с процессами в модуле ram.process
определены Watch-объекты.
Эти объекты инкапсулируют логику работы с внешними источниками событий.
Каждый экземпляр класса Watch ассоциирован с файловым дескриптором,
который и является источником событий (например, может быть использован совместно с вызовом select
).
Помимо этого, Watch-объекты на основе своей логики интерпретируют данные, поступающие из файлового дескриптора,
и формируют из них очередь поступающих значений.
Watch-объекты не должны создаваться в коде явно.
Для их создания модуль ram.process
предоставляет набор контекстных менеджеров.
Помимо этого, контекстные менеджеры также обеспечивают освобождение связанных с Watch-объектами ресурсов
(например, завершение процессов, запущенных в фоновом режиме).
Для отслеживания событий, поступающих от внешних процессов, в релизе фреймворка ram 0.4.0 определены следующие контекстные менеджеры:
-
ram.process.
watch_status
(command) Порождает Watch-объект для отслеживания завершения процесса, запущенного в фоновом режиме.
-
ram.process.
watch_stdout
(command) Порождает Watch-объект для отслеживания данных, поступающих в поток stdout процесса.
-
ram.process.
watch_stderr
(command) Порождает Watch-объект для отслеживания данных, поступающих в поток stderr процесса.
Например, работа с Watch-объектами может выглядеть следующим образом:
from ram.process import watch_status
# using context manager to get Watch object
# once background process is executed, watch is returned
with watch_status('ping -c1 localhost') as watch:
# blocking call to wait until process exited
# exit status of the process will be printed
print watch()
import time
from ram.process import watch_output
with watch_stdout('ping -c1 localhost') as watch:
# check background process is active
while watch:
# non-blocking iterate over incoming data
for data in watch:
print data
# give a chance for background process
# to continue execution
time.sleep(1.0)
Watch
objects advanced¶
Публичный интерфейс для работы с Watch-объектами:
-
class
ram.process.
Watch
Объект, инкапсулирующий работу с внешними источниками событий.
-
__nonzero__
() Возвращает статус Watch-объекта: True, если источник активен и может посылать события, или False, если источник неактивен (например, если данные, поступающие из файлового дескриптора, закончились).
-
__call__
(timeout=None, iterate=True) Блокирует выполнение до поступления события.
Если параметр iterate принимает значение True, то объект возвращает первое непрочитанное значение из очереди. В ином случае очередь поступивших значений не изменяется, и метод возвращает значение
None
.Время ожидания события можно ограничить с помощью параметра timeout. По умолчанию значение параметра равно
None
, что соответствует бесконечному ожиданию. В качестве значения параметра можно указывать время ожидания в секундах. Если в течение заданного времени события не поступают, функция порождает исключение.
-
__iter__
()¶ Итерация по очереди событий в неблокирующем режиме.
-
Функции для отслеживания событий от внешних процессов, описанные в предыдущем разделе, используют следующие классы Watch-объектов:
-
class
ram.process.
ExitWatch
(ram.process.Watch) Watch-объект для отслеживания завершения процесса, запущенного в фоновом режиме. Значением, получаемым в результате вызова
__call__
, является код возврата процесса.-
__init__(proc):
Для инициализации этого объекта ему необходимо передать объект процесса, порожденный в результате вызова
subprocess.Popen
.Этот объект генерирует событие в момент завершения внешнего процесса. Одновременно с этим объект становится неактивным. Все последующие вызовы
__call__
для этого объекта будут неблокирующими, функция будет возвращать код возврата процесса.
-
-
class
ram.process.
PipeWatch
(ram.process.Watch) Watch-объект для отслеживания данных, поступающих в файловый дескриптор. Значением, получаемым в результате вызова
__call__
, являются данные, считанные из файлового дескриптора.-
__init__(file):
Для инициализации этого объекта ему необходимо передать файловый объект {file-like object}. Объект должен поддерживать метод
fileno()
для получения ассоциированного файлового дескриптора.
-
Tracking watches¶
Описанные выше функции watch_status
, watch_stdout
и watch_stderr
удобны для отслеживания событий, поступающих от внешних процессов.
Используя их совместно с произвольными shell-скриптами, можно создавать более сложные конструкции.
Например, наивная реализация интервального таймера, генерирующего события каждую секунду, могла бы выглядеть так:
from ram.process import watch_stdout
# run script that repeatedly:
# sleeps for a second
# prints current date and time
with watch_stdout('while true; do sleep 1; date; done') as watch:
# wait for timer tick
# print current time
print watch()
Однако подобное использование Watch-объектов имеет следующие проблемы:
- Необходимо оформлять логику генерации событий в виде отдельного скрипта, что затрудняет процесс отладки и ограничивает переиспользование кода.
- Данные от внешнего процесса поступают в виде непрерывного потока символов, то есть отдельные сообщения в нем не имеют гарантированных границ.
Для преодоления этих ограничений
модуль ram.process
предоставляет функцию watch_iterable
. С ее помощью можно построить Watch-объекты
на основе произвольного блокирующего генератора, реализованного на языке Python.
Запуск генератора осуществляется асинхронно с использованием библиотеки multiprocessing
.
Объекты, возвращаемые оператором yield
в коде генератора,
поступают в очередь Watch-объекта в неизменном виде.
Возвращаемые объекты должны быть сериализуемы с помощью модуля pickle {picklable}.
При этом не требуется специальной адаптации кода функции-генератора для создания Watch-объекта -
оригинальный генератор по-прежнему можно использовать в синхронном режиме.
Для реализации аналогичной функциональности наивного интервального таймера средствами языка Python можно использовать такой код:
import time
def naive_timer():
while True:
time.sleep(1.0)
yield time.time()
Для того чтобы сделать Watch-объект на основе этого кода, необходимо воспользоваться функцией watch_iterable
:
-
watch_iterable
(iterable, name=None) Запускает итерацию по переданному итерируемому объекту в параллельном процессе. Порождает Watch-объект для отслеживания событий и получения данных от этого генератора.
Parameters: - iterable – Итерируемый объект (например, инициализированный экземпляр функции-генератора).
- name – Имя, используемое для создаваемого процесса в сообщениях об ошибках.
Например, чтобы сделать Watch-объект на основе функции-генератора naive_timer()
, можно использовать следюущий код:
from ram.process import watch_iterable
# create watch based on naive_timer generator
with watch_iterable(naive_timer()) as watch:
# wait for timer tick
# print current time
print watch()
Помимо возвращаемых итератором значений, контекстный менеджер watch_iterable
передает в основной процесс необработанные исключения, возникшие в результате выполнения генератора.
Исключение, порождаемое в асинхронно-запущенном генераторе, передается в основной процесс при попытке получить следующее сообщение из очереди Watch-объекта и повторно выкидывается в основном процессе.
Объект стека {stack trace} для порожденного исключения не сериализуем,
поэтому он форматируется и передается в основной процесс в виде текста.
Когда исключение повторно генерируется на стороне основного процесса, эта строка добавляется к тексту исключения.
Тип исходного исключения при этом сохраняется.
>>> from ram.process import watch_iterable
>>>
>>> def faulty():
... raise ValueError('error!')
... yield None
...
>>> with watch_iterable(faulty(), name='run-faulty') as watch:
... print watch()
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "ram/process.py", line 176, in __call__
return self.update()
File "ram/process.py", line 276, in update
raise exc(_ev)
ValueError: error!
Process: run-faulty
Traceback (most recent call last):
File "ram/process.py", line 296, in _wrap_iter
for index, obj in enumerate(iterable):
File "<stdin>", line 2, in faulty
ValueError: error!
Batteries included!¶
Помимо средств для построения Watch-объектов на основе произвольного генератора,
модуль ram.process
содержит набор предопределенных генераторов и специализированных контекстных менеджеров:
-
track_timer
(timeout=None)¶ Блокирующий генератор, реализующий интервальный таймер. В качестве возвращаемых значений используется результат выполения функции
time.time
.Parameters: timeout – Интервал таймера. Значение по умолчаню: 1 секунда.
>>> from ram.process import track_timer
>>>
>>> for event in track_timer():
... print event
...
1479909647.81
1479909648.81
1479909649.81
...
-
watch_timer
(timeout=None)¶ Контекстный менеджер для создания Watch-объекта на основе генератора
track_timer
.
-
track_output
(command, timeout=None)¶ Блокирующий генератор для отслеживания изменений в выводе программы. Может быть удобен для поллинга команд, отображающих какое-либо состояние. В качестве возвращаемых значений используется актуальный вывод команды.
Parameters: - command – Команда, изменения в выводе которой требуется отслеживать.
- timeout – Интервал между запусками команды. Значение по умолчанию: 1 секунда.
>>> from ram.process import track_output
>>>
>>> for event in track_output('dmesg | wc -l'):
... print event,
1124
1127
1131
...
-
watch_output
(command, timeout=None)¶ Контекстный менеджер для создания Watch-объекта на основе генератора
track_output
.
-
track_dir
(dirname, match=None, files=True, dirs=False, rec=False)¶ Блокирующий генератор, отслеживающий создание и удаление файлов и директорий по указанному пути. Для отслеживания событий, поступивших от файловой системы, реализация использует библиотеку
pyinotify
. В качестве возвращаемых значений используются кортежи из четырех элементов:- путь к отслеживаемой директории;
- имя созданного или удаленного файла (директории);
- булевское значение True для директорий и False для файлов;
- булевское значение True для созданных объектов и False для удаленных объектов.
Parameters: - dirname – Путь к отслеживаемой директории.
- match –
Маска имен файлов, для которых нужно получать события. По умолчанию события генерируются для всех файлов.
Если в качестве параметра указана строка, то события генерируются только для файлов, имена которых соответствуют заданной маске.
Если в качестве параметра указан список строк, то события генерируются только для файлов, имена которых соответсвутют хотя бы одной маске из списка.
- files – Булевское значение. Если оно равно True, то генерируются события для файлов.
- dirs – Булевское значение. Если оно равно True, то генерируются события для директорий.
- rec – Булевское значение. Если оно равно True, то отслеживаются события в поддиректориях.
>>> from ram.process import track_dir
>>>
>>> for event in track_dir('/tmp'):
... print event
('/tmp', 'q7p8AD', False, True)
('/tmp', 'q7p8AD', False, False)
...
-
watch_dir
(dirname, match=None, files=True, dirs=False, rec=False)¶ Контекстный менеджер для создания Watch-объекта на основе генератора
track_dir
.