Metadata-Version: 2.4
Name: nonesafe_ex
Version: 0.1.2
Summary: A none-safe wrapper for containers
License-Expression: MIT
Keywords: none-safe,wrapper,container
Author: originalFactor
Author-email: 2438926613@qq.com
Requires-Python: >=3.9,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Project-URL: Repository, https://github.com/originalFactor/nonesafe_ex
Description-Content-Type: text/markdown

# NoneSafe-EX

A None-safe wrapper for Python containers, preventing `KeyError`, `IndexError`, and `AttributeError` when accessing missing or None values.

** Warn: `NoneLike` is not `None`. Please do not use `NoneLike is None` to check if a value is None. `== None` or `is NoneLike` is recommended.**

## Features

- **NoneSafeDict**: None-safe dictionary - returns `NoneLike` instead of raising `KeyError`
- **NoneSafeList**: None-safe list - returns `NoneLike` for out-of-bounds access
- **NoneLike**: Sentinel object that supports arbitrary chained access without raising exceptions

## Installation

```bash
pip install nonesafe_ex
```

## Usage

### NoneSafeDict

```python
from nonesafe_ex import NoneSafeDict

data = NoneSafeDict({"name": "Alice", "info": {"age": 25}})

# Access existing key
print(data["name"])  # "Alice"

# Access non-existing key - returns NoneLike instead of KeyError
print(data["non_existing"])  # None
print(bool(data["non_existing"]))  # False

# Nested access with automatic wrapping
print(data["info"]["age"])  # 25
print(data["info"]["non_existing"])  # None (NoneLike)

# Safe chained access
result = data["info"]["address"]["city"]
print(result)  # None
```

### NoneSafeList

```python
from nonesafe import NoneSafeList

items = NoneSafeList([1, 2, {"value": 3}])

# Access existing index
print(items[0])  # 1

# Out-of-bounds access - returns NoneLike instead of IndexError
print(items[100])  # None
print(items[-100])  # None

# Nested access with automatic wrapping
print(items[2]["value"])  # 3
print(items[2]["non_existing"])  # None

# Iteration with automatic wrapping
for item in items:
    print(item)
```

### NoneLike

```python
from nonesafe import NoneLike

# Supports arbitrary chained access
result = NoneLike.foo.bar.baz()["key"]
print(result)  # None
print(bool(result))  # False

# Safe for any operation
print(NoneLike + 1)  # None
print(NoneLike * 2)  # None
print(len(NoneLike))  # 0
```

## API

### NoneSafeDict

- `__getitem__(key)` - Returns wrapped value or `NoneLike` if key not found
- `get(key, default=None)` - Returns wrapped value or wrapped default
- `values()` - Returns wrapped values view
- `items()` - Returns items view with wrapped values
- `pop(key, default=None)` - Removes and returns wrapped value
- `copy()` - Returns new NoneSafeDict

### NoneSafeList

- `__getitem__(index)` - Returns wrapped value or `NoneLike` if out of bounds
- `pop(index=-1)` - Removes and returns wrapped value
- `copy()` - Returns new NoneSafeList
- `first(default=None)` - Returns first item or wrapped default
- `last(default=None)` - Returns last item or wrapped default

### NoneLike

- Supports chained attribute access: `NoneLike.attr.subattr`
- Supports chained item access: `NoneLike["key"]["subkey"]`
- Supports function calls: `NoneLike.method()`
- Boolean value is `False`
- String representation is `"None"`

## License

MIT

