Metadata-Version: 2.4
Name: pystructlight
Version: 0.1.3
Summary: A lightweight, immutable struct library with type enforcement.
Author: Naphon jangjit
Project-URL: Homepage, https://github.com/NaphonJangjit/pystruct
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

```markdown
# 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).

```python
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…

```python
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):

```python
book2 = Book.new("1984", "George Orwell", 328, True)
```

### 3. Access and Use

```python
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")`). |

```python
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.                                             |

```python
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.   |

```python
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 as `Union[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:

```python
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.

```python
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 in `repr` and error messages)
- `schema` – dictionary where each key is a field name and each value is either a **type** or a `FieldDef` (created by `field()`).

### `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`. Return `False` or a string to raise an error.
- `coerce` – if `True`, tries to convert the input to the given type using `typ(value)`.
