PickleDB – лёгкая база данных

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

PickleDB — это легковесная база данных типа ключ-значение, написанная на чистом Python и не требующая установки дополнительных серверов или зависимостей. Данная библиотека представляет собой простое решение для хранения небольших объемов данных в формате JSON, что делает ее идеальным выбором для быстрых прототипов, CLI-утилит, конфигурационных файлов и микропроектов.

Основное преимущество PickleDB заключается в простоте использования при сохранении достаточного функционала для решения широкого спектра задач хранения данных. Библиотека поддерживает работу с различными типами данных: строками, числами, списками, словарями и булевыми значениями.

Что такое PickleDB и где она используется

PickleDB — это NoSQL база данных, которая хранит данные в виде пар ключ-значение в JSON-файле. Библиотека была создана как альтернатива для случаев, когда использование полноценных СУБД типа PostgreSQL или MySQL является избыточным.

Основные области применения PickleDB включают:

  • Хранение пользовательских настроек и конфигураций
  • Кэширование промежуточных результатов вычислений
  • Сохранение токенов авторизации и API ключей
  • Ведение простых логов и счетчиков
  • Создание быстрых прототипов с персистентностью данных
  • Разработка CLI-приложений с локальным хранением

Установка и первоначальная настройка

Для установки PickleDB используется стандартный менеджер пакетов Python:

pip install pickledb

После установки библиотека готова к использованию без дополнительной конфигурации:

import pickledb

# Создание или подключение к базе данных
db = pickledb.load('database.db', auto_dump=True)

Параметр auto_dump=True обеспечивает автоматическое сохранение всех изменений в файл, что гарантирует сохранность данных даже при неожиданном завершении программы.

Архитектура и принципы работы

Структура хранения данных

PickleDB использует JSON как формат хранения, что обеспечивает читаемость данных и их переносимость между различными системами. Файл базы данных представляет собой обычный JSON-документ со следующей структурой:

{
  "simple_key": "строковое значение",
  "numeric_key": 42,
  "boolean_key": true,
  "list_key": ["элемент1", "элемент2", "элемент3"],
  "dict_key": {
    "nested_field": "вложенное значение",
    "nested_number": 100
  }
}

Механизм сохранения

При работе с PickleDB все данные загружаются в память при инициализации базы. Изменения применяются к объекту в памяти, а затем, в зависимости от настроек, сохраняются в файл:

  • При auto_dump=True — сохранение происходит после каждой операции записи
  • При auto_dump=False — требуется ручной вызов метода dump()

Полный справочник методов и функций PickleDB

Основные методы работы с данными

Метод Описание Пример использования
set(key, value) Сохраняет значение по ключу db.set('name', 'Иван')
get(key) Получает значение по ключу name = db.get('name')
exists(key) Проверяет существование ключа if db.exists('name'):
rem(key) Удаляет ключ и его значение db.rem('name')
getall() Возвращает все ключи keys = db.getall()
totalkeys() Возвращает количество ключей count = db.totalkeys()
append(key, value) Добавляет строку к существующему значению db.append('text', ' дополнение')

Методы для работы с числами

Метод Описание Пример использования
incr(key, by=1) Увеличивает числовое значение db.incr('counter')
decr(key, by=1) Уменьшает числовое значение db.decr('counter', 5)

Методы для работы со списками

Метод Описание Пример использования
lcreate(name) Создает новый список db.lcreate('items')
ladd(name, value) Добавляет элемент в список db.ladd('items', 'новый_элемент')
lget(name, pos) Получает элемент по позиции item = db.lget('items', 0)
lgetall(name) Получает все элементы списка items = db.lgetall('items')
lremlist(name) Удаляет весь список db.lremlist('items')
lremvalue(name, value) Удаляет первое вхождение значения db.lremvalue('items', 'элемент')
lpop(name, pos) Удаляет и возвращает элемент по позиции item = db.lpop('items', -1)
llen(name) Возвращает длину списка length = db.llen('items')
lextend(name, seq) Расширяет список последовательностью db.lextend('items', [1, 2, 3])
lexists(name, value) Проверяет наличие значения в списке exists = db.lexists('items', 'элемент')

Методы для работы со словарями

Метод Описание Пример использования
dcreate(name) Создает новый словарь db.dcreate('user_data')
dadd(name, pair) Добавляет пару ключ-значение в словарь db.dadd('user_data', ('age', 25))
dget(name, key) Получает значение из словаря по ключу age = db.dget('user_data', 'age')
dgetall(name) Получает весь словарь user = db.dgetall('user_data')
drem(name) Удаляет весь словарь db.drem('user_data')
dpop(name, key) Удаляет и возвращает значение по ключу value = db.dpop('user_data', 'age')
dkeys(name) Возвращает ключи словаря keys = db.dkeys('user_data')
dvals(name) Возвращает значения словаря values = db.dvals('user_data')
dexists(name, key) Проверяет наличие ключа в словаре exists = db.dexists('user_data', 'age')
dmerge(name1, name2) Объединяет два словаря db.dmerge('dict1', 'dict2')

Служебные методы

Метод Описание Пример использования
dump() Принудительно сохраняет данные в файл db.dump()
load(location, auto_dump) Загружает базу данных из файла db = pickledb.load('db.json', True)
deldb() Удаляет все данные из базы db.deldb()

Работа с различными типами данных

Простые типы данных

PickleDB поддерживает все основные типы данных Python:

import pickledb

db = pickledb.load('example.db', auto_dump=True)

# Строки
db.set('username', 'admin')
db.set('email', 'user@example.com')

# Числа
db.set('user_id', 12345)
db.set('balance', 1000.50)

# Булевые значения
db.set('is_active', True)
db.set('email_verified', False)

# Получение данных
username = db.get('username')  # 'admin'
balance = db.get('balance')    # 1000.5
is_active = db.get('is_active')  # True

Работа со списками

PickleDB предоставляет расширенный функционал для работы со списками:

# Создание и заполнение списка
db.lcreate('shopping_list')
db.ladd('shopping_list', 'молоко')
db.ladd('shopping_list', 'хлеб') 
db.ladd('shopping_list', 'яйца')

# Получение элементов
all_items = db.lgetall('shopping_list')  # ['молоко', 'хлеб', 'яйца']
first_item = db.lget('shopping_list', 0)  # 'молоко'
last_item = db.lget('shopping_list', -1)  # 'яйца'

# Проверка наличия элемента
has_milk = db.lexists('shopping_list', 'молоко')  # True

# Удаление элементов
db.lremvalue('shopping_list', 'хлеб')  # Удаляет 'хлеб'
removed_item = db.lpop('shopping_list', 0)  # Удаляет и возвращает первый элемент

# Расширение списка
db.lextend('shopping_list', ['масло', 'сыр', 'йогурт'])

Работа со словарями

Для сложных структур данных PickleDB предоставляет методы работы со словарями:

# Создание пользовательского профиля
db.dcreate('user_profile')
db.dadd('user_profile', ('name', 'Анна Петрова'))
db.dadd('user_profile', ('age', 28))
db.dadd('user_profile', ('city', 'Москва'))
db.dadd('user_profile', ('occupation', 'Разработчик'))

# Получение данных
user_name = db.dget('user_profile', 'name')  # 'Анна Петрова'
full_profile = db.dgetall('user_profile')    # Весь словарь

# Проверка наличия поля
has_phone = db.dexists('user_profile', 'phone')  # False

# Получение ключей и значений
profile_keys = db.dkeys('user_profile')   # ['name', 'age', 'city', 'occupation']
profile_values = db.dvals('user_profile') # ['Анна Петрова', 28, 'Москва', 'Разработчик']

# Удаление поля
old_age = db.dpop('user_profile', 'age')  # Удаляет и возвращает возраст

Режимы сохранения данных

Автоматическое сохранение

При использовании параметра auto_dump=True все изменения немедленно сохраняются в файл:

db = pickledb.load('auto_save.db', auto_dump=True)
db.set('last_update', '2024-01-15')  # Автоматически сохраняется

Ручное сохранение

Режим auto_dump=False позволяет контролировать момент сохранения данных:

db = pickledb.load('manual_save.db', auto_dump=False)

# Выполняем множество операций
db.set('counter', 1)
db.incr('counter')
db.lcreate('items')
db.ladd('items', 'item1')

# Сохраняем все изменения одним вызовом
db.dump()

Этот подход полезен для операций с большим количеством записей, так как уменьшает количество операций записи на диск.

Работа в памяти

Для временных данных можно использовать режим работы только в памяти:

db = pickledb.load('temp.db', auto_dump=False)
# Работаем с данными, но не вызываем dump()
# Данные будут потеряны при завершении программы

Интеграция с веб-фреймворками

Использование с Flask

PickleDB легко интегрируется с Flask для хранения конфигураций и пользовательских данных:

from flask import Flask, request, jsonify
import pickledb

app = Flask(__name__)
db = pickledb.load('flask_app.db', auto_dump=True)

@app.route('/user/<user_id>', methods=['GET'])
def get_user(user_id):
    if db.exists(f'user_{user_id}'):
        user_data = db.get(f'user_{user_id}')
        return jsonify(user_data)
    return jsonify({'error': 'Пользователь не найден'}), 404

@app.route('/user/<user_id>', methods=['POST'])
def create_user(user_id):
    user_data = request.get_json()
    db.set(f'user_{user_id}', user_data)
    return jsonify({'status': 'Пользователь создан'})

@app.route('/stats')
def get_stats():
    return jsonify({
        'total_users': db.totalkeys(),
        'all_keys': db.getall()
    })

if __name__ == '__main__':
    app.run(debug=True)

Использование с FastAPI

Интеграция с FastAPI для создания быстрых API:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import pickledb

app = FastAPI()
db = pickledb.load('fastapi_app.db', auto_dump=True)

class UserModel(BaseModel):
    name: str
    email: str
    age: int

@app.get('/users/{user_id}')
async def get_user(user_id: str):
    if not db.exists(f'user_{user_id}'):
        raise HTTPException(status_code=404, detail="Пользователь не найден")
    return db.get(f'user_{user_id}')

@app.post('/users/{user_id}')
async def create_user(user_id: str, user: UserModel):
    db.set(f'user_{user_id}', user.dict())
    return {"message": "Пользователь создан успешно"}

@app.delete('/users/{user_id}')
async def delete_user(user_id: str):
    if not db.exists(f'user_{user_id}'):
        raise HTTPException(status_code=404, detail="Пользователь не найден")
    db.rem(f'user_{user_id}')
    return {"message": "Пользователь удален успешно"}

Практические примеры использования

Система конфигурации приложения

import pickledb

class AppConfig:
    def __init__(self, config_file='app_config.db'):
        self.db = pickledb.load(config_file, auto_dump=True)
        self._set_defaults()
    
    def _set_defaults(self):
        defaults = {
            'theme': 'light',
            'language': 'ru',
            'auto_save': True,
            'log_level': 'INFO',
            'max_connections': 100
        }
        
        for key, value in defaults.items():
            if not self.db.exists(key):
                self.db.set(key, value)
    
    def get(self, key):
        return self.db.get(key)
    
    def set(self, key, value):
        self.db.set(key, value)
    
    def reset_to_defaults(self):
        self.db.deldb()
        self._set_defaults()

# Использование
config = AppConfig()
print(f"Текущая тема: {config.get('theme')}")
config.set('theme', 'dark')

Кэширование API ответов

import pickledb
import requests
import time
from datetime import datetime, timedelta

class APICache:
    def __init__(self, cache_file='api_cache.db', ttl_minutes=30):
        self.db = pickledb.load(cache_file, auto_dump=True)
        self.ttl_minutes = ttl_minutes
    
    def _is_expired(self, timestamp):
        cache_time = datetime.fromisoformat(timestamp)
        return datetime.now() - cache_time > timedelta(minutes=self.ttl_minutes)
    
    def get_cached_response(self, url):
        cache_key = f"cache_{url}"
        timestamp_key = f"time_{url}"
        
        if self.db.exists(cache_key) and self.db.exists(timestamp_key):
            if not self._is_expired(self.db.get(timestamp_key)):
                return self.db.get(cache_key)
        
        return None
    
    def cache_response(self, url, response_data):
        cache_key = f"cache_{url}"
        timestamp_key = f"time_{url}"
        
        self.db.set(cache_key, response_data)
        self.db.set(timestamp_key, datetime.now().isoformat())
    
    def clear_expired(self):
        all_keys = self.db.getall()
        for key in all_keys:
            if key.startswith('time_'):
                if self._is_expired(self.db.get(key)):
                    url = key.replace('time_', '')
                    self.db.rem(f'cache_{url}')
                    self.db.rem(key)

# Использование
cache = APICache()

def get_weather_data(city):
    cached_data = cache.get_cached_response(f'weather_{city}')
    if cached_data:
        return cached_data
    
    # Имитация API запроса
    api_data = {'city': city, 'temp': 20, 'humidity': 60}
    cache.cache_response(f'weather_{city}', api_data)
    return api_data

CLI утилита с сохранением истории

import pickledb
import argparse
from datetime import datetime

class TaskManager:
    def __init__(self):
        self.db = pickledb.load('tasks.db', auto_dump=True)
        if not self.db.exists('tasks'):
            self.db.lcreate('tasks')
        if not self.db.exists('completed_tasks'):
            self.db.lcreate('completed_tasks')
    
    def add_task(self, description):
        task = {
            'id': self.db.llen('tasks') + 1,
            'description': description,
            'created_at': datetime.now().isoformat(),
            'completed': False
        }
        self.db.ladd('tasks', task)
        print(f"Задача добавлена: {description}")
    
    def list_tasks(self):
        tasks = self.db.lgetall('tasks')
        if not tasks:
            print("Нет активных задач")
            return
        
        print("Активные задачи:")
        for task in tasks:
            print(f"  [{task['id']}] {task['description']}")
    
    def complete_task(self, task_id):
        tasks = self.db.lgetall('tasks')
        for i, task in enumerate(tasks):
            if task['id'] == task_id:
                task['completed'] = True
                task['completed_at'] = datetime.now().isoformat()
                
                self.db.ladd('completed_tasks', task)
                self.db.lpop('tasks', i)
                print(f"Задача {task_id} выполнена!")
                return
        
        print(f"Задача с ID {task_id} не найдена")
    
    def show_stats(self):
        active_count = self.db.llen('tasks')
        completed_count = self.db.llen('completed_tasks')
        print(f"Активных задач: {active_count}")
        print(f"Выполненных задач: {completed_count}")

def main():
    parser = argparse.ArgumentParser(description='Менеджер задач')
    parser.add_argument('command', choices=['add', 'list', 'complete', 'stats'])
    parser.add_argument('--description', help='Описание задачи')
    parser.add_argument('--id', type=int, help='ID задачи')
    
    args = parser.parse_args()
    manager = TaskManager()
    
    if args.command == 'add' and args.description:
        manager.add_task(args.description)
    elif args.command == 'list':
        manager.list_tasks()
    elif args.command == 'complete' and args.id:
        manager.complete_task(args.id)
    elif args.command == 'stats':
        manager.show_stats()

if __name__ == '__main__':
    main()

Оптимизация производительности

Пакетные операции

Для повышения производительности при работе с большим количеством операций записи используйте режим auto_dump=False:

db = pickledb.load('bulk_data.db', auto_dump=False)

# Выполняем много операций
for i in range(1000):
    db.set(f'item_{i}', {'value': i, 'processed': False})

# Сохраняем все изменения одним вызовом
db.dump()

Управление размером базы данных

Регулярно очищайте устаревшие данные для поддержания производительности:

def cleanup_old_data(db, days_old=30):
    from datetime import datetime, timedelta
    cutoff_date = datetime.now() - timedelta(days=days_old)
    
    all_keys = db.getall()
    for key in all_keys:
        if key.startswith('temp_'):
            data = db.get(key)
            if isinstance(data, dict) and 'created_at' in data:
                created_at = datetime.fromisoformat(data['created_at'])
                if created_at < cutoff_date:
                    db.rem(key)

Обработка ошибок и отладка

Проверка целостности данных

def validate_database(db):
    try:
        all_keys = db.getall()
        print(f"Всего ключей: {len(all_keys)}")
        
        for key in all_keys:
            value = db.get(key)
            if value is None:
                print(f"Предупреждение: ключ '{key}' имеет значение None")
            
        return True
    except Exception as e:
        print(f"Ошибка валидации: {e}")
        return False

Создание резервных копий

import shutil
from datetime import datetime

def backup_database(db_file):
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_file = f"{db_file}.backup_{timestamp}"
    
    try:
        shutil.copy2(db_file, backup_file)
        print(f"Резервная копия создана: {backup_file}")
        return backup_file
    except Exception as e:
        print(f"Ошибка создания резервной копии: {e}")
        return None

Сравнение с альтернативами

PickleDB vs TinyDB vs SQLite

Характеристика PickleDB TinyDB SQLite
Размер библиотеки Очень малый Средний Встроен в Python
Синтаксис запросов Простой API Query API SQL
Производительность Высокая для малых данных Средняя Высокая
Поддержка транзакций Нет Ограниченная Полная
Индексация Нет Есть Полная
Подходит для Конфигурации, кэш Средние приложения Реляционные данные
Максимальный размер До 100MB До 1GB Практически неограничен

Тестирование приложений с PickleDB

Использование временных баз данных

import unittest
import tempfile
import os
import pickledb

class TestPickleDBApp(unittest.TestCase):
    
    def setUp(self):
        # Создаем временный файл для тестов
        self.temp_file = tempfile.NamedTemporaryFile(delete=False)
        self.temp_file.close()
        self.db = pickledb.load(self.temp_file.name, auto_dump=True)
    
    def tearDown(self):
        # Удаляем временный файл после тестов
        os.unlink(self.temp_file.name)
    
    def test_basic_operations(self):
        self.db.set('test_key', 'test_value')
        self.assertEqual(self.db.get('test_key'), 'test_value')
        self.assertTrue(self.db.exists('test_key'))
        
        self.db.rem('test_key')
        self.assertFalse(self.db.exists('test_key'))
    
    def test_list_operations(self):
        self.db.lcreate('test_list')
        self.db.ladd('test_list', 'item1')
        self.db.ladd('test_list', 'item2')
        
        self.assertEqual(self.db.llen('test_list'), 2)
        self.assertEqual(self.db.lget('test_list', 0), 'item1')
        self.assertTrue(self.db.lexists('test_list', 'item2'))

if __name__ == '__main__':
    unittest.main()

Частые вопросы и решения

Как обработать повреждение JSON файла?

При работе с PickleDB может произойти повреждение JSON файла. Рекомендуется реализовать обработку исключений:

import pickledb
import json

def safe_load_db(filename):
    try:
        return pickledb.load(filename, auto_dump=True)
    except (json.JSONDecodeError, FileNotFoundError):
        print(f"Файл {filename} поврежден или не найден. Создается новая база.")
        return pickledb.load(filename, auto_dump=True)

Можно ли использовать PickleDB в многопоточных приложениях?

PickleDB не является потокобезопасной. Для многопоточных приложений используйте блокировки:

import threading
import pickledb

class ThreadSafePickleDB:
    def __init__(self, filename):
        self.db = pickledb.load(filename, auto_dump=True)
        self.lock = threading.Lock()
    
    def set(self, key, value):
        with self.lock:
            return self.db.set(key, value)
    
    def get(self, key):
        with self.lock:
            return self.db.get(key)

Как мигрировать данные между версиями приложения?

def migrate_database(db, current_version, target_version):
    if current_version < 2 and target_version >= 2:
        # Миграция с версии 1 на версию 2
        all_keys = db.getall()
        for key in all_keys:
            if key.startswith('old_'):
                value = db.get(key)
                new_key = key.replace('old_', 'new_')
                db.set(new_key, value)
                db.rem(key)
        
        db.set('db_version', 2)

Как ограничить размер базы данных?

def enforce_size_limit(db, max_keys=1000):
    if db.totalkeys() > max_keys:
        all_keys = db.getall()
        # Удаляем самые старые ключи (предполагается, что они имеют временные метки)
        keys_to_remove = sorted(all_keys)[:db.totalkeys() - max_keys]
        for key in keys_to_remove:
            db.rem(key)

Как экспортировать данные в другие форматы?

import csv
import json

def export_to_json(db, output_file):
    data = {}
    for key in db.getall():
        data[key] = db.get(key)
    
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def export_to_csv(db, output_file):
    with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(['Key', 'Value'])
        
        for key in db.getall():
            value = db.get(key)
            # Сериализуем сложные объекты в JSON
            if isinstance(value, (dict, list)):
                value = json.dumps(value, ensure_ascii=False)
            writer.writerow([key, value])

Лучшие практики использования

Структурирование ключей

Используйте префиксы для организации данных:

# Плохо
db.set('user1', {'name': 'Иван'})
db.set('config1', {'theme': 'dark'})

# Хорошо
db.set('user:1:profile', {'name': 'Иван'})
db.set('user:1:settings', {'theme': 'dark'})
db.set('config:app:theme', 'dark')
db.set('cache:weather:moscow', {'temp': 20})

Валидация данных

Всегда проверяйте данные перед сохранением:

def save_user_profile(db, user_id, profile_data):
    required_fields = ['name', 'email']
    
    if not all(field in profile_data for field in required_fields):
        raise ValueError("Отсутствуют обязательные поля")
    
    # Валидация email
    if '@' not in profile_data['email']:
        raise ValueError("Некорректный email адрес")
    
    db.set(f'user:{user_id}:profile', profile_data)

Мониторинг производительности

import time
import functools

def timing_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} выполнен за {end - start:.4f} секунд")
        return result
    return wrapper

@timing_decorator
def bulk_insert(db, data):
    for key, value in data.items():
        db.set(key, value)

Преимущества и ограничения PickleDB

Преимущества

Простота использования: Минимальный API, не требует изучения SQL или сложных концепций баз данных.

Нулевые зависимости: Написана на чистом Python, не требует дополнительных библиотек.

Читаемый формат данных: JSON файлы можно просматривать и редактировать в любом текстовом редакторе.

Быстрая разработка: Идеально подходит для прототипирования и быстрых решений.

Встроенная персистентность: Автоматическое сохранение данных без дополнительной настройки.

Ограничения

Масштабируемость: Не подходит для больших объемов данных (рекомендуется до 100MB).

Отсутствие индексации: Поиск по значениям требует полного сканирования.

Нет транзакций: Отсутствует поддержка ACID свойств.

Блокировка файла: Может возникнуть проблема при одновременном доступе нескольких процессов.

Загрузка в память: Вся база данных загружается в память при инициализации.

Заключение

PickleDB представляет собой отличное решение для задач, требующих простого и надежного хранения данных без сложности полноценных СУБД. Библиотека идеально подходит для конфигурационных файлов, кэширования, небольших веб-приложений, CLI утилит и прототипирования.

Основные сценарии использования включают разработку быстрых прототипов, хранение пользовательских настроек, создание простых API для небольших проектов, кэширование результатов вычислений и создание локальных баз данных для desktop приложений.

При выборе PickleDB важно понимать ее ограничения и использовать только там, где требуется простота, а объемы данных невелики. Для более сложных задач рекомендуется рассмотреть альтернативы вроде SQLite, TinyDB или полноценные СУБД.

Благодаря минималистичному API, отличной документации и активному сообществу, PickleDB остается популярным выбором для Python разработчиков, которым нужно быстро добавить персистентность данных в свои проекты.

Новости