Создание файла 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.

Создание файла setup.py для упаковки Python-проекта - comments

En
Setup file python (python)