DEV Community

Water Run
Water Run

Posted on

SimpSave 10: A “ready-to-use” lightweight database, now supporting multiple engine options (including SQLite).

How to persist data in Python?
The most standard method is naturally to use a database. However, for many scenarios such as simple scripts or student assignments, even something as lightweight as TinyDB might be too "heavy" and cumbersome.
SimpSave is a featherweight database with a functional minimalist API that is extremely easy to pick up (almost the simplest form of data persistence), featuring "read-and-use" capability (storage and retrieval preserve types, no need for type conversion), making it very suitable for use in such scenarios. SimpSave 10 brings multi-engine support, and support for SQLITE gives it a certain level of production-grade capability.
"Read-and-use" example:

import simpsave as ss
ss.write('key', [1, 2, 3])
print(ss.read('key1').append(4)) # [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

If the project helps you, please feel free to give it a Star on GitHub :)
https://github.com/Water-Run/SimpSave

SimpSave Version 10

Introduction

SimpSave is a Python featherweight key-value storage database for Python basic variables, leveraging Python's native powerful data structure support, "read-and-use", extremely easy to get started with, very suitable for use in various small scripts such as student assignments, or as configuration files, etc.
SimpSave 10 is a major upgrade, bringing optional engine capabilities: the engine wrapper for sqlite provides it with a usable level in some lightweight production environments (although the functional API has no connection pool mechanism); while for use in simple environments, optional dependencies can maintain the original zero-dependency minimalist characteristics.

Core Features

  • Extremely lightweight: Core code is concise and efficient, minimal installation has no dependencies
  • Extremely easy to get started: Functional API (read(), write(), etc.) is very intuitive, almost no learning curve, can be used directly
  import simpsave as ss
  ss.write('key1', 'value1')
  ss.read('key1')  # 'value1'
Enter fullscreen mode Exit fullscreen mode
  • Read-and-use: Stored type and retrieved type remain consistent, no need for type judgment and conversion, further simplifying usage
  import simpsave as ss
  ss.write('key1', 1)
  type(ss.read('key1')).__name__  # 'int'
  ss.read('key1') + 1  # 2
Enter fullscreen mode Exit fullscreen mode
  • Multi-engine support: From the dependency-free lightweight XML engine to the production-performance SQLITE engine. SimpSave automatically selects the engine based on file extension, no manual configuration needed

Installation

SimpSave is published on PyPI, install using pip:

pip install simpsave
Enter fullscreen mode Exit fullscreen mode

Then, import it in your project:

import simpsave as ss  # commonly aliased as 'ss'
Enter fullscreen mode Exit fullscreen mode

And you can start using it.

Optional Dependencies

SimpSave supports optional dependencies, which can be selected for installation based on actual needs:

pip install simpsave                # Install all dependencies
pip install simpsave[XML]           # Minimal: only includes XML engine (requires xml.etree)
pip install simpsave[INI]           # Includes XML and INI engines (no extra dependencies)
pip install simpsave[YML]           # Includes XML and YML engines (requires PyYAML)
pip install simpsave[TOML]          # Includes XML and TOML engines (requires tomli)
pip install simpsave[JSON]          # Includes XML and JSON engines (no extra dependencies)
pip install simpsave[SQLITE]        # Includes XML and SQLITE engines (requires sqlite3)
Enter fullscreen mode Exit fullscreen mode

The SQLITE engine requires the SQLite environment to be deployed on the local machine in advance

Quick Start Example

The following code provides a quick start example for SimpSave:

import simpsave as ss

# Write data
ss.write('name', 'Alice')
ss.write('age', 25)
ss.write('scores', [90, 85, 92])

# Read data
print(ss.read('name'))     # Alice
print(ss.read('age'))      # 25
print(ss.read('scores'))   # [90, 85, 92]

# Check keys
print(ss.has('name'))      # True
print(ss.has('email'))     # False

# Delete key
ss.remove('age')
print(ss.has('age'))       # False

# Regex matching
ss.write('user_admin', True)
ss.write('user_guest', False)
print(ss.match(r'^user_'))  # {'user_admin': True, 'user_guest': False}

# Use different files (automatically select corresponding engine)
ss.write('theme', 'dark', file='config.yml')
print(ss.read('theme', file='config.yml'))  # dark

# Use :ss: mode (save to installation directory)
ss.write('键1', '值1', file=':ss:config.toml') # Use Chinese key names and TOML engine
print(ss.read('键1', file=':ss:config.toml'))  # 值1

# Delete files
ss.delete()
ss.delete(file='config.yml')
Enter fullscreen mode Exit fullscreen mode

If you have some programming foundation, I believe SimpSave's extreme intuitiveness has already taught you its basic usage.

Engines

SimpSave 10 supports multiple storage engines, which can be selected based on actual needs. Engines are stored in the ss.ENGINE enumeration:

Engine Name File Format Dependencies Description
XML .xml xml.etree (built-in) Uses XML format for storage, lightweight and requires no extra dependencies
INI .ini configparser (built-in) Uses INI format for storage, not fully compatible with Unicode
YML .yml PyYAML Uses YAML format for storage
TOML .toml tomli Uses TOML format for storage
JSON .json json (built-in) Uses JSON format for storage
SQLITE .db sqlite3 (built-in) Uses SQLite database, has production-grade performance

Automatic Engine Selection

SimpSave automatically selects the appropriate engine based on the file extension of the file parameter:

import simpsave as ss

ss.write('key1', 'value1', file='data.yml')     # Automatically uses YML engine
ss.write('key2', 'value2', file='config.toml')  # Automatically uses TOML engine
ss.write('key3', 'value3', file='data.db')      # Automatically uses SQLITE engine
Enter fullscreen mode Exit fullscreen mode

Principles

SimpSave uses key-value pairs to save Python's basic type data. Depending on the chosen engine, data will be stored in different formats.

By default, data is saved to the __ss__.xml file in the current working directory

:ss: Mode

As with the old version, SimpSave retains support for the unique :ss: path mode: if the file path starts with :ss: (such as :ss:config.json), the file will be saved in the SimpSave installation directory, thus ensuring cross-environment compatibility.

import simpsave as ss

ss.write('key1', 'value1', file=':ss:config.yml')  # Save to SimpSave installation directory
print(ss.read('key1', file=':ss:config.yml'))      # Read from installation directory
Enter fullscreen mode Exit fullscreen mode

:ss: mode requires SimpSave to be installed via pip

Data Types

SimpSave fully supports Python's built-in basic types, including:

  • int
  • float
  • str
  • bool
  • list (including nested, items within nesting also need to be Python's basic type data, same below)
  • dict
  • tuple
  • None

When reading data, SimpSave automatically restores it to the original Python type, achieving "read-and-use".

API Reference

Writing Data

The write function is used to write key-value pairs to a specified file:

def write(key: str, value: any, *, file: str | None = None) -> bool:
    ...
Enter fullscreen mode Exit fullscreen mode

If the specified storage file does not exist, it will be created automatically.

Parameter Description

  • key: The key to be stored, must be a valid string
  • value: The value to be stored, supports Python basic types
  • file: The file path to write to, defaults to __ss__.xml, can use :ss: mode. The engine will be automatically selected based on the file extension

Return Value

  • Returns True if written successfully, returns False if failed.

Exceptions

  • ValueError: Value is not a Python basic type value
  • IOError: Write error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

ss.write('key1', 'Hello 世界')           # Write Unicode string
ss.write('key2', 3.14)                  # Write float
ss.write('key3', [1, 2, 3, '中文'])      # Write list containing Chinese
ss.write('key4', {'a': 1, 'b': 2})      # Write dictionary

# Use different engines
ss.write('config', 'value', file='settings.yml')   # Use YML engine
ss.write('data', 100, file='cache.db')             # Use SQLITE engine
Enter fullscreen mode Exit fullscreen mode

If the file does not exist, SimpSave will create it automatically.

Reading Data

The read function is used to read data from a specified file:

def read(key: str, *, file: str | None = None) -> any:
    ...
Enter fullscreen mode Exit fullscreen mode

Parameter Description

  • key: The key name to read.
  • file: The file path to read from, defaults to __ss__.xml.

Return Value

  • Returns the value corresponding to the specified key, automatically restored to the original type.
  • If the key does not exist, returns None.

Exceptions

  • IOError: Read error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

print(ss.read('key1'))  # Output: 'Hello 世界'
print(ss.read('key2'))  # Output: 3.14
print(ss.read('key3'))  # Output: [1, 2, 3, '中文']

# Read from different files
value = ss.read('config', file='settings.yml')
Enter fullscreen mode Exit fullscreen mode

Checking if Key Exists

The has function is used to check if a certain key exists in the file:

def has(key: str, *, file: str | None = None) -> bool:
    ...
Enter fullscreen mode Exit fullscreen mode

Parameter Description

  • key: The key name to check.
  • file: The file path to check, defaults to __ss__.xml.

Return Value

  • Returns True if the key exists, returns False if it does not exist.

Exceptions

  • IOError: Read error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

print(ss.has('key1'))        # Output: True
print(ss.has('nonexistent')) # Output: False
Enter fullscreen mode Exit fullscreen mode

Deleting Keys

The remove function is used to delete a key and its corresponding value from a file:

def remove(key: str, *, file: str | None = None) -> bool:
    ...
Enter fullscreen mode Exit fullscreen mode

Parameter Description

  • key: The key name to delete.
  • file: The file path to operate on, defaults to __ss__.xml.

Return Value

  • Returns True if deleted successfully, returns False if failed.

Exceptions

  • IOError: Write error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

ss.remove('key1')  # Delete key 'key1'
print(ss.has('key1'))  # Output: False
Enter fullscreen mode Exit fullscreen mode

Regex Matching Keys

The match function can match all keys that meet the conditions through regular expressions and return the corresponding key-value pairs:

def match(re: str = "", *, file: str | None = None) -> dict[str, any]:
    ...
Enter fullscreen mode Exit fullscreen mode

Parameter Description

  • re: Regular expression string, used to match key names. Empty string means match all keys.
  • file: The file path to operate on, defaults to __ss__.xml.

Return Value

  • Returns a dictionary containing all matched key-value pairs.

Exceptions

  • IOError: Read error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

ss.write('user_name', 'Alice')
ss.write('user_age', 25)
ss.write('admin_name', 'Bob')

result = ss.match(r'^user_.*')  # Match all keys starting with 'user_'
print(result)  # Output: {'user_name': 'Alice', 'user_age': 25}

all_data = ss.match()  # Get all key-value pairs
print(all_data)
Enter fullscreen mode Exit fullscreen mode

Deleting Files

The delete function can delete the entire storage file:

def delete(*, file: str | None = None) -> bool:
    ...
Enter fullscreen mode Exit fullscreen mode

Parameter Description

  • file: The file path to delete, defaults to __ss__.xml.

Return Value

  • Returns True if deleted successfully, returns False if failed.

Exceptions

  • IOError: Delete error
  • RuntimeError: Other runtime errors, such as selected engine not installed, etc.

Example

import simpsave as ss

ss.delete()  # Delete the default save file
ss.delete(file='config.yml')  # Delete specified file
Enter fullscreen mode Exit fullscreen mode

Exception Handling

SimpSave may throw the following exceptions during operation. Understanding these exceptions helps write more robust code.

Common Exception Types

ValueError

Thrown when the passed value is not a Python basic type.

Trigger Scenarios:

  • Attempting to store unsupported complex objects (such as custom class instances, functions, etc.)
  • The passed value exceeds the type range supported by the engine

Example:

import simpsave as ss

class CustomClass:
    pass

try:
    ss.write('key1', CustomClass())  # Throws ValueError
except ValueError as e:
    print(f"Error: {e}")
Enter fullscreen mode Exit fullscreen mode

IOError

Thrown when file read/write operations fail.

Trigger Scenarios:

  • Insufficient file permissions
  • Insufficient disk space
  • File occupied by other processes
  • File path does not exist or is invalid

Example:

import simpsave as ss

try:
    ss.read('key1', file='/root/protected.db')  # May throw IOError
except IOError as e:
    print(f"File operation error: {e}")
Enter fullscreen mode Exit fullscreen mode

RuntimeError

Other runtime errors, usually engine internal errors or configuration issues.

Trigger Scenarios:

  • Engine initialization failed
  • Data format corrupted
  • Dependency library missing or version incompatible

Example:

import simpsave as ss

try:
    ss.write('key1', 'value1', file='data.unknown')  # May throw RuntimeError
except RuntimeError as e:
    print(f"Runtime error: {e}")
Enter fullscreen mode Exit fullscreen mode

Exception Handling Best Practices

It is recommended to use try-except statements to handle possible exceptions:

import simpsave as ss

# Safe write
try:
    ss.write('key1', 'value1')
except ValueError as e:
    print(f"Value type error: {e}")
except IOError as e:
    print(f"File write failed: {e}")
except RuntimeError as e:
    print(f"Runtime error: {e}")

# Safe read
try:
    value = ss.read('key1')
    if value is None:
        print("Key does not exist")
except IOError as e:
    print(f"File read failed: {e}")
except RuntimeError as e:
    print(f"Runtime error: {e}")
Enter fullscreen mode Exit fullscreen mode

Practice Suggestions

  1. When using non-SQLITE engines, control data volume and complexity;
  2. Before reading data, use has or try-except statements for safe reading:

    import simpsave as ss
    value = 'default value'
    if ss.has('key_1'):
        value = ss.read('key_1')
    else:
        ss.write('key_1', 'default value')
    
- When unable to confirm whether the corresponding file exists, use `try-except` combined with initialization statements.
Enter fullscreen mode Exit fullscreen mode

Learn more, visit GitHub

Top comments (0)