Создание файла setup.py: упаковка и распространение Python-приложений
Основы создания setup.py для упаковки проекта
Файл setup.py является центральным элементом традиционной системы сборки Python-пакетов на базе setuptools. Он содержит метаданные проекта, зависимости, точки входа и инструкции по сборке. Правильно составленный setup.py позволяет распространять код через PyPI, устанавливать его с помощью pip и управлять зависимостями.
Универсальный шаблон setup.py для большинства проектов
Наиболее эффективное решение объединяет все необходимые элементы: описание, версионирование, список зависимостей, точки входа для командной строки, включение не-Python файлов и классификаторы. Пример ниже подходит для приложений и библиотек.
from setuptools import setup, find_packages
setup(
name='mypackage',
version='0.1.0',
description='Пример пакета для демонстрации setup.py',
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
author='Имя Разработчика',
author_email='dev@example.com',
url='https://github.com/user/mypackage',
license='MIT',
packages=find_packages(where='src'),
package_dir={'': 'src'},
include_package_data=True,
package_data={
'mypackage': ['templates/*.html', 'static/**/*'],
},
install_requires=[
'requests>=2.25.0',
'flask>=2.0',
],
extras_require={
'dev': ['pytest', 'sphinx'],
'extra': ['numpy>=1.20'],
},
entry_points={
'console_scripts': [
'mycli = mypackage.cli:main',
],
},
classifiers=[
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
],
python_requires='>=3.8',
)
Setup file python (создание файла setup.py для упаковки python-проекта)
Пояснение параметров:
- name – уникальное имя пакета на PyPI.
- version – текущая версия, рекомендуется setuptools_scm для автоматического считывания из git.
- find_packages с where указывает на каталог с исходниками, что упрощает структуру.
- include_package_data и package_data – для включения файлов, не являющихся .py (шаблоны, изображения).
- entry_points создаёт исполняемые скрипты при установке.
- extras_require – опциональные зависимости для разработки или дополнительных функций.
Типичные проблемы:
- Некорректный путь в package_dir – пакет не будет найден. Решение: проверить реальную структуру каталогов.
- Забытый MANIFEST.in для включения дополнительных файлов (например, README.md) – файлы не попадут в sdist. Решение: создать MANIFEST.in с директивами
include README.md. - Конфликт версий зависимостей – рекомендуется задавать минимальные версии с
>=, но не жёсткую фиксацию.
Как создать минимальный setup.py только с именем и версией?
Для простого однофайлового модуля можно обойтись без find_packages.
from setuptools import setup
setup(
name='simple_module',
version='0.1',
py_modules=['simple_module'],
)
Подходит для скриптов, не требующих сложной структуры. Проблема: при добавлении пакетов придётся вручную перечислять все модули.
Как включить данные (не-Python файлы) в пакет?
Используйте include_package_data=True и package_data. Если данные лежат вне пакета, дополните MANIFEST.in.
package_data={
'mypkg': ['data/*.json', 'config/*.yaml'],
}
Ошибка: файлы не устанавливаются из wheel. Решение – проверить, что include_package_data установлен в True, и добавить MANIFEST.in для sdist.
Как добавить точку входа для командной строки (CLI)?
entry_points со словарём console_scripts.
entry_points={
'console_scripts': [
'myapp = myapp.main:run',
],
}
После установки команда myapp будет доступна в терминале. Важно: функция run должна быть вызываемой (не импортировать с побочными эффектами).
Как настроить автоматическое версионирование через git (setuptools_scm)?
Установите setuptools_scm и добавьте в setup.py:
from setuptools import setup
setup(
name='mypkg',
use_scm_version=True,
setup_requires=['setuptools_scm'],
...
)
Тег git будет автоматически преобразован в версию. Проблема: если нет git-репозитория, сборка упадёт. Решение: задать fallback-версию через configuration в pyproject.toml.
Как упаковать проект с C-расширениями (например, модуль на Cython)?
Используйте ext_modules с Extension.
from setuptools import setup, Extension
module = Extension('mypkg._native', sources=['src/native.c'])
setup(..., ext_modules=[module])
Для Cython укажите .pyx файл и добавьте cython в setup_requires. Проблемы: компилятор не найден – требуется установить build-essential (Ubuntu) или Xcode (macOS).
Общие ошибки и их решение
- ImportError: No module named 'setuptools' – предварительно установите setuptools:
pip install setuptools. - Ошибка кодировки при чтении README.md – укажите encoding='utf-8' в open().
- Файл не найден при установке – проверьте пути относительно setup.py и наличие MANIFEST.in.
- Консольный скрипт не устанавливается – проверьте, что функция в entry_points определена корректно и не содержит синтаксических ошибок.
Расширенные примеры использования setup.py
Пример 1: Полный проект с тестами и документацией
Структура каталогов:
project_root/
├── src/
│ └── myapp/
│ ├── __init__.py
│ ├── main.py
│ └── data/
│ └── config.json
├── tests/
│ └── test_main.py
├── docs/
│ └── index.rst
├── setup.py
├── MANIFEST.in
├── README.md
└── pyproject.toml (опционально)
Содержимое setup.py:
from setuptools import setup, find_packages
setup(
name='myapp',
version='1.2.3',
description='Приложение для обработки данных',
long_description=open('README.md', encoding='utf-8').read(),
long_description_content_type='text/markdown',
packages=find_packages(where='src'),
package_dir={'': 'src'},
include_package_data=True,
package_data={
'myapp': ['data/*.json'],
},
install_requires=[
'pandas>=1.3',
'click',
],
extras_require={
'dev': [
'pytest>=6',
'sphinx',
],
},
entry_points={
'console_scripts': [
'myapp = myapp.main:cli',
],
},
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.8',
)
MANIFEST.in:
include README.md
include LICENSE
recursive-include docs *
recursive-include tests *
Сборка и проверка:
python setup.py sdist bdist_wheel
pip install dist/myapp-1.2.3-py3-none-any.whl
myapp --help
Usage: myapp [OPTIONS] COMMAND [ARGS]... ...
Пример 2: Проект с C-расширением на Cython
Файл ext_module.pyx:
def add(int a, int b):
return a + b
setup.py:
from setuptools import setup, Extension
from Cython.Build import cythonize
setup(
name='cython_ext',
version='0.1',
ext_modules=cythonize([Extension('myext', ['ext_module.pyx'])]),
setup_requires=['Cython'],
)
Сборка:
python setup.py build_ext --inplace
python -c "import myext; print(myext.add(3,4))"
7
Пример 3: Использование setup.cfg вместо setup.py
Метаданные можно вынести в setup.cfg, а setup.py оставить минимальным:
# setup.py
from setuptools import setup
setup()
# setup.cfg
[metadata]
name = mypackage
version = 0.1.0
description = Пример конфигурации через setup.cfg
[options]
packages = find:
package_dir =
= src
include_package_data = True
install_requires =
requests>=2.25
[options.packages.find]
where = src
[options.entry_points]
console_scripts =
mycli = mypackage.cli:main
Такой подход разделяет код и конфигурацию, облегчая чтение. Проблемы: некоторые опции (например, ext_modules) не поддерживаются в setup.cfg – требуется setup.py.
Пример 4: Динамическое чтение версии из __init__.py
Можно определить версию в самом модуле и импортировать её:
# src/mypackage/__init__.py
__version__ = '2.0.0'
# setup.py
import re
with open('src/mypackage/__init__.py') as f:
version = re.search(r'__version__ = [\'"]([^\'"]+)[\'"]', f.read()).group(1)
setup(
name='mypackage',
version=version,
...
)
Проблемы: если модуль содержит импорты, они могут выполняться при чтении. Решение – использовать setuptools_scm или выделить версию в отдельный файл version.py.