В последнее время сайт всё больше обрастает ретроспективой уже позабытых микросервисов на Python. Сегодняшний пост не исключение: наткнулся на парочку стареньких скриптов для добавления и удаления пользователей проекта на основе базы данных SQLite и решил оставить их здесь. Может быть кому-то пригодится.
Впервые эти скрипты я применил в процессе внедрения веб-приложения множественного размещения файлов. Мне очень уж не хотелось строить систему прав пользователей и городить ещё один flask-маршрут. Решил оставить как есть и обойтись скриптами с запросами к базе данных.
Упомянутое выше приложение хранило в SQLite только логины и пароли пользователей. На тот момент, данный метод хранения учётных данных мне казался простым и логичным. Использовать в качестве хранилища, например, json-файл — бессмысленно. С этим форматом нельзя пообщаться на языке запросов, да и база данных давала возможность масштабируемости в будущем.
Итак, сначала код скрипта для добавления пользователя:
## Скрипт для добавления пользователя в базу данных SQLite ##
import sys
import os
import sqlite3
import hashlib
import argparse
import getpass
import platform
import ctypes
# Основные переменные #
APPNAME = "Добавление пользователя в базу данных приложения" # <== Название приложения
APPDB = "users.db" # <== Имя базы данных
APPTABLE = "logins" # <== Имя таблицы
APPUSER = "name" # <== Имя столбца с логинами
APPPASS = "password" # <== Имя столбца с паролями
APPDESC = f"Обратите внимание, что по умолчанию используется база данных {APPDB}!" # <== Предупреждение при старте приложения
def add_user_advanced():
parser = argparse.ArgumentParser(description=APPNAME)
parser.add_argument('--username', '-u', help='Имя пользователя')
parser.add_argument('--password', '-p', help='Пароль пользователя')
parser.add_argument('--db-path', help='Путь к базе данных (по умолчанию: автоматическое определение)')
parser.add_argument('--table', help=f'Имя таблицы с пользователями в базе данных (по умолчанию: {APPTABLE})')
args = parser.parse_args()
# Если параметры не переданы, запрашиваем их интерактивно #
username = args.username
if not username:
username = input("Введите имя пользователя: ").strip()
password = args.password
if not password:
password = getpass.getpass("Введите пароль: ")
table_name = args.table if args.table else APPTABLE
if not table_name:
table_name = input(f"Введите имя таблицы (по умолчанию {APPTABLE}): ").strip() or APPTABLE
if not username or not password:
print("Ошибка: Имя пользователя и пароль обязательны")
return False
# Определяем путь к базе данных #
if args.db_path:
db_path = args.db_path
else:
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(base_dir, APPDB)
return add_user(username, password, db_path, table_name)
def add_user(username, password, db_path=None, table_name=None):
if db_path is None:
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(base_dir, APPDB)
if table_name is None:
table_name = APPTABLE
if not os.path.exists(db_path):
print(f"Ошибка: База данных не найдена по пути: {db_path}")
return False
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Проверяем существование указанной таблицы #
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
if not cursor.fetchone():
print(f"Ошибка: Таблица '{table_name}' не найдена в базе данных")
return False
# Проверяем существование пользователя #
cursor.execute(f"SELECT * FROM {table_name} WHERE {APPUSER} = ?", (username,))
if cursor.fetchone():
print(f"Ошибка: Пользователь '{username}' уже существует в таблице '{table_name}'")
return False
# Добавляем пользователя #
hashed_password = hash_password(password)
cursor.execute(f"INSERT INTO {table_name} ({APPUSER}, {APPPASS}) VALUES (?, ?)",
(username, hashed_password))
conn.commit()
conn.close()
print(f"Пользователь '{username}' успешно добавлен в таблицу '{table_name}'!")
return True
except sqlite3.Error as e:
print(f"Ошибка базы данных: {e}")
return False
def hash_password(password):
return hashlib.sha1(password.encode()).hexdigest()
# Заголовок окна консоли #
def set_console_title(title):
if platform.system() == "Windows":
ctypes.windll.kernel32.SetConsoleTitleW(title)
else:
print(f"\033]0;{title}\a", end='', flush=True)
if __name__ == '__main__':
set_console_title(APPNAME)
print(APPDESC)
# Считывание аргументов #
if len(sys.argv) > 1:
success = add_user_advanced()
else:
# Интерактивный режим #
success = add_user_advanced()
sys.exit(0 if success else 1)
Обращаю особое внимание, что функция hash_password строит хэш пароля по алгоритму SHA1. Если нужен более серьёзный принцип генерации контрольной суммы можете использовать одну из этих функций:
# SHA256
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
# SHA512
def hash_password(password):
return hashlib.sha512(password.encode()).hexdigest()
Конечно, всё зависит от потребностей и реализации конкретного проекта. Можно уж совсем заморочиться и предусмотреть генерацию контрольной суммы вместе с salt, обеспечив её хранение в базе данных. Впрочем для интранет-сервисов без обработки каких-либо критичных данных этот вариант может быть избыточным.
Теперь скрипт для удаления пользователя из базы данных приложения:
## Скрипт для удаления пользователя из базы данных SQLite ##
import sys
import os
import sqlite3
import argparse
import platform
import ctypes
# Основные переменные #
APPNAME = "Удаление пользователя из базы данных приложения" # <== Название приложения
APPDB = "users.db" # <== Имя базы данных
APPTABLE = "logins" # <== Имя таблицы
APPUSER = "name" # <== Имя столбца с логинами
APPDESC = f"Обратите внимание, что по умолчанию используется база данных {APPDB}!" # <== Предупреждение при старте приложения
def remove_user_advanced():
parser = argparse.ArgumentParser(description=APPNAME)
parser.add_argument('--username', '-u', required=True, help='Имя пользователя для удаления')
parser.add_argument('--db-path', help='Путь к базе данных (по умолчанию: автоматическое определение)')
parser.add_argument('--table', help=f'Имя таблицы с пользователями в базе данных (по умолчанию: {APPTABLE})')
args = parser.parse_args()
username = args.username.strip()
table_name = args.table if args.table else APPTABLE
if not username:
print("Ошибка: Имя пользователя обязательно")
return False
# Определяем путь к базе данных #
if args.db_path:
db_path = args.db_path
else:
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(base_dir, APPDB)
return remove_user(username, db_path, table_name)
def remove_user(username, db_path=None, table_name=None):
if db_path is None:
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(base_dir, APPDB)
if table_name is None:
table_name = APPTABLE
if not os.path.exists(db_path):
print(f"Ошибка: База данных не найдена по пути: {db_path}")
return False
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Проверяем существование указанной таблицы #
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
if not cursor.fetchone():
print(f"Ошибка: Таблица '{table_name}' не найдена в базе данных")
return False
# Проверяем существование пользователя #
cursor.execute(f"SELECT * FROM {table_name} WHERE {APPUSER} = ?", (username,))
if not cursor.fetchone():
print(f"Ошибка: Пользователь '{username}' не найден в таблице '{table_name}'")
return False
# Подтверждение удаления #
confirm = input(f"Вы уверены, что хотите удалить пользователя '{username}'? (y/N): ").strip().lower()
if confirm not in ['y', 'yes', 'д', 'да']:
print("Удаление отменено")
return False
# Удаляем пользователя #
cursor.execute(f"DELETE FROM {table_name} WHERE {APPUSER} = ?", (username,))
conn.commit()
conn.close()
print(f"Пользователь '{username}' успешно удален из таблицы '{table_name}'!")
return True
except sqlite3.Error as e:
print(f"Ошибка базы данных: {e}")
return False
# Заголовок окна консоли #
def set_console_title(title):
if platform.system() == "Windows":
ctypes.windll.kernel32.SetConsoleTitleW(title)
else:
print(f"\033]0;{title}\a", end='', flush=True)
if __name__ == '__main__':
set_console_title(APPNAME)
print(APPDESC)
# Считывание аргументов #
if len(sys.argv) > 1:
success = remove_user_advanced()
else:
# Интерактивный режим #
username = input("Введите имя пользователя для удаления: ").strip()
if username:
success = remove_user(username)
else:
print("Ошибка: Имя пользователя не может быть пустым")
success = False
sys.exit(0 if success else 1)
Оба скрипта могут работать как в интерактивном режиме, так и с параметрами запуска:
# Добавление пользователя python sqlite_adduser.py --username "ivanov" --password "Very_Strong_Pass" --table "admin_users" # Удаление пользователя python sqlite_deleteuser.py --username "ivanov"
Более подробно о параметрах запуска можно узнать, использовав ключ —help или -h:
# Добавление пользователя sqlite_adduser.py [-h] [--username USERNAME] [--password PASSWORD] [--db-path DB_PATH] [--table TABLE] # Удаление пользователя sqlite_deleteuser.py [-h] --username USERNAME [--db-path DB_PATH] [--table TABLE]
Этот утилитарный наборчик без всяческих проблем применялся к десятку микросервисов, с учётом, что создание и удаление пользователей в проектах было далеко не ежедневной задачей.