mutablekeysdict 0.1.1
Pure Python mutable keys dictionary class.
License: MIT
Requires Python: >=3.8

# MutableKeysDict

[![Tests](https://github.com/eddiethedean/mutablekeysdict/actions/workflows/test.yml/badge.svg)](https://github.com/eddiethedean/mutablekeysdict/actions/workflows/test.yml)
[![PyPI version](https://badge.fury.io/py/mutablekeysdict.svg)](https://badge.fury.io/py/mutablekeysdict)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A pure Python dictionary class that allows the keys to be mutated after insertion.

## Why MutableKeysDict?

Standard Python dictionaries use the hash of a key at insertion time. If you mutate a hashable object after using it as a key, the dictionary can't find it anymore because the hash has changed. `MutableKeysDict` solves this problem by tracking key mutations and maintaining access to your values even after the keys change.

**Perfect for:**
- Using mutable hashable objects as dictionary keys
- Tracking objects that change over time
- Building data structures where key identity matters more than key value

**Note:** Keys must still be hashable (implement `__hash__`). If you need truly unhashable keys, check out [dictanykey](https://github.com/eddiethedean/dictanykey).

## Installation

```bash
pip install mutablekeysdict
```

**Requirements:** Python 3.8+

## Quick Start

```python
from mutablekeysdict import MutableKeysDict

# Create a hashable list class
class HashableList(list):
    def __hash__(self):
        return hash(tuple(self))

# Use it as a dictionary key
h = HashableList([1, 2, 3])
d = MutableKeysDict({h: 6})

# Mutate the key
h.append(4)

# Still works! 🎉
print(d[h])  # Output: 6

# Standard dict would raise KeyError here
standard_dict = {h: 6}
h.append(5)
# standard_dict[h]  # KeyError!
```

## Features

- ✅ **Full dict API compatibility** - Drop-in replacement for standard dictionaries
- ✅ **Mutable key support** - Access values even after keys are mutated
- ✅ **Type hints** - Full mypy compatibility
- ✅ **Thoroughly tested** - 95%+ code coverage with 52+ tests
- ✅ **Modern Python** - Supports Python 3.8+
- ✅ **Pure Python** - No dependencies
- ✅ **Dict union operators** - Supports `|` and `|=` operators (Python 3.9+)

## Usage Examples

### Basic Operations

```python
from mutablekeysdict import MutableKeysDict

# Create from dict
d = MutableKeysDict({'a': 1, 'b': 2})

# All standard dict operations work
d['c'] = 3
print(len(d))  # 3
print('a' in d)  # True
print(list(d.keys()))  # ['a', 'b', 'c']

# Update with another dict
d.update({'d': 4})

# Use dict union operators (Python 3.9+)
d2 = d | {'e': 5}
```

### Working with Mutable Keys

```python
class MutableKey:
    def __init__(self, value):
        self.value = value
        self.metadata = []
    
    def __hash__(self):
        return hash(self.value)
    
    def __eq__(self, other):
        return self.value == other.value

# Create dictionary with mutable keys
k1 = MutableKey("key1")
k2 = MutableKey("key2")
d = MutableKeysDict({k1: "value1", k2: "value2"})

# Mutate the keys
k1.metadata.append("modified")
k2.metadata.append("also modified")

# Dictionary still works!
print(d[k1])  # "value1"
print(d[k2])  # "value2"

# Can iterate over mutated keys
for key in d.keys():
    print(f"{key.value}: {key.metadata}")
```

### Copy and Create Methods

```python
# Create from keys
d = MutableKeysDict.fromkeys(['a', 'b', 'c'], 0)
# {'a': 0, 'b': 0, 'c': 0}

# Shallow copy
d2 = d.copy()

# Replace key
d.replace_key('a', 'new_a')
```

## How It Works

`MutableKeysDict` maintains an internal mapping between keys and stable indices. When you access a key:

1. It checks if any keys have been mutated (hash changed)
2. If mutations are detected, it rebuilds the internal mapping
3. Returns the value associated with the stable index

This allows the dictionary to track the same key object even when its hash changes, as long as you use the same object reference.

## API Reference

`MutableKeysDict` implements the full `collections.abc.MutableMapping` interface. All standard dictionary methods are supported:

**Standard Methods:**
- `__getitem__`, `__setitem__`, `__delitem__`
- `keys()`, `values()`, `items()`
- `get()`, `pop()`, `popitem()`
- `update()`, `setdefault()`, `clear()`
- `copy()`, `fromkeys()` (class method)

**Additional Methods:**
- `replace_key(old_key, new_key)` - Replace a key with a new one while preserving the value

**Operators:**
- `==`, `!=` - Equality comparison
- `|`, `|=` - Dict union operators (Python 3.9+)
- `in` - Membership testing
- `len()` - Get size
- `iter()` - Iterate over keys

## Version History

### v0.1.0 (2025-10-22)
- 🐛 Fixed critical bugs in `__setitem__`, `popitem()`, `get()`, and `update()`
- ✨ Added missing dict methods: `copy()`, `fromkeys()`, `__or__()`, `__ior__()`
- 🎯 Full mypy type checking support
- ✅ Comprehensive test suite with 95%+ coverage
- 📦 Modernized packaging with pyproject.toml
- 🔧 Python 3.8+ support

## Contributing

Contributions are welcome! Here's how to set up your development environment:

### Setting Up Development Environment

1. Clone the repository:
```bash
git clone https://github.com/eddiethedean/mutablekeysdict.git
cd mutablekeysdict
```

2. Install development dependencies:
```bash
pip install -e ".[dev]"
```

This installs the package in editable mode along with:
- `pytest` - Testing framework
- `pytest-cov` - Coverage reporting
- `mypy` - Type checking
- `ruff` - Fast linting
- `black` - Code formatting

### Running Tests

Run the full test suite:
```bash
pytest tests/
```

With coverage report:
```bash
pytest tests/ --cov=mutablekeysdict --cov-report=term-missing
```

Run specific test:
```bash
pytest tests/test_mutablekeysdict.py::TestMutableKeysDictBasics::test_empty_init
```

### Code Quality

**Linting:**
```bash
ruff check mutablekeysdict tests
```

Auto-fix issues:
```bash
ruff check --fix mutablekeysdict tests
```

**Formatting:**
```bash
black mutablekeysdict tests
```

**Type checking:**
```bash
mypy mutablekeysdict
```

**Run all checks:**
```bash
ruff check mutablekeysdict tests && \
black --check mutablekeysdict tests && \
mypy mutablekeysdict && \
pytest tests/
```

### Project Structure

```
mutablekeysdict/
├── mutablekeysdict/
│   ├── __init__.py          # Package initialization
│   └── mutablekeysdict.py   # Main implementation
├── tests/
│   └── test_mutablekeysdict.py  # Test suite
├── pyproject.toml           # Project configuration
├── README.md                # This file
└── CHANGELOG.md             # Version history
```

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Author

**Odos Matthews**
- Email: odosmatthews@gmail.com
- GitHub: [@eddiethedean](https://github.com/eddiethedean)

## Links

- [PyPI Package](https://pypi.org/project/mutablekeysdict/)
- [GitHub Repository](https://github.com/eddiethedean/mutablekeysdict)
- [Issue Tracker](https://github.com/eddiethedean/mutablekeysdict/issues)
- [Changelog](CHANGELOG.md)