В своей практике, неоднократно сталкивался с необходимостью развертывания внешнего по отношению к 1С Веб-сервиса (почти всегда это был именно REST). Городить много таких сервисов на 1С - просто не удобно, плюс ограничения 1С и т.д. и т.п. Нужно сказать, что для реализации Веб сервисов придумано множество инструментов на всевозможных языках и платформах. Многие чуть лучше в одном, другие - в другом, однако, по итогу, самым простым и удобным мне показался именно FastAPI.
FastAPI - очень простой и довольно быстрый фреймворк, предназначенный именно для организации REST API сервисов. Фреймворк написан на python, может работать на различных операционках, снабжен хорошей документацией. Главное удобство его использования заключается в простоте кодинга, а кроме того - в автоматическом формировании документации по вашему API в формате Open API. Кроме этого, в FastAPI имеется встроенная асинхронность, валидация параметров запросов. Официальный репозитарий проекта - тут, документация - тут.
В статье я не буду разбирать подробно FastAPI, цель статьи - ознакомительная (просто надеюсь, что кому-то из 1С-ов это будет полезно). Вопросы архитектуры приложения, использования ORM, асинхронности и многопоточности, производительности, особенностей использования валидаторов и т.п. - здесь не рассматриваются.
Итак, начнём. Первое что нужно для работы - python. Пакет можно скачать отсюда - https://www.python.org. Далее, нужна среда разработки. Рекомендую использовать pycharm - тут (у них есть бесплатная версия Community), но вполне подойдет и Visual Studio Code (тоже легко найдёте).
Надеюсь у вас получилось всё скачать и установить, а если нет, то смотрите информацию в Интернете - её море.
Далее, создаём новый проект и устанавливаем FastAPI:
pip install fastapi
Теперь установим wsgi сервер, с которым работает FastAPI:
pip install uvicorn
Вот в принципе и вся установка.
Окей, далее, в папке проекта создадим файл main.py (если конечно среда разработки его не создала). Напишем в нем такой код:
import uvicorn
if __name__ == '__main__':
uvicorn.run(
"app:app",
host='localhost',
port=8080,
reload=True
)
здесь мы описываем запуск wsgi сервера и приложения под именем app.py на нём.
Теперь создадим в папке проекта сам файл app.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
Не в даваясь в детали, мы здесь описали наш api, правда пока только два метода(функции). Видно, что это обработчики GET запросов, а также адреса методов. Сохраняем файлы. Проверим работоспособность.
В терминале пишем:
main.py
В терминале должны появиться подобные строки:
Ошибок нет, сервер стартанул. Наше API работает? Проверим. Переходим в браузер:
Перейдём по адресу http://localhost:8080/docs:
Это и есть интерактивная документая (документация доступна также и по адресу - http://localhost:8080/redoc, но немного в другом формате). Обратите внимание на то, что кнопки активны, можно вручную передавать туда параметры и смотреть результаты. Это своего рода консоль (swagger).
В нашем примере реализовано лишь две очень простые функции, вызываемые GET запросами. Однако, вы можете создавать и PUT, и DELETE, и POST запросы. Для этого, в декораторе необходимо лишь указать необходимый метод. Подробности ищите в документации и примерах к ней. Мы же в завершении статьи, прикрутим к нашему API базу данных.
Самым простым вариантом при выборе базы данных в python является SQLite. Это файловая база данных, идущая "в коробке" с python. Разумеется, вы можете установить и настроить использование других СУБД, но сейчас это не требуется. Для примера, пусть в базе будет только одна таблица items из которой и будут извлекаться данные для функций API.
Для удобства, создадим в проекте два новых файла: controller.py - для описания финкций, database_scripts - для описания взаимодействия с базой. Изменим также и app.py - здесь нужно подключить файл controller.py для вызова соответствующих функций. Для данной задачи мне удобно было организовать модули именно так.
app.py:
from fastapi import FastAPI
import controller as controller
tags_metadata = [
{
'name': 'items',
'description': 'items',
}
]
app = FastAPI(
title='Mini api',
description='This is my mini api (description)',
version='1.0.0',
openapi_tags=tags_metadata
)
@app.get("/")
async def root():
return controller.get_items()
@app.get("/{item_id}")
async def read_item(item_id: int):
return controller.get_item(item_id)
controller.py:
import database_scripts as db_scripts
def get_items():
return db_scripts.select_items()
def get_item(item_id):
results = db_scripts.select_items(item_id)
return results
database_scripts.py:
import sqlite3 as sq
# Параметры базы данных
DATABASE = 'database.db'
SCHEME = 'schem.sql'
# Подключение базы данных
def connect_db():
return sq.connect(DATABASE)
# Создание базы данных (по скрипту из SCHEME)
def create_db():
connection = connect_db()
cursor = connection.cursor()
with open(SCHEME, mode='r') as file:
scheme_script = file.read()
cursor.executescript(scheme_script)
connection.commit()
cursor.close()
connection.close()
# Вспомогательная функция для упрощения получения результатов по ключу
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def select_items(item_id=None):
my_list = []
connection = connect_db()
if item_id == None:
sql = "SELECT * FROM items"
else:
sql = f"SELECT * FROM items WHERE id = {item_id}"
try:
cursor = connection.execute(sql)
cursor.row_factory = dict_factory
for row in cursor:
record = {
'name': row.get('name'),
'id': row.get('id'),
'folder_id': row.get('folder_id'),
'is_weighted': row.get('is_weighted'),
'description': row.get('description'),
}
my_list.append(record)
except connection.Error as error:
print("Error connection to database", error)
finally:
if connection: connection.close()
return my_list
Кроме этого, создадим в каталоге проекта также и файл schem.sql, где напишем скрипт создания нашей базы (всего одна таблица, но если потребуется больше, то как и в любой СУБД - таблицы описываются через точку с запятой):
CREATE TABLE IF NOT EXISTS items (
id integer PRIMARY KEY,
name char(150) NOT NULL,
folder_id integer,
is_weighted boolean,
description text
);
Думаю что то, что описано в файлах ясно всем - из модуля app.py вызываются соответствующие функции из controllers.py, где при необходимости из database_scripts.py вызываются функции, связанные с взаимодействием с базой данных.
Ок. Теперь осталось создать саму базу и заполнить её чем-то. Создавать и заполнять базу можно вручную (н-р с помощью DB Browser for SQLite), либо выполнив такой незамысловатый код в консоли:
>>> from database_scripts import create_db
>>> create_db()
Для заполнения можно добавить в database_scripts.py такой код:
items = [
(1, 'Вентилятор BINATONE ALPINE 160вт, напольный ,', 1, 0, 'Вентилятор BINATONE ALPINE 160вт, напольный , оконный'),
(2, 'Вентилятор JIPONIC (Тайв.),', 1, 0, 'Вентилятор JIPONIC (Тайв.), напольный'),
(3, 'Вентилятор настольный', 1, 0, 'Вентилятор настольный'),
(4, 'Вентилятор ОРБИТА,STERLING,ЯП.', 1, 0, 'Вентилятор ОРБИТА,STERLING,ЯП.'),
(5, 'Пылесос "Омега" 1250вт', 1, 0, 'Пылесос "Омега" 1250вт'),
(6, 'Телевизор "SHARP"', 3, 0, 'Телевизор "SHARP"'),
(7, 'Набор кухонной мебели (цвет белый)', 4, 0, 'Набор кухонной мебели (цвет белый)'),
]
def insert_testdata():
connection = connect_db()
cursor = connection.cursor()
cursor.executemany("INSERT INTO items VALUES(?, ?, ?, ?, ?)", items)
connection.commit()
connection.close()
а затем выполним его в консоли:
>>> from database_scripts import insert_testdata
>>> insert_testdata()
Всё, в каталоге проекта должна появиться база, причем заполненная нашими данными.
Вот собственно и всё. Перезапустим сервер и проверим работу API.
ну и второй API метод:
На этом всё. Чтож, надеюсь мой поток мыслей был вам понятен, а информация окажется хоть немного полезной. Как видите, почти без программирования (и без ООП) вполне себе можно быстро и легко создавать REST API. Конечно, здесь нет ни валидации, ни ORM или моделей, нет даже авторизации, а многие вещи даже и вовсе не упоминались, но это и не в ходило в цели обзора.