Metadata-Version: 2.4
Name: dotc
Version: 0.7.0
Summary: DOTC (like Yahtzee) - Access Nested Dicts and Lists via Dots
Author-email: Mike Steele <mike@mikesteele.us>
License: MIT License
        
        Copyright (c) 2023 soulrx
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/soulrx/dotc
Keywords: nested,dot,dict,list,datastructure,dotspace,dotdict,dotlist,xml,dotc
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: bumpver; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: wheel; extra == "dev"
Requires-Dist: setuptools; extra == "dev"
Requires-Dist: setuptools-rust>=1.11.0; extra == "dev"
Provides-Extra: rust
Requires-Dist: setuptools-rust>=1.11.0; extra == "rust"
Dynamic: license-file

# DOTC 🎯

**Access nested Python data structures using simple dot notation**

Transform complex nested dictionaries and lists into easily navigable objects with dot notation access.

## Installation and Backends

```bash
python -m pip install dotc
```

`dotc` has two compatible implementations:

- **Rust-backed `Dotc`** uses the optional PyO3 extension for XML parsing,
  traversal, and key discovery.
- **Pure-Python `DotcPy`** has no Rust runtime or compiler requirement.

The normal import selects the best available backend automatically:

```python
from dotc import Dotc
```

If the native extension was installed successfully, `Dotc` is Rust-backed. If
Rust is unavailable during a source installation, native compilation fails, or
the extension cannot be imported, `Dotc` falls back to the pure-Python class.
Application code does not need to change.

### Check the Active Backend

```python
import dotc

print(dotc.BACKEND)  # "rust" or "python"
```

`dotc.BACKEND` describes the automatically selected `Dotc` export. `DotcPy`
is always pure Python.

### Explicitly Select Pure Python

Use the explicit class when a particular call site must never use the native
extension:

```python
from dotc import DotcPy

d = DotcPy({'user': {'name': 'Alice'}})
```

It can also be aliased when existing code expects the name `Dotc`:

```python
from dotc import DotcPy as Dotc
```

To force the automatic `Dotc` export to use Python for an entire process, set
the environment variable before Python starts or before `dotc` is imported:

```bash
DOTC_FORCE_PYTHON=1 python app.py
```

Accepted true values are `1`, `true`, and `yes`, ignoring case.

### Installing Rust Support

Prebuilt native wheels can use the Rust backend without installing a Rust
compiler. When installing from source, `dotc` attempts to build the extension
if a Rust toolchain is available and otherwise installs the Python fallback.

After a fallback installation, macOS and Linux users can opt into Rust with
the included helper:

```bash
dotc-install-rust
python -m pip install --force-reinstall --no-cache-dir dotc
```

The helper asks for confirmation, downloads the official rustup installer, and
installs its minimal profile. For a non-interactive installation:

```bash
dotc-install-rust --yes
```

You may instead install Rust directly from [rustup.rs](https://rustup.rs). On
Windows, use the installer documented there. Restart the shell after installing
Rust, then reinstall `dotc` so pip has an opportunity to build the extension.

## Quick Start

```python
from dotc import Dotc

# Your nested data
data = {
    'user': {
        'name': 'Alice',
        'scores': [85, 92, 78]
    }
}

# Convert to a dot-accessible object
d = Dotc(data)

print(d.user.name)      # Alice
print(d.user.scores._0) # 85
```

## ✨ Core Features

### 🔍 Simple Dot Access
Access nested data structures naturally:

```python
data = {'a': 1, 'b': {'c': 3, 'd': [4, 5, 6]}}
d = Dotc(data)

d.a           # Returns: 1
d.b.c         # Returns: 3
d.b.d._0      # Returns: 4 (list access with _index)
```

### 🚀 Instant Results with Spawn
Get both the object and a specific value in one call during instantiation:

```python
# Get object and value simultaneously
staff_data = {
    'staff': {
        'coders': ['mike', 'jeremie', 'trey', 'donnie']
    }
}

# During instantiation with _pathget
dc, result = Dotc(staff_data, _pathget='staff.coders.0')
print(result)  # mike

# You can also use different paths
dc2, result2 = Dotc(staff_data, _pathget='staff.coders.1')
print(result2)  # jeremie

dc3, result3 = Dotc(staff_data, _pathget='staff.coders.2')
print(result3)  # trey
```

### 🎯 Programmatic Path Access with `__call__`
Use the call method to get values by path after instantiation:

```python
staff_data = {
    'staff': {
        'coders': ['mike', 'jeremie', 'trey', 'donnie']
    }
}

# Create with initial path extraction
dc, result = Dotc(staff_data, _pathget='staff.coders.0')
print(result)  # mike

# Then use __call__ for additional path queries
result2 = dc('staff.coders.1')  # jeremie
result3 = dc('staff.coders.3')  # donnie
```

### 🛡️ Safe Access with Defaults
Never worry about missing keys:

```python
d = Dotc({'a': {'b': 1}})

d.a.b         # Returns: 1
d.a.missing   # Returns: None (default)
d.x.y.z       # Returns: None (safe traversal)
```

### 🔄 Full Data Resolution
Get the complete resolved data structure:

```python
d = Dotc({'a': 1, 'b': {'c': [2, 3]}})

d._           # Returns: {'a': 1, 'b': {'c': [2, 3]}}
d.b._         # Returns: {'c': [2, 3]}
d.b.c._       # Returns: [2, 3]
```

## 📖 Tutorial

### Basic Usage

```python
from dotc import Dotc

# Simple dictionary
data = {'name': 'John', 'age': 30}
d = Dotc(data)
print(d.name)  # John
print(d.age)   # 30
```

### Nested Dictionaries

```python
data = {
    'person': {
        'details': {
            'name': 'Jane',
            'location': 'NYC'
        }
    }
}

d = Dotc(data)
print(d.person.details.name)      # Jane
print(d.person.details.location)  # NYC
```

### Working with Lists

```python
data = {
    'fruits': ['apple', 'banana', 'cherry'],
    'numbers': [1, 2, 3, 4, 5]
}

d = Dotc(data)
print(d.fruits._0)    # apple
print(d.fruits._1)    # banana
print(d.numbers._4)   # 5
```

### Mixed Nested Structures

```python
data = {
    'users': [
        {'name': 'Alice', 'scores': [95, 87]},
        {'name': 'Bob', 'scores': [78, 92]}
    ]
}

d = Dotc(data)
print(d.users._0.name)        # Alice
print(d.users._0.scores._0)   # 95
print(d.users._1.scores._1)   # 92
```

### XML

Both backends provide `from_xml()` and produce the same nested Python data.
It accepts a filesystem path, a path-like object, an XML string, or XML bytes:

```python
from pathlib import Path
from dotc import Dotc

from_file = Dotc.from_xml("document.xml")
from_path = Dotc.from_xml(Path("document.xml"))
from_text = Dotc.from_xml("<catalog><item id='1'>Book</item></catalog>")
from_bytes = Dotc.from_xml(b"<catalog><item>Book</item></catalog>")

print(from_text.catalog.item._)  # {'id': '1', '_text': 'Book'}
```

When `Dotc` is Rust-backed, XML parsing and recursive conversion are performed
in Rust. `DotcPy.from_xml()` uses Python's `xml.etree.ElementTree`. In the
repository benchmark using two fixtures totaling 97,821 bytes, the Rust path
was approximately 3.25x faster (0.585 ms versus 1.904 ms). Results will vary by
document shape, size, Python version, and machine.

Repeated sibling elements become lists, attributes become dictionary keys, and
text stored alongside attributes or children is available under `_text`.

## 🎯 Advanced Features

### Inspection and Debugging

```python
d = Dotc({'a': 1, 'b': {'c': [1, 2, 3]}})

# Basic inspection
d._show()

# Verbose inspection
d._show(v=1)

# Inspect specific parts
d._show(d.b, v=1)
```

### Using DataPath for Programmatic Access

For cases where you need programmatic path traversal:

```python
from dotc import DataPath

data = {'a': {'b': [1, 2, 3]}}
d = Dotc(data)

dp = DataPath()
result = dp.get('a.b.0', d)  # Returns: 1

# Works with regular Python objects too
result = dp.get('a.b.0', data)  # Returns: 1
```

## 🔧 Configuration Options

```python
d = Dotc(
    data={'a': 1},
    node='custom_name',      # Custom node name
    default='N/A',           # Custom default value
    _strict=1,              # Raise errors for missing keys
    _debug=1                # Enable debug output
)
```

## 🎪 Use Cases

### Configuration Management
```python
config = {
    'database': {'host': 'localhost', 'port': 5432},
    'api': {'timeout': 30, 'retries': 3}
}

cfg = Dotc(config)
db_host = cfg.database.host      # localhost
api_timeout = cfg.api.timeout    # 30
```

### JSON API Response Handling
```python
api_response = {
    'data': {
        'user': {'id': 123, 'profile': {'email': 'user@example.com'}}
    }
}

resp = Dotc(api_response)
email = resp.data.user.profile.email  # user@example.com
```

### Data Processing Pipelines
```python
# Get object and extract value in one step
staff_data = {
    'staff': {
        'coders': ['mike', 'jeremie', 'trey', 'donnie'],
        'metrics': {'team_size': 4, 'experience': 'senior'}
    }
}

# Extract initial value during instantiation
processor, first_coder = Dotc(staff_data, _pathget='staff.coders.0')
print(f"Lead developer: {first_coder}")  # Lead developer: mike

# Use the same object for additional queries
team_size = processor('staff.metrics.team_size')
print(f"Team size: {team_size}")  # Team size: 4
```

## 📚 API Reference

### Classes

- `Dotc(data, node=None, default=None, _pathget=None, **kwargs)` creates an
  automatically selected Rust-backed or fallback object.
- `DotcPy(data, node=None, default=None, _pathget=None, **kwargs)` always uses
  the pure-Python implementation.
- When `_pathget` is provided, construction returns an `(instance, result)`
  tuple.

### Class Methods

- `Dotc.from_xml(source, **kwargs)` parses XML using the selected backend.
- `DotcPy.from_xml(source, **kwargs)` always parses XML with ElementTree.

### Instance Methods

- `obj(path)` - Get value at path programmatically
- `obj._` - Get fully resolved data structure
- `obj._show(verbosity=0)` - Inspect object structure

### Utility Classes

- `DataPath.get(path, obj, default)` - Static path traversal

---

**DOTC** - Making nested data navigation as easy as ABC! 🎯
