Рубрики
Без рубрики

Эксперименты на базель: Python (1), fastapi

Цели настроить проект Python с: разделить код в пакеты (например, веб, услуги, модель … Помечено базелем, Python, Fastapi.

Цели

Настройка проекта 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 альтернативы:

Оба имеют плюсы и минусы, потому что мы будем использовать 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”