# pystructlight
**A lightweight, memory‑efficient, and type‑safe immutable data structure for Python.**
`pystructlight` lets you define schemas for your data objects and ensures that every instance adheres to those types at runtime – all while keeping a tiny memory footprint thanks to `__slots__`.
## Features
- **Immutable** – instances cannot be modified after creation
- **Runtime type enforcement** – validates that all field values match your schema
- **Memory efficient** – uses `__slots__` to avoid per‑instance `__dict__`
- **Flexible field definitions** – support for default values, custom validators, and type coercion
- **Rich type support** – `Union`, `Optional`, `Literal`, `Any`, generic containers (`list[T]`, `dict[K, V]`, etc.)
- **Convenient methods** – `evolve()`, `matches()`, `to_dict()`, `from_dict()`, `is_instance()`
- **Positional or keyword instantiation** – choose the style you prefer
## Installation
```bash
pip install pystructlight
Quick Start
1. Define a Struct
Pass a name and a dictionary mapping field names to their expected types (or to a FieldDef for advanced options).
from pystructlight import Struct, field
# Simple type‑only schema
Book = Struct("Book", {
"title": str,
"author": str,
"pages": int,
"is_hardcover": bool,
})
# Schema with defaults, validator, and coercion
Person = Struct("Person", {
"name": str,
"age": field(int, default=0, validator=lambda x: x >= 0),
"email": str,
"tags": field(list[str], default=list),
})
2. Create Instances
Use keyword arguments…
book1 = Book.new(
title="The Great Gatsby",
author="F. Scott Fitzgerald",
pages=180,
is_hardcover=False
)
…or positional arguments (in the order the fields were defined):
book2 = Book.new("1984", "George Orwell", 328, True)
3. Access and Use
print(book1.title) # "The Great Gatsby"
print(book1.pages) # 180
# Immutability
try:
book1.pages = 200
except AttributeError:
print("Cannot modify immutable struct!")
# Runtime type checking
try:
Book.new("The Hobbit", "J.R.R. Tolkien", "many", True)
except TypeError as e:
print(e) # Field 'pages': expected int, got str
# Convert to dict
data = book1.to_dict()
# {'title': 'The Great Gatsby', 'author': 'F. Scott Fitzgerald', 'pages': 180, 'is_hardcover': False}
# Create from dict
book3 = Book.from_dict({"title": "Moby Dick", "author": "Herman Melville", "pages": 585, "is_hardcover": False})
Advanced Field Definitions
Use the field() helper to add defaults, validators, or type coercion.
| Parameter | Description |
|---|---|
type |
Expected type (e.g. str, int, list[str], Union[int, str], Any) |
default |
Default value. Can be a constant or a callable (e.g. list, dict). |
validator |
Callable that receives the value and returns True, a string error, or False. |
coerce |
If True, attempts to convert the input to the expected type (e.g. int("42")). |
from pystructlight import Struct, field
Config = Struct("Config", {
"timeout": field(float, default=5.0, coerce=True),
"retries": field(int, default=3, validator=lambda x: 0 <= x <= 10),
"tags": field(list[str], default=list),
})
c = Config.new(timeout="2.5", retries=5, tags=["prod", "api"])
print(c.timeout) # 2.5 (coerced from string)
print(c.retries) # 5
print(c.tags) # ['prod', 'api']
Methods on Struct Instances
Every instance provides the following methods:
| Method | Description |
|---|---|
to_dict() |
Returns a dict mapping field names to values. |
evolve(**changes) |
Returns a new instance with updated fields (original unchanged). |
matches(**kwargs) |
Returns True if all given fields equal the specified values. |
__iter__() |
Iterates over field values (in definition order). |
__len__() |
Number of fields. |
__repr__() |
Human‑readable representation. |
book = Book.new(title="Brave New World", author="Aldous Huxley", pages=268, is_hardcover=False)
# Evolve
new_book = book.evolve(pages=300, is_hardcover=True)
print(new_book.pages) # 300
print(book.pages) # 268 (unchanged)
# Matching
if book.matches(title="Brave New World"):
print("It's the same book!")
# Iteration
for value in book:
print(value)
Methods on the Struct Type
The object returned by Struct() (a StructType) provides:
| Method | Description |
|---|---|
new(*args, **kwargs) |
Creates a new instance. |
from_dict(data: dict) |
Creates an instance from a dictionary (calls new(**data)). |
is_instance(obj) |
Returns True if obj is an instance of this exact struct type. |
Book.is_instance(book) # True
Book.is_instance("foo") # False
Runtime Type Checking
pystructlight validates values against your schema when you create the instance.
Supported types include:
- Built‑in types:
int,float,str,bool,bytes,None - Type unions:
Union[int, str],int | str(Python 3.10+) Optional[T](same asUnion[T, None])Literal["red", "green"]Any(no checking)- Generic containers:
list[T],tuple[T],set[T],frozenset[T],dict[K, V] - Any user‑defined class
Example:
from typing import Union, Literal, Any
Data = Struct("Data", {
"id": Union[int, str],
"status": Literal["active", "inactive"],
"payload": Any,
})
d = Data.new(id=42, status="active", payload={"anything": 123}) # OK
d2 = Data.new(id="abc", status="inactive", payload=None) # OK
# This fails:
# Data.new(id=3.14, status="active", payload=None) # id must be int or str
Memory Efficiency
Instances do not have a __dict__. All fields are stored in a compact tuple, and access is routed through __slots__. The overhead is roughly the same as a namedtuple, but with full runtime type checking.
import sys
Book = Struct("Book", {"title": str, "pages": int})
b = Book.new("The Pearl", 96)
print(sys.getsizeof(b)) # typically 56 bytes (plus the tuple, but overall very small)
API Reference
Struct(name: str, schema: dict) -> StructType
Creates a new struct type.
name– the name of the struct (used inreprand error messages)schema– dictionary where each key is a field name and each value is either a type or aFieldDef(created byfield()).
field(type, default=_MISSING, validator=None, coerce=False) -> FieldDef
Returns a field definition for use in a schema.
type– the expected type (e.g.str,list[int]).default– optional default value (if callable, it is called each time a default is needed).validator– optional callable(value) -> bool | str | None. ReturnFalseor a string to raise an error.coerce– ifTrue, tries to convert the input to the given type usingtyp(value).
Metadata
Release files for pystructlight 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pystructlight-0.1.3.tar.gz | 13.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pystructlight-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.1 kB
Release files / pystructlight-0.1.3.tar.gz
| Download URL | pystructlight-0.1.3.tar.gz |
|---|---|
| Size | 13.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e1f112e61c172314b9fc244c51a34a6436e75321727783d6537d261f5249f3d5
|
|
BLAKE2b-256 checksum How to use checksums |
597558f1965b68413bd559790ccc0151d03d75e3da02bd248033365af29bdc36
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / pystructlight-0.1.3-py3-none-any.whl
| Download URL | pystructlight-0.1.3-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
470faad1d922f22d09d672276f10ec493e9d56251315dcd6d08d40b6b62003d3
|
|
BLAKE2b-256 checksum How to use checksums |
4d2de4d5c37e5abbf69bdb592fcb8a2291c4c251ebacdc51799b1066369eaf37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for pystructlight 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.