# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview MyUtils is a Python utility library providing dynamic object manipulation capabilities. The library focuses on three core utilities: Observable (attribute change tracking), Expando (dynamic dictionary wrapper), and Dummy (null-safe mock object). ## Development Workflow & Collaboration Guidelines ### Developer Profile You are an experienced Python developer producing Python 3.12 code. ### Development Process - All proposed code must be testable - **Before writing any code**, explain the possible implementation options - Wait for mutual validation and understanding of requirements before producing code ### Collaboration Style - Ask clarifying questions to refine understanding or suggest alternative approaches - **Ask questions one at a time** - wait for complete understanding of the answer before asking the next - When you have multiple questions, indicate progress (e.g., "Question 1/5") ### Communication - Conversations can be in French or English - **All code, documentation, and comments must be exclusively in English** ### Code Standards - Follow PEP 8 conventions for Python code style - Use Google or NumPy format for docstrings - Variable and function names must be explicit and in snake_case - Never use emojis in code ### Dependency Management - List all external dependencies required for installation - When possible, propose alternatives using only Python standard library ### Unit Testing - Use pytest library for unit tests - **Before writing tests**, list the tests you plan to implement with explanations of why they're important - Wait for validation before implementing tests - Unless explicitly needed (e.g., inheritance), write tests as functions, not classes - Test function naming patterns: - `test_i_can_xxx` for tests that should pass - `test_i_cannot_xxx` for edge cases that should raise errors/exceptions ### File Management - When adding or modifying a file, always provide the complete file path ### Error Handling - When presented with a code execution error: 1. First provide a clear explanation of the problem 2. **Do not** immediately propose a new version 3. Wait for validation of the bug analysis before proposing solutions ## Development Commands ### Testing ```bash # Run all tests pytest # Run specific test file pytest tests/test_observable.py pytest tests/test_expando.py pytest tests/test_dummy.py # Run with verbose output pytest -v # Run specific test pytest tests/test_observable.py::test_i_can_make_an_object_observable ``` ### Package Management ```bash # Install development dependencies pip install -r requirements.txt # Install package in development mode pip install -e . # Install with development extras pip install -e ".[dev]" ``` ## Architecture ### Observable Pattern Implementation The Observable module uses **dynamic class substitution** to add observability to any Python object: 1. `make_observable(obj)` replaces the object's class with a dynamically created subclass 2. The subclass overrides `__setattr__` to intercept attribute changes 3. Listeners are stored in a `_listeners` dict on the object instance 4. Two types of callbacks exist: - **Attribute callbacks**: Triggered on specific attribute changes via `bind()` - **Event listeners**: Triggered via `add_event_listener()` for events like `AFTER_PROPERTY_CHANGE` #### Event System - `AFTER_PROPERTY_CHANGE` event fires after all attribute callbacks execute - Event listeners receive: `(attr_name, old_value, new_value, callback_results)` - Empty string `""` as attr_name registers listener for ALL attributes - Return values from attribute callbacks are collected and passed to event listeners ### Expando Pattern Expando wraps dictionaries to enable dot-notation access: - Uses `__getattr__` to intercept attribute access and delegate to internal dict - Nested dicts are automatically wrapped in Expando instances - `get(path)` method supports path notation (`"a.b.c"`) and list traversal - When path encounters a list, it collects values from all list items ### ProxyObject ProxyObject (currently in development) maps object attributes to a different structure using a mappings dictionary. Note: Implementation appears incomplete - `_create_props()` is defined but never called. ## Testing Conventions - Test files use pytest fixtures (`data`, `observable_data`) - Test names follow pattern: `test_i_can_` or `test_` - Tests are organized by feature with separator comments - Each test is self-contained with its own callbacks/data ## Package Structure ``` src/myutils/ # Main package code ├── __init__.py # Package exports (currently empty) ├── observable.py # Observable implementation ├── Expando.py # Expando wrapper ├── Dummy.py # Dummy mock object └── ProxyObject.py # ProxyObject (incomplete) tests/ # Test suite ├── test_observable.py ├── test_expando.py └── test_dummy.py ``` ## Important Patterns ### Observable Callback Semantics - Callbacks must be the **same object** to unbind (not just equivalent lambdas) - `unbind()` and `unbind_all()` fail silently if callback/attribute doesn't exist - Multiple instances have independent listeners - `make_observable()` is idempotent (safe to call multiple times) ### Naming Convention - Use English for persona names (per global CLAUDE.md) - Class names use PascalCase - Functions use snake_case - Private attributes prefixed with `_`