Обзор
- проект — библиотека, фреймворк, скрипт, плагин, приложение или набор данных или других ресурсов, или любая комбинация этих элементов. Предполагается, что проекты имеют уникальные имена, т.е. имена, зарегистрированные в PyPI.
- релиз — снапшот проекта на некий момент времени, определяемый идентификатором версии
- дистрибутив — файл или файлы, представляющие конкретный релиз
- импортируемый дистрибутив — файл или катаолг, который, если его поместить в sys.path, позволяет Python'y импортировать любые модули, расположенные в нём
- дистрибутив-плагин — импортируемый дистрибутив, где имена файлов однозначно определяют их релиз (т.е. проект и версию), и чьё содержимое однозначно определяет какие релизы других проектов ему необходимы для работы.
- экстра — необязательные возможности релиза, которые могут налагать дополнительные требования в процессе выполнения. Например, если поддержка pdf в docutils требует библиотеку поддержки pdf, docutils может объявить поддержку pdf как экстра и указать, какие ещё релизы проектов нужны для обеспечения этой функциональности
- окружение — набор дистрибутивов потенциально доступный для импорта, но не обязательно активных. В окружении может присутствовать более одного дистрибутива (т.е. релиза) для данного проекта.
- рабочий набор — набор дистрибутивов актуально доступных для импорта из sys.path. Максимум один дистрибутив (релиз) данного проекта может присутствовать в рабочем наборе, иначе нет однозначности того, что импортировать.
- яйца — яйца — это дистибутив-плагин в одном из трёх форматов, поддерживаемых на данный момент pkg_resources. Есть собранные яйца, разрабатываемые яйца и яйца-ссылки. Собранные яйца — это каталоги или zip файлы с имененм, заканчивающимся на .egg и соответствующим договорённости об имени яиц, и содержащих подкаталог EGG-INFO. Разрабатываемые яйца — обычные каталоги кода Python c одним или более подкаталогом ИмяПроекта.egg-info. Яйца-ссылки — это файлы .egg-link, которые содержат имя собранного яйца или разрабатываемого яйца для того, чтобы обеспечить функционал символических ссылок на платформах, где нет встроенной поддержки такой функциональности.
(Более подробно эти термины и концепты описаны в обзоре архитектуры pkg_resources и Python Eggs).
Руководство разработчика
Этот раздел ещё не написан. Планируемое содержание:
- Accessing Resources
- Finding and Activating Package Distributions
- get_provider()
- require()
- WorkingSet
- iter_distributions
- Running Scripts
- Configuration
- Namespace Packages
- Extensible Applications and Frameworks
- Locating entry points
- Activation listeners
- Metadata access
- Extended Discovery and Installation
- Supporting Custom PEP 302 Implementations
На данный момент пока обращайтесь к Руководству по API
Руководство по API
Поддержка пакета пространства имён
Пакет пространства имён — это пакет, который содержит только другие пакеты и модули, без своего собственного содержимого. Такие пакеты могут быть разделены ме
жду множественными, отдельно упакованными дистрибутивами. Обычно Вам не требуется напрямую использовать API пакетов пространств имён; вместо этого Вы должны предоставить аргумент namespace_package функции setup() в файле setup.py вашего проекта. Более подробно это описано в документации setuptools о пакетах пространства имён.
Тем не менее, если по какой-то причине Вам нужно работать с пакетами пространства имён или напрямую изменить sys.path в процессе выполнения, Вам может помочь следющие API:
declare_namespace(name) — объявляет, что точечное имя пакета name является пакетом пространства имён и пакеты и модули, содержащиеся в нём, могут быть распространены между различными дистрибутивами. __path__ этого пакета будет расширен для добавления соответствующих пакетов из всех дистрибутивов sys.path'a, которые содержат пакет с этим именем. (Более точно, если импортер find_module(name) возвращает загрузчик, тогда он тоже будет исследован в поисках содержимого пакета.) Всякий раз, когда вызван ли метод activate(), проверяется наличие пакета пространства имён и обновляется его __path__.
fixup_namespace_backages(path_item) — объявляет, что path_item является новым добавленым элементом в sys.path, который может быть необходимо использовать для обновления существующего пакета пространства имён. Обычно он вызывается для вас при автоматическом добавлении яйца в sys.path, но если ваше приложение изменяет sys.path для добавления мест, которые могут содержать части пакетов пространства имён, Вам может потребоваться вызвать эту функцию чтобы убедиться, что они добавлены в существующий пакет пространства имён.
Хотя по умолчанию pkg_resources поддерживает пакеты пространства имён только для импортёров файловой системы и zip файлов, Вы можете расширить его для поддержки других импортёров, совместимых с PEP 302 при помощи функции register_namespace_handler(). См ниже раздел «Поддержка пользовательских импортёров».
Объект WorkingSet (рабочий набор)
Класс WorkingSet предоставляет доступ к коллекции активных дистрибутивов. В общем есть только один осмысленный экземпляр WorkingSet — тот, который представляет дистрибутивы, которые на данный момент активны в sys.path. Этот глобальный экземпляр доступен по имени working_set в модуле pkg_resources. Тем не менее, специализированные инструменты могут манитулировать рабочими наборами, которые не отвечают sys.path и потому создавать другие экземпляры WorkingSet.
Важно помнить, что глобальный объект working_set инициализируется из sys.path при первом импорте pkg_resources, а при всех последующих манипуляциях с sys.path ghb помощи API pkg_resources он лишь обновляется. Если Вы вручную изменяете sys.path, Вы должны вызвать соответствующий метод экземпляра working_set для его синхронизации. К сожалению, Python не предоставляет способа обнаружения произвольных изменений в объектах списка, вроде sys.path, так что pkg_resources не может автоматически обновить working_set на основании изменений в sys.path.
WorkingSet(entries=None) — создаёт WorkingSet из итерируемого аргумента entries. Если entries не передан, то по умолчанию используется значение sys.path при вызове конструктора.
Обратите внимание, что обычно Вы не будете создавать экземпляры WorkingSet, вместо этого Вы будете явно или неявно использовать глобальный экземпляр working_set. По большей части API pkg_resources разработан так, что working_set используется по умолчанию, так что Вам не нужно явно обращаться к нему большую часть времени.
Основные методы WorkingSet
Следующие методы объектов WorkingSet доступны так же в качестве функций уровня модуля в pkg_resources и они применяются к экземпляру по умолчанию working_set. Таким образом Вы можете, например, вызвать pkg_resources.require() как более короткую версию pkg_resources.working_set.require().
require(*requirements) — проверяет, что дистрибутивы, соответствующие requirements активны.
requirements должно быть строкой или (с возможностью вложения) последовательностью строк, определяющих требуемые дистрибутивы и версии. Возвращаемым значением является последовательность дистрибутивов, которые необходимо активировать для удволетворения требований; все относящиеся дистрибутивы включены, даже если они уже активированы в этом рабочем наборе.
Относительно синтаксиса передачи требуемых дистрибутивов смотрите раздел ниже «Парсинг требований».
В общем Вам не должно понадобиться использовать этот метод напрямую. Он больше предназначен для использования в быстрых скриптах и интерактивных интерпретаторах, чем для промышленного использования. Если Вы создаёте библиотеку или приложение, крайне рекомендуется, чтобы Вы создали скрипт setup.py, использующий setuptools, и объявили в нём все ваши треб
ования. В этом случае такие инструменты, как EasyInstall, смогут автоматически определить требования вашего пакета и соответственно их обработать.
Обратите внимание, что вызов require('SomePackage') не установит SomePackage, если он отсутствует в системе. Если Вам нужно это сделать, Вы должны использовать метод resolve(), который позволяет Вам передать коллбек installer, который будет вызван в случае, если ye;ysq дистрибуетив не будет найден на локальной машине. Этот коллбэк может затем отобразить вопрос на продолжение операции, автоматически загрузить нужный дистрибутив или сделать ещё что-нибудь. Более подробно это описано в документакции метода resolve() и obtain() объекта Environment.
run_script(requires, script_name) — обнаруживает дистрибутивы, указаные в requires и затем запускает скрипт script_name. requires должно быть строкой, содержащей спецификаторы требований (подробнее — смотрите раздел ниже «Парсинг требований»)
Скрипт, если он найден, будет выполнен в глобальном окружении вызывающего. Причина этого в том, что этот метод предназанчен для вызова из обёртывающего скрипта, который работает как прокси для «настоящего» скрипта в дистрибутиве. Скрипт-обёртка обычно не должен делать ничего, кроме вызова этой функции с корректными аргументами.
Если Вам нужно больше контроля над окружением выполнения скрипта, Вы, возможно, захотите использовать метод run_script() из Metadata API объекта Distribution.
iter_entry_points(group, name=None) — выдаёт (yield) точки входа из group, соответствующие name.
Если name=None, выдаются все точки входа в group из всех дистрибутивов в рабочем наборе; в противном случае будут выданы только те, которые соответствуют и group и name. Точки входа выдаётся из активнрых дистрибутивов в том порядке, в котором эти дистрибутивы возникают в рабочем наборе. Для глобального working_set это должно быть тем же самым порядком, в котором они перечислены в sys.path. Обратите внимание, что точки входа, предоставляемые индивидуальными дистрибутивами, не имеют конкретного порядка.
Более подробно смотрите ниже, в разделе «Точки входа»
Методы и атрибуты WorkingSet
Эти методы используются для запросов или управления содержимым конкретного рабочего набора, так что они должны быть вызваны для конкретного экземпляра WorkingSet.
add_entry(entry) — добавляет путь к entries, находя там все дистрибутивы. Вы должны использовать его когда Вы добавляете элементы к sys.path и хотите, чтобы глобальный working_set отражал эти изменения. Этот метод так же вызывается конструкотором WorkingSet().
Этот метод использует find_distributions(entry, True) для поиска дистрибутивов, которые соответствуют элементу пути, а, затем, добавляют их вызовом add(). entry всегда добавляется к атрибуту entries, даже если он уже присутствует там (причина этого в том, что sys.path может содержать одно и то же значение несколько раз, и атрибут entries должен быть способным отображать это)
__contains__(dist) — True, если dist активен в этом рабочем наборе. Обратите внимание, что только один дистрибутив для данного проекта может быть активен в данном WorkingSet.
__iter__() — выдаёт дистрибутивы для неповторяющихся проектов в рабочем наборе. Порядок выдачи соответствует порядку, в котором пути элементов были добавлены в рабочий набор.
find(req) — обнаруживает дистрибутивы, соответсвующие req (экземпляру класса Requirement). Если это активный дистрибутив для запрашиваемого проекта, то он будет возвращён, если он соответствует версии, определённой в req. Но если есть активный дистрибутив для проекта, который не соответствует требованиям req, будет вызвано исключение VersionConflict. Если нет активного дистрибутива для этого проекта, то будет возвращено None.
resolve(requirements, env=None, installer=None) — список дистрибутивов, необходимых для (рекурсивного) соответствия requirements.
requirements должен быть последовательностью объектов Requirement. env, если предоставлен, должен быть экземпляром Environment. Если он не передан, Environment создаётся из entries рабочего каталога. installer, если передан, будет вызван для каждого требования, которое не удволетворено уже установленными дистрибутивами; он должен возвращать Distribution или None. (Смотрите метод obtain() объекта Environment, где более подробно рассказано об аргументе installer)
add(dist, entry=None) — добавляет dist в рабочий набор, ассоциированный с entry. Если entry не определён, по умолчанию будет использован dist.location. При выходе из этой процедуры, entry добавляется в конец .entries рабочего набора
(если его там ещё нет).
dist добавляется в рабочий набор только если это касается проекта, у которого ещё нет этого активного дистрибутива в рабочем наборе. Если он успешно добавлен, все коллбэки, зарегистрированные методом subscritbe(), будут вызваны. (См «Получение оповещений об изменениях» ниже)
Примечание: add() автоматически вызывается для Вас методом require(), так что обычно Вам не потребуется вызывать этот метод напрямую.
entries — этот атрибут представляет «тень» sys.path, в первую очередь полезную для отладки. Если Вы столкнулись с проблемами импорта, проверьте entries глобального объекта working_set и сравните его с sys.path. Если они не совпадают, значит какая-то часть вашей программы работает с sys.path не обновляя соответственно working_set. Важное замечание: не изменяйте напрямую этот атрибут! Установка его эквивалентным sys.path решит вашу проблему не лучше, чем замазывание аварийки починит вашу машину. Если этот атрибут не соответствует sys.path, то это сигнал о проблеме, а не её причина.
Получение оповещений об изменениях
Расширяемые приложения и фреймворки могут иметь потребность в получении оповещений, когда новые дистрибутивы (например, плагины) добавляются в рабочий набор. Для этого предназначены метод subscribe() и функция add_activation_listener().
subscribe(callback) — вызывает callback(distribution) один раз для каждого активного дистрибутива, который уже находится в наборе или будет добавлен позже. Так как коллбэк вызывается и для уже активных дистрибутивов, Вам не нужно делать цикл по рабочему набору, чтобы обработать существующие элементы; просто зарегистрируйте коллбэк и будьте готовы к тому, что он будет немендленно вызван этим методом.
Обратите внимание, что коллбэки не должны позволять исключениям распространяться, иначе они наложатся на операции других коллбэков, что, возможно, приведёт к противоречивому состоянию рабочего набора. Коллбэки должны использовать блоки try/except чтобы игнорировать, логировать или как-либо ещё обрабатывать ошибки, особенно с учётом того, что код, вызвавший коллбэк скорее всего не сможет обработать ошибки лушче, чем сам коллбэк.
pkg_resources.add_activation_listener() является альтернативой pkg_resources.working_set.subscribe().
Обнаружение плагинов
Расширяемые приложения иногда имеют что-то вроде «каталога плагинов» или набора таких каталогов, откуда они хотят загрузить точки входа или другие метаданные. Метод find_plugins() позволяет Вам сделать это, сканируя окружения в поисках новых версий каждого проекта, которые могут быть безопасно загружены без конфликтов или невыполненных требований.
find_plugins(plugin_env, full_env=None, fallback=True) — сканирует plugin_env и определяет, какие дистрибутивы могут быть добавлены в этот рабочий набор без конфликта версий или невыполненных требований.
Пример использования:
plugin_env должен быть экземпляром Environment, который содержит только те дистрибутивы, которые находятся в каталоге плагинов проекта. full_env, если указан, должен быть экземпляром Environment, который содержит все текущие доступные дистрибутивы.
Если full_env не задан, он создаётся автоматически из WorkingSet, на котором вызывается этот метод, что обычно означает, что каждый каталог в sys.path будет просканирован в поисках дистрибутивов.
Этот метод возвращает два кортежа: ('distributions', 'error_info'), где distributions — список дистрибутивов, найденых в plugin_env, которые были загружаемы, вместе с другими дистрибутивами, которые были нужны для разврешения их зависимостей. error_info — это словарь, который отображает незагружаемые дистрибутивы плагинов на экземпляры исключений, которые произошли. Обычно это будут экземпляры классов DistributionNotFound или VersionConflict.
Большая часть приложений обычно использует этот метод на экземпляре working_set, а, затем, тут же добавляет возвращённые дистрибутивы в рабочий набор, так что они будут доступны в sys.path. Таким образом, оказывается возможным найти все точки вода и позволяет отслеживать другие метаданные и активировать хуки.
Алгоритм разрешения используемый find_plugins() таков. Сперва имена проектов дистрибутивов, находящихся в plugin_env, сортируются. Затем, проверяется яйцо каждого проекта в порядке уменьшения версии (то есть, начиная с более новой версии проекта).
Производится попытка разрешить зависимости каждого яйца. Если попытка успешна, яйцо и его зависимости добавляются в список вывода и во временную копию рабочего набора. Процесс разрешения продолжается на следующем имени проекта и более старые яйца для этого проекта даже не затрагиваются.
Если попытка разрешения не удаётся, ошибка добавляется в словарь ошибок. Если флаг fallback=True, пробуется более старая версия плагина, до тех пор, пока не будет найдена работающая версия. Если False, то процесс разрешения продолжается для следующего имени проекта.
Некоторые приложения могут иметь более строгие требования к «отступлению (fallback)», чем другие. Например, приложение, у которого есть схема БД или постоянные объекты, может быть не может так просто провести доунгрейд версии пакета. Другие приложения могут хотеть быть уверенными, что новая конфигурация плагинов либо на 100% хороша, либо откатиться к другой гарантированно работающей конфигруации. (То есть, они могут хотеть откатиться к гарантированно работающей конфигурации если значение error_info не пустое.)
Обратите внимание, что этот алгоритм даёт преимущество при удволетворении зависимостей в алфавитном порядке имён проектов в случае конфликта версий. Если два проекта с именами AaronsPlugin и ZekesPlugin оба требуют разные версии TomsLibrary, в таком случае AaronsPlugin получит своё, а ZekesPlugin нет из-за конфликта версий.
Объекты Environment
environment — колекция объектов Distribution, обычно тех, которые присутствуют и потенциально импортируемы на текущей платформе. Объекты Environment используются pkg_resources для индексирования доступных дистрибутивов в процессе разрешения зависимостей.
Environment(search_paht=None, platform=get_supported_platform(), python=PY_MAJOR) — создаёт снимок окружения сканируя search_path в поисках дистрибутивов, совместимых с platform и python. search_path должен быть последовательностью строк, которая могла бы использоваться для sys.paht. Если search_path не указан, будет использоваться sys.path.
platform — опциональная строка, определяющая имя платформы, с которой должны быть совместимы не кроссплатформенные дистрибутивы. Если он не указан, то будет использоваться текущая платформа. python — опциональная строка, указывающая на версию Python (например, «2.4»); по умолчанию — это текущая запущенная версия.
Вы можете принудительно установить platform (и/или python) в None, если Вы хотите включить все дистрибутивы, не только совместимые с текущей платформой или версией.
Обратите внимани, что search_path немендленно сканируется в поисках дистрибутивов и результирующий Environment является снимком найденных дистрибутивов. Он не обновляется автоматически если состояние системы изменяется в следствии, например, установки или удаления дистрибутивов.
__getitem__(project_name) — возвращает список дистрибутивов для данного имени проекта, упорядоченный от более новых к более старым версиям. (Этот формат даёт приоритет для дистрибутивов, которые содержат ту же версию проекта). Если дистрибутивов для проекта не найдено, возвращается пустой список.
__iter__() — выдёт уникальные имена проектов дистрибутивов в этом окружении. Выдаваемые имена всё время в низком регистре.
add(dist) — добавляет dist в окружение, если он соответствует платформе и версии python, определённой на момент создания, и только если дистрибутив ещё не был добавлен (то есть, добавть один дистрибутив более одного раза не получится).
remove(dist) — удаляет dist из окружения.
can_add(dist) — проверяет, приемлем ли dist для этого окружения. Если он не совместим с платформой или версией Python, определёнными при создании окружения, возвращается false.
__add__(dist_or_env) (оператор +) — добавляет дистрибутив или окружение в экземпляр Environment, возвращая новый объект окружения, который содержит все дистрибутивы, содежавшиеся в предыдущих объектах. Новое окружение будет иметь platform и python равным None, что означает, что никакие дистрибутивы не будут отклонены при попытке добавить их; всё, что будет добавляться будет добавлено. Если Вы хотите, чтобы добавляемые дистрибутивы отфильтровывались по платформе или версии, или Вы хотите добавить их в <
u>тот же экземпляр окружения, в таком случае Вы должны использовать оператор +=
__iadd__(dist_or_env) (оператор +=) — добавляет дистрибутивы или окружение в экземпляр Environment «на месте», обновляя существующий экземпляр и возвращая его. Дистрибутивы фильтруются по платформе и версии Python.
best_match(req, working_set, installer=None) — находит дистрибутивы, лучше всего удволетворяющие req и которые можно использовать в working_set.
Он вызывает метод find(req) на working_set, чтобы увидеть, активирован ли подходящий дистрибутив. (Это может вызвать исключение VersionConflict, если неподходящая версия проекта уже активирована в заданом working_set). Если нужный дистрибутив не активирован, этот метод возвращает новейший дистрибутив в окружении, который отвечает Requirement в req. Если подходящего дистрибутива не найдено и указан installer, тогда возвращается результат вызова метода obtain(req, installer) окружения.
obtain(requirement, installer=None) — получает дистрибутив, который отвечает требованиям (например, загружая его). В базовом классе Environment эта процедура лишь возвращает installer(requirement), а если installer=None, возвращается None. Этот метод является «ловушкой», которая позволяет субклассу попробовать другие пути получения дистрибутива до того, как откатиться до аргумента installer.
scan(search_path=None) — сканирует search_path в поисках дистрибутивов, которые можно использовать на platform.
Все найденные дистрибутивы добавляются в окружение. search_path должен быть последовательностью строк, которую можно было бы использовать в качестве sys.path. Если этот аргумент не передан, то используется sys.path. Добавляются только дистрибутивы, подходящие для платформы и версии Python, указанной при инициализации. Этот метод является сокращением для функции find_distributions(), чтобы найти дистрибутивы для каждого элемента в search_path и затем вызвать add() для добавления каждого дистрибутива в окружение.
Объекты Requirement
Объекты Requirement выражают какая версия проекта подходит для каких целей. Эти объекты (или их строковая форма) используются разными API pkg_resources для обнаружения дистрибутивов, которые нужны скрипту или другим дистрибутивам.
Парсинг требований
parse_requirements(s) — выдаёт объекты Requirement для строк или итерируемых строк. Каждое требование должно начинаться с новой строки. См ниже описание синтаксиса.
Requirement.parse(s) — создаёт объект Requirement и строки или интерируемых строк. Исключение ValueError возбуждается, если строка или строки не содержат корректного определителя требований или содержат более одного определителя. (Для обработки нескольких определителей из строки или итерируемого набора строк используйте parse_requirements().)
Синтаксис определителей требований может быть определён в РБНФ таким образом:
Токены могут быть разделены пробелами, а требования могут быть расположены на нескольких строках при помощи бэкслеша (\). Комментарии в конце строки (с символом #) тоже можно использовать.
Вот несколько примеров корректных определителей требований:
FooProject >= 1.2
Fizzy [foo,bar]
PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1
SomethingWhoseVersionIDontCareAbout
Имя проекта — единственная требуемая часть строки, и если оно указано, то требованию будет отвечать любая версия этого проекта.
extras в требовании используется для запроса опциональных функций проекта, что может требовать дополнительных дистрибутивов для работы. Например, если гипотетический проект Report-O-Rama предлагает опциональную поддержку PDF, он может требовать дополнительную библиотеку для предоставления этой возм
Новый набор на курсы Learn Python
Асинхронное программирование
Сетевые библиотеки, например twisted, tornado, gevent, tulip — при кажущейся разнице в подходах имеют очень похожее ядро, называемое reactor, io loop, hub, event loop соответственно. Именно созданием этого ядра с нуля своими руками мы и займемся.
Цель курса: дать знания о том, как происходит работа с сетевыми подключениями (сокетами) на примере создания собственной библиотеки.
Требования к участникам: знание Python на достаточно приличном уровне. Введения в программирование не будет, с другой стороны создаваемый код не потребует чего-то сложного. Все сложности будут в работе с сетью а не в создании хитрых питоновских конструкций.
Потребуется Python 3.3. Хотя код мало зависит от версии языка, всё же в Python 3.3 появились некоторые удобные штуки которыми мы воспользуемся.
Операционная система: Linux, MacOS X или FreeBSD на выбор. Если есть сильное желание писать на Windows — тоже можно.
Коротко о чём будут занятия:
- Написание примитивного клиент-серверного кода на потоках.
- Объяснение почему производительные программы такой подход не используют. Нужно делать на epoll или kqueue, в крайнем случае select. Создание своего event loop. Сначала для обработки отложенных событий. Что это такое и какой должен быть интерфейс — расскажу по ходу дела.
- Описание того, как работает select/epoll/kqueue. Добавляем к event loop работу с TCP сокетами, основанную на обратных вызовах. Правильная обработка ошибок.
- Добавляем понятия транспорта-протокола.
- Строим поверх этого удобный интерфейс для пользовательского кода. На yeild from или greenlets — по желанию.
- Окончательный разбор результатов, ответы на возникшие вопросы.
Получившийся код в целом будет в основе следовать дизайну tulip в сильно упрощённом виде.
Курс состоит из шести занятий. Лектор: Андрей Светлов
Каждое занятие длится 2 часа.
Стоимость занятия: 300 грн.
Создание эффективных web-приложений
В 2013м году никого не удивишь веб-приложением, построенным при помощи Django, Pyramid или даже Flask. Однако куда сложнее удивить грамотным и эффективным web-приложением, способным одинаково успешно справлятся с нагрузкой реального мира и оставаться простым и легким для разработки.
Поэтому главной целью курса будет показать, как создавать высоконагруженные приложения, какие инструменты помогут в этом, при чем при здесь тестирование, профайлинг, деплоймент и изначально правильно выбранная архитектура и откуда приходят основные ошибки.
На протяжении всего курса мы будем создавать веб-приложение, ориентированное на работу на ARM микро-компьютере Raspberry PI.
Требования к участникам: опыт в создании сайтов или проектов при помощи Python и популярных фреймворков. Учить создавать сайты с нуля не буду, буду помогать перейти на новый уровень и избегать довольно популярных и тем не менее назойливых ошибок.
Краткое содержания курса:
- Архитектура высоконагруженного приложения, разделение проекта на бэкенд и фронтэнд, удаленное выполнение задач
- Оптимизация архитектуры и оптимизация кода, что идет за чем
- Тестирование как двигатель разработки, а не наоборот
- Непрерывная интеграция и непрерывный деплоймент, сравнение мест для развертки проектов
- Откуда берутся основные ошибки веб-приложений, зачем мы наступаем на одни и те же грабли
Курс состоит из шести занятий. Лектор: Игорь Давыденко
Каждое занятие длится 2 часа с перерывом в 15 минут.
Стоимость занятия: 200 грн, стоимость полного курса: 1000 грн.
Оптимизация Python кода
Чтобы делать высокоэффективный код нужно уметь пользоваться профайлером, читать байткод, выполнять алгоритмическую оптимизацию и писать Python C Extensions если алгоритмически выжать уже ничего не получается.
Всем этим мы и займемся.
Требования к участникам: уметь программировать на Python и C. Последнее очень желательно хотя бы на уровне остаточных знаний из институтского курса — половина рассматриваемого кода будет на С.
Python 3.3, операционная система любая.
Краткое со
держание курса:
- Профилирование через cProfile и timeit, анализ измеренных результатов. Рассматриваем из чего состоит функция с точки зрения Python и добираемся до байткода. Несколько простых вариантов оптимизации.
- Создаём простейший модуль Python C Extension.
- Учимся делать Python классы на C.
- Теперь пишем на Cython и радуемся как легко всё получается. В нагрузку ctypes.
- Показываю, как устроена виртуальная CPython машина изнутри. Интерпретаторы, потоки, стек. GIL. Как PyEval_EvalFrameEx исполняет байткод.
Курс состоит из пяти занятий. Лектор: Андрей Светлов.
Каждое занятие длится 2 часа.
Стоимость занятия: 300 грн.
UPD. Для тех кто ещё не понял: online версии не будет. Ни в каком виде.
Автор: Andrew Svetlov
Использование try-finally
Хочу обратить внимание на маленькую особенность написания конструкции try-finally.
Возьмём для примера многопоточность, а конкретно блокировки.
Где-то (наверное, в конструкторе класса) мы создали объект блокировки:
self.locker = threading.RLock()
Затем в каком-то методе мы пытаемся использовать эту блокировку в try-finally statement. Да, я знаю что RLock поддерживает context manager protocol и может использоваться в with statement. Так будет даже лучше, но мы сейчас говорим о другом варианте использования.
try:
self.locker.acquire()
do_some_work()
finally:
self.locker.release()
В чём ошибка? .acquire() может выбросить исключение. Блокировка не будет захвачена и попытка её освободить в .release() выбросит новое (другое) исключение. Что крайне нежелательно. Особенно в python 2.x, где нет цепочек исключений. Т.е. ошибка в .acquire() будет просто скрыта, понять в чём было дело невозможно.
Правильно писать так:
self.locker.acquire()
try:
do_some_work()
finally:
self.locker.release()
Если было исключение в .acquire() — то блокировка не захвачена и освобождать её не нужно. Пусть обработка исключения разворачивается своим ходом, .release() в finally block совершенно не нужен.
Правило простое и понятное, тем не менее я сам нередко писал ошибочный код. А сегодня опять увидел это проблему при чтении чужих исходников.
Проблема усугубляется тем, что обычно .acquire() работает успешно, и лишь в редких случаях выбрасывает исключение. Которое мы видим в логах (все используют логи, верно?) и недоумеваем, что именно произошло.
Это замечание относится к любому коду, выполняемому в finally block.
Переменные, блокировки, захват ресурсов, открытие файлов и т.д. должны быть выполнены перед try.
P.S.
На открытие файлов хочу обратить особое внимание как на самый частый случай. Куда более частый чем работа с многопоточностью. Правильно писать:
f = open('filename')
try:
f.read()
finally:
f.close()
Надеюсь, последний пример запомнится хорошо и внесёт ясность в головы уважаемых молодых коллег.
Автор: Andrew Svetlov
Продвинутое руководство по логированию (Перевод)
- Logger представляет интерфейс, который использует непосредственно код приложения.
- Handler посылает запись лога (созданную logger'ом) в соответствующее расположение.
- Filter позволяет определить, какую запись лога выводить.
- Formatter определяет расположение записи лога в итоговом выводе.
logger = logging.getLogger(__name__)
severity:logger name:message) в аргументе format. Более подробно все опции форматирования описаны в Formatter Objects.
Поток форматирования
Перемещение информации лога между logger'ами и handler'ами иллюстрирован диаграммой по ссылке выше.
Logger'ы
Объекты Logger имеют тройную работу. Во-первых, они предоставляют методы коду приложения, так что приложение может в процессе выполнения логировать сообщения. Во-вторых, объекты logger определяют какие сообщения логов будут работать на этом уровне (объекте фильтра). В третих, объекты logger передают подходящие сообщения логов всем заинтересованным handler'ам.
Чаще всего используемые методы объекта logger относятся к одной из двух категорий: настройка и отправка сообщений.
Вот наиболее часто использемые методы настройки:
- Logger.setLevel() определяет минимальный уровень сообщений, которые будут обработаны; debug — минимальный встроенный уровень, а critical — максимальный. Например, если установлен уровень INFO, logger будет обрабатывать сообщения уровня INFO, WARNING, ERROR и CRITICAL и игнорировать сообщения уровня DEBUG.
- Logger.addHandler() и Logger.removeHandler() добавляют и удаляют объекты handler из объекта logger. Handler'ы более подробно обсуждены в Handlers.
- Logger.addFilter() и Logger.removeFilter() добавляют и удалют объекты filter из объекта logger. Filter'ы более подробно обсуждаются в Filter Objects.
Вам не нужно вызывать эти методы каждый раз для каждого logger, который Вы создаёте. Смотрите последние два абазца этого раздела.
Когда объект logger настроен, следующие методы создают сообщения логов:
- Logger.debug(), Logger.info(), Logger.warning(), Logger.error() и Logger.critical() создают записи логов с сообщением и уровнем, соответствующим названию метода. Сообщение — это строка формата, которая может содержать стандартный синтаксис подстановки, такой как %s, %d, %f. Остальные аргументы — список объектов, которые должны быть подставлены в поля подстановки сообщения. В соответствии с **kwargs, методы логирования учитывают только именованый аргумент exc_info и использует его для того, чтобы определить, логировать ли информацию об исключении.
- Logger.exception() создаёт запись в логе, аналогичную методу Logger.error(). Разница в том, что Logger.exception() делает дамп трасировки стека при вызове. Вызывайте этот метод только из обработчика исключений.
- Logger.log() принимает уровень логирования в качестве аргумента. В этом случае приходится больше печатать, но зато это способ залогировать события пользовательского уровня.
getLogger() возвращает ссылку на экземпляр logger'а с именем, если оно задано, или root, если нет. Имена представляют из себя иерархическую структуру с точками в качестве разделителей. Множественные вызовы getLogger() с одним и тем же имененм будут возвращать ссылку на один и тот же объект logger'а. Logger'ы, находящиеся ниже в иерархии являются дочерними для logger'ов, которые находятся выше. Например, для logger'а с имененм foo logger'ы с именами foo.bar, foo.bar.baz и foo.bam будут дочерними.
Logger'ы поддерживают концепцию эффективного уровня. Если для logger'а не задан уровень явно, то используется уровень его родителя. Если и его родитель не имеет заданного уровня — смотрится родитель родителя, и так далее. Корневой logger всегда имеет явно заданный уровень (по умолчанию это WARNING). При решении вопроса обрабатывать ли событи используется именно эффективный уровень.
Дочерние logger'ы распространяют сообщения handler'ам, связанным с родительским logger'ом. Из-за этого нет необходимости определять и настраивать handler'ы для всех logger'ов в приложении. Но важно сконфигурировать handler'ы для logger'ов верхнего уровня и уже потом создавать при необходимости дочерние logger'ы. (Однако, распространение сообщений можно отключить, задав значением атрибута propagate logger'a равным False.)
Handler'ы
Объекты Handler отвечают за отправку соответствующего сообщения (соответствующего уровня) к его месту назначения, определённого в handler'e. Объекты logger могут добавить себе ноль или более handler'ов при помощи метода addHandler(). Например, приложение может хотеть отправлять все сообщения в файл логов, сообщения уровня ошибки и выше в stdout, а все критические сообщения отправлять на почту. Этот сценарий требует три handler'a, каждый из которых отвечает за отправку сообщений определённого уровня в определённое место.
Стандартная библиотека включает в себя несколько типов handler'ов (см Useful Handlers); это руководство по большей части использует в примерах StreamHandler и FileHandler.
Есть несколько методов у handler'a для разработчиков приложений, о которых стоит позаботиться. Методы, которые нужны тем, кто будет пользоваться встроенными handler'ами (то есть, не будет использовать самописные) следующие:
- Метод Handler.setLevel() аналогичен методу объекта logger, он определяет минимальный уровень, который будет направлен в соответствующее место. Зачем нужно два метода setLevel()? Уровень, заданный в logger'e определяет уровень сообщений, который будет передан в handler'ы. Уровень, заданный в каждом handler'e определяет сообщения, которые этот handler будут посылать.
- setFormatter() определяет объект Formatter, который будет использовать этот handler.
- addFilter() и removeFilter() соответственно добавляют и удаляют объекты фильтров из handler'a.
Код приложения не должен напрямую инициализировать и использовать экземпляры Handler. Вместо этого, класс Handler является базовым классом, который определяет интерфейс, которым должны обладать все handler'ы и определяет некоторое поведение по умолчанию, которое могут использовать дочерние классы (или переопределять их).
Formatter'ы
Fabric: Операции
fabric.operations.get(remote_path, local_path = None)
- host: Значение env.host_string, например, myhostname или user@myhostname-222 (двоеточие между именем узла и номером порта заменено на тире для увеличения совместимости с разными ФС)
- dirname: Часть каталога из удалённого пути, т.е. src/projectname в /src/projectname/utils.py
- basename: Часть файла из удалённого пути, т.е. utils.py в /src/projectname/utils.py
- path: Полный удалённый путь: /src/projectname/utils.py
- ПримечаниеЕсли remote_path — абсолютный путь, то только подкаталоги будут созданы локально и переданы в переменные выше. Так, например, get('/var/log', '%(path)s') будет записывать файлы как apache2/access.log,postgresql/8.4/postgresql.log и т.д. в локальный рабочий каталог. Он не будет записывать файлы как var/log/apache2/access.log.Кроме того, когда скачивается один файл, %(dirname)s и %(path)s не имеют особого смысла и будут, соответственно иметь пустое значение и значение, равное %(basename)s. Таким образом, вызов вроде get('/var/log/apache2/access.log', '%(path)s') сохранит локальный файл под именем access.log, а не var/log/apache2/access.log.Такое поведение предназначено для соответствия программе командной строки scp.Если значение не задано, то local_path получает значение "%(host)s/%(path)s" чтобы это было безопасно для использования на нескольких узлах.ПредупреждениеЕсли ваш аргумент local_path не содержит %(host)s и ваш вызов get запускается на нескольких хостах, ваши локальные файлы будут перезаписаны при каждом удачном запуске!Если local_path не использует вышеозначенные переменные (т.е. это просто явный путь) он будет действовать похоже командам scp или cp, перезаписывая при необходимости существующие файлы, скачивая их в нужное место (т.е. get('/path/to/remote_file.txt', 'local_directory') создаст local_directory/remote_file.txt) и т.п.local_path может быть и файлоподобным объектом, например, результатом open('path', 'w') или экземпляром StringIO.ПримечаниеПопытка выполнить get каталога в файлоподобный объект некорректна и вернёт ошибку.ПримечаниеЭта функция будет использовать seek и tell для перезаписи всего содержимого файлоподобного объекта, чтобы это соответствовало поведению put (который тоже использует весь файл). Однако, в отличие от put, указатель файла не будет восстановлен в предыдущую локацию, так как это достаточно бессмыслен
но и/или даже не возможно.ПримечаниеИз-за того, как работают наши слои SSH, временные файлы всё равно будут записаны во временный файл на диске, даже если Вы передаёте в качестве аргумента local_path файлоподобный объект или StringIO. Временные файлы будут удалены, но имейте это в виду и не ожидайте прямой передачи данных в память. (Мы надеемся исправить это в будущем, чтобы дать возможность прямой передачи данных в память.)ПримечаниеЕсли файлоподбный объект, такой как StringIO имеет атрибут name, он будет использован в выводе Fabric, вместо простогоobj> Изменения в версии 1.0: Теперь есть отдельный удалённый рабочий каталог, с которым работает cd, и локальный рабочий каталог, с которым работает lcd.Изменения в версии 1.0: Теперь можно использовать файлоподобные объекты в качестве аргумента local_path.Изменения в версии 1.0: local_path может теперь содержать переменные genb и узла.Изменения в версии 1.0: В аргументе remote_path можно использовать каталоги, которые будут рекурсивно загружены.Изменения в версии 1.0: Возвращает итерируемый объект, содержащий локальные пути, содержащий атрибуты .failed и.succeeded.Изменения в версии 1.5: Использует атрибут name файлоподобного объекта для вывода лога
- fabric.operations.local(command, capture=False, shell=None)
- Выполняет команду на локальной системе.local — это лишь оболочка вокруг использования встроенного модуля Python subprocess с shell=True. Если Вам надо сделать что-то особое — используйте модуль subprocess напрямую.shell передаётся напрямую в аргумент execute subprocess.Popen‘а (что определяет используемую локально оболочку.) В соответствии со документацией, на Unix по умолчанию используется /bin/sh, так что эта опция полезна для установки других значений, например, /bin/bash.local на данный момент не способен одновременно вводить и выводить информация, в отличие от run/sudo. Именованный аргумент capture позволяет переключаться между выводом и вводом при необходимости, значение по умолчанию — False.Когда capture=False, локальные ввод и вывод subprocess’a направляется напрямую на ваш терминал, хотя Вы можете использовать глобальные настройки вывода output.stdout и output.stderr, чтобы спрятать один из них или оба. В этом режиме возвращаемые значения stdout/stderr всегда будут пустыми.Если capture=True, то Вы не увидите никакого вывода на вашем терминале, но возвращаемое значение будет храниться в stdout/stderr.В любом случае, как и при запуске run и sudo, возвращаемое значение содержит атрибуты return_code, stderr, failed и succeeded. Более подробно это описано в run.local будет учитывать менеджер контекста lcd, позволяющий Вам контролировать текущий рабочий каталог вне зависимости от удалённого каталога (которым управляет cd).Изменения в версии 1.0: Добавлены атрибуты succeeded и stderr.Изменения в версии 1.0: Значение по умолчанию для capture изменено с True на False.
- fabric.operations.open_shell(command=None)
- Вызывает интерактивную оболочку на удалённой системе.Если передан аргумент command, то он будет передан по каналу перед тем, как передать управление пользователю.Эта функция чаще всего используется когда Вам нужно взаимодействовать с серьёзными командами оболочки или набором команд, например, при отладке или когда нужно провести полностью интерактивное восстановление при ошибке удалённой программы.Следует иметь ввиду, что интерактивная оболочка в середине скрипта и не является замено
й команды run, которая тоже может взаимодействовать с удалённым хостом (хотя только во время выполнения переданной команды) и подразумевает решение большего количества программных проблем, таких как обработка ошибок и захват ввода/вывода.Конкретнее, open_shell предоставляет больше интерактивности, чем run, использование полноценной удалённой оболочки не позволяет Fabric определить, завершилась ли программа с ошибкой и заполняет ввод/вывод вводом/выводом оболочки, таким как заставки входа, приглашения и т.д.Таким образом, эта функция не имеет возвращаемого значения и не вызывает обработку ошибок Fabric, если какая-либо удалённая программа завершилась с ошибкой.Начиная с версии 1.0.
- fabric.operations.prompt(text, key=None, default='', validate=None)
- Выдаёт пользователю запрос с текстом text и возвращает полученное значение (как raw_input).Для удобства к text будет добавлен одиночный пробел, ничего больше. Таким образом Вы можете завершить свой запрос вопросительным знаком или двоеточием, например prompt("What hostname?").Если указан key, ввод пользователя и будет сохранён как env.
и возвращён этой функцией prompt. Если этот ключ уже существует в env, то его значение будет перезаписано и пользователю будет выдано предупреждение. Если передан параметр default, то он будет выведен в квадратных скобках и будет использоваться в случае, если пользователь ничего не введёт (т.e. нажмёт Enter без ввода текста). По умолчанию значением default является пустая строка. Если значением является не пустая строка, то к нему будет добавлен пробел, так что вызов prompt("Каково имя узла?", default="foo") приведёт к отображению Каково имя узла? [foo] (с пробелом после [foo].)Опциональный именованный аргумент validate может быть вызываемым объектом или строкой:- Если это вызываемый объект, то он будет вызван с полученной от пользователя строкой и должен вернуть значение для сохранения в случае успеха. При ошибке он должен вызвать исключение с сообщением, которое будет показано пользователю.
- Если это строка, то значение, переданное в параметре validate используется как регулярное выражение. Поэтому рекомендуется использовать «сырые» строки. Обратите внимание, что регулярное выражение будет (в любом случае?) требовать полного совпадения.
В любом случае prompt будет перезапрашивать ввод от пользователя до тех пор, пока не пройдёт валидация (или пользователь не нажмёт Ctrl-C).Примечаниеprompt учитывает env.abort_on_prompts и будет вызывать abort вместо запроса, если этот флаг установлен в True. Если Вы хотите заблокировать ввод пользователя безусловно, но в этом случае запросить информацию у пользователя — оберните код при помощи settings.Примеры:
- fabric.operations.put(local_path=None, remote_path=None, use_sudo=False, mirror_local_mode=False, mode=None)
- Загружает один или более файлов на удалённый хост.put возвращает итерируемый объект, содержащий абсолютные пути ко всем загруженным файлам. Этот итерируемый объект содержит также атрибут .failed, содержащий пути к локальным файлам, которые не удалось выгрузить (и потому его можно использовать для логической проверки.) Кроме того, Вы можете использовать .succeeded, который эквивалентен not .failed.local_path может быть как относительным, так и абсолютным путём, или даже каталогом, может содержать символы подстановки оболочки, как они используются в модуле glob. Можно использовать и ~ (так же как в os.path.expanduser).local_path может быть и файлоподобным объектом, как, например, результатом open('path') или экземпляром StringIO.ПримечаниеВ таком случае put постарается прочитать всё содержимое файлоподобного объекта, проматывая его при помощи seek (и будет использовать tell для сохранения предыдущей позиции файла).ПримечаниеИспользование файлоподобного объекта в команде put в аргументе local_path приведёт к удалению временного файла из-за нашей реалиации слоя SSH.remote_path также может быть абсолютным или относительным путём, но применяется к удалённому узлу. Относительный genm трактуется относительно домашней директории удалённого пользователя, но, при необходимости, можно использовать и тильду (т.е. ~/.ssh/).Пустая строка в качестве значения любого из двух аргументов пути будет замещена на текущий рабочий каталог.Хотя протокол SFTP (который используется put) не имеет возможности загрузить файл в локацию, которая не принадлежит подключившемуся пользователю, Вы можете задать use_sudo=True, чтобы обойти это. При использовании этого параметра put загружает файлы во временный каталог на удалённом хосте и затем использует sudo для перемещения их в remote_path.В некоторых случаях желательно чтобы загруженные файлы имели аналогичный режим, как и их локальные «коллеги» (например, когда Вы загружаете выполняемые скрипты). Для этого используйте mirror_local_mode=True.В качестве альтернативы Вы можете использовать именованный аргумент mode для того, чтобы задать режим извлечения так же как и os.chmod или команда Unix chmod.put учитывает результат cd, так что относительные пути в remote_path будут толковаться относительно текущего удалённого рабочего каталога, если это возможно. Таким образом, например, код ниже выгрузит файл в /tmp/files/test.txt а не в ~/files/test.txt:Использование lcd будет влиять на local_path аналогичным образом.Примеры:ПримечаниеЕсли файлоподобный объект, такой как StringIO, имеет атрибут name, то его значение будет использоваться в выводе Fabric, вместо стандартного
obj> Изменения в версии 1.0: Теперь учитывается удалённый рабочий каталог, управляемый cd, и локальный рабочий каталог, управляемый lcd.Изменения в версии 1.0: Теперь можно использовать файлоподобные объекты в аргументе local_path.Изменения в версии 1.0: В аргументе local_path можно задать каталог, который будет рекурсивно выгружен на удалённый узел.Изменения в версии 1.0: Возвращает итерируемый объект, содержащий удалённые пути с атрибутами .failed и.succeeded.Изменения в версии 1.5: Позволяет использовать атрибут name файлоподобного объекта для вывода лога
- fabric.operations.reboot(wait=120)
- Перезагружает удалённую систему.Временно изменяет настройки переподключения (timeout и connection_attempts) чтобы убедиться, что попытки подключения не прекратятся как минимум раньше wait секунд.ПримечаниеПо состоянию на Fabric 1.4, способность переподключиться в пределах сессии больше не требует использования внутреннего API. Хотя мы и не объявляем эту функцию нежелательной, тем не менее добавление к ней новых возможностей не стоит у нас в приоритетах.Пользователи, желающие получить больше возможностей должны посмотреть исходники этой функции (6 хорошо задокументированных строк) и написать свою собственную адаптацию с различными значениями timeout/attempts или дополнительной логикой.Появился в версии 0.9.2.Изменения в версии 1.4: Изменен именованный аргумент wait — ему добавлено значение по умолчанию.
- fabric.operations.require(*keys, **kwargs)
- Проверяет наличие переданных ключей в общем словаре окружения и прерывает выполнение, если их не находит.Позиционными аргументами должны быть строки, определяющие то, какие переменные окружения должны быть проверены. Если какой-либо из этих переменных не существует, Fabric прервёт выполнение и выведен имена отсутствующих ключей.Опциональный именованный аргумент used_for может быть строкой, которая будет отправлена на стандартный вывод, чтобы сообщить пользователю, для каких целей будет использоваться отсутствующая переменная. used_for выводится как часть строки вроде этой:Опциональный именованный аргумент provided_by может быть списком функций или имён функций, или одной функцией или её именем, которые должны быть выполнены для того, чтобы задать эти переменные; он будет включён в сообщение об ошибке.Примечание: Подразумевается, что именованные аргументы применяются ко всем переданным ключам как к группе. Если Вам надо, например, указать для разных ключей разные used_for, Вы должны использовать несколько вызовов require().Изменения в версии 1.1: Позволяет использовать итерируемые значения provided_by, а не одиночные значения.
- fabric.operations.run(command, shell=True, pty=True, combine_stderr=None, quiet=False, warn_only=False, stdout=None,stderr=None)
- Запускает команду оболочки на удалённом узле.Если shell=True (значение gj умолчанию), run выполнит заданную команду через оболочку, которая определяется переменной env.shell (в сумме получается что-то вроде /bin/bash -l -c "
" .) Все двойные кавычки (") или знаки доллара ($) в command будут автоматически экранированы, если shell=True.run возвращает результирующий вывод выполнения удалённой программы в качестве одной (скорее всего состоящей из нескольких строк) строки. Эта строка будет иметь логические атрибуты failed и succeeded, определяющие успешно или нет была выполнена команда, а в атрибуте return_code Вы получите код возврата команды. Более того, он содержит копии запрошенной и реальной строки команды в аргументах .command и .real_command соответственно.Любой текст, введённый в вашем локальном терминале будет перенаправлен в удалённую программу, пока она выполняется; таким образом Вы можете взаимодействовать удалённой программой, вводя пароли и т.п. Более подробно смотрите в разделе Взаимодействие с удалённой программой.Вы можете задать pty=False чтобы не создавать псевдотерминал на удалённом хосте в том случае, если это создаёт проблемы для выполняемой команды. Но, таким образом, Fabric надо будет самому передавать весь ввод удалённой программе, в том числе и пароли. (Если pty=True, удалённый псеводтерминал бдует делать всё это сам.) Подробнее об этом в разделе Псевдотерминалы.Конкретнее, если Вам нужно программно проверить поток вывода ошибок удалённой программы (доступный через атрибут stderr возвращаемого значения функции), Вы можете задать combine_stderr=False. В этом случае есть большая вероятность получения путанного вывода на вашем терминале (хотя возвращаемая run строка будет разделена надлежащим образом). Более подробно смотрите в разделе Комбинирование stdout и stderr.Чтобы игнорировать ненулевой код возвращения задайте warn_only=True. Чтобы и игнорировать ненулевое возвращаемое значение и принудить команду выполняться тихо, задайте quiet=True.Чтобы переопределить какие локальные потоки будут использованы для отображения удалённых stdout и/ил
и stderr, определите stdout или stderr. (По умолчанию используются потоковые объекты Python sys.stdout и sys.stderr.)Например, run("command", stderr=sys.stdout) будет выводить удалённый поток ошибок в локальный поток вывода, сохраняя его как свой собственный атрибут возвращаемого значения (как выше). Или же Вы можете даже передать свой собственный потоковый объект или логировщик, например myout = StringIO(); run("command", stdout=myout).Примеры:Начиная с версии 1.0: Атрибуты succeeded и stderr возвращаемого значения, именованный аргумент combine_stderr, и интерактивное поведение.Изменения в версии 1.0: Значением по умолчанию pty теперь является True.Изменения в версии 1.0.2: Значением по умолчанию combine_stderr теперь является None вместо True. Однако поведение по умолчанFabric: Модель выполнения
Если Вы читали руководство, то Вы должны быть уже знакомы с тем, как Fabric работает (с одной задачей на одном хосте). Однако, во многих ситуациях Вы можете захотеть выполнить несколько задач и/или на нескольких хостах. Возможно, Вы захотите разделить одну большую задачу на несколько маленьких, или обойти список серверов в поисках тех, на которых надо удалить выбранного пользователя. Все эти сценарии требуют некоторых правил о том, как и когда выполняются задачи.Этот документ описывает модель выполнения Fabric, включая главный цикл выполнения, определение списка хостов, создание подключений и т.д.Стратегия выполнения
По умолчанию Fabric работает в одиночном, последовательном режиме выполнения, хотя, начиная с версии 1.3, доступна параллельная модель выполнения (см параллельное выполнение). Поведение по умолчанию заключается в следующем:- Создаётся список задач. На данный момент это просто список аргументов, переданных fab'y, сохраняя порядок аргументов
- Для каждой задачи из разных источников создаётся список хостов (подробнее см «Как создаётся список хостов» ниже)
- Проходится список задач, каждая задача запускается один раз для каждого хоста в списке
- Задачи, для которых нет хостов в списке хостов, выполняются только локально и всегда запускаются только один раз
Таким образом, если у нас есть следующий fabfile:from fabric.api import run, env
env.hosts = ['host1', 'host2']
def taskA():
run('ls')
def taskB():
run('whoami')и мы выполняем команду:$ fab taskA taskB
мы увидим, что Fabric выполнит следующее:- taskA выполняется на host1
- taskA выполняется на host2
- taskB выполняется на host1
- taskB выполняется на host2
Хотя это и упрощённый подход, он позволяет очевидно сопоставить задачи и хосты и (в отличие от инструментов, которые запускают задачу сразу на нескольких узлах) позволяют реализовать логику скрипта, где Вы проверяете результат выполнения предыдущей команды и на основании этого решаете что делать дальше.Определение задач
Подробнее о том, что такое задачи Fabric и с чем их едят, смотрите «Определение задач«Определение списка хостов
Если только Вы не используете Fabric как просто систему запуска локальных скриптов (что возможно, но не является его основной задачей), наличие задач без возможности определить хосты для их выполнения было бы малополезным. Есть несколько способов сделать это, область воздействия этих методов меняется от глобальной до «только на одну задачу» и их можно смешивать в нужных пропорциях.Хосты
Хостами, в данном контексте, называется то, что обычно называется «строками хостов»: строка, которая определяет имя пользователя, имя хоста и номер порта: `username@hostname:port`. Пользователя и порт (и, соответственно, `@` и `:`) могут быть опущены; в таком случае будет использоваться локальное имя пользователя и порт 22. Таким образом, «admin@foo.com:222», «deploy@website» и «nameserver1» могут быть использованы в качестве строк хостов.Так же поддерживается нотация IPv6, например, «::1», «[::1]:1222», «user@2001:db8::1» или «user@[2001:db8::1]:1222». Квадратные скобки нужны только для того, чтобы отделить адрес от номера порта. Если номер порта не указан, то скобки тоже можно не использовать. Кроме того, если строка хоста задаётся через командную строку, то в некоторых оболочках может потребоваться экранировать эти скобки.Примечание: Раздел между именем пользователя и именем хоста происходит по последнему найденному знаку @, так что можно вполне использовать email адрес в качестве имени пользователя.В процессе выполнения Fabric нормализует полученную строку хоста и затем сохраняет каждую часть (имя пользователя / имя хоста / порт) в словаре окружения и для их использования и для того, чтобы задачи по необходимости могли на них сослаться. Более детально смотри в «Словарь окружения«.Роли
Строка хоста определяют один хост, но иногда бывает полезно объединить хосты в группы. Возможно, у Вас есть несколько Web серверов за балансировщиком нагрузки, и Вы хотите обновить их все; или Вы хотите запустить задачу на «всех клиентских серверах». Роли п