Архив рубрики: Python

Скачивание файла с Google Docs и преобразование ods в csv

Возникла передо мной такая задача: мы храним таблицу соответсвия мака, ip и имени ПК в гуглодоксе, она и поддерживается в актуальном состоянии. DHCP сервер работает на FreeBSD, соответственно файл с настройками сервера вполне себе текстовый. И чтобы не обновлять данные и там и там был сделан скриптик. Наверняка можно было бы сделать проще, в некоторых случаях даже понятно как (например в функции csv2dhcp), но для небольшой таблицы я заморачиваться не стал.
Для работы понадобится pyquery и второй питон (считаем, что это у Вас уже есть). Кроме того, нам нужна библиотека для доступа к Google API.

Итак, по порядку:
  1. Заходим на страницу
  2. Выбираем Drive API и Python/Command Line
  3. Нажимаем на Настроить проект
  4. Вводим имя проекта, Далее
  5. По двум появившимся ссылкам скачиваем starter application и client secret, которым заменяем такой же файл в распакованном архиве
  6. Редактируем файл sample.py
Ниже будет код. Функцию csv2dhcp писал сам, ods2csv взял отсюда, остальное из документации по Google API. Немножно моих комментариев в коде.
Если будут комментарии/улучшения — буду благодарен

# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the «License»);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an «AS IS» BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

«»»Command-line skeleton application for Drive API.
Usage:
  $ python sample.py

You can also get help on all the command-line flags the program understands
by running:

  $ python sample.py —help

To get detailed log output run:

  $ python sample.py —logging_level=DEBUG
 
Для работы надо поставить gdata:
 
  #$ pip install gdata
  $ pip install pyquery
 
Подключение API:

  https://developers.google.com/drive/quickstart-python
«»»

import gflags
import httplib2
import logging
import os
import sys

from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import flow_from_clientsecrets
from oauth2client.tools import run
from apiclient import errors

FLAGS = gflags.FLAGS

# CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this
# application, including client_id and client_secret.
# You can see the Client ID and Client secret on the API Access tab on the
# Google APIs Console
CLIENT_SECRETS = 'client_secrets.json'

# Helpful message to display if the CLIENT_SECRETS file is missing.
MISSING_CLIENT_SECRETS_MESSAGE = «»»
WARNING: Please configure OAuth 2.0

To make this sample run you will need to download the client_secrets.json file
and save it at:

   %s

«»» % os.path.join(os.path.dirname(__file__), CLIENT_SECRETS)
# Временные файлы, куда сохраняется скачаный файл и конвертированый в csv
TEMP_FILE = '/home/ishayahu/IP range.ods'
TEMP_FILE2 = '/home/ishayahu/IP range.csv'
# Имя итогового файла
dhcp_file_name = '/home/ishayahu/dhcp'
# Set up a Flow object to be used for authentication.
# Add one or more of the following scopes. PLEASE ONLY ADD THE SCOPES YOU
# NEED. For more information on using scopes please see
# .
FLOW = flow_from_clientsecrets(CLIENT_SECRETS,
    scope=[
      'https://www.googleapis.com/auth/drive',
      'https://www.googleapis.com/auth/drive.apps.readonly',
      'https://www.googleapis.com/auth/drive.metadata.readonly',
      'https://www.googleapis.com/auth/drive.file',
      'https://www.googleapis.com/auth/drive.scripts',
      'https://www.googleapis.com/auth/drive.readonly',
    ],
    message=MISSING_CLIENT_SECRETS_MESSAGE)

# The gflags module makes defining command-line options easy for
# applications. Run this program with the '—help' argument to see
# all the flags that it understands.
gflags.DEFINE_enum('logging_level', 'ERROR',
    ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
    'Set the level of logging detail.')

def retrieve_all_files(service):
  «»»Retrieve a list of File resources.

  Args:
    service: Drive API service instance.
  Returns:
    List of File resources.
  «»»
  result = []
  page_token = None
  while True:
    try:
      param = {}
      if page_token:
        param['pageToken'] = page_token
      files = service.files().list(**param).execute()

      result.extend(files['items'])
      page_token = files.get('nextPageToken')
      if not page_token:
        break
    except errors.HttpError, error:
      print 'An error occurred: %s' % error
      break
  return result

def download_file(service, drive_file):
  «»»Download a file's content.

  Args:
    service: Drive API service instance.
    drive_file: Drive File instance.

  Returns:
    File's content if successful, None otherwise.
  «»»
  #download_url = drive_file.get('downloadUrl&#
39;)
  # Так как файл в родом формате для гугла, то скачать его нельзя и
  # downloadUrl просто отсутствует. Поэтому используем ссылки для экспорта
  download_url = drive_file[u'exportLinks'][u'application/x-vnd.oasis.opendocument.spreadsheet']
  if download_url:
    resp, content = service._http.request(download_url)
    if resp.status == 200:
      #print 'Status: %s' % resp
      return content
    else:
      print 'An error occurred: %s' % resp
      return None
  else:
    # The file doesn't have any content stored on Drive.
    return None

def ods2csv(filepath): 
  # комментарии тут: http://python-example.blogspot.ru/2012/10/how-to-convert-ods-to-csv.html
  import sys,zipfile,re,os,csv 
  from pyquery import PyQuery as pq 
  from lxml.cssselect import CSSSelector 
     
  xml = zipfile.ZipFile(filepath).read('content.xml') 
   
  def rep_repl(match): 
    return '%s' %match.group(2) * int(match.group(1)) 
  def repl_empt(match): 
    n = int(match.group(1)) 
    pat = '
    return pat*n if (n<100) else pat 
     
  p_repl = re.compile(r']*?repeated=»(d+)[^/>]*>(.+?table-cell>)') 
  p_empt = re.compile(r']*?repeated=»(d+)[^>]*>') 
  xml = re.sub(p_repl, rep_repl, xml) 
  xml = re.sub(p_empt, repl_empt, xml) 
     
  d = pq(xml, parser='xml') 
  ns={'table': 'urn:oasis:names:tc:opendocument:pen(''.join([root,'.csv']),'wb') as f: 
    for row in data: 
      dw = csv.writer(f) 
      dw.writerow(row) 
     
def csv2dhcp(filepath):
  dhcp_file=open(dhcp_file_name,'w')
  for line in open(filepath,'r'):
    if len(line.strip().split(','))>=6 and line.strip().split(',')[2]:
      dhcp_file.write(«host %s { hardware ethernet %s; fixed-address 192.168.1.%s; }n» % (line.strip().split(',')[5],line.strip().split(',')[2],line.strip().split(',')[1]))
  dhcp_file.close()
   
def main(argv):
  # Let the gflags module process the command-line arguments
  try:
    argv = FLAGS(argv)
  except gflags.FlagsError, e:
    print '%s\nUsage: %s ARGS\n%s' % (e, argv[0], FLAGS)
    sys.exit(1)

  # Set the logging according to the command-line flag
  logging.getLogger().setLevel(getattr(logging, FLAGS.logging_level))

  # If the Credentials don't exist or are invalid, run through the native
  # client flow. The Storage object will ensure that if successful the good
  # Credentials will get written back to a file.
  storage = Storage('sample.dat')
  credentials = storage.get()

  if credentials is None or credentials.invalid:
    credentials = run(FLOW, storage)

  # Create an httplib2.Http object to handle our HTTP requests and authorize it
  # with our good Credentials.
  http = httplib2.Http()
  http = credentials.authorize(http)

  service = build('drive', 'v2', http=http)

  try:
    print «Login success! Starting to work.»
    for entry in retrieve_all_files(service):
    # Пролистываем список всех файлов (как ещё получить нужный не знаю)
    # IP range — имя нужного мне файла
    if entry[u'title']=='IP range':
        print «Writing content to %s» % TEMP_FILE
        open(TEMP_FILE,'w').write(download_file(service, entry))
        print «Converting ods to csv»
        ods2csv(TEMP_FILE)
        csv2dhcp(TEMP_FILE2)
        os.remove(TEMP_FILE)
        os.remove(TEMP_FILE2)
    # For more information on the Drive API API you can visit:
    #
    #   https://developers.google.com/drive/
    #
    # For more information on the Drive API API python library surface you
    # can visit:
    #
    #   https://google-api-client-libraries.appspot.com/documentation/drive/v2/python/latest/
    #
    # For information on the Python Client Library visit:
    #
    #   https://developers.google.com/api-client-library/python/start/get_started

  except AccessTokenRefreshError:
    print («The credentials have been revoked or expired, please re-run»
      «the application to re-authorize»)

if __name__ == '__main__':
  main(sys.argv)

Автор: Ishayahu Lastov

Запуск MoinMoin2.0 под Apache22 на FreeBSD9

Предполагается, что moin2 расположен в /home/ishayahu/moin-2.0
Для начала нам надо установить сам апач и mod_wsgi, чтобы он мог работать с Flask (я использую для этого portmaster (/usr/ports/ports-mgmt/portmaster)):
#portmaster www/apache22 www/mod_wsgi
Далее создаём файл moin-2.0/moinmoin2.wsgi, чтобы апач мог запускать Flask приложение:
#для работы mod_wsgi, так как он блокирует sys.stdout
import sys
sys.stdout=sys.stderr
# Собственно для Flask
from MoinMoin.app import create_app
application = create_app('/home/ishayahu/moin-2.0/wikiconfig.py')

Теперь будем настроивать апач. Создадим конфигурацию виртульного хоста: файл /usr/local/etc/apache22/Includes/wiki.local.conf

    ServerAdmin meoc-it@mail.ru
    DocumentRoot /home/ishayahu/moin-2.0
    ServerName wiki.local
    ServerAlias www.wiki.local
    ErrorLog /home/ishayahu/wiki.local-error_log
    CustomLog /home/ishayahu/wiki.local-access_log combined
    WSGIDaemonProcess moinmoin2 user=ishayahu group=ishayahu threads=5
    WSGIScriptAlias / /home/ishayahu/moin-2.0/moinmoin2.wsgi
   
        WSGIProcessGroup moinmoin2
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
   
Настроиваем запуск вики как сервиса. Создаём файл для запуска апача в виртуальном окружении (нужно виртуальное окнужение для работы moin; не забыть chmod +x /root/start_wiki) /root/start_wiki:
#!/bin/bash
source /home/ishayahu/moin-2.0/env/bin/activate
/usr/local/etc/rc.d/apache22 onestart
Создаём файл для регистрации вики как сервиса /etc/rc.d/wiki (не забыть chmod +x /etc/rc.d/wiki):
#!/bin/sh
#
# PROVIDE: wiki
# REQUIRE: LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name=»wiki»
start_cmd=»${name}_start»
stop_cmd=»/usr/local/etc/rc.d/apache22 stop»

wiki_start()
{
    /bin/bash /root/start_wiki
}

load_rc_config $name
run_rc_command «$1»
И в /etc/rc.conf:
wiki_enable=»YES»

Автор: Ishayahu Lastov

MoinMoin2. Документация. Введение в настройку MoinMoin

Типы конфигурационных файлов

Для изменения того, как moinmoin себя ведёт и выглядит, Вы можете настроить его, редактируя конфигурационные файлы:
  • Конфигурация движка Wiki
    • файл чаще всего называется wikiconfig.py, но может иметь и другое имя
    • в этом файле находятся классы конфига движка вики
    • он написан на Python
  • Конфигурация фреймворка
    • расположена в том же файле, что и конфигурация движка вики
    • содержит некоторые настройки в ВЕРХНЕМ РЕГИСТРЕ в конце файла. Это конфигурация фреймворка (Flask и его расширений)
    • он написан на Python
  • Конфигурация логирования
    • опциональна; если Вы не хотите настраивать логирование — будут использованы встроенные настройки
    • это отдельный файл, назвается чаще всего logging.conf
    • имеет .ini подобный формат файла

Меняйте понемногу и храните резервные копии

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

Редактирование файлов Python

Когда Вы редактируете файлы Python, будьте осторожны с отступами, используйте лишь отступы, кратные 4 пробелам и не используйте табуляцию.
Кроме того, будьте внимательны к синтаксису вообще; это должен быть корректный код на Python, иначе Вы получите ошибку при попытке загрузки конфигурации. В таком случае прочитайте внимательно сообщение об ошибке, в нём скорее всего будет содержаться номер строки и описание проблемы. Если у Вас не получается решить проблему — восстановите из резервной копии последний рабочий конфиг.

Зачем использовать Python для конфигурации?

Вы, конечно, можете удивиться, почему мы используем код Python для конфигурации. Одна из причин в том, что это очень мощный язык. Moinmoin сам по себе разработан на Python и использование чего-либо другого потребовало бы больше времени для введения новой функциональности.

wikiconfig.py

wikiconf.py выглядит таким образом:
# -*- coding: utf-8 -*-
from MoinMoin.config.default import DefaultConfig

class Config(DefaultConfig):
    # некоторый комментарий
    sometext = u'your value'
    somelist = [1, 2, 3]

MOINCFG = Config # Flask любит только верхний регистр
SOMETHING_FLASKY = 'foobar'
Давайте разберём это строка за строкой:
  1.  Мы объявляем кодировку конфигурационного файла; убедитесь, что ваш редактор использует ту же самую кодировку, особенно, если Вы используете не ASCII символы
  2. импортируем класс DefaultConfig из кода moin; в нём содержатся значения по умолчанию для всех настроек, что экономит ваш труд, так как Вы должны указать тут только те параметры, которые Вы хотите переопределить
  3. пустая строка для лучшего восприятия
  4. определяем новый класс Config, дочерний классу DefaultConfig. Это и есть настройки движка вики и он переопределяет значения из DefaultConfig.
  5. знак # указывает на комментарий в вашем конфиге. Эта строка, как и последующие строки в пределах Config имет отступ в 4 пробела, так как Python определяет блоки по отступам
  6. определяем атрибует Config'a c именем sometext и значением u'your value', где «u» указывает на то, что это строка юникода
  7. определяем атрибует Config'a c именем somelist и значением [1, 2, 3], это список с эементами 1, 2 и 3
  8. пустая строка для лучшего восприятия
  9. специальная строка «MOINCFG = Config» должна иметь именно такой вид по техническим причинам
  10. код в верхнем регистре находится в конце файла, вне класса Config и является настройкой фреймворка; обычно это что-то для Flask'a или его расшире

Flask. Документация. Предисловие

Прочитайте это перед тем, как Вы начнёте работать с Flask. Это наверняка ответит на некоторые ваши вопросы о целях этого проекта и поможет Вам понять, когда его стоит использовать, а когда нет.

Что означает «микро»?

Микро не означает, что всё ваше веб-приложение уместиться в один файл Python, хотя это и возможно. Кроме того, это не означает, что Flask не хватает какой-то функциональности. «Микро» в слове «микрофреймворк» означает, что Flask старается держать своё ядро простым, но расширяемым. Flask не будет принимать какие-то решения за Вас, например, какую БД использовать. Те решения, которые он делает, например, использование конкретного движка шаблонов, очень легко можно изменить. Всё остальное зависит полностью от Вас, так что Flask может быть всем, что Вам нужно и не быть тем, чем не нужен.
По умолчанию, Flask не включает слой абстракции БД, валидации форм или что-либо ещё, что может сделать другая существующая библиотека. Вместо этого Flask поддерживает расширения, которые позволяют добавить эту функциональность к вашему приложению, как если бы это было реализовано в самом Flask. Некоторые расширения предоставляют интеграцию с БД, валидацию форм, обработку загрузок, различные открытые технологии аутентификации и так далее. Flask может быть «микро», но готов к использованию в различных целях.

Конфигурация и допущения

Flask имеет много конфигурационных значений, с адекватными значениями по умолчанию, и использует некоторые допущения. В частности, предполагается, что шаблоны и статические файлы хранятся в подкаталогах в дереве приложения Python с именами templates и static соответственно. Хотя это и можно изменить, скорее всего Вы не будете это делать, особенно в самом начале.

Двигаемся дальше

После того, как Вы начали пользоваться Flask, Вы обнаружите большое количество доступных расширений, которые Вы можете интегрировать в ваш проект. Команда Flask просматривает эти расширения и следит за тем, чтобы их можно было использовать в новых релизах.
По мере роста вашего кода Вы можете выбрать любой дизайн кода, который Вам больше нравится. Flask продолжит предоставлять Вам лучшее, что может предоставить Python. Вы можете реализовать продвинутые шаблоны в SQLAlchemy или другом инструменте работы с БД, хранить данные не в релиационном хранилище и использовать преимущества инструментов, разработанных для WSGI — Python веб-интерфейса.
Flask включает множество хуков для настройки его поведения. Если Вам нужна более тонкая настройка, класс Flask может быть использован для создания подклассов. Если Вам это интересно, обратитесь к главе Becoming Big. Если Вам любопытны принципы дизайна Flask, обращайтесь к разделу Дизайнерские решения в Flask.

Автор: Ishayahu Lastov

Flask. Документация. Предисловие для опытных программистов

Один из дизайнерских подходов Flask — что простые задачи должны быть простыми, не требовать большого количества кода и при этом не должны ограничивать Вас. Поэтому Flask реализует некоторые решения, которые некоторые могут счесть внезапными или не ортодоксальными. Например, Flask использует локальные для потоков объекты, так что Вы не должны передавать объекты из функции в функцию в пределах запроса чтобы избежать проблем с потоками. Это общепринятый подход, но он требует корректного контекста запроса для внедрения зависимостей или если Вы хотите повторно использовать код, который использует значения, привязанные к запросу. Flask гордится этим, не скрывает этого и заявляет об этом и в коде и в докуменации.

Осторожная разработка для веба

Всегда, когда Вы создаёте веб-приложение, помните о безопасности.
Если Вы пишете веб-приложение, Вы, скорее всего, разрешаете пользователям регистрироваться и оставлять данные на вашем сервере. Пользователи доверяют Вам свои данные. И даже если Вы единственный, кто хранит данные в вашем приложении, Вы всё равно хотите чтобы ваши данные надёжно хранились.
К сожалению, есть множество способов скомпроментировать безопасность веб-приложений. Flask защитит Вас от наиболее популярных проблем современных веб-приложений: cross-site scripting (XSS). Пока Вы намеренно не пометите небезопасный HTML как безопасный, Flask, и работающий с ним движок шаблонов Jinja2, будут Вас прикрывать. Но всё равно у Вас ещё остаётся куча способов оставить дыры в приложении.
Документация будет предупреждать Вас о тех аспектах веб-разработки, которые требуют особого внимания к обеспечению безопасности. Некоторые из них гораздо сложнее, чем Вы могли бы подумать, и все мы иногда недооцениваем возможность эксплуатации уязвимости, пока какой-нибудь сообразительный парень не найдёт способ это сделать. И не думайте, что ваше приложение слишком малоценно, чтобы заинтересовать атакующего. В зависимости от типа атаки, есть вероятность того, что боты автоматически постараются заполнить вашу БД спамом, ссылками на вирусное ПО и т.п.
Flask не отличается от других фреймворков в том плане, что Вы, как разработчик, должны быть осторожны, учитывая возможные векторы атаки.

Статус Python 3

На данный момент сообщество Python работает над тем, чтобы библиотеки поддерживали новую версию Python. Хотя ситуация улучшается, тем не менее всё ещё есть несколько проблем, которые не позволяют нам до сих пор переключиться на Python3. Частично эти проблемы связаны  изменениями в языке, которые слишком долго остаются не пересмотренными, частично из-за того, что мы не до конца понимаем, как должно измениться API для учёта изменения подхода к юникоду в Python 3.
Werkzeug и Flask будут портированны на Python 3 как только будет надено решение этих проблем и мы предоставим советы по переходу на новую версию языка. До тех пор мы строго рекомендуем использовать Python 2.6 и 2.7 со включёнными предупреждениями Python 3 при разработке. Если Вы планируете переход на Python 3 в ближайшем будущем, мы крайне рекомендуем Вам прочитать «Как писать совместимый с будущим код Python«7
Продолжением является либо Установка, либо Быстрый старт

Автор: Ishayahu Lastov

Обнаружение пакетов и доступ к ресурсам при помощи pkg_resources

Модуль pkg_resouces, распространяемый с setuptools, предоставляет API для библиотек Python для доступа к их файлам ресурсов и для предоставления возможности приложениям и фреймворкам автоматически обнаруживать плагины. Кроме того, он предоставляет поддержку времени выполнения для использования расширений C, находящихся в zip файле формата egg, поддержки слияния пакетов, которые имеют отдельно распространяемые модули или подпакеты, API для управления текущим «рабочим набором» активных пакетов Python.

Обзор

«Яйца» — это формат распространения модулей Python, похожий на jar в Java или gem в Ruby. Они отличаются от предыдщуего формата распространения Python тем, что он импортируемый (то есть, они могут быть добавлены в sys.path) и они «отыскиваемые», то есть, они содержат метаданные, которые однозначно идентифицируют их содержимое и зависимости, и потому могут быть автоматически обнаружены и добавлены в sys.path в ответ на простой запрос вроде «дайте мне всё, что мне нужно для использования поддержки PDF в docutils».
Модуль pkg_resources предоставляет средства времени выполнения для поиска, исследования (интроспекции), активирования и использования яиц и других «подключаемых» форматов распространения. Поскольку это новые концепции для Python (и не настолько хорошо проработаны и в других языках), неплохо бы сперва обозначить несколько специальных терминов для обсуждения «яиц» и того, как они могут быть использованы:

  • проект — библиотека, фреймворк, скрипт, плагин, приложение или набор данных или других ресурсов, или любая комбинация этих элементов. Предполагается, что проекты имеют уникальные имена, т.е. имена, зарегистрированные в 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 и определяет, какие дистрибутивы могут быть добавлены в этот рабочий набор без конфликта версий или невыполненных требований.
Пример использования:

distributions, errors = working_set.find_plugins(
                             Environment(plugin_dirlist)
                        )
map(working_set.add, distributions) # добавляем плагины
                                    # и библиотеки в sys.path
print «Не получилось загрузить», errors # показать ошибки

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().)
Синтаксис определителей требований может быть определён в РБНФ таким образом:

version      ::= [-A-Za-z0-9_.]+
identifier   ::= [-A-Za-z0-9_]+
project_name ::= identifier
extralist    ::= identifier (',' identifier)*
extras       ::= '[' extralist? ']'
comparison   ::= '<' | '<=' | '!=' | '==' | '>=' | '>'
versionspec  ::=comparison version (',' comparison version)*
requirement  ::=project_name versionspec? extras?

Токены могут быть разделены пробелами, а требования могут быть расположены на нескольких строках при помощи бэкслеша (\). Комментарии в конце строки (с символом #) тоже можно использовать.
Вот несколько примеров корректных определителей требований:

FooProject >= 1.2
Fizzy [foo,bar]
PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1
SomethingWhoseVersionIDontCareAbout

Имя проекта — единственная требуемая часть строки, и если оно указано, то требованию будет отвечать любая версия этого проекта.
extras в требовании используется для запроса опциональных функций проекта, что может требовать дополнительных дистрибутивов для работы. Например, если гипотетический проект Report-O-Rama предлагает опциональную поддержку PDF, он может требовать дополнительную библиотеку для предоставления этой возм