Files
blog/content/ru/project-start.md
Pavel Kirilin 68dcb8da74 Added k3s page.
Signed-off-by: Pavel Kirilin <win10@list.ru>
2021-12-09 15:38:19 +04:00

17 KiB
Raw Blame History

title, description, category, position
title description category position
Как стартовать проект Лучший вариант начала проекта на питоне. Python 1

Как стартовать проект

В целом, вариантов несколько. Например, можно просто хранить несколько скриптов на github и быть довольным, можно дописать к ним setup.py и выложиться на pypi. Но лучший способ - структурированный проект с poetry.

Почему?

Особенностей у poetry много. Сейчас расскажу про то, как использовать и что делать.

Для начала создадим проект.

$ poetry new new_proj
Created package new_proj in new_proj
$ cd new_proj

Данная команда сгенерирует следующую структуру:

new_proj
├── new_proj
│   └── __init__.py
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_new_proj.py

Это наш новый проект с небольшими заготовками. В целом, тут нет ничего необычного. Весь код библиотеки/проекта будет в папке с названием проекта. README.rst - дефолтный README. pyproject.toml - мета-данные о проекте, такие как: зависимости, описание, дополнительные установочные опции и многое другое. Ты можешь прочитать побольше о pyproject.toml тут.

Как работать с poetry

Вся информация есть в официальной документации poetry. Тут я расскажу про основные моменты.

Управление зависимостями

Чтобы добавить зависимость проекта достаточно выполнить

$ poetry add ${dependency}

Данная команда найдет последнюю нужную версию и запишет её в pyproject.toml и в poetry.lock.

Для того, чтобы установить зависимость для разработки (линтеры например), достаточно добавить флаг --dev.

$ poetry add ${dependency} --dev 

Чтобы удалить зависимость достаточно просто add заменить на remove.

Примеры работы:

$ poetry add loguru   
Using version ^0.5.3 for loguru  

Updating dependencies  
Resolving dependencies... (0.1s)  

Writing lock file
  
Package operations: 1 install, 0 updates, 0 removals  
 • Installing loguru (0.5.3)
$ poetry remove loguru
Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Package operations: 0 installs, 0 updates, 1 removal  
 • Removing loguru (0.5.3)

Выполнение команд

При использовании virtualenv для входа в оболочку всегда надо было вводить

source venv/bin/activate

Однако с poetry это излишне. Он сам создает и менеджит виртуальные среды. Чтобы выполнить одиночную команду можно вызвать run.

Например:

$ poetry install black --dev
Using version ^20.8b1 for black   
 • Installing appdirs (1.4.4)  
 • Installing click (7.1.2)  
 • Installing mypy-extensions (0.4.3)  
 • Installing pathspec (0.8.1)  
 • Installing regex (2020.11.13)  
 • Installing toml (0.10.2)  
 • Installing typed-ast (1.4.2)  
 • Installing typing-extensions (3.7.4.3)  
 • Installing black (20.8b1)
$ poetry run black .
reformatted new_proj/new_proj/__init__.py
reformatted new_proj/tests/test_new_proj.py
All done! ✨ 🍰 ✨
2 files reformatted, 1 file left unchanged.

Для выполнения нескольких комманд подряд, можно войти в shell. Это аналог source venv/bin/activate.

$ poetry shell
Spawning shell within /home/s3rius/.cache/pypoetry/virtualenvs/new-proj-eutP4v0O-py3.9
$ black .
reformatted new_proj/new_proj/__init__.py
reformatted new_proj/tests/test_new_proj.py
All done! ✨ 🍰 ✨
2 files reformatted, 1 file left unchanged.

Версионирование

Менять версии пакета вручную больше не нужно.

У poetry есть кое что для тебя.

$ poetry version patch   
Bumping version from 0.1.0 to 0.1.1
$ poetry version preminor
Bumping version from 0.1.1 to 0.2.0-alpha.0
$ poetry version minor   
Bumping version from 0.2.0-alpha.0 to 0.2.0
$ poetry version premajor
Bumping version from 0.2.0 to 1.0.0-alpha.0
$ poetry version major   
Bumping version from 1.0.0-alpha.0 to 1.0.0

pyproject.toml

Как было сказано ранее, данный файл содержит в себе мета-информацию пакета.

Пример pyproject.toml

[tool.poetry]
name = "new_proj"
version = "0.1.0"
description = "Test library for example"
readme = "README.rst"
homepage = "https://test_url.com/"
repository = "https://github.meme/"
authors = ["Pavel Kirilin <win10@list.ru>"]

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
pytest = "^6.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Для того чтобы устновить все зависимости требуется просто ввести

$ poetry install

Данная команда создаст виртуальную среду сама и установит все зависимости. Включая dev-зависимости. Чтобы установить зависимости только для приложения можно добавить --no-dev ключ.

Как паковать и публиковать на pypi

Всё очень просто. Давайте добавим какую-нибудь функцию в проект.

def ab_problem(a: int, b: int) -> int:
    return a + b
from new_proj.main import ab_problem

__all__ = [
    'ab_problem'
]

Теперь соберем проект.

$ poetry build
Building new_proj (0.1.0)
  - Building sdist
  - Built new_proj-0.1.0.tar.gz
  - Building wheel
  - Built new_proj-0.1.0-py3-none-any.whl

Та-да. Это готовый к публикации на pypi пакет. Лежит он в папке dist.

Давайте проверим, что всё работает корректно.

$ pip install "./dist/new_proj-0.1.0-py3-none-any.whl"  
Processing ./dist/new_proj-0.1.0-py3-none-any.whl
Installing collected packages: new-proj
Successfully installed new-proj-0.1.0

$ python
Python 3.9.1 (default, Feb  1 2021, 04:02:33) 
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
$ from new_proj import ab_problem
$ ab_problem(1,33)
34

Как можно видеть всё работает корректно и теперь мы можем использовать наш пакет.

Для публикации следует использовать:

$ poetry publish -u "user" -p "password"

Подробнее можно почитать тут.

Конфигурация проекта

Конечно, такого рода конфигурация проекта всё равно никуда не годиться. Давайте настроим автоматический линтинг.

$ poetry add \
$	flake8 \
$	black \
$	isort \
$	mypy \
$	pre-commit \
$	yesqa \
$	autoflake \
$	wemake-python-styleguide --dev
---> 100%

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

Для конфигурации сортировки импортов и проверки типов добавим следющее в наш основной файл проекта.

Обычно я добавляю эти секции сразу после секции [tool.poetry.dev-dependencies].

...

[tool.mypy]
strict = true
ignore_missing_imports = true
allow_subclassing_any = true
allow_untyped_calls = true
pretty = true
show_error_codes = true
implicit_reexport = true
allow_untyped_decorators = true
warn_return_any = false

[tool.isort]
profile = "black"
multi_line_output = 3

...

.flake8 - конфигурация линтинга. Тут довольно много. Это игнорирование ненужных кодов ошибок, которые не особо-то и ошибки.

[flake8]
max-complexity = 6
inline-quotes = double
max-line-length = 88
extend-ignore = E203
docstring_style=sphinx

ignore =
  ; Found `f` string
  WPS305,
  ; Missing docstring in public module
  D100,
  ; Missing docstring in magic method
  D105,
  ; Missing docstring in __init__
  D107,
  ; Found class without a base class
  WPS306,
  ; Missing docstring in public nested class
  D106,
  ; First line should be in imperative mood
  D401,
  ; Found `__init__.py` module with logic
  WPS412,
  ; Found implicit string concatenation
  WPS326,
  ; Found string constant over-use
  WPS226,
  ; Found upper-case constant in a class
  WPS115,
  ; Found nested function
  WPS430,
  ; Found using `@staticmethod`
  WPS602,
  ; Found method without arguments
  WPS605,
  ; Found overused expression
  WPS204,
  ; Found too many module members
  WPS202,
  ; Found too high module cognitive complexity
  WPS232,
  ; line break before binary operator
  W503,
  ; Found module with too many imports
  WPS201,
  ; Found vague import that may cause confusion: X
  WPS347,
  ; Inline strong start-string without end-string.
  RST210,
  ; subprocess call with shell=True seems safe, but may be changed in the future.
  S602,
  ; Starting a process with a partial executable path.
  S607,
  ; Consider possible security implications associated with subprocess module.
  S404,
  ; Found nested class
  WPS431,
  ; Found wrong module name
  WPS100,
  ; Found too many methods
  WPS214,
  ; Found too long ``try`` body
  WPS229,
  ; Found function with too much cognitive complexity
  WPS231,
  
  ; all init files
  __init__.py:
  ; ignore not used imports
  F401,
  ; ignore import with wildcard
  F403,
  ; Found wrong metadata variable
  WPS410,

per-file-ignores =
  ; all tests
  test_*.py,tests.py,tests_*.py,*/tests/*:
  ; Use of assert detected
  S101,

exclude =
  ./.git,
  ./venv,
  ./cached_venv,
  ./var,

.pre-commit-config.yaml - конфигураци хуков для запуска всех линтеров перед коммитом.

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.4.0
    hooks:
      - id: check-ast
      - id: trailing-whitespace
      - id: check-toml
      - id: end-of-file-fixer

  - repo: https://github.com/asottile/add-trailing-comma
    rev: v2.1.0
    hooks:
    -   id: add-trailing-comma

  - repo: local
    hooks:
      - id: black
        name: Format with Black
        entry: black
        language: system
        types: [python]

      - id: autoflake
        name: autoflake
        entry: autoflake
        language: system
        types: [ python ]
        args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ]

      - id: isort
        name: isort
        entry: isort
        language: system
        types: [ python ]

      - id: flake8
        name: Check with Flake8
        entry: flake8
        language: system
        pass_filenames: false
        types: [ python ]
        args: [--count, .]

      - id: mypy
        name: Validate types with MyPy
        entry: mypy
        language: system
        types: [ python ]

      - id: yesqa
        name: Remove usless noqa
        entry: yesqa
        language: system
        types: [ python ]

И не забываем про .gitignore. Его можно найти тут.

ОН НЕОБХОДИМ ЧТОБЫ pre-commit РАБОТЛ МАКСИМАЛЬНО КОРРЕКТНО.

Теперь установим хуки в репозиторий.

$ git init
Initialized empty Git repository in .git/
$ poetry shell
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
$ git commit
... # Упадет с кучей ошибок

Теперь поправим все возникшие проблемы.

Во первых исправим тесты.

В tests/test_new_proj.py напишем следующее:

from new_proj import ab_problem


def test_ab() -> None:
    """AB problecm success case."""
    assert ab_problem(1, 2) == 3

Добавим описания в __init__ файлы.

"""Tests for new_proj."""
"""Project for solving ab problem."""
from new_proj.main import ab_problem

__all__ = [
    "ab_problem",
]

Пофиксим основной файл проекта.

def ab_problem(first: int, second: int) -> int:
    """
    Solve AB problem.

    The function sums two integers.

    :param first: a argument.
    :param second: b argument.
    :returns: sum.
    """
    return first + second

Теперь вы можете сделать свой первый коммит.

$ git commit     
Check python ast................Passed
Trim Trailing Whitespace........Passed
Check Toml......................Passed
Fix End of Files................Passed
Add trailing commas.............Passed
Format with Black...............Passed
autoflake.......................Passed
isort...........................Passed
Check with Flake8...............Passed
Validate types with MyPy........Passed
Remove usless noqa..............Passed
pytest..........................Passed

Теперь ты знаешь как создавать шедевры.

Создание CLI-приложения

А что если я хочу cli-приложение? Ты не представляешь насколько это просто.

Пойдем модифицируем наш основной файл.

import argparse


def parse_args() -> argparse.Namespace:
    """
    Parse CLI arguments.

    :returns: parsed namespace.
    """
    parser = argparse.ArgumentParser()

    parser.add_argument("a", type=int)
    parser.add_argument("b", type=int)

    return parser.parse_args()


def ab_problem(first: int, second: int) -> int:
    """
    Solve AB problem.

    The function sums two integers.

    :param first: a argument.
    :param second: b argument.
    :returns: sum.
    """
    return first + second


def main() -> None:
    """Main function."""
    args = parse_args()
    print(ab_problem(args.a, args.b))  # noqa: WPS421

Теперь поправим pyproject.toml таким образом чтобы он создал cli для нашей функции.

Добавим следующую секцию куда-нибудь в pyproject.toml:

[tool.poetry.scripts]
ab_solver = "new_proj.main:main"

Теперь нам доступна программа ab_solver внутри shell.

$ poetry install
$ poetry shell
$ ab_solver 1 2
3

Хочешь установить? Пожалуйста.

$ poetry build
Building new_proj (0.1.0)
  - Building sdist
  - Built new_proj-0.1.0.tar.gz
  - Building wheel
  - Built new_proj-0.1.0-py3-none-any.whl
$ pip install "./dist/new_proj-0.1.0-py3-none-any.whl"
Processing ./dist/new_proj-0.1.0-py3-none-any.whl
Installing collected packages: new-proj
Successfully installed new-proj-0.1.0
$ ab_solver 1 2                                     
3

Если запаблишить проект, то у пользователя тоже установится ваша cli-программа.