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]
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'
- 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
-
Multi-engine support: From the dependency-free lightweight
XMLengine to the production-performanceSQLITEengine. 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
Then, import it in your project:
import simpsave as ss # commonly aliased as 'ss'
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)
The
SQLITEengine requires theSQLiteenvironment 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')
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
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__.xmlfile 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
:ss:mode requires SimpSave to be installed viapip
Data Types
SimpSave fully supports Python's built-in basic types, including:
intfloatstrbool-
list(including nested, items within nesting also need to be Python's basic type data, same below) dicttupleNone
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:
...
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
Trueif written successfully, returnsFalseif 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
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:
...
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')
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:
...
Parameter Description
-
key: The key name to check. -
file: The file path to check, defaults to__ss__.xml.
Return Value
- Returns
Trueif the key exists, returnsFalseif 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
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:
...
Parameter Description
-
key: The key name to delete. -
file: The file path to operate on, defaults to__ss__.xml.
Return Value
- Returns
Trueif deleted successfully, returnsFalseif 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
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]:
...
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)
Deleting Files
The delete function can delete the entire storage file:
def delete(*, file: str | None = None) -> bool:
...
Parameter Description
-
file: The file path to delete, defaults to__ss__.xml.
Return Value
- Returns
Trueif deleted successfully, returnsFalseif 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
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}")
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}")
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}")
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}")
Practice Suggestions
- When using non-
SQLITEengines, control data volume and complexity; -
Before reading data, use
hasortry-exceptstatements 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.
Learn more, visit GitHub
Top comments (0)