Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Константин Есьмуков. Scaraplate: шаблоны проектов с автообновлениями. UM MeetUp #6

Константин Есьмуков. Scaraplate: шаблоны проектов с автообновлениями. UM MeetUp #6

Константин Есьмуков, старший инженер-разработчик в проекте Рекомендации "Модели пользователя", maintainer проекта scaraplate.
UM MeetUp #6
Тема: Scaraplate: шаблоны проектов с автообновлениями.
У нас было много проектов, и везде всё по-разному: разные линтеры, тестраннеры, сборка; хотя везде на выходе одно и то же. Мы захотели это унифицировать и сделать одинаково, но столкнулись с проблемой -- нет тулинга. Поэтому мы сделали scaraplate. Доклад о том, зачем мы его сделали, и как с помощью него вы можете привести в порядок скелеты своих проектов.

Artyom Vybornov

October 23, 2019
Tweet

More Decks by Artyom Vybornov

Other Decks in Programming

Transcript

  1. Была проблема: везде всё разное - Куча Python проектов -

    Разный набор линтеров, разные настройки линтеров - Разные пайплайны CI - Разные лэйауты (расположение исходников и тестов) - Разные мейкфайлы - Разные тестраннеры - ...
  2. 3 одинаковых разных Makefile check-coverage: $(COVERAGE) run --rcfile=.coveragerc -m $(PYTEST)

    -v tests/ $(COVERAGE) report -m --fail-under=40 --show-missing -- check-coverage: @coverage run --rcfile=.coveragerc -m $(PYTEST) -v tests/ @coverage report -m --show-missing -- check-coverage: AIRFLOW_HOME=/tmp $(PYTEST) $(PYTEST_FLAGS) --cov src/infra --cov tests
  3. Расхождения в линтерах [isort] atomic = true default_section = THIRDPARTY

    force_grid_wrap = 0 include_trailing_comma = true indent = ' ' known_first_party = recommender_api known_third_party = line_length = 88 lines_after_imports = 2 multi_line_output = 3 not_skip = __init__.py order_by_type = true sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOC ALFOLDER use_parentheses = True [isort] atomic=true default_section=FIRSTPARTY include_trailing_comma=true indent=' ' known_first_party= known_third_party= line_length=79 lines_after_imports=2 multi_line_output=3 order_by_type=true sections=FUTURE,STDLIB,THIRDPARTY,FIRST PARTY,LOCALFOLDER
  4. Кому это надо - Если у вас много (десятки?) однотипных

    проектов - Если они постоянно разъезжаются - Если вы хотите унифицировать их, чтобы упростить поддержку
  5. Недостаток: нет автообновлений - Раскатить cookiecutter можно только для нового

    проекта - Все обновления в шаблоне нужно докатывать на целевые проекты руками
  6. Scaraplate vs cookiecutter - Оборачивает cookiecutter - Позволяет обновлять проект

    из обновлённого шаблона - Переменные шаблона сохраняются в целевом проекте - Файлы между шаблоном и проектом мержатся с помощью стратегий - Обновления могут накатываться автоматически
  7. Как этим пользоваться: изменение шаблона Шаблон Проект #1 Проект #2

    Проект #3 Изменили ширину строки в линтере с 80 на 120 В проектах пока всё по-старому
  8. Как этим пользоваться: накат на один проект Шаблон Проект #1

    Проект #2 Проект #3 В #2 и #3 пока всё по-старому scaraplate rollup В #1 изменения прилетели после rollup
  9. Как этим пользоваться: накат на все проекты Шаблон Проект #1

    Проект #2 Проект #3 До всех проектов доехали изменения scaraplate rollup scaraplate rollup
  10. Как мержить изменения Дано: Файл из временного проекта Файл из

    целевого проекта Итоговый файл в целевом проекте ???
  11. Git 3-way merge A0 B1 C0 D1 A0 B0 C0

    D0 A0 B0 C2 D2 A0 B1 C2 D? base X Y T3’ (result) conflict!
  12. Как мержить изменения: git merge – 3-way merge A B

    T1’ C T2’ D T1 T2 T3 T3’ template master
  13. Как мержить изменения: стратегии SortedUniqueLines: - Union lines - Sort

    - Keep unique Файл из временного проекта: .gitignore Файл из целевого проекта: .gitignore Итоговый файл в целевом проекте Стратегия SortedUniqueLines
  14. Git merge vs стратегии + Легко и универсально - Давно

    замерженное никак не пересматривается - Не автоматизируется: могут быть конфликты - Требуется держать в каждом репе бранч с шаблоном - Стратегии специфичные, их нужно реализовывать + Логика стратегий ограничивает скоуп “вольности” со стороны целевого проекта
  15. scaraplate - Написан на Python (>=3.6) - Использует cookiecutter (написанный

    на Python) - Содержит набор дефолтных стратегий - Легко расширяется - Может раскатываться на любые проекты, не только на Python Находится в опенсорсе! pip install scaraplate
  16. Структура шаблона Базовый шаблон . ├── .gitignore ├── README.md ├──

    cookiecutter.json ├── scaraplate.yaml └── {{cookiecutter.project_dest}} ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── MANIFEST.in ├── Makefile ├── Makefile.inc ├── README.md ├── setup.cfg ├── setup.py ├── src │ └── {{cookiecutter.python_package}} │ └── __init__.py └── tests ├── __init__.py └── test_metadata.py Project . ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── Jenkinsfile ├── MANIFEST.in ├── Makefile ├── Makefile.inc ├── README.md ├── mypy.ini ├── setup.cfg ├── setup.py ├── src │ └── myproject │ ├── __init__.py │ └── app.py └── tests ├── __init__.py ├── test_app.py └── test_metadata.py Scaraplate rollup
  17. Стратегии обновления файлов Базовый шаблон . ├── .gitignore ├── README.md

    ├── cookiecutter.json ├── scaraplate.yaml └── {{cookiecutter.project_dest}} ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── MANIFEST.in ├── Makefile ├── Makefile.inc ├── README.md ├── setup.cfg ├── setup.py ├── src │ └── {{cookiecutter.python_package}} │ └── __init__.py └── tests ├── __init__.py └── test_metadata.py default_strategy: scaraplate.strategies.Overwrite strategies_mapping: .gitignore: scaraplate.strategies.SortedUniqueLines README.md: scaraplate.strategies.IfMissing src/*/__init__.py: scaraplate.strategies.IfMissing tests/__init__.py: scaraplate.strategies.IfMissing setup.py: strategy: scaraplate.strategies.TemplateHash config: line_comment_start: '#' max_line_length: 87 max_line_linter_ignore_mark: ' # noqa' setup.cfg: strategy: scaraplate.strategies.SetupCfgMerge config: ...
  18. Конфиги стратегий strategies_mapping: setup.cfg: strategy: scaraplate.strategies. SetupCfgMerge config: preserve_keys: -

    sections: ^coverage:report$ keys: ^omit$ preserve_sections: - sections: ^options\.data_files$ - sections: ^options\.entry_points$ merge_requirements: - sections: ^options$ keys: ^install_requires$ [coverage:report] exclude_lines = NotImplementedError pragma: no cover __repr__ __str__ fail_under = 98 omit = src/myproject/cli/*.py precision = 2 show_missing = True
  19. cookiecutter.json Базовый шаблон . ├── .gitignore ├── README.md ├── cookiecutter.json

    ├── scaraplate.yaml └── {{cookiecutter.project_dest}} ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── MANIFEST.in ├── Makefile ├── Makefile.inc ├── README.md ├── setup.cfg ├── setup.py ├── src │ └── {{cookiecutter. python_package}} │ └── __init__.py └── tests ├── __init__.py └── test_metadata.py { "project_dest": null, "project_monorepo_name": "", "python_package": "{{ [cookiecutter.project_monorepo_name, cookiecutter.project_dest]|select()|join('_')|replace( '-', '_') }}", "metadata_name": "{{ cookiecutter.python_package|replace('_', '-') }}", "metadata_author": null, "metadata_author_email": null, "metadata_description": null, "metadata_long_description": "file: README.md", "metadata_url": "https://github.com/rambler-digital-solutions/{{ cookiecutter.project_monorepo_name or cookiecutter.project_dest }}", "coverage_fail_under": "100", "mypy_enabled": "1" }
  20. Монореп это - Монолит: весь проект одним куском в одном

    git репе - Мультиреп: проект разбит на части, каждая лежит в отдельном git репе - Монореп: проект разбит на части, все лежат в одном git репе: GIT /api /worker
  21. cookiecutter.json Базовый шаблон . ├── .gitignore ├── README.md ├── cookiecutter.json

    ├── scaraplate.yaml └── {{cookiecutter.project_dest}} ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── MANIFEST.in ├── Makefile ├── Makefile.inc ├── README.md ├── setup.cfg ├── setup.py ├── src │ └── {{cookiecutter. python_package}} │ └── __init__.py └── tests ├── __init__.py └── test_metadata.py { "project_dest": null, "project_monorepo_name": "", "python_package": "{{ [cookiecutter.project_monorepo_name, cookiecutter.project_dest]|select()|join('_')|replace( '-', '_') }}", "metadata_name": "{{ cookiecutter.python_package|replace('_', '-') }}", "metadata_author": null, "metadata_author_email": null, "metadata_description": null, "metadata_long_description": "file: README.md", "metadata_url": "https://github.com/rambler-digital-solutions/{{ cookiecutter.project_monorepo_name or cookiecutter.project_dest }}", "coverage_fail_under": "100", "mypy_enabled": "1" }
  22. scaraplate - Имеет всего 1 команду – scaraplate rollup -

    Читает переменные из setup.cfg или .scaraplate.conf (если файл существует); - Запускает накат временного проекта cookiecutter в пустую tmp директорию; - Для каждого сгенеренного файла применяет стратегию, которая формирует файл в целевом проекте. Шаблон Временный проект Целевой проект cookiecutter Стратегии
  23. scaraplate rollup - Имеет 2 режима работы: - Интерактивный (значения

    переменных шаблона вводятся через CLI руками); - Не-интерактивный (используются ранее введённые значения переменных из setup.cfg или .scaraplate.conf).
  24. Как накатить шаблон на проект $ pip install scaraplate …

    $ git clone https://github.com/rambler-digital-solutions/scaraplate-example-template.git $ scaraplate rollup ./scaraplate-example-template ./myproject `myproject1/.scaraplate.conf` file doesn't exist, continuing with an empty context... `project_dest` must equal to "myproject" project_dest [myproject]: project_monorepo_name []: python_package [myproject]: metadata_name [myproject]: metadata_author: Kostya Esmukov metadata_author_email: [email protected] metadata_description: My example project metadata_long_description [file: README.md]: metadata_url [https://github.com/rambler-digital-solutions/myproject]: coverage_fail_under [100]: 90 mypy_enabled [1]: Done!
  25. Как накатить шаблон на проект $ tree -a myproject myproject

    ├── .editorconfig ├── .gitignore ├── .scaraplate.conf ├── MANIFEST.in ├── Makefile ├── README.md ├── mypy.ini ├── setup.cfg ├── setup.py ├── src │ └── myproject │ └── __init__.py └── tests ├── __init__.py └── test_metadata.py 3 directories, 12 files
  26. .scaraplate.conf [cookiecutter_context] _template = scaraplate-example-template coverage_fail_under = 90 metadata_author =

    Kostya Esmukov metadata_author_email = [email protected] metadata_description = My example project metadata_long_description = file: README.md metadata_name = myproject metadata_url = https://github.com/rambler-digital-solutions/myproject mypy_enabled = 1 project_dest = myproject project_monorepo_name = python_package = myproject
  27. Как обновить проект по обновлённому шаблону $ scaraplate rollup ./scaraplate-example-template

    ./myproject --no-input Continuing with the following context from the `myproject/.scaraplate.conf` file: {'_template': 'scaraplate-example-template', 'coverage_fail_under': '90', 'metadata_author': 'Kostya Esmukov', 'metadata_author_email': '[email protected]', 'metadata_description': 'My example project', 'metadata_long_description': 'file: README.md', 'metadata_name': 'myproject', 'metadata_url': 'https://github.com/rambler-digital-solutions/myproject', 'mypy_enabled': '1', 'project_dest': 'myproject', 'project_monorepo_name': '', 'python_package': 'myproject'} Done!
  28. Остаётся закоммитить изменения и готово! $ git add . $

    git status On branch master Your branch is up to date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: .editorconfig modified: .pylintrc modified: MANIFEST.in modified: Makefile modified: setup.cfg modified: setup.py new file: tests/test_metadata.py
  29. Как начать пользоваться - Составить cookiecutter template - Выбрать стратегии,

    дописать новые по необходимости - Раскатить на свои проекты - Подключить создавалку PR (пока из коробки есть только поддержка GitLab) Дока: https://scaraplate.readthedocs.io/