Подготовка и публикация модуля Django

Этот материал рассчитан на тех, кто уже написал свой модуль для Django и хочет, чтобы его труд оценили.

Я бы хотел пояснить, зачем нужно готовить пакет для python. Как написано на сайте redsolutioncms.org,

мы столкнулись с тем, что какая-нибудь наша библиотека уходит
за ненадобностью, потому что OpenSource сообщество сделално
подобную вещь хоть позже нас, но лучше

Итак, для чего вам нужно опубликовать свой модуль:

  • Чтобы снискать славы в OpenSource-сообществе;
  • грамотно опубликованный модуль заведомо имеет хорошую организацию кода, снабжен тестами и документацией;
  • оформляя свой модуль, вы показываете уважение в первую очередь вашим коллегам, которым предстоит работать с вашим кодом, а так же всем разработчикам, которым ваш код может быть полезен;
  • наконец, самому приятно, если твой код выглядит красиво и ухожено.

Требования к модулям

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

1. Зависимости

Все зависимости от других должны быть прописаны в setup.py, зависимость от самой Django мы не прописываем, чтобы при установке её не скачивал easy_install.

2. Интернационализация

В модуле не должно содержаться ни строчки по-русски. В идеале не должно быть комментариев и текстов коммита не на английском. Мы стараемся в общем придерживаться этого идеала. Ко всем модулям создается русская локаль, так что сразу заметно, если что-то не переведено.

3. Тесты

Наше слабое место. Тем не менее я категорически настаиваю на наличии тестов у сколь бы то ни было сложных модулей. Существует множество удобных фреймворков для тестов (от Selenium до webtest), пользуйетсь ими на здоровье!

4. Документация

Каждый наш модуль содержит, как минимум, описание в README. Если модуль требует бОльшей документации, то её необходимо предоставить пользователю.

5. Контроль версий

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

[0].[мажорный релиз].[минорный релиз]

Вместо нуля первой цифрой станет 1, когда мы посчитаем модуль стабильным и неизменным. В гите мы делаем так: для мажорных версий созданы ветки, а для минорных - тэги. Это сделано для того, чтобы при использовании одной и той же мажорной версии модуля можно было получить багфиксы в минорных версиях.

Чем мажорные от минорных отличаются спрашиваете? Между двумя мажорными версиями сохраняется API и структура БД. Минорные версии могут добавлять новые возможности и исправлять ошибки.

Стандарт оформления

Мы стараемся оформлять модули по одному алгоритму:

В корень проекта добавляются в обязательном порядке файлы:

  • MANIFEST.in - файл для подключения медиа-файлов в проект.
  • README.rst - файл с кратким описанием и примерами применения модуля в формате RST
  • README -> README.rst симлинк. Именно такой. Дело в том, что гитхаб не понимает симлинки, а хочется видеть документацию на гитхабе и на PYPI
  • DESCRIPTION - такое описание модуля, чтобы человек не в теме понял для чего нужен этот модуль
  • setup.py - файл установщика

Помимо этого, в __init__.py файле модуля должна быть переменная __version__, которая содержит номер текущей версии модуля. Например

__version__ = '0.1.0'

Текст README проверяйте на валидность http://www.tele3.cz/jbar/rest/rest.html, иначе на Python Package Index форматирование не будет отображаться, документация будет в текстовом формате.

Пример MANIFEST.in

Файл MANIFEST.in нужен для того, чтобы в egg-пакет вошли медиа-файлы (шаблоны, стили, js-скрипты)

recursive-include chunks *
include README README.rst DESCRIPTION INSTALL.txt
exclude *.orig *.pyc

Пример setup.py

setup.py по сути, самый главный файл при публикации модуля. Он определяет всю мета-информацию о пакете, разработчике, лицензии, файлах и т.п. в модуле

# -*- coding: utf-8 -*-
    import os
    from setuptools import setup, find_packages

# Utility function to read the README file.
# Used for the long_description.  It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
# string in below ...
    def read(fname):
        try:
            return open(os.path.join(os.path.dirname(__file__), fname)).read()
        except IOError:
            return ''

    setup(
        name="redsolutioncms.django-myapp",
        version=__import__('myapp').__version__,
        description=read('DESCRIPTION'),
        license="GPL",
        keywords="django tag1 tag2",

        author="John Doe",
        author_email="author_email@example.com",

        maintainer='John Doe',
        maintainer_email='maintainer_email@example.com',

        url="http://github.com/redsolution/myapp",
        classifiers=[
            'Development Status :: 3 - Alpha',
            'Intended Audience :: Developers',
            'License :: GPL',
            'Framework :: Django',
            'Environment :: Web Environment',
            'Natural Language :: Russian',
            'Natural Language :: English',
            'Operating System :: OS Independent',
            'Programming Language :: Python',
            'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
        ],
        packages=find_packages(exclude=['example', 'example.*']),
        install_requires=[],
        include_package_data=True,
        zip_safe=False,
        long_description=read('README'),
        entry_points={
            'redsolutioncms': ['myapp = myapp.redsolution_setup', ],
        }
    )

Следует привести комментарии к некоторым строкам. Во-первых, данный setup.py подразумевает, что файлы README и DESCRIPTION существуют. Если это не так - опсание будет пустое. Более того, версия будет пустая, если в __init__.py не будет переменной __version__.

Строка packages=find_packages(exclude=['example', 'example.*']), говорит о том, что если в модуле есть папка example, и она является питоновским модулем, то её нужно исключить из пакета - представьте, если дюжина модулей будет импортировать один и тот же модуль example.

Параметр entry_points нужен для интеграции с Redsolution CMS, об этом в статье про интеграцию.

Полная спецификация по параметрам приведена на сайте разработчика setuptools. На самом деле, я наблюдаю тенденцию отчуждения от модуля setuptools в пользу того же distribute, однако мы остановили выбор на стабильной и проверенной библиотеке.

Публикация

Опубликовать модуль на PYPI очень легко. В первую очередь, вам нужен будет аккаунт на PYPI. Затем, зайдите в папку проекта и наберите

python setup.py register