Strictly-typed, human-readable data management for Python.
Rheel Data Management (RDM) provides a clean .rdm file format and
Python API for structured, section-based data storage with enforced
types.
It is designed for developers who want more structure and type safety than JSON or TOML --- without the complexity of a database.
- Strict type enforcement
- Native Python type support
- Nested generics (
list[int],dict[int, str]) - Union types (
str | int) - Mixed generics (
list[str | int]) - Dynamic operations (
add(),extend(),multiply()) - Key deletion support (
delete()) - Section-based structure
- Human-readable aligned format
- Atomic file saves (corruption protection)
- Custom type registry
- Supports
datetime,date,time, andPathnatively - Safe variable handling with deep copy
pip install Rheel-Data-Managementimport rheelDM[user123]
name : str = "Steve"
score : int = 42
tags : list[str] = ["admin", "tester"]
prefs : dict[int,str] = {1: "dark", 2: "light"}
Clean. Typed. Readable.
import rheelDM
data = rheelDM.Obj()
user = data.section("user123") # for simplicity user always refers to this section
user.set("name", str, "Steve")
user.set("score", int, 42)
user.set("tags", list[str], ["admin", "tester"])
user.set("prefs", dict[int, str], {1: "dark", 2: "light"})
data.save("botdata.rdm")from datetime import datetime
default_data = rheelDM.Obj()
default_data.section("user123").set("name", str, "noName")
default_data.section("user123").set("score", int, 0)
default_items = rheelDM.Obj()
default_items.section("user123").set("items", list, [1])
loaded = rheelDM.Obj.load("botdata.rdm", default_data) # uses a copy of default if file not found (this can also be a boolean for catching missing files)
user = loaded.section("user123")
print(user.get("name")) # "Steve"
print(user.get("score")) # 42
print(user.get("items", default_items)) # [1] (uses a copy of default because "items" is not defined in botdata.rdm)user.set("name", str, "Alex")
print(user.get("name")) # "Alex"
# How to prevent overwrites:
user.set("name", str, "Steve", overwrite=False) # -> raise KeyError
print(user.get("name")) # "Alex"from datetime import datetime
user.set("score", int, 10) # 10
user.add("score", 5) # 15
user.add("score", -3) # 12
user.multiply("score", 2) # 24
user.multiply("score", 0.25) # 6
user.set("last_login", datetime, datetime.now()) # 2026-02-21T21:39:18.398038
user.add("last_login", 3605) # 2026-02-21T22:39:23.398038from pathlib import Path
user.set("name", str, "Steve") # "Steve"
user.extend("name", " the Hero") # "Steve the Hero"
user.set("items", list[int], [1]) # [1]
user.extend("items", 2) # [1, 2]
user.extend("items", [3, 4]) # [1, 2, 3, 4]
user.set("tags", set[str], {"a"}) # {"a"}
user.extend("tags", "b") # {"a", "b"}
user.set("settings", dict[str,int], {"a":1}) # {"a": 1}
user.extend("settings", {"b":2}) # {"a": 1, "b": 2}
config_dir = Path("/settings")
user.set("config_file", Path, config_dir) # Path(/settings)
user.extend("config_file", "steve3828.rdm") # Path(/settings/steve3828.rdm)user.delete("score") # key, value and type of "score" will be deleted entirely from the RDM file.strintfloatboolNonelist[T]set[T]tuple[T]dict[K, V]- Nested generics (e.g.
list[dict[int, str]]) - Union types (e.g.
str | int) - Mixed generics (e.g.
list[str | int]) - datetime:
datetimedatetime
- pathlib:
Path
Example:
from datetime import datetime
from pathlib import Path
user.set("id", int, 1234567890)
user.set("names", dict[str, str], {"username": "steve3828", "displayname": "Steve"})
user.set("last_login", datetime, datetime.now())
user.set("config_file", Path, Path("config/settings.txt"))You can register your own types globally.
import rheelDM
class Color:
def __init__(self, hex_code: str):
self.hex = hex_code
rheelDM.TypeRegistry.register(
"Color",
Color,
lambda v: f'"{v.hex}"',
lambda v: Color(v.strip('"'))
)Now you can use it like any native type:
data.section("settings").set("theme", Color, Color("#ff8800"))JSON and TOML do not enforce types.
RDM validates everything on write and load.
JSON cannot store:
datetimePathsettuple- Union types
- Nested generics
intasdict-keysNone(stores it asnull)
TOML cannot store:
PathsetNoneintasdict-keys
RDM can store all of these, even the most complex and custom types.
Section-based and auto-aligned format keeps large files organized and readable.
Minimal syntax makes manual edits easier than ever:
key : type = value
- Game save systems
- Discord bot data
- Typed configuration systems
- CLI tool configs
- Small to medium persistent data storage