Цели
Настройка проекта Python с:
- Разделите код в пакеты (например, веб, услуги, модели, …)
- использовать Fastapi. Как веб-каркас для обработки HTTP-запроса
- Используйте Pтойцы для тестирования кода
- Формат код с чернить
- Проверьте/audit код с Marpy и другие льмина (управляется [Пилама]
- Обеспечить применение версии Python для создания, проверки, …
- Интеграция с IDE/редактором (VSCode в моем случае)
Шаги
Установка правил Python
Следуйте инструкциям от bazelbuild/pulrument_python.
Добавить в Workspace.bazel.
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") #------------------------------------------------------------------------------ # Python #------------------------------------------------------------------------------ # enable python rules http_archive( name = "rules_python", url = "https://github.com/bazelbuild/rules_python/releases/download/0.2.0/rules_python-0.2.0.tar.gz", sha256 = "778197e26c5fbeb07ac2a2c5ae405b30f6cb7ad1f5510ea6fdac03bded96cc6f", )
ℹ️. В каждом исходном файле Starlark (язык, используемый для * .bazel, * .bzl), чтобы иметь возможность использовать функцию, вы должны начать с помощью Загрузить (
это как Импорт
или использовать
на другом языке программирования.
Чтобы подготовить использование внешних зависимостей (например, fastapi, pteest, …), создать файл Third_party/требования. atxt
Отказ
mkdir third_party touch third_party/BUILD.bazel cat >third_party/requirements.txt <
Обновление Workspace.bazel
Создать репо внешние зависимости для каждой пакеты Python.
# Create a central repo that knows about the dependencies needed for # requirements.txt. load("@rules_python//python:pip.bzl", "pip_install") pip_install( name = "my_python_deps", requirements = "//third_party:requirements.txt", )
Но эта настройка использует Python, установленный в вашей локальной среде, и мы хотим обеспечить применение версии Python, которая будет использоваться. Я нашел 2 альтернативы:
- Строительство Python от источника, как описать в Герметический Python с базелем | Вдумчивый коала
- Настройка некоторых PENV через Digital-Plumers-Union/Pulrue_Pyenv
Оба имеют плюсы и минусы, потому что мы будем использовать PEENV для интеграции с IDE/Editor, перейти на второй (это главный недостаток – это установить 2 версии Python, потому что правила Python должны знать переводчик для Python 2.x и 3.x ). Так что обновите Workspace.bazel
с участием
# use pyenv to enforce python version http_archive( name = "dpu_rules_pyenv", sha256 = "d057168a757efa74e6345edd4776a1c0f38134c2d48eea4f3ef4783e1ea2cb0f", strip_prefix = "rules_pyenv-0.1.4", urls = ["https://github.com/digital-plumbers-union/rules_pyenv/archive/v0.1.4.tar.gz"], ) load("@dpu_rules_pyenv//pyenv:defs.bzl", "pyenv_install") pyenv_install( hermetic = False, py2 = "2.7.18", py3 = "3.9.2", )
Добавить код fastapi
Создайте файл exp_python/webapp/main.py
с участием
from fastapi import FastAPI app = FastAPI() @app.get("/status") def read_root(): return {"status": "UP", "version": "0.1.0"}
Создайте файл exp_python/webapp/build.bazel
с участием
load("@rules_python//python:defs.bzl", "py_library") load("@my_python_deps//:requirements.bzl", "requirement") py_library( name = "webapp", srcs = ["main.py"], srcs_version = "PY3", deps = [requirement("fastapi")], )
Если вы попытаетесь построить сейчас Bazel Build//exp_python/webapp
У вас будет ошибка с:
ERROR: /home/david/src/github.com/davidB/sandbox_bazel/exp_python/webapp/BUILD.bazel:4:11: no such package '@my_python_deps//pypi__fastapi': BUILD file not found in directory 'pypi__fastapi' of external repository @my_python_deps. Add a BUILD file to a directory to mark it as a package. and referenced by '//exp_python/webapp:webapp'
Потому что Требование («Фастапи»)
не определен, чтобы исправить это, обновить Third_party/требования. atxt
изменять
# list externals dependencies available for every python packages fastapi==0.63.0
Тест WebApp
Добавить exp_python/webapp/test.py
, с кодом, аналогичным руководству Fastapi, но с Отстаивать истину
В конце, поэтому тест не пройден (потому что никакой ошибки не может означать, что тест не запускается, особенно когда вы устанавливаете тестовый поток).
from fastapi.testclient import TestClient from .main import app client = TestClient(app) def test_read_main(): response = client.get("/status") assert response.status_code == 200 assert response.json() == {"status": "UP", "version": "0.1.0"} assert True == False
И модифицировать Build.bazel
использовать Py_test.
load("@rules_python//python:defs.bzl", "py_library", "py_test") load("@my_python_deps//:requirements.bzl", "requirement") ... py_test( name = "test", srcs = [ "test.py", ], # main = "test.py", python_version = "PY3", srcs_version = "PY3", )
Призыв Bazel Test//Exp_Python/WebApp: Test
Не удалось, но сказал, чтобы посмотреть в файл под базелью, чтобы увидеть журнал. Это не удобно, поэтому настройте Bazel для вывода ошибки на консоль, добавив в .bazelrc
(См. Предыдущая статья) Опция CLI по умолчанию для тестирования для отображения ошибок на STDOUT
test --test_output=errors
Теперь мы можем увидеть ошибку на STDOUT: Нет модуля по имени 'fastapi'
Отказ Фактически, чтобы иметь возможность запустить тест, мы должны добавить в качестве зависимостей для теста:
: WebApp
Чтобы иметь возможность получить доступ к SUT (тестирование системы)fastapi
Потому что тест импортировать его (на самом деле это транзитно доступно через: WebApp
)Запросы
, может быть, это ошибка; Это зависимости Fastapi> Starlette для тестирования Но это не так, кажется, транзитно доступно.pteest
Потому что без, тест не запускался (всегда зеленый)
py_test( name = "test", srcs = [ "test.py", ], # main = "test.py", args = [ "--capture=no", ], python_version = "PY3", srcs_version = "PY3", deps = [ ":webapp", requirement("requests"), requirement("fastapi"), requirement("pytest"), ], )
thirt_party/truse.txt.
# list externals dependencies available for every python packages fastapi==0.63.0 #test requests==2.25.1 pytest==6.1.2
На самом деле, при запуске теста запуск команд – это что-то вроде Python Test.py
С помощью Test.py ID Значение из Главная
аргумент Py_test
(по умолчанию значение имя
+ .py
Несомненно И это должно быть членом SRC. Так что никак не позвонить модулю pteest
Отказ Обходной путь должен звонить pteest
от Test.py.
.
from fastapi.testclient import TestClient from exp_python.webapp.main import app client = TestClient(app) def test_read_main(): response = client.get("/status") assert response.status_code == 200 assert response.json() == {"status": "UP", "version": "0.1.0"} assert True == False # if using 'bazel test ...' if __name__ == "__main__": import sys import pytest sys.exit(pytest.main([__file__] + sys.argv[1:]))
Обратите внимание, что приложение
сейчас импортируется из exp_python.webapp.main.
и больше не относительно. Я не нашел, как справиться с этим, не предоставив полное имя пакета из корневой рабочей области.
Теперь при запуске теста Bazel Test//Exp_Python/WebApp: Test
, это не удалось, как ожидалось (я позволю вам исправить тест)
INFO: From Testing //exp_python/webapp:test: ==================== Test output for //exp_python/webapp:test: ============================= test session starts ============================== platform linux -- Python 3.9.2, pytest-6.1.2, py-1.10.0, pluggy-0.13.1 rootdir: /home/david/.cache/bazel/_bazel_david/76e87152cc51687aee6e05b5bdcf89aa/sandbox/linux-sandbox/53/execroot/__main__/bazel-out/k8-fastbuild/bin/exp_python/webapp/test.runfiles/__main__ collected 1 item exp_python/webapp/test.py F =================================== FAILURES =================================== ________________________________ test_read_main ________________________________ def test_read_main(): response = client.get("/status") assert response.status_code == 200 assert response.json() == {"status": "UP", "version": "0.1.0"} > assert True == False E assert True == False exp_python/webapp/test.py:12: AssertionError =========================== short test summary info ============================ FAILED exp_python/webapp/test.py::test_read_main - assert True == False ============================== 1 failed in 0.10s =============================== ================================================================================ Target //exp_python/webapp:test up-to-date: bazel-bin/exp_python/webapp/test INFO: Elapsed time: 0.923s, Critical Path: 0.85s INFO: 2 processes: 2 linux-sandbox. INFO: Build completed, 1 test FAILED, 2 total actions //exp_python/webapp:test FAILED in 0.8s /home/david/.cache/bazel/_bazel_david/76e87152cc51687aee6e05b5bdcf89aa/execroot/__main__/bazel-out/k8-fastbuild/testlogs/exp_python/webapp/test/test.log INFO: Build completed, 1 test FAILED, 2 total actions
Запустить webapp.
Цель также сможет запустить WebApp. Чтобы запустить Fastapi WebApp, рекомендуемый способ использовать UVICORN
# list externals dependencies available for every python packages fastapi==0.63.0 uvicorn==0.13.4 #test requests==2.25.1 pytest==6.1.2
Но мы не можем запустить UVicorn из командной строки, потому что мы не устанавливаем его «глобали» в системе. С другой стороны, наша цель состоит в том, чтобы создать исполняемый файл, что мы можем позже запустить локальный или в контейнер.
Исполняемость здания – цель правила с суффиксом _Binary
В базельской экосистеме, как py_binary
Отказ
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") load("@my_python_deps//:requirements.bzl", "requirement") ... # to add additionals parameters place them after "--" in bazel call, like: # `bazel run //exp_python/webapp:run -- --reload` py_binary( name = "run", srcs = ["run.py"], python_version = "PY3", srcs_version = "PY3", visibility = ["//visibility:public"], deps = [ ":webapp", requirement("uvicorn"), ], )
Как вы можете видеть, мы также представим run.py.
потому что для Py_test ранее есть основной атрибут, и можно использовать только файл из SRC.
Так что создайте exp_python/webapp/run.py
import uvicorn import sys if __name__ == '__main__': # freeze_support() sys.argv.insert(1, "exp_python.webapp.main:app") sys.exit(uvicorn.main())
И попробуйте
bazel run //exp_python/webapp:run # open into browser or via curl http://127.0.0.1:8000/status
Если вы запускаете с Bazel Run//Exp_Python/WebApp: Run - --reload
и изменить в main.py их следует обнаруживаться и применять.
редактор
На данный момент у нас есть проект Bazel, который работает, но Bazel не очень хорошо поддерживается IDE/Editor (за исключением издания файла конфигурации Bazel, а иногда и запуска команды). Что мы можем сделать, чтобы немного улучшить, состоит в том, чтобы создать виртуальную виртуальную зависть Python, одинаковую версию Python и внешние зависимости Python (не считая пакетами, целью, использованием).
В настоящее время я не знаю, как это сделать лучше (предложения приветствуются), поэтому мы создадим файл в корне рабочего пространства setup_localdev.sh
:
#!/bin/bash PYENV_VERSION="3.9.2" eval "$(pyenv init -)" pyenv install ${PYENV_VERSION} --skip-existing pyenv local ${PYENV_VERSION} python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -r third_party/requirements.txt python --version
Этот файл может быть сгенерирован Bazel, но я не удался:
- Найдите способ поделиться версией Python с
Workspace.bazel.
- Создайте сценарий оболочки с помощью
Eval "$ (init pyenv init -)"
(Может быть, предмет для другой статьи)
Запустите этот скрипт, настройте редактор, чтобы использовать виртуальную среду .venv
и продолжать редактировать код. Переполните скрипт каждый раз, когда вы обновите требования .txt
(Удалить зависимости, не очищайте виртуальную среду).
Продолжение следует
Это не конец, у нас есть больше вещей для настройки (линтам, …), но мы находимся в состоянии работать достаточно для работы.
Sandbox_Bazel размещена на GitHub (не с той же историей, из-за ошибок), используйте тег, чтобы иметь ожидаемый вид на конец статьи: Статья/4_пьютор_1 Отказ Я буду рад, что ваши комментарии к этой статье или Обсудить на Github Repo.
Оригинал: “https://dev.to/davidb31/experimentations-on-bazel-python-fastapi-1-a02”