Статьи

Как написать свой собственный пакет Python

Python — замечательный язык программирования и многое другое. Одним из его слабых мест является упаковка. Это общеизвестный факт в сообществе. Установка, импорт, использование и создание пакетов с годами улучшились, но они все еще не соответствуют новым языкам, таким как Go и Rust, которые могли бы многому научиться из борьбы Python и других более зрелых языков.

В этом уроке вы узнаете все, что вам нужно знать, чтобы создавать свои собственные пакеты и делиться ими. Общие сведения о пакетах Python см. В разделе « Как использовать пакеты Python» .

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

Давайте начнем с быстрого примера. Пакет conman — это пакет для управления конфигурацией. Он поддерживает несколько форматов файлов, а также распределенную конфигурацию с использованием etcd .

Содержимое пакета, как правило, хранится в одном каталоге (хотя распространено разделение подпакетов в нескольких каталогах), а иногда, как в этом случае, в его собственном git-репозитории.

Корневой каталог содержит различные файлы конфигурации ( setup.py является обязательным и самым важным), а сам код пакета обычно находится в подкаталоге, имя которого является именем пакета и, в идеале, каталогом тестов. Вот как это выглядит для «conman»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
> tree
 
.
 
├── LICENSE
 
├── MANIFEST.in
 
├── README.md
 
├── conman
 
│ ├── __init__.py
 
│ ├── __pycache__
 
│ ├── conman_base.py
 
│ ├── conman_etcd.py
 
│ └── conman_file.py
 
├── requirements.txt
 
├── setup.cfg
 
├── setup.py
 
├── test-requirements.txt
 
├── tests
 
│ ├── __pycache__
 
│ ├── conman_etcd_test.py
 
│ ├── conman_file_test.py
 
│ └── etcd_test_util.py
 
└── tox.ini

Давайте быстро взглянем на файл setup.py . Он импортирует две функции из пакета setuptools : setup() и find_packages() . Затем он вызывает функцию setup() и использует find_packages() для одного из параметров.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from setuptools import setup, find_packages
 
 
 
setup(name=’conman’,
 
      version=’0.3′,
 
      url=’https://github.com/the-gigi/conman’,
 
      license=’MIT’,
 
      author=’Gigi Sayfan’,
 
      author_email=’the.gigi@gmail.com’,
 
      description=’Manage configuration files’,
 
      packages=find_packages(exclude=[‘tests’]),
 
      long_description=open(‘README.md’).read(),
 
      zip_safe=False,
 
      setup_requires=[‘nose>=1.0’],
 
      test_suite=’nose.collector’)

Это довольно нормально. Хотя файл setup.py является обычным файлом Python, и вы можете в нем делать все, что захотите, его основная задача — вызывать функцию setup() с соответствующими параметрами, поскольку при установке он будет вызываться различными инструментами стандартным способом. ваш пакет Я расскажу подробности в следующем разделе.

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

Функция setup() принимает большое количество именованных аргументов для управления многими аспектами установки пакета, а также выполнения различных команд. Многие аргументы указывают метаданные, используемые для поиска и фильтрации при загрузке вашего пакета в хранилище.

  • name: название вашего пакета (и как оно будет указано в PYPI)
  • версия: это важно для правильного управления зависимостями
  • url: URL вашего пакета, обычно GitHub или, возможно, URL readthedocs
  • пакеты: список подпакетов, которые должны быть включены; find_packages() помогает здесь
  • setup_requires: здесь вы указываете зависимости
  • test_suite: какой инструмент запустить во время теста

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

Файл setup.py также служит интерфейсом командной строки для запуска различных команд. Например, чтобы запустить модульные тесты, вы можете набрать: python setup.py test

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
running test
 
running egg_info
 
writing conman.egg-info/PKG-INFO
 
writing top-level names to conman.egg-info/top_level.txt
 
writing dependency_links to conman.egg-info/dependency_links.txt
 
reading manifest file ‘conman.egg-info/SOURCES.txt’
 
reading manifest template ‘MANIFEST.in’
 
writing manifest file ‘conman.egg-info/SOURCES.txt’
 
running build_ext
 
test_add_bad_key (conman_etcd_test.ConManEtcdTest) … ok
 
test_add_good_key (conman_etcd_test.ConManEtcdTest) … ok
 
test_dictionary_access (conman_etcd_test.ConManEtcdTest) … ok
 
test_initialization (conman_etcd_test.ConManEtcdTest) … ok
 
test_refresh (conman_etcd_test.ConManEtcdTest) … ok
 
test_add_config_file_from_env_var (conman_file_test.ConmanFileTest) … ok
 
test_add_config_file_simple_guess_file_type (conman_file_test.ConmanFileTest) … ok
 
test_add_config_file_simple_unknown_wrong_file_type (conman_file_test.ConmanFileTest) … ok
 
test_add_config_file_simple_with_file_type (conman_file_test.ConmanFileTest) … ok
 
test_add_config_file_simple_wrong_file_type (conman_file_test.ConmanFileTest) … ok
 
test_add_config_file_with_base_dir (conman_file_test.ConmanFileTest) … ok
 
test_dictionary_access (conman_file_test.ConmanFileTest) … ok
 
test_guess_file_type (conman_file_test.ConmanFileTest) … ok
 
test_init_no_files (conman_file_test.ConmanFileTest) … ok
 
test_init_some_bad_files (conman_file_test.ConmanFileTest) … ok
 
test_init_some_good_files (conman_file_test.ConmanFileTest) … ok
 
 
 
———————————————————————-
 
Ran 16 tests in 0.160s
 
 
 
OK

Setup.cfg — это файл в формате ini, который может содержать значения по умолчанию для команд, которые вы передаете в setup.py . Здесь setup.cfg содержит несколько опций для nosetests (наш тестовый бегун):

1
2
3
4
5
[nosetests]
 
verbose=1
 
nocapture=1

Этот файл содержит файлы, которые не являются частью внутренней директории пакета, но вы все еще хотите включить. Это, как правило, файл readme файл лицензии и тому подобное. Важным файлом является файл requirements.txt . Этот файл используется pip для установки других необходимых пакетов.

Вот файл MANIFEST.in Конмана:

1
2
3
4
5
include LICENSE
 
include README.md
 
include requirements.txt

Вы можете указать зависимости как в разделе install_requires файла setup.py и в файле require.txt. Pip автоматически установит зависимости из install_requires , но не из файла needs.txt. Чтобы установить эти требования, вы должны будете явно указать их при запуске pip: pip install -r requirements.txt .

Опция install_requires предназначена для указания минимальных и более абстрактных требований на уровне основной версии. Файл needs.txt предназначен для более конкретных требований, часто с закрепленными второстепенными версиями.

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

01
02
03
04
05
06
07
08
09
10
11
PyYAML==3.11
 
python-etcd==0.4.3
 
urllib3==1.7
 
pyOpenSSL==0.15.1
 
psutil==4.0.0
 
six==1.7.3

Закрепление дает вам предсказуемость и душевное спокойствие. Это особенно важно, если многие люди устанавливают ваш пакет в разное время. Без закрепления, каждый человек получит различное сочетание версий зависимостей в зависимости от того, когда они его установили. Недостатком закрепления является то, что если вы не успеваете за развитием зависимостей, вы можете застрять в старой, плохо работающей и даже уязвимой версии зависимости.

Первоначально я написал conman в 2014 году и не обращал на это особого внимания. Теперь, для этого урока, я обновил все, и было несколько серьезных улучшений для почти каждой зависимости.

Вы можете создать исходный дистрибутив или бинарный дистрибутив. Я покрою оба.

Вы создаете исходный дистрибутив с помощью команды: python setup.py sdist . Вот вывод для conman:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
> python setup.py sdist
 
running sdist
 
running egg_info
 
writing conman.egg-info/PKG-INFO
 
writing top-level names to conman.egg-info/top_level.txt
 
writing dependency_links to conman.egg-info/dependency_links.txt
 
reading manifest file ‘conman.egg-info/SOURCES.txt’
 
reading manifest template ‘MANIFEST.in’
 
writing manifest file ‘conman.egg-info/SOURCES.txt’
 
warning: sdist: standard file not found: should have one of README, README.rst, README.txt
 
 
 
running check
 
creating conman-0.3
 
creating conman-0.3/conman
 
creating conman-0.3/conman.egg-info
 
making hard links in conman-0.3…
 
hard linking LICENSE -> conman-0.3
 
hard linking MANIFEST.in -> conman-0.3
 
hard linking README.md -> conman-0.3
 
hard linking requirements.txt -> conman-0.3
 
hard linking setup.cfg -> conman-0.3
 
hard linking setup.py -> conman-0.3
 
hard linking conman/__init__.py -> conman-0.3/conman
 
hard linking conman/conman_base.py -> conman-0.3/conman
 
hard linking conman/conman_etcd.py -> conman-0.3/conman
 
hard linking conman/conman_file.py -> conman-0.3/conman
 
hard linking conman.egg-info/PKG-INFO -> conman-0.3/conman.egg-info
 
hard linking conman.egg-info/SOURCES.txt -> conman-0.3/conman.egg-info
 
hard linking conman.egg-info/dependency_links.txt -> conman-0.3/conman.egg-info
 
hard linking conman.egg-info/not-zip-safe -> conman-0.3/conman.egg-info
 
hard linking conman.egg-info/top_level.txt -> conman-0.3/conman.egg-info
 
copying setup.cfg -> conman-0.3
 
Writing conman-0.3/setup.cfg
 
creating dist
 
Creating tar archive
 
removing ‘conman-0.3’ (and everything under it)

Как вы можете видеть, я получил одно предупреждение об отсутствии файла README с одним из стандартных префиксов, потому что мне нравится Markdown, поэтому вместо этого у меня есть «README.md». Кроме этого, все исходные файлы пакета были включены и дополнительные файлы. Затем в conman.egg-info была создана группа метаданных. Наконец, создается сжатый tar-архив с именем conman-0.3.tar.gz который помещается в подкаталог dist .

Для установки этого пакета потребуется этап сборки (даже если это чистый Python). Вы можете установить его с помощью pip как обычно, просто передав путь к пакету. Например:

1
2
3
4
5
6
7
8
9
pip install dist/conman-0.3.tar.gz
 
Processing ./dist/conman-0.3.tar.gz
 
Installing collected packages: conman
 
  Running setup.py install for conman … done
 
Successfully installed conman-0.3

Conman был установлен в site-пакеты и может быть импортирован как любой другой пакет:

1
2
3
4
5
import conman
 
conman.__file__
 
‘/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc’

Колеса — это относительно новый способ упаковки кода Python и, возможно, расширений C. Они заменяют формат яйца. Существует несколько типов колес: колеса чистого Python, колеса платформы и универсальные колеса. Чистые колеса Python — это пакеты типа conman, которые не имеют кода расширения C.

Колеса платформы имеют код расширения C. Универсальные колеса — это чистые колеса Python, которые совместимы как с Python 2, так и с Python 3 с одинаковой кодовой базой (для них не требуется даже 2to3). Если у вас есть чистый пакет Python и вы хотите, чтобы ваш пакет поддерживал как Python 2, так и Python 3 (что становится все более и более важным), то вы можете собрать одну универсальную сборку вместо одного колеса для Python 2 и одного колеса для Python 3.

Если ваш пакет имеет код расширения C, вы должны создать колесо платформы для каждой платформы. Огромное преимущество колес, особенно для пакетов с расширениями C, заключается в том, что нет необходимости иметь компилятор и вспомогательные библиотеки, доступные на целевой машине. Колесо уже содержит встроенный пакет. Таким образом, вы знаете, что его не удастся собрать, и установить его намного быстрее, потому что это буквально просто копия. Люди, которые используют научные библиотеки, такие как Numpy и Pandas, могут по достоинству оценить это, поскольку установка таких пакетов занимала много времени и могла привести к сбою, если какая-то библиотека отсутствовала или компилятор не был правильно настроен.

Команда для создания чистых колес или платформ: python setup.py bdist_wheel .

Setuptools — движок, который обеспечивает функцию setup() — автоматически обнаружит, нужно ли чистое колесо или колесо платформы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
running bdist_wheel
 
running build
 
running build_py
 
creating build
 
creating build/lib
 
creating build/lib/conman
 
copying conman/__init__.py -> build/lib/conman
 
copying conman/conman_base.py -> build/lib/conman
 
copying conman/conman_etcd.py -> build/lib/conman
 
copying conman/conman_file.py -> build/lib/conman
 
installing to build/bdist.macosx-10.9-x86_64/wheel
 
running install
 
running install_lib
 
creating build/bdist.macosx-10.9-x86_64
 
creating build/bdist.macosx-10.9-x86_64/wheel
 
creating build/bdist.macosx-10.9-x86_64/wheel/conman
 
copying build/lib/conman/__init__.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
 
copying build/lib/conman/conman_base.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
 
copying build/lib/conman/conman_etcd.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
 
copying build/lib/conman/conman_file.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
 
running install_egg_info
 
running egg_info
 
creating conman.egg-info
 
writing conman.egg-info/PKG-INFO
 
writing top-level names to conman.egg-info/top_level.txt
 
writing dependency_links to conman.egg-info/dependency_links.txt
 
writing manifest file ‘conman.egg-info/SOURCES.txt’
 
reading manifest file ‘conman.egg-info/SOURCES.txt’
 
reading manifest template ‘MANIFEST.in’
 
writing manifest file ‘conman.egg-info/SOURCES.txt’
 
Copying conman.egg-info to build/bdist.macosx-10.9-x86_64/wheel/conman-0.3-py2.7.egg-info
 
running install_scripts
 
creating build/bdist.macosx-10.9-x86_64/wheel/conman-0.3.dist-info/WHEEL

Проверяя каталог dist , вы можете увидеть, что было создано чистое колесо Python:

01
02
03
04
05
06
07
08
09
10
11
ls -la dist
 
 
 
dist/
 
total 32
 
-rw-r—r— 1 gigi staff 5.5K Feb 29 07:57 conman-0.3-py2-none-any.whl
 
-rw-r—r— 1 gigi staff 4.4K Feb 28 23:33 conman-0.3.tar.gz

Имя «conman-0.3-py2-none-any.whl» имеет несколько компонентов: имя пакета, версия пакета, версия Python, версия платформы и, наконец, расширение «whl».

Чтобы создать универсальные пакеты, вы просто добавляете --universal , как в python setup.py bdist_wheel --universal .

Полученное колесо называется «conman-0.3-py2.py3-none-any.whl».

Обратите внимание, что вы несете ответственность за то, чтобы ваш код действительно работал под Python 2 и Python 3, если вы создаете универсальный пакет.

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

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