Модуль подпроцесса Python | Postgres pg_dump с паролем

У меня есть база данных, которую я хочу создать резервную копию с помощью кода Python.

Я попытался основывать свой код на коде в этом обсуждении, который использует модуль подпроцесса и pg_dump. Моя проблема сейчас в том, что мне нужно вручную ввести пароль, чтобы получить файл резервной копии. Я где-то читал, что выполняется .pgpass, но я хочу посмотреть, возможно ли это сделать в модуле подпроцесса.

Мой код следует ниже:

from subprocess import Popen, PIPE
from pathlib import Path, PureWindowsPath

def backup():
    version = 11
    postgresDir = Path("C:/Program Files/PostgreSQL/{}/bin/".format(version))
    directory = PureWindowsPath(postgresDir)
    filename = 'myBackUp2'  # output filename here
    saveDir = Path("D:/Desktop/{}.tar".format(filename))  # output directory here
    file = PureWindowsPath(saveDir)

    host = 'localhost'
    user = 'postgres'
    port = '5434'
    dbname = 'database_name'  # database name here
    proc = Popen(['pg_dump', '-h', host, '-U', user, '-W', '-p', port,
                   '-F', 't', '-f', str(file), '-d', dbname],
                    cwd=directory, shell=True, stdin=PIPE)
    proc.wait()

backup()

Приведенный выше код работает, и резервная копия создается, если я ввожу пароль. Я попытался заменить proc.wait() приведенным ниже кодом, чтобы избавиться от необходимости вводить пароль вручную:

return proc.communicate('{}\n'.format(database_password))

Но я бы получил эту ошибку:

TypeError: требуется байтовый объект, а не 'str'

Возможно ли это сделать в подпроцессе? Если да, то как?


person Teacher Mik    schedule 02.02.2019    source источник
comment
Добавьте параметр text=True к вызову Popen().   -  person Boris    schedule 02.02.2019
comment
В качестве альтернативы вы можете передать пароль в виде байтов, а не в виде строки.   -  person Paul Cornelius    schedule 02.02.2019


Ответы (4)


Используйте файл паролей.

В Microsoft Windows файл называется %APPDATA%\postgresql\pgpass.conf (где %APPDATA% относится к подкаталогу Application Data в профиле пользователя).

и параметр командной строки -w или --no-password (вместо -W)

-w

--no-password

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

person klin    schedule 02.02.2019

Проще всего использовать переменную среды PGPASSWORD.

person Tometzky    schedule 02.02.2019

Есть два класса:

  1. Первый класс, необходимый для создания строки dsn. Затем попробуйте подключиться с параметрами dsn. Если не удается подключиться, перейдите во второй класс.
  2. Второй класс необходим для создания базы данных и восстановления всех таблиц из файла. Вам нужно переделать эти строки:

для правильного открытия вашего файла базы данных dump_file

__folder_name = Path(__file__).parent.parent
__folder_name_data = os.path.join(__folder_name, 'data')
__file_to_open = os.path.join(__folder_name_data, 'bd.backup')

import os
import textwrap
from pathlib import Path
from subprocess import Popen, PIPE


class DataBaseAPI:
    __slots__ = ('__dsn', 'cur')

    def __init__(self):
        self.__dsn = self.__dsn_string()
        self.cur = self.__connection()

    @staticmethod
    def __dsn_string() -> dict:
        print(f'INPUT name of DataBase')
        name = input()
        print(f'INPUT password of DataBase')
        password = input()
        print(f'INPUT user_name of DataBase or press ENTER if user_name="postgres"')
        user_name = input()
        if len(user_name) == 0:
            user_name = 'postgres'
        print(f'INPUT host_name of DataBase or press ENTER if host_name="localhost"')
        host_name = input()
        if len(host_name) == 0:
            host_name = 'localhost'
        return {'dbname': name, 'user': user_name, 'password': password, 'host': host_name}

    def __connection(self):
        try:
            conn = psycopg2.connect(dbname=self.__dsn['dbname'], user=self.__dsn['user'],
                                    host=self.__dsn['host'], password=self.__dsn['password'], port=5432)
        except psycopg2.OperationalError:
            print(textwrap.fill(f'There is no existing DataBase. Creating new DataBase', 80,
                                   subsequent_indent='                   '))
            DataBaseCreator(self.__dsn)
            conn = psycopg2.connect(dbname=self.__dsn['dbname'], user=self.__dsn['user'],
                                    host=self.__dsn['host'], password=self.__dsn['password'], port=5432)
        finally:
            conn.autocommit = True
            cur = conn.cursor()
            print(f'DataBase connection complete')
            return cur



class DataBaseCreator:
    def __init__(self, dsn):
        self.__dsn = dsn
        self.__check_conf_file()
        self.__create_data_base()
        self.__restore_data_base()

    def __check_conf_file(self):
        __app_data = os.environ.copy()["APPDATA"]
        __postgres_path = Path(f'{__app_data}\postgresql')
        __pgpass_file = Path(f'{__postgres_path}\pgpass.conf')
        parameters = f'{self.__dsn["host"]}:{5432}:{self.__dsn["dbname"]}:' \
                     f'{self.__dsn["user"]}:{int(self.__dsn["password"])}\n'
        if not os.path.isdir(__postgres_path):
            os.makedirs(__postgres_path)
        if os.path.isfile(__pgpass_file):
            log.debug(f'File "pgpass.conf" already exists')
            with open(__pgpass_file, 'r+') as f:
                content = f.readlines()
                if parameters not in content:
                    # сервер: порт:база_данных: имя_пользователя:пароль
                    f.write(parameters)
                else:
                    log.info(f' {parameters} already in "pgpass.conf" file')
        else:
            log.debug(f'File "pgpass.conf" not exists')
            with open(__pgpass_file, 'x') as f:
                # сервер: порт:база_данных: имя_пользователя:пароль
                f.write(parameters)

    def __create_data_base(self):
        try:
            __conn = psycopg2.connect(dbname='postgres', user=self.__dsn['user'],
                                      host=self.__dsn['host'], password=self.__dsn['password'], port=5432)
        except Exception as _:
            log.exception(f'{_}')
        else:
            __conn.autocommit = True
            __cur = __conn.cursor()
            __query = f'CREATE DATABASE "{self.__dsn["dbname"]}"'
            __cur.execute(__query)
            log.info(f'{__query}')

    def __restore_data_base(self):
        __col = [x for x in self.__dsn.values()]
        __folder_name = Path(__file__).parent.parent
        __folder_name_data = os.path.join(__folder_name, 'data')
        __file_to_open = os.path.join(__folder_name_data, 'bd.backup')
        __cmd = f'pg_restore --host={__col[3]} --dbname={__col[0]} --username={__col[1]} ' \
                f'--verbose=True --no-password ' \
                f'{__file_to_open}'
        try:
            __proc = Popen(__cmd, stdout=PIPE, stderr=PIPE)
        except FileNotFoundError:
            log.info(f'FileNotFoundError: [WinError 2] Не удается найти указанный файл')
            log.info(textwrap.fill(f'You need to SET Windows $PATH for use "pg_restore" in cmd', 80,
                                   subsequent_indent='                   '))
        else:
            __stderr = __proc.communicate()[1].decode('utf-8', errors="ignore").strip()
            log.debug(textwrap.fill(f'{__stderr}', 80))
person Kirill Vladi    schedule 10.12.2019

Еще один вариант - использовать параметр dbname

'pg_dump --dbname=postgresql://{}:{}@{}:{}/{}'.format(user, password, host, port, database_name)
person Vadym    schedule 08.02.2021