Интеграция H2 с Python и Flask

Введение H2 [https://www.h2database.com/] - это облегченный сервер баз данных, написанный на Java. Он может быть встроен в приложения Java или работать как отдельный сервер. В этом руководстве мы рассмотрим, почему H2 может быть хорошим вариантом для ваших проектов. Мы также узнаем, как интегрировать H2 с Python, создав простой API Flask. Характеристики H2 H2 были созданы с учетом производительности. > «H2 - это сочетание: быстрого, стабильного, простого в использовании и функционального». Хотя H2 выделяется в основном

Вступление

H2 - это легкий сервер баз данных, написанный на Java. Он может быть встроен в приложения Java или работать как отдельный сервер.

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

Особенности H2

H2 был построен с учетом производительности.

« H2 - это сочетание: быстрого, стабильного, простого в использовании и функционального».

Хотя H2 известен в основном потому, что он может быть встроен в приложения Java, он имеет некоторые интересные особенности, которые также применимы к его серверной версии. Давайте посмотрим на некоторые из них дальше.

Размер и производительность

Размер файла .jar, используемого для серверной версии, составляет около 2 МБ. Мы можем скачать его с сайта H2 в комплекте с дополнительными скриптами и документацией. Однако, если мы будем искать в Maven Central, мы сможем загрузить файл .jar самостоятельно.

Производительность H2 проявляется во встроенной версии. Тем не менее, официальный тест показывает, что его версия клиент-сервер также впечатляет.

Базы данных в памяти и шифрование

Базы данных в памяти не являются постоянными. Все данные хранятся в памяти, поэтому скорость значительно увеличивается.

На сайте H2 объясняется, что базы данных In-Memory особенно полезны при создании прототипов или при использовании баз данных только для чтения.

Шифрование - еще одна полезная функция для защиты данных в состоянии покоя. Базы данных можно зашифровать с помощью алгоритма AES-128.

Другие полезные функции

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

H2 удивляет своей простотой. Он предоставляет несколько полезных функций и прост в настройке.

Давайте запустим сервер H2 в рамках подготовки к следующим разделам:

 $ java -cp ./h2-1.4.200.jar org.h2.tools.Server -tcp -tcpAllowOthers -tcpPort 5234 -baseDir ./ -ifNotExists 

Аргументы, начинающиеся с tcp разрешают связь с сервером. ifNotExists позволяет создать базу данных при первом доступе к ней.

Описание API и общая схема

Предположим, мы пишем API для регистрации всех найденных на сегодняшний день экзопланет. Экзопланеты - это планеты, находящиеся за пределами нашей Солнечной системы, вращающиеся вокруг других звезд.

Это наше простое определение API , CRUD для одного ресурса:

Определение RESTAPI{.ezlazyload}

Это определение вместе с остальной частью кода, который мы увидим далее, доступно в этом репозитории GitHub .

Вот как наше приложение будет выглядеть в конце этого урока:

Общаясхема{.ezlazyload}

Слева от схемы мы видим API Client. Этим клиентом может быть функция «Попробовать» редактора Swagger или любой другой клиент, например Postman или cURL.

На другом конце мы находим сервер базы данных H2 , работающий на TCP-порту 5234 как описано выше.

Наконец, наше приложение посередине состоит из трех файлов Python. У первого будет приложение Flask , которое будет отвечать на все запросы REST API. Все конечные точки, которые мы описали в определении выше, будут добавлены в этот файл.

Второй файл будет иметь постоянство, функции, которые обращаются к базе данных для выполнения операций CRUD с использованием пакета JayDeBeApi

Наконец, третий файл будет содержать схему, представляющую ресурс, которым управляет API, Exoplanet . Мы будем использовать Marshmallow для представления этой схемы. Первые два файла Python будут использовать эту схему для представления ресурсов и передачи их друг другу.

Начнем с файла постоянства.

Схема базы данных

Чтобы сохранить ресурс Exoplanet в базе данных H2, мы должны сначала написать основные функции CRUD. Начнем с написания создания базы данных. Мы используем пакет JayDeBeApi для доступа к базам данных через JDBC:

 import jaydebeapi 
 
 def initialize(): 
 _execute( 
 ("CREATE TABLE IF NOT EXISTS exoplanets (" 
 " id INT PRIMARY KEY AUTO_INCREMENT," 
 " name VARCHAR NOT NULL," 
 " year_discovered SIGNED," 
 " light_years FLOAT," 
 " mass FLOAT," 
 " link VARCHAR)")) 
 
 def _execute(query, returnResult=None): 
 connection = jaydebeapi.connect( 
 "org.h2.Driver", 
 "jdbc:h2:tcp://localhost:5234/exoplanets", 
 ["SA", ""], 
 "../h2-1.4.200.jar") 
 cursor = connection.cursor() 
 cursor.execute(query) 
 if returnResult: 
 returnResult = _convert_to_schema(cursor) 
 cursor.close() 
 connection.close() 
 
 return returnResult 

Функция initialize() достаточно проста из-за вспомогательных функций после. Он создает таблицу экзопланет, если она еще не существует. Эта функция должна быть выполнена до того, как наш API начнет получать запросы. Позже мы увидим, как это сделать с помощью Flask .

Функция _execute() содержит строку подключения и учетные данные для доступа к серверу базы данных. Для этого примера это проще, но есть возможности для улучшения безопасности. Мы могли бы сохранить наши учетные данные в другом месте, например, в переменных среды.

Кроме того, мы добавили путь к файлу jar H2 connect() , поскольку в нем есть драйвер, который нам нужен для подключения к H2 - org.h2.Driver .

Строка подключения JDBC заканчивается на /exoplanets . Это означает, что при первом подключении будет создана exoplanets

Вы могли заметить, что _execute() может возвращать результат SQL-запроса с помощью функции _convert_to_schema() . Давайте теперь посмотрим, как работает эта функция.

Схемы Marshmallow и функции базы данных CRUD

Некоторые запросы SQL возвращают табличные результаты, особенно SELECT . JayDeBeApi отформатирует эти результаты как список кортежей. Например, для схемы, определенной в последнем разделе, мы могли бы получить результат, подобный этому:

 >>> connection = jaydebeapi.connect(... 
 >>> cursor = connection.cursor() 
 >>> cursor.execute("SELECT * FROM exoplanets") 
 >>> cursor.fetchall() 

 [(1, 'Sample1', 2019, 4.5, 1.2, 'http://sample1.com')] 

Ничто не мешает нам управлять результатами в этом формате и в конечном итоге возвращать их клиенту API. Но забегая вперед, мы знаем, что будем использовать Flask , поэтому было бы неплохо уже возвращать результаты в формате, рекомендованном Flask.

В частности, мы будем использовать Flask-RESTful для облегчения использования маршрутов API. Этот пакет рекомендует использовать Marshmallow для анализа запросов. Этот шаг позволяет нормализовать объекты. Таким образом, мы можем отбросить неизвестные свойства и, например, выделить ошибки проверки.

Давайте посмотрим, как будет выглядеть класс Exoplanet, чтобы мы могли продолжить обсуждение:

 from marshmallow import Schema, fields, EXCLUDE 
 
 class ExoplanetSchema(Schema): 
 id = fields.Integer(allow_none=True) 
 name = fields.Str(required=True, error_messages={"required": "An exoplanet needs at least a name"}) 
 year_discovered = fields.Integer(allow_none=True) 
 light_years = fields.Float(allow_none=True) 
 mass = fields.Float(allow_none=True) 
 link = fields.Url(allow_none=True) 
 class Meta: 
 unknown = EXCLUDE 

Определение свойств кажется знакомым. Это то же самое, что и схема базы данных, включая определение обязательных полей. Все поля имеют тип, определяющий некоторую проверку по умолчанию. Например, link определяется как URL-адрес, поэтому строка, не похожая на URL-адрес, не будет действительной.

Сюда также могут быть включены конкретные сообщения об ошибках, например проверка name .

В этом примере проекта мы хотим отбросить или исключить все неизвестные поля, и клиент API может отправлять ошибочные данные. Это достигается во вложенном классе Meta

Теперь мы можем использовать load() и loads() Методы Зефир преобразовать и проверить наши ресурсы.

Теперь, когда мы знакомы с Marshmallow , мы можем объяснить, что _convert_to_schema() :

 def _convert_to_schema(cursor): 
 column_names = [record[0].lower() for record in cursor.description] 
 column_and_values = [dict(zip(column_names, record)) for record in cursor.fetchall()] 
 
 return ExoplanetSchema().load(column_and_values, many=True) 

В JayDeBeApi имена столбцов сохраняются в description курсора, а данные можно получить с помощью метода fetchall() Мы использовали составные части списков в первых двух строках, чтобы получить имена и значения столбцов, и zip() чтобы объединить их.

Последняя строка принимает объединенный результат и преобразует их в объекты ExoplanetSchema которые Flask может обрабатывать в дальнейшем.

Теперь, когда мы объяснили _execute() и ExoplanetSchema , давайте посмотрим все функции базы данных CRUD:

 def get_all(): 
 return _execute("SELECT * FROM exoplanets", returnResult=True) 
 
 def get(Id): 
 return _execute("SELECT * FROM exoplanets WHERE id = {}".format(Id), returnResult=True) 
 
 def create(exoplanet): 
 count = _execute("SELECT count(*) AS count FROM exoplanets WHERE name LIKE '{}'".format(exoplanet.get("name")), returnResult=True) 
 if count[0]["count"] > 0: 
 return 
 
 columns = ", ".join(exoplanet.keys()) 
 values = ", ".join("'{}'".format(value) for value in exoplanet.values()) 
 _execute("INSERT INTO exoplanets ({}) VALUES({})".format(columns, values)) 
 
 return {} 
 
 def update(exoplanet, Id): 
 count = _execute("SELECT count(*) AS count FROM exoplanets WHERE id = {}".format(Id), returnResult=True) 
 if count[0]["count"] == 0: 
 return 
 
 values = ["'{}'".format(value) for value in exoplanet.values()] 
 update_values = ", ".join("{} = {}".format(key, value) for key, value in zip(exoplanet.keys(), values)) 
 _execute("UPDATE exoplanets SET {} WHERE id = {}".format(update_values, Id)) 
 
 return {} 
 
 def delete(Id): 
 count = _execute("SELECT count(*) AS count FROM exoplanets WHERE id = {}".format(Id), returnResult=True) 
 if count[0]["count"] == 0: 
 return 
 
 _execute("DELETE FROM exoplanets WHERE id = {}".format(Id)) 
 return {} 

Все функции в основном являются SQL-запросами, но create() и update() заслуживают дополнительных пояснений.

Оператор INSERT SQL может получать разделенные столбцы и значения в форме INSERT INTO table (column1Name) VALUES ('column1Value') . Мы можем использовать join() чтобы объединить все столбцы и разделить их запятыми, и сделать что-то подобное, чтобы объединить все значения, которые мы хотим вставить.

Оператор UPDATE SQL немного сложнее. Его форма - UPDATE table SET column1Name = 'column1Value' . Итак, нам нужно чередовать ключи и значения, и мы сделали это с помощью функции zip() .

Все эти функции возвращают None при возникновении проблемы. Позже, когда мы их вызовем, нам нужно будет проверить это значение.

Давайте сохраним все функции базы данных в отдельном файле persistence.py , чтобы мы могли добавить некоторый контекст при вызове функций, например:

 import persistence 
 
 persistence.get_all() 

REST API с Flask

Теперь, когда мы написали слой для абстрагирования доступа к базе данных, мы готовы написать REST API. Мы будем использовать пакеты Flask и Flask-RESTful, чтобы сделать наше определение максимально простым. Как мы узнали ранее, мы также будем использовать Marshmallow для проверки ресурсов.

Flask-RESTful требует определения одного класса для каждого ресурса API, в нашем случае только ресурса Exoplanet Затем мы можем связать этот ресурс с таким маршрутом:

 from flask import Flask 
 from flask_restful import Resource, Api 
 
 app = Flask(__name__) 
 api = Api(app) 
 
 class Exoplanet(Resource): 
 # ... 
 
 api.add_resource(Exoplanet, "/exoplanets", "/exoplanets/<int:Id>") 

Таким образом, все наши маршруты, /exoplanets и /exoplanets/<int:Id> будут направлены в определенный нами класс.

Например, на конечную точку GET /exoplanets get() внутри класса Exoplanet Поскольку у нас также есть конечная точка GET /exoplanet/<Id> get() должен иметь необязательный параметр с именем Id .

Давайте посмотрим на весь класс, чтобы лучше понять это:

 from flask import request 
 from flask_restful import Resource, abort 
 from marshmallow import ValidationError 
 import persistence 
 
 class Exoplanet(Resource): 
 def get(self, Id=None): 
 if Id is None: 
 return persistence.get_all() 
 
 exoplanet = persistence.get(Id) 
 if not exoplanet: 
 abort(404, errors={"errors": {"message": "Exoplanet with Id {} does not exist".format(Id)}}) 
 return exoplanet 
 
 def post(self): 
 try: 
 exoplanet = ExoplanetSchema(exclude=["id"]).loads(request.json) 
 if not persistence.create(exoplanet): 
 abort(404, errors={"errors": {"message": "Exoplanet with name {} already exists".format(request.json["name"])}}) 
 except ValidationError as e: 
 abort(405, errors=e.messages) 
 
 def put(self, Id): 
 try: 
 exoplanet = ExoplanetSchema(exclude=["id"]).loads(request.json) 
 if not persistence.update(exoplanet, Id): 
 abort(404, errors={"errors": {"message": "Exoplanet with Id {} does not exist".format(Id)}}) 
 except ValidationError as e: 
 abort(405, errors=e.messages) 
 
 def delete(self, Id): 
 if not persistence.delete(Id): 
 abort(404, errors={"errors": {"message": "Exoplanet with Id {} does not exist".format(Id)}}) 

Остальные HTTP-команды обрабатываются так же, как GET , методами с именем post() , put() и delete() .

Как мы уже говорили ранее, логические ошибки при доступе к базе данных заставят функции возвращать None . Эти ошибки фиксируются здесь, когда это необходимо.

Кроме того, исключения, которые представляют ошибки проверки, запускаются Marshmallow , поэтому эти ошибки также фиксируются и возвращаются пользователю вместе с соответствующей ошибкой возврата.

Заключение

H2 - полезный, производительный и простой в использовании сервер баз данных. Хотя это пакет Java, он также может работать как автономный сервер, поэтому мы можем использовать его в Python с пакетом JayDeBeApi

В этом руководстве мы определили простое приложение CRUD, чтобы проиллюстрировать, как получить доступ к базе данных и какие функции доступны. После этого мы определили REST API с Flask и Flask-RESTful .

Хотя некоторые концепции были опущены для краткости, такие как аутентификация и разбиение на страницы, это руководство является хорошей справкой для начала использования H2 в наших проектах Flask.

comments powered by Disqus