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

В зависимости от ваших конкретных потребностей и размера проекта вам может не потребоваться установка тонны дополнительных уровней абстракции или фреймворков для обработки запросов в Postgres. Создание простой оболочки для различных запросов и часто используемых функций несложно и эффективно на таких языках, как Python.

Если вы создаете прототип крошечного приложения или работаете со встроенной системой с жесткими ограничениями, то создание легкой оболочки может оказаться большим преимуществом. Получить результаты JSON из PG очень просто, используя чрезвычайно популярный модуль psycopg2. Этот низкоуровневый модуль подключения к базе данных для Python позволяет подключаться к Postgres, выполнять SQL и другие функции базы данных. Давайте рассмотрим, как настроить оболочку базы данных на Python с помощью этого модуля, чтобы сделать выполнение запросов быстрым и простым.

База данных

В нашем примере сервера Postgres мы собираемся использовать предварительно заполненную базу данных, полную тестовых данных. Это дает нам набор образцов данных для запроса. В конце концов, какое удовольствие - это пустая база данных.

Мы будем использовать Docker для настройки контейнера базы данных, чтобы не загрязнять нашу хост-операционную систему тестовыми установками. Если вы никогда раньше не использовали Docker или нуждаетесь в быстром обновлении, Docker - Руководство для новичков - Часть 1: изображения и контейнеры - отличное руководство от Себастьяна Эшвайлера, которое поможет вам вставай и работай.

Загляните в postgres-dataset на Docker Hub (благодарим aa8y за создание этого невероятно удобного инструмента), чтобы получить последний образ и развернуть его, используя приведенные ниже команды. Вам нужно изменить первый порт с 5432 на другой, если на вашем компьютере уже запущен экземпляр Postgres.

docker pull aa8y/postgres-dataset
docker run -d -p 5432:5432 --name pg-ds-fake aa8y/postgres-dataset:latest

Дайте базе данных несколько минут, чтобы заполнить тестовые данные, а затем присоединитесь к контейнеру, используя следующую команду:

docker exec -it pg-ds-fake /bin/bash

Нам нужно будет сделать одну настройку, чтобы разрешить доступ с хост-машины к экземпляру PG контейнера. При подключении к контейнеру запустите следующее:

echo "host all all 0.0.0.0/0 trust" > /var/lib/postgresql/data/pg_hba.conf
pg_ctl reload

Первая строка разрешит все соединения TCP / IP с Postgres и удалит всю остальную аутентификацию. Это строго для тестирования, чтобы упростить работу и не иметь дело с паролями и аутентификацией с хоста. Ни при каких обстоятельствах вы не должны добавлять эту конфигурацию в производственный экземпляр Postgres. Последняя команда перезагружает Postgres, чтобы он принял новую конфигурацию.

Теперь у вас должна быть возможность подключиться к Postgres с помощью psql с хост-машины, запустив (сначала выйдите из контейнера):

psql -U postgres -h localhost

Если у вас не установлено psql, вы можете воспользоваться руководством по установке, доступным здесь. Попробуйте выполнить тестовый запрос к базе данных world:

\c world
SELECT * FROM country LIMIT 5;

Обертка

Теперь, когда база данных настроена и работает с некоторыми тестовыми данными, мы можем приступить к настройке нашей оболочки на Python. Давайте откроем новый файл Python и начнем редактирование. Сейчас мы можем просто назвать это wrapper.py и добавить следующее:

#!/usr/bin/env python3
import simplejson as json
import psycopg2
from psycopg2.extras import RealDictCursor
class PostgresWrapper:
    def __init__(self, **args):
        self.user = args.get('user', 'postgres')
        self.port = args.get('port', 5432)
        self.dbname = args.get('dbname', 'world')
        self.host = args.get('host', 'localhost')
        self.connection = None

Во-первых, мы используем simplejson вместо стандартной json библиотеки, потому что, когда мы запрашиваем Decimal значения, они будут корректно сериализованы только с использованием более обновленной библиотеки с поддержкой сброса этого типа данных.

Затем мы добавляем библиотеку psycopg2 и фабрику RealDictCursor. Эта фабрика позволит нам легко сериализовать результаты запроса непосредственно в тип Dict, а затем впоследствии выгружать его в JSON. Мы также определим базовый класс PostgresWrapper для хранения всех наших функций и предоставления некоторых значений инициализации по умолчанию. Теперь давайте добавим несколько простых функций подключения:

    def connect(self):
        pg_conn = psycopg2.connect(
            user=self.user,
            port=self.port,
            dbname=self.dbname,
            host=self.host
        )
        self.connection = pg_conn
    def get_json_cursor(self):
        return self.connection.cursor(cursor_factory=RealDictCursor)

Внутри функции connect() мы используем psycopg2 для прямого подключения к базе данных Postgres, которую мы настроили ранее. Это соединение также указывает, к какой базе данных подключаться внутри Postgres. В этом примере мы будем использовать world данные, которые содержат такие вещи, как города и страны. Следующая функция использует предыдущее соединение для установки нового курсора. Курсор похож на интерактивное приглашение psql, в которое вы вводите операторы SQL, за исключением того, что оно не интерактивно и может управляться программно. Этот курсор также использует спецификацию RealDictCursor для возврата Dict типов, которые позже легко возвращаются в виде строки JSON.

Наконец, давайте настроим функции для фактического получения некоторых данных:

    @staticmethod
    def execute_and_fetch(cursor, query):
        cursor.execute(query)
        res = cursor.fetchall()
        cursor.close()
        return res
    def get_json_response(self, query):
        cursor = self.get_json_cursor()
        response = self.execute_and_fetch(cursor, query)
        return json.dumps(response)
    def get_countries(self):
        query = "SELECT * FROM country LIMIT 2;"
        print(self.get_json_response(query))

Функция execute_and_fetch() использует курсор, который мы установили ранее, чтобы выполнить запрос SQL, закрыть соединение курсора и затем вернуть результаты. По завершении действия важно закрыть курсор, поскольку в противном случае каждый вызов execute_and_fetch() будет создавать экземпляры нескольких курсоров и потреблять больше ресурсов в базе данных. Функция get_json_response() - это вспомогательная функция, которая обрабатывает создание экземпляра курсора и передает курсор и запрос в execute_and_fetch(). Впоследствии эта функция возвращает ответ в виде строки JSON.

Функция get_countries() будет использовать get_json_response() и передавать запрос для стран. Когда мы вызываем эту функцию, мы получаем последний JSON-объект запрошенных строк из базы данных.

Собираем все вместе

Напомним, как должен выглядеть весь класс-оболочка:

#!/usr/bin/env python3
import simplejson as json
import psycopg2
from psycopg2.extras import RealDictCursor
class PostgresWrapper:
    def __init__(self, **args):
        self.user = args.get('user', 'postgres')
        self.port = args.get('port', 5432)
        self.dbname = args.get('dbname', 'world')
        self.host = args.get('host', 'localhost')
        self.connection = None
    def connect(self):
        pg_conn = psycopg2.connect(
            user=self.user,
            port=self.port,
            dbname=self.dbname,
            host=self.host
        )
        self.connection = pg_conn
    def get_json_cursor(self):
        return self.connection.cursor(cursor_factory=RealDictCursor)
    @staticmethod
    def execute_and_fetch(cursor, query):
        cursor.execute(query)
        res = cursor.fetchall()
        cursor.close()
        return res
    def get_json_response(self, query):
        cursor = self.get_json_cursor()
        response = self.execute_and_fetch(cursor, query)
        return json.dumps(response)
    def get_countries(self):
        query = "SELECT * FROM country LIMIT 2;"
        print(self.get_json_response(query))
dbconn = PostgresWrapper()
dbconn.connect()
dbconn.get_countries()

Внизу файла мы создаем экземпляр PostgresWrapper, подключаемся и затем вызываем get_countries() для печати некоторых образцов данных JSON.

Вы должны иметь возможность просто запустить файл и получить результаты:

./wrapper.py

Теперь вы должны увидеть вывод JSON, подобный этому:

[{"code": "AFG", "name": "Afghanistan", "continent": "Asia", "region": "Southern and Central Asia", ...}]

Если все работает правильно, вы получите очень простой класс-оболочку для Postgres, который можно настроить для вывода BLOB-объектов JSON различных запросов. Его можно включить в существующее приложение Python или даже использовать в качестве рудиментарного бэкэнда для API. Имейте в виду, что это простой пример класса, вы можете немного расширить этот класс и сделать выполнение запросов еще более гибким.

Сочетая Postgres с мощью Python и отличными модулями, вы можете упростить и абстрагироваться от сложности подключения к базам данных и получения данных из них. Для еще более изысканного и элегантного подхода рассмотрите возможность изучения модуля ORM под названием SQLAlchemy. Это позволяет вам взаимодействовать с результатами базы данных как с реальными объектами Python и писать еще менее специфичный для базы данных код.

Спасибо за внимание! Postgres и Python - брак, заключенный на небесах. Эта оболочка очень легкая и простая в использовании, но ее можно расширить, добавив еще больше функций. Напишите в Twitter о своих приключениях, связанных с Python и Postgres.