Несколько недель назад я решил, что хочу создать свой первый пакет Python, и я попытался понять, с чего лучше начать.
Так вот, пытаясь найти какой-нибудь простой учебник, который подошел бы для начала, я запутался и начал немного переживать. Именно поэтому я решил написать эту статью. Я захотел создать руководство, которое бы описывало процесс создания моего первого пакета Python.
Что такое пакет Python?
Прежде чем мы начнем, мы должны понять, что такое пакет Python.
Пакет Python – это каталог, который содержит набор модулей и файл зависимостей __init__.py. Этот файл может быть абсолютно пустым. Он необходим для того, чтобы пометить каталог на диске как пакет Python.
Ниже приведен пример каталога пакета:
package
__init__.py
module_a.py
module_b.py
module_c.py
Пример каталога пакета
__init__.py - это файл зависимостей, который помогает Python находить доступные модули в каталоге пакета. Если мы удалим этот файл, то Python не сможет импортировать модули.
Пакеты vs. Модули
Теперь вы знаете, что пакеты Python создают структурированный каталог с несколькими модулями внутри, а что насчет модулей? Давайте убедимся, что мы понимаем, в чем разница между пакетом и модулем:
Модуль – это всегда один файл Python (turtle.py). Он содержит логику, то есть классы, функции и константы.
Пакет – это, по сути, тоже модуль, но он может содержать множество других модулей и подпакетов.
Структура пакета
И все же, пакеты содержат не только модули. Они также содержат высокоуровневые сценарии, документацию и тесты. Ниже приведен пример того, как можно структурировать стандартный пакет Python:
package_name/
docs/
scripts/
src/
package_a
__init__.py
module_a.py
package_b
__init__.py
module_b.py
tests/
__init__.py
test_module_a.py
test_module_b.py
LICENSE.txt
CHANGES.txt
MANIFEST.in
README.txt
pyproject.toml
setup.py
setup.cfg
Пример структуры пакета
Давайте разберемся, для чего нужен каждый файл в этом дереве:
- package_name: обозначает основной пакет.
- docs: содержит документацию по использованию пакета.
- scripts/: ваши высокоуровневые сценарии.
- src: то, куда заходит ваш код. Он содержит пакеты, модули, подпакеты и т.д.
- tests: здесь вы можете хранить модульные тестирования.
- LICENSE.txt: содержит текст лицензии (например, MIT).
- CHANGES.txt: регистрирует изменения, внесенные в каждую версию.
- MANIFEST.in: то, куда вы помещаете инструкции о том, какие дополнительные файлы вы хотите добавить (файлы без кода).
- README.txt: содержит описание пакета (markdown-формат).
- pyproject.toml: файл, предназначенный для регистрации систем сборки.
- setup.py: содержит сценарий сборки для ваших соответствующих систем.
- setup.cfg: файл конфигурации ваших систем сборки.
Обратите внимание, что мы можем добавить тестовые файлы в основной пакет двумя способами. Мы можем оставить их на верхнем уровне, как мы это делали выше, или поместить их внутрь пакета, как показано ниже:
package_name/
__init__.py
module_a.py
module_b.py
test/
__init__.py
test_module_a.py
test_module_b.py
Пример независимых тестирований
Как по мне, подход с хранением тестирований на верхнем уровне может оказаться крайне полезным, особенно когда эти тесты подразумевают чтение и запись других внешних файлов.
Что использовать: setup.cfg или setup.py?
setup.py и setup.cfg – это инструменты организации пакетов по умолчанию в PyPl, setuptools, pip и стандартной библиотеке Python.
Здесь они представляют собой сценарии конфигурации и сборки для setuptools. Они оба указывают setuprools, как собирать и устанавливать пакет.
Упомянутый файл содержит такую информацию, как версия, пакеты и файлы, которые необходимо добавить, а также любые другие требования.
Ниже приведен пример файла setup.py, который использует некоторые аргументы setup().
import setuptools
with open("README.md", "r", encoding = "utf-8") as fh:
long_description = fh.read()
setuptools.setup(
name = "package-name",
version = "0.0.1",
author = "author",
author_email = "author@example.com",
description = "short package description",
long_description = long_description,
long_description_content_type = "text/markdown",
url = "package URL",
project_urls = {
"Bug Tracker": "package issues URL",
},
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
package_dir = {"": "src"},
packages = setuptools.find_packages(where="src"),
python_requires = ">=3.6"
)
setup.py
Файл setup.cfg выглядит совсем иначе. Как правило, он содержит только два обязательных ключа: command и options.
Ключ command – это одна из команд distutils, а ключ options определяет параметры, которые может поддерживать команда.
[command]
option = value
setup.cfg
Ниже приведен пример файла setup.cfg, который использует некоторые параметры и метаданные.
[metadata]
name = package-name
version = 0.0.1
author = name of the author
author_email = author@example.com
description = short package description
long_description = file: README.md
long_description_content_type = text/markdown
url = package url
project_urls =
Bug Tracker = package issues url
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
package_dir =
= src
packages = find:
python_requires = >=3.6
[options.packages.find]
where = src
setup.cfg
Оба файла - setup.py и setup.cfg, предназначены только для setuptools. Кроме того, файл setup.cfg можно безопасно переместить в pyproject.toml.
В данном случае идея состоит в том, что, если мы однажды заходим сменить систему сборки, например, на flit или poetry, все, что нам нужно будет сделать, это изменить запись о системе сборки (например, setuptools) в pyproject.toml на что-то вроде flit или poetry, а не писать все заново.
Абсолютно не важно, какой файл конфигурации мы выбрали (pyproject.toml, setup.cfg или setup.py), мы обязаны сопровождать этот файл.
Руководство по работе с пакетами Python гласит, что лучше всего использовать файл setup.cfg, так как он статичен, понятно написан, легко читается и позволяет избежать ошибок кодирования.
Как создать свой первый пакет Python
А теперь пришло время создать простой пакет Python. В качестве системы сборки мы возьмем setuptools, а setup.cfg и pyproject.toml помогут нам с настройкой нашего проекта.
Настроим файлы пакета
Мы создаем довольно простой пакет, и мы должны структурировать наш каталог, добавив в него файлы зависимостей, которые необходимы для того, чтобы мы могли распространять пакет. Структура пакета будет выглядеть так:
basicpkg/
src/
divide
__init__.py
divide_by_three.py
multiply
__init__.py
multiply_by_three.py
tests/
__init__.py
test_divide_by_three.py
test_multiply_by_three.py
LICENSE.txt
README.txt
pyproject.toml
setup.cfg
Структура нашего пакета
Наш основной пакет состоит из двух пакетов: первый предназначен для деления чисел на три, а второй – для умножения чисел на три.
В данном случае мы игнорируем некоторые файлы, например, CONTEXT.txt, MANIFEST.in и каталог docs/, для того, чтобы упростить задачу. Но при этом нам нужен каталог test/ для того, чтобы добавить туда наши модульные тесты для проверки того, как ведет себя пакет.
Добавим в наши модули некоторую логику
Как и всегда, мы оставляем файл __init__.py пустым. Далее мы должны добавить в наши модули некоторую логику, чтобы мы могли выполнять различные операции.
Возьмем пакет, предназначенный для деления чисел. Добавим в файл divide_by_three.py следующее содержимое, чтобы иметь возможность делить числа на три:
def divide_by_three(num):
return num / 3
divide_by_three.py
Аналогичную логику добавляем в файл multiply_by_three.py, который находится внутри пакета, предназначенного для умножения чисел. Только в этом случае мы хотим иметь возможность умножать числа на три:
def multiply_by_three(num):
return num * 3
multiply_by_three.py
Не бойтесь добавлять другие пакеты и модули для каких-то других действий. Например, вы можете добавить пакеты для выполнения сложения и вычитания чисел.
Протестируем наши модули
Лучше всего добавлять автоматизированные тестирования в программы, которые мы создаем. В данном случае для проведения тестирования наших модулей и поведения пакета мы будем использовать unittest.
Давайте добавим следующий код в файл test_divide_by_three.py, расположенный в каталоге test/:
import unittest
from divide.by_three import divide_by_three
class TestDivideByThree(unittest.TestCase):
def test_divide_by_three(self):
self.assertEqual(divide_by_three(12), 4)
unittest.main()
test_divide_by_three.py
Для того, чтобы выполнить автоматизированное тестирование, мы импортировали TestCase из unittest. После чего мы импортировали наш метод divide_by_three() из модуля by_three, который расположен в пакете, отвечающем за деление чисел.
Если мы удалим здесь файл __init__.py, то Python больше не сможет найти наши модули.
Метод .assertEqual() предназначен для проверки двух значений (divide_by_three(12) и 4) на равенство. А метод unittest.main() был реализован для запуска всех наших тестирований.
Логика для test_multiply_by_three.py аналогична:
import unittest
from multiply.by_three import multiply_by_three
class TestMultiplyByThree(unittest.TestCase):
def test_multiply_by_three(self):
self.assertEqual(multiply_by_three(3), 9)
unittest.main()
test_multiply_by_three.py
Для того, чтобы запустить тестирования, введите в терминал/командную строку следующее:
Для Linux:
python3 tests/test_divide_by_three.py
Для Windows:
py tests/test_divide_by_three.py
Для того, чтобы выполнить тестирование модуля, отвечающего за умножение чисел, мы должны сделать тоже самое. Если наши тестирования будут запущены успешно, вы увидите следующее:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Если вы захотите добавить дополнительные пакеты и модули, попробуйте добавить к ним несколько методов из unittest. Отличная задачка для вас.
Настроим метаданные с помощью setup.cfg
Теперь нам нужно добавить файл конфигурации для setuptools. Как мы уже упоминали, этот файл конфигурации укажет setuptools, как собрать и установить наш пакет.
Итак, мы должны добавить в наш файл setup.cfg следующие метаданные и параметры. Не забудьте изменить имя, так как я уже загрузил пакет под таким названием на TestPyPI. Кроме того, поменяйте дополнительную информацию, то есть автора, адрес электронной почты и URL-адреса проекта, чтобы ваш пакет распространялся с вашей информацией.
[metadata]
name = basicpkg
version = 1.0.0
author = your name
author_email = your email
description = A simple Python package
long_description = file: README.md, LICENSE.txt
long_description_content_type = text/markdown
url = https://gitlab.com/codasteroid/basicpkg
project_urls =
Bug Tracker = https://gitlab.com/codasteroid/basicpkg/-/issues
repository = https://gitlab.com/codasteroid/basicpkg
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
package_dir =
= src
packages = find:
python_requires = >=3.6
[options.packages.find]
where = src
setup.cfg
Что касается параметров, оставьте значения по умолчанию. Параметр package_dir определяет местонахождение корневого каталога пакета, где хранятся пакеты, модули и все исходные файлы Python.
С помощью ключа packages мы можем перечислить наши пакеты вручную, то есть [divide, multiply]. Но если мы хотим указать все пакеты, то можем просто написать find: и указать, где нам нужно искать эти пакеты с помощью [options.packages.find], где ключу where присваивается имя корневого каталога пакета.
Обязательно добавим в файл конфигурации ключ classifiers (это нужно делать всегда). С его помощью мы можем добавить некоторую важную информацию, например, версию Python и операционную систему, для которой подходит ваш пакет.
Создаем pyproject.toml
В качестве системы сборки мы будем использовать setuptools. Для того, чтобы сообщить pip или какому-то другому инструменту сборки о нашей системе сборки, нам потребуются две переменные (см. ниже).
Для того, чтобы добавить все то, что нам нужно для сборки пакета, мы воспользуемся build-system.require, и при этом system.build-back-end определяет объект, который будет выполнять сборку.
Итак, давайте добавим следующее содержимое в файл pyproject.toml:
[build-system]
requires = ['setuptools>=42']
build-backend = 'setuptools.build_meta'
pyproject.toml
Обратите внимание, если вы захотите сменить систему сборки, например, на flit или poetry, вы можете безопасно переместить все параметры конфигурации из файла setup.cfg в pyproject.toml. Эта функция сэкономит вам время.
Создаем README.md
Очень важно создать грамотный файл README.md. Давайте добавим описание к нашему пакету и некоторые инструкции по его установке.
# `basicpkg`
The `basicpkg` is a simple testing example to understand the basics of developing your first Python package.
README.md
Кроме того, мы можем добавить информацию о том, как его использовать:
from multiply.by_three import multiply_by_three
from divide.by_three import divide_by_three
multiply_by_three(9)
divide_by_three(21)
README.md
Не стесняйтесь добавлять любую информацию, которая может помочь другим разработчикам понять, для чего нужен ваш пакет, как его устанавливать и как с ним работать.
Обратите внимание, что наш файл конфигурации будет загружать README.md и будет добавлен при распространении нашего пакета.
Добавляем лицензию
Крайне важно добавить лицензию в ваш проект, чтобы пользователи знали, как они могут использовать ваш код. Давайте выберем для нашего пакета лицензию MIT и добавим в файл LICENSE.txt следующее содержимое:
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LICENSE.txt
Не забудьте поменять [year] на текущий год, а [fullname] - на ваше имя или имена правообладателей.
Создаем архивы дистрибутива
И остался последний шаг перед тем, как мы сможем перейти к распространению нашего пакета: создать архивы дистрибутива нашего пакета Python. Для этого нам нужно обновить нашу сборку PyPA, а затем создать архивы исходного кода и сборки.
Запустим следующие команды в терминале/командной строке из того же каталога, где хранится файл pyproject.toml:
Для Linux:
python3 -m pip install --upgrade build
python3 -m build
Для Windows:
py -m pip install --upgrade build
py -m build
Как только этот процесс завершится, будет создан новый каталог под названием dist/, в котором будут находиться два файла. Файл .tag.tz - архив исходного кода, и файл .whl* - архив сборки. Эти файлы – это и есть архивы дистрибутива нашего пакета Python, который мы далее загрузим в Python Package Index и установим с помощью pip.
Как загрузить пакет в Python
Python Package Index – это, то куда мы будем загружать наш пакет. Поскольку наш пакет находится на этапе тестирования, и мы все еще можем добавлять в него новые функции в качестве эксперимента, нам стоит использовать отдельную версию PyPI под названием TestPyPI. Эта версия была создана специально для проведения экспериментов и тестирования. После чего, когда ваш пакет будет готов и будет полностью вас удовлетворять, вы сможете загрузить его в PyPI как готовый пакет.
Следующие инструкции помогут нам подготовить TestPyPI к загрузке нашего пакета:
- Перейдите на TestPyPI и создайте там учетную запись.
- Подтвердите свой адрес электронной почты, чтобы вы могли загружать пакеты.
- Настройте профиль (добавьте фотографию и т.д.).
- Перейдите в api-tokens и создайте свой токен API для того, чтобы вы могли безопасно загружать свои пакеты.
- На этой же странице установите область действия («вся учетная запись»).
- Скопируйте и сохраните токен в безопасном месте на диске.
Далее мы должны загрузить наши архивы дистрибутива. Для этого нам понадобиться инструмент загрузки. Официальный инструмент загрузки PyPI – это Twine. Поэтому мы должны установить Twine и загрузить архивы дистрибутива в каталог dist/.
Запустим следующие команды в терминале/командной строке из того же каталога, где хранится файл pyproject.toml:
Для Linux:
python3 -m pip install --upgrade twine
python3 -m twine upload --repository testpypi dist/*
Для Windows:
py -m pip install --upgrade twine
py -m twine upload --repository testpypi dist/*
Затем введите __token__ в качестве имени пользователя, а токен, который вы сохранили (в том числе префикс pypi-), - в качестве пароля. А теперь нажмите Enter, чтобы загрузить дистрибутивы.
Как установить загруженный пакет Python
А теперь пришло время установить наш пакет. Для того, чтобы установить его из TestPyPI, вы можете создать виртуальное окружение и воспользоваться инструментом pip:
Для Linux:
python3 -m venv env
source env/bin/activate
(env) python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps basicpkg
Для Windows:
py -m venv env
.\env\Scripts\activate
(env) py -m pip install --index-url https://test.pypi.org/simple/ --no-deps basicpkg
Прежде чем проверять, правильно ли работает ваш пакет, убедитесь, что ваше виртуальное окружение активировано.
Откройте терминал/командную строку и запустите команду python3 (для пользователей Linux) или команду py (для пользователей Windows). После чего введите следующий код, чтобы убедиться, что пакеты, отвечающие за умножение и деление чисел, работают как надо:
from multiply.by_three import multiply_by_three
from divide.by_three import divide_by_three
multiply_by_three(9)
divide_by_three(21)
# Output: 27
# Output: 7
Не забывайте, что для выполнения необходимых операций, мы должны импортировать соответствующие методы из наших модулей.
Ура! Наш пакет работает как надо.
Дальше, после того, как вы поэкспериментируете и протестируете свой пакет, вам нужно выполнить следующие инструкции для того, чтобы загрузить свой пакет в настоящий PyPI:
- Перейдите на PyPI и создайте учетную запись.
- Запустите в терминале/командной строке команду twine upload dist/*.
- Введите учетные данные, с которыми вы регистрировались на PyPI.
- Затем запустите команду pip install [package_name] для того, чтобы установить ваш пакет.
Поздравляю! Ваш пакет был установлен с реального PyPI.
Что дальше?
Было бы просто замечательно, если бы вы придумали какую-нибудь простенькую идею, взяли ее за основу и создали свой первый настоящий пакет Python. В этой статье я сделал акцент на основах, которые нужны для того, чтобы начать работать, но в мире модулей и пакетов есть еще чему поучиться.
Надеюсь, что мой первый опыт разработки пакетов на Python помог вам понять, что вам нужно для того, чтобы создать свой собственный.