584 lines
17 KiB
Markdown
584 lines
17 KiB
Markdown
---
|
||
title: Как стартовать проект
|
||
description: Лучший вариант начала проекта на питоне.
|
||
category: Python
|
||
position: 1
|
||
---
|
||
|
||
# Как стартовать проект
|
||
|
||
В целом, вариантов несколько. Например, можно просто хранить несколько скриптов на github и быть довольным, можно дописать к ним `setup.py` и выложиться на pypi. Но лучший способ - структурированный проект с `poetry`.
|
||
|
||
Почему?
|
||
|
||
Особенностей у poetry много. Сейчас расскажу про то, как использовать и что делать.
|
||
|
||
Для начала создадим проект.
|
||
|
||
```console
|
||
$ 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` [тут](https://python-poetry.org/docs/pyproject/).
|
||
|
||
## Как работать с poetry
|
||
|
||
Вся информация есть в [официальной документации](https://python-poetry.org/docs) poetry. Тут я расскажу про основные моменты.
|
||
|
||
### Управление зависимостями
|
||
|
||
Чтобы добавить зависимость проекта достаточно выполнить
|
||
```console
|
||
$ poetry add ${dependency}
|
||
```
|
||
|
||
Данная команда найдет последнюю нужную версию и запишет её в `pyproject.toml` и в `poetry.lock`.
|
||
|
||
Для того, чтобы установить зависимость для разработки (линтеры например), достаточно добавить флаг `--dev`.
|
||
|
||
```console
|
||
$ poetry add ${dependency} --dev
|
||
```
|
||
|
||
Чтобы удалить зависимость достаточно просто `add` заменить на `remove`.
|
||
|
||
Примеры работы:
|
||
```console
|
||
$ 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 для входа в оболочку всегда надо было вводить
|
||
```bash
|
||
source venv/bin/activate
|
||
```
|
||
|
||
Однако с poetry это излишне. Он сам создает и менеджит виртуальные среды.
|
||
Чтобы выполнить одиночную команду можно вызвать `run`.
|
||
|
||
Например:
|
||
```console
|
||
$ 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`.
|
||
|
||
```console
|
||
$ 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 есть кое что для тебя.
|
||
```console
|
||
$ 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`
|
||
```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"
|
||
```
|
||
|
||
Для того чтобы устновить все зависимости требуется просто ввести
|
||
```console
|
||
$ poetry install
|
||
```
|
||
|
||
Данная команда создаст виртуальную среду сама и установит **все** зависимости. Включая dev-зависимости. Чтобы установить зависимости только для приложения можно добавить `--no-dev` ключ.
|
||
|
||
## Как паковать и публиковать на pypi
|
||
Всё очень просто.
|
||
Давайте добавим какую-нибудь функцию в проект.
|
||
|
||
```python{}[new_proj/main.py]
|
||
def ab_problem(a: int, b: int) -> int:
|
||
return a + b
|
||
```
|
||
|
||
```python{}[new_proj/__init.py]
|
||
from new_proj.main import ab_problem
|
||
|
||
__all__ = [
|
||
'ab_problem'
|
||
]
|
||
```
|
||
|
||
Теперь соберем проект.
|
||
|
||
```console
|
||
$ 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.
|
||
|
||
Давайте проверим, что всё работает корректно.
|
||
|
||
```console
|
||
$ 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
|
||
```
|
||
|
||
Как можно видеть всё работает корректно и теперь мы можем использовать наш пакет.
|
||
|
||
Для публикации следует использовать:
|
||
```console
|
||
$ poetry publish -u "user" -p "password"
|
||
```
|
||
|
||
Подробнее можно почитать [тут](https://python-poetry.org/docs/cli/#publish).
|
||
|
||
# Конфигурация проекта
|
||
Конечно, такого рода конфигурация проекта всё равно никуда не годиться.
|
||
Давайте настроим автоматический линтинг.
|
||
|
||
```console
|
||
$ poetry add \
|
||
$ flake8 \
|
||
$ black \
|
||
$ isort \
|
||
$ mypy \
|
||
$ pre-commit \
|
||
$ yesqa \
|
||
$ autoflake \
|
||
$ wemake-python-styleguide --dev
|
||
---> 100%
|
||
```
|
||
|
||
Теперь добавим конфигурационных файлов в корень проекта.
|
||
Это мои конфигурации, которые я настроил под себя, можешь менять их как хочешь.
|
||
|
||
|
||
Для конфигурации сортировки импортов и проверки типов добавим следющее
|
||
в наш основной файл проекта.
|
||
|
||
Обычно я добавляю эти секции сразу после секции `[tool.poetry.dev-dependencies]`.
|
||
|
||
```toml{}[pyproject.toml]
|
||
...
|
||
|
||
[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` - конфигурация линтинга. Тут довольно много. Это игнорирование ненужных кодов ошибок, которые не особо-то и ошибки.
|
||
```ini{}[.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` - конфигураци хуков для запуска всех линтеров перед коммитом.
|
||
```yaml{}[.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`. Его можно найти [тут](https://github.com/github/gitignore/blob/master/Python.gitignore).
|
||
|
||
ОН НЕОБХОДИМ ЧТОБЫ pre-commit РАБОТЛ МАКСИМАЛЬНО КОРРЕКТНО.
|
||
|
||
Теперь установим хуки в репозиторий.
|
||
```console
|
||
$ 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` напишем следующее:
|
||
```python{}[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__` файлы.
|
||
|
||
```python{}[tests/__init__.py]
|
||
"""Tests for new_proj."""
|
||
```
|
||
|
||
```python{}[new_proj/__init__.py]
|
||
"""Project for solving ab problem."""
|
||
from new_proj.main import ab_problem
|
||
|
||
__all__ = [
|
||
"ab_problem",
|
||
]
|
||
```
|
||
|
||
Пофиксим основной файл проекта.
|
||
|
||
```python{}[new_proj/main.py]
|
||
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
|
||
```
|
||
|
||
Теперь вы можете сделать свой первый коммит.
|
||
|
||
```console
|
||
$ 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-приложение?
|
||
Ты не представляешь насколько это просто.
|
||
|
||
Пойдем модифицируем наш основной файл.
|
||
|
||
```python{}[new_proj/main.py]
|
||
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`:
|
||
```toml
|
||
[tool.poetry.scripts]
|
||
ab_solver = "new_proj.main:main"
|
||
```
|
||
|
||
Теперь нам доступна программа `ab_solver` внутри shell.
|
||
|
||
```console
|
||
$ poetry install
|
||
$ poetry shell
|
||
$ ab_solver 1 2
|
||
3
|
||
```
|
||
|
||
Хочешь установить? Пожалуйста.
|
||
```console
|
||
$ 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-программа.
|