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

Создайте и разверните микросервис API REST с помощью колбы Python и Docker

Цели: Создайте простой, но реального использования API REST Следуйте REST и MicroService A … Tagged с помощью Python, Flask, Docker, Microservices.

Цели:

  • Создайте простой, но реальный пользовательский API REST
  • Следуйте за лучшими практиками Arch Rest and Microservice
  • Развернуть в контейнер Docker

Используются технологии:

  • Питон
  • Фляжка
  • Флэста спокойный
  • Докер

Предварительные условия:

  • Основополагающее понимание Python
  • Знакомство с флекой MicroFramework
  • Основное понимание управления контейнерами Docker и Docker

Сложность: ⚡⚡intermediate

Настраивать:

Убедитесь, что у вас установлен Pipenv.

pip install pipenv

Затем начните с создания каталога для проекта и CD в него

mkdir FlaskBookApi/
cd FlaskBookApi/

Затем установите колбу и пакеты с возвышением колбы, используя Pipenv. Это сделает пару вещей:

pipenv install flask flask-restful

Он создает виртуальную среду Python с именем вашего каталога в центральном месте и устанавливает указанные пакеты (в данном случае, колба и Флэк-Требовой ) Наряду с этим, в вашем рабочем каталоге, он создает Pipfile, который содержит все зависимости проектов с их соответствующими номерами версий, а также версия Python, используемая проектом. Это также создает Pipfile.lock, чтобы обеспечить детерминированные сборки в производстве.

Это не учебник Pipenv, поэтому я не буду вдаваться в подробности об этом.

Напишите API:

Хорошо, теперь самое интересное! Давайте построим эту вещь!

Начните с создания файла Python в рабочем каталоге.

touch api.py

Большой! Теперь импортируйте и инициализируйте колбу и колбу спокойно

from flask import Flask
from flask_restful import Resource, Api, reqparse, abort, marshal, fields

# Initialize Flask
app = Flask(__name__)
api = Api(app)

Обязательно импортируйте необходимые модули из-за то, что он показал выше.

Мы будем строить простой API, который проходит через некоторые основные операции CRUD на данных о данных (или, как это происходит, в данном случае, список словарей).

Мы собираемся использовать простой словарь Python для хранения, но его можно легко поменяться на реальном решении базы данных, если вам нужно. Но ради простоты я решил пойти с простым списком словарей

Давайте начнем. Сначала объявить список, содержащий несколько словарей, представляющих индивидуальные элементы «книги»

# A List of Dicts to store all of the books
books = [{
    "id": 1,
    "title": "Zero to One",
    "author": "Peter Thiel",
    "length": 195,
    "rating": 4.17
},
    {
    "id": 2,
    "title": "Atomic Habits ",
    "author": "James Clear",
    "length": 319,
    "rating": 4.35
}
]

Затем установите словарь, чтобы определить схему для объекта книги, как и ожидалось API. Это поможет с подтверждением запросов позже.

# Schema For the Book Request JSON
bookFields = {
    "id": fields.Integer,
    "title": fields.String,
    "author": fields.String,
    "length": fields.Integer,
    "rating": fields.Float
}

Flask Restful – это расширение на флянге, которая намного облегчает создание RESTFUL APIS

Фундаментальный строительный блок, предоставленный Flask-Restful,-это ресурсы. Ресурсы дают вам легкий доступ к нескольким методам HTTP, просто определяя методы вашего класса ресурсов.

Вот класс книжных ресурсов: Он содержит маршруты HTTP для доступа, изменения и удаления каждой отдельной книжной сущности. В настоящее время, Давай разберем это …

# Resource: Individual Book Routes
class Book(Resource):
    def __init__(self):
        # Initialize The Flsak Request Parser and add arguments as in an expected request
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument("title", type=str, location="json")
        self.reqparse.add_argument("author", type=str, location="json")
        self.reqparse.add_argument("length", type=int, location="json")
        self.reqparse.add_argument("rating", type=float, location="json")

        super(Book, self).__init__()

    # GET - Returns a single book object given a matching id
    def get(self, id):
        book = [book for book in books if book['id'] == id]

        if(len(book) == 0):
            abort(404)

        return{"book": marshal(book[0], bookFields)}

    # PUT - Given an id
    def put(self, id):
        book = [book for book in books if book['id'] == id]

        if len(book) == 0:
            abort(404)

        book = book[0]

        # Loop Through all the passed agruments
        args = self.reqparse.parse_args()
        for k, v in args.items():
            # Check if the passed value is not null
            if v is not None:
                # if not, set the element in the books dict with the 'k' object to the value provided in the request.
                book[k] = v

        return{"book": marshal(book, bookFields)}

        # Delete - Given an id
    def delete(self, id):
        book = [book for book in books if book['id'] == id]

        if(len(book) == 0):
            abort(404)

        books.remove(book[0])

        return 201

Во -первых, в методе инициации класса вы инициализируете анализатор запроса. Это позволит вам легкий доступ к любой переменной на Flask.Request а также подтверждает ответ на основе предоставленных аргументов

class Book(Resource):
    def __init__(self):
        # Initialize The Flsak Request Parser and add arguments as in an expected request
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument("title", type=str, location="json")
        self.reqparse.add_argument("author", type=str, location="json")
        self.reqparse.add_argument("length", type=int, location="json")
        self.reqparse.add_argument("rating", type=float, location="json")

        super(Book, self).__init__()

Затем, метод получить, этот действительно простой, он берет идентификатор и проходит через список книг и проверяет идентификатор каждого элемента с указанным идентификатором, если найдено совпадение, он возвращает этот диктат. маршал Метод просто гарантирует, что возвращаемый объект фильтруется через поля, определенные в DICT Bookfields.

# GET - Returns a single book object given a matching id
    def get(self, id):
        book = [book for book in books if book['id'] == id]

        if(len(book) == 0):
            abort(404)

        return{"book": marshal(book[0], bookFields)}

Метод POT используется для обновления элемента с указанным идентификатором, он требует объекта ответа с полями, которые должны быть обновлены сначала, он проходит через список книг и проверяет идентификатор каждого элемента с указанным идентификатором, если совпадение найдено, оно Саганы все предоставленные аргументы с использованием рекрессера.

Затем пробирается через анализируемые аргументы и обновляет поля, как в объекте запроса.

# PUT - Given an id
    def put(self, id):
        book = [book for book in books if book['id'] == id]

        if len(book) == 0:
            abort(404)

        book = book[0]

        # Loop Through all the passed agruments
        args = self.reqparse.parse_args()
        for k, v in args.items():
            # Check if the passed value is not null
            if v is not None:
                # if not, set the element in the books dict with the 'k' object to the value provided in the request.
                book[k] = v

        return{"book": marshal(book, bookFields)}

Метод удаления просто принимает идентификатор и удаляет элемент в списке книг с помощью идентификатора.

# Delete - Given an id
    def delete(self, id):
        book = [book for book in books if book['id'] == id]

        if(len(book) == 0):
            abort(404)

        books.remove(book[0])

        return 201

Далее находится класс Booklist, он содержит маршруты, посвященные операциям во всей базе данных.

class BookList(Resource):
    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument(
            "title", type=str, required=True, help="The title of the book must be provided", location="json")
        self.reqparse.add_argument(
            "author", type=str, required=True, help="The author of the book must be provided", location="json")
        self.reqparse.add_argument("length", type=int, required=True,
                                   help="The length of the book (in pages)", location="json")
        self.reqparse.add_argument(
            "rating", type=float, required=True, help="The rating must be provided", location="json")

    def get(self):
        return{"books": [marshal(book, bookFields) for book in books]}

    def post(self):
        args = self.reqparse.parse_args()
        book = {
            "id": books[-1]['id'] + 1 if len(books) > 0 else 1,
            "title": args["title"],
            "author": args["author"],
            "length": args["length"],
            "rating": args["rating"]
        }

        books.append(book)
        return{"book": marshal(book, bookFields)}, 201

Метод инициализирует анализатор запроса. Он анализирует объект запроса JSON, а также подтверждает его на основе предоставленных аргументов.

def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument(
            "title", type=str, required=True, help="The title of the book must be provided", location="json")
        self.reqparse.add_argument(
            "author", type=str, required=True, help="The author of the book must be provided", location="json")
        self.reqparse.add_argument("length", type=int, required=True,
                                   help="The length of the book (in pages)", location="json")
        self.reqparse.add_argument(
            "rating", type=float, required=True, help="The rating must be provided", location="json")

Метод получить просто возвращает все элементы в списке книг

def get(self):
        return{"books": [marshal(book, bookFields) for book in books]}

Метод сообщения принимает объект JSON. Подготовка его создает новый дикт и добавляет его в список книг.

def post(self):
        args = self.reqparse.parse_args()
        book = {
            "id": books[-1]['id'] + 1 if len(books) > 0 else 1,
            "title": args["title"],
            "author": args["author"],
            "length": args["length"],
            "rating": args["rating"]
        }

        books.append(book)
        return{"book": marshal(book, bookFields)}, 201

Вот и все! Теперь просто прикрепите эти классы ресурсов к некоторым конечным точкам и протестируйте их!

api.add_resource(BookList, "/books")
api.add_resource(Book, "/books/")

if __name__ == "__main__":
    app.run(debug=True)

Развернуть на Docker:

Хорошо, теперь давайте развернуть этот API MicroService в контейнер Docker.

Во -первых, создайте Dockerfile в каталоге проекта.

Файл Docker, по сути, является набором инструкций по созданию изображения, который представляет собой план, из которого будет работать ваш контейнер.

Пойдем через эту линию по линии.

FROM python:3.8

RUN pip3 install pipenv

ENV PROJECT_DIR /usr/src/flaskbookapi

WORKDIR ${PROJECT_DIR}

COPY Pipfile .
COPY Pipfile.lock .
COPY . .

RUN pipenv install --deploy --ignore-pipfile

EXPOSE 5000

CMD ["pipenv", "run", "python", "api.py"]

Это использует изображение Python 3.8 из Docker Hub в качестве базового изображения, это гарантирует, что у нас есть Python и все его зависимости от контейнера

FROM python:3.8

Это устанавливает Pipenv на контейнер.

RUN pip3 install pipenv

Первая строка устанавливает переменную среды в качестве пути к каталогу для хранения кода проектов.

Затем следующая команда устанавливает этот каталог как рабочий каталог.

ENV PROJECT_DIR /usr/src/flaskbookapi

WORKDIR ${PROJECT_DIR}

Далее мы копируем все наши файлы в контейнер

COPY Pipfile .
COPY Pipfile.lock .
COPY . .

Эта команда запускает установку Pipenv с флагом – Deploy, а также устанавливает ее, чтобы игнорировать Pipfile и просто использовать Pipfile.lock для установки зависимостей

RUN pipenv install --deploy --ignore-pipfile

Затем мы выставляем порт 5000, чтобы иметь его использовать.

EXPOSE 5000

Наконец, мы запускаем нашу программу, используя команду CMD.

CMD ["pipenv", "run", "python", "api.py"]

Теперь давайте используем этот DockerFile для создания изображения, которое впоследствии будет использоваться для создания контейнера

docker build -t flaskbookapi:1.0 .

Теперь наконец! Запустите контейнер с изображением, которое мы только что построили!

docker run -p 5000:5000 --name FlaskBookAPI flaskbookapi:1.0

И бум! Out API работает и работает! На контейнере Docker

$ docker run -p 5000:5000 --name FlaskBookAPI flaskbookapi:1.0
 * Serving Flask app "api" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Загрузите проект с GitHub:

https://github.com/SwarnimWalavalkar/rest-api-microservice-docker

Дальнейшее чтение:

Колбальная документация: https://flask.palletsprojects.com/en/1.1.x/

Фластная документация: https://flask-restful.readthedocs.io/en/latest/

Докурная документация: https://docs.docker.com/

Pipenv: https://pipenv-fork.readthedocs.io/en/latest/

Оригинал: “https://dev.to/swarnimwalavalkar/build-and-deploy-a-rest-api-microservice-with-python-flask-and-docker-5c2d”