grimoire-model 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. grimoire_model-0.2.0/LICENSE +21 -0
  2. grimoire_model-0.2.0/PKG-INFO +647 -0
  3. grimoire_model-0.2.0/README.md +600 -0
  4. grimoire_model-0.2.0/pyproject.toml +130 -0
  5. grimoire_model-0.2.0/setup.cfg +4 -0
  6. grimoire_model-0.2.0/src/grimoire_model/__init__.py +260 -0
  7. grimoire_model-0.2.0/src/grimoire_model/core/__init__.py +1 -0
  8. grimoire_model-0.2.0/src/grimoire_model/core/exceptions.py +287 -0
  9. grimoire_model-0.2.0/src/grimoire_model/core/model.py +473 -0
  10. grimoire_model-0.2.0/src/grimoire_model/core/registry.py +380 -0
  11. grimoire_model-0.2.0/src/grimoire_model/core/schema.py +367 -0
  12. grimoire_model-0.2.0/src/grimoire_model/logging.py +39 -0
  13. grimoire_model-0.2.0/src/grimoire_model/resolvers/__init__.py +1 -0
  14. grimoire_model-0.2.0/src/grimoire_model/resolvers/derived.py +528 -0
  15. grimoire_model-0.2.0/src/grimoire_model/resolvers/template.py +402 -0
  16. grimoire_model-0.2.0/src/grimoire_model/utils/__init__.py +1 -0
  17. grimoire_model-0.2.0/src/grimoire_model/utils/inheritance.py +446 -0
  18. grimoire_model-0.2.0/src/grimoire_model/utils/paths.py +332 -0
  19. grimoire_model-0.2.0/src/grimoire_model/validation/__init__.py +1 -0
  20. grimoire_model-0.2.0/src/grimoire_model/validation/validators.py +440 -0
  21. grimoire_model-0.2.0/src/grimoire_model.egg-info/PKG-INFO +647 -0
  22. grimoire_model-0.2.0/src/grimoire_model.egg-info/SOURCES.txt +32 -0
  23. grimoire_model-0.2.0/src/grimoire_model.egg-info/dependency_links.txt +1 -0
  24. grimoire_model-0.2.0/src/grimoire_model.egg-info/requires.txt +24 -0
  25. grimoire_model-0.2.0/src/grimoire_model.egg-info/top_level.txt +1 -0
  26. grimoire_model-0.2.0/tests/test_derived_resolver.py +420 -0
  27. grimoire_model-0.2.0/tests/test_exceptions.py +169 -0
  28. grimoire_model-0.2.0/tests/test_logging.py +732 -0
  29. grimoire_model-0.2.0/tests/test_model.py +1183 -0
  30. grimoire_model-0.2.0/tests/test_registry.py +456 -0
  31. grimoire_model-0.2.0/tests/test_schema.py +282 -0
  32. grimoire_model-0.2.0/tests/test_template_resolver.py +254 -0
  33. grimoire_model-0.2.0/tests/test_utils.py +553 -0
  34. grimoire_model-0.2.0/tests/test_validators.py +492 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 The Wyrd One
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,647 @@
1
+ Metadata-Version: 2.4
2
+ Name: grimoire-model
3
+ Version: 0.2.0
4
+ Summary: Dict-like model system with schema validation, derived fields, and inheritance for Grimoire
5
+ Author-email: The Wyrd One <wyrdbound@proton.me>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wyrdbound/grimoire-model
8
+ Project-URL: Repository, https://github.com/wyrdbound/grimoire-model
9
+ Project-URL: Issues, https://github.com/wyrdbound/grimoire-model/issues
10
+ Keywords: gaming,rpg,tabletop,model,validation,schema
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Games/Entertainment :: Role-Playing
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: pyrsistent>=0.19.0
27
+ Requires-Dist: jinja2>=3.1.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: grimoire-logging>=0.1.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
34
+ Requires-Dist: hypothesis>=6.0.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.0.290; extra == "dev"
37
+ Provides-Extra: docs
38
+ Requires-Dist: sphinx>=4.0.0; extra == "docs"
39
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
40
+ Requires-Dist: sphinxcontrib-napoleon>=0.7; extra == "docs"
41
+ Provides-Extra: test
42
+ Requires-Dist: pytest>=7.0.0; extra == "test"
43
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
44
+ Requires-Dist: pytest-mock>=3.0.0; extra == "test"
45
+ Requires-Dist: hypothesis>=6.0.0; extra == "test"
46
+ Dynamic: license-file
47
+
48
+ # Grimoire Model
49
+
50
+ [![Tests](https://github.com/wyrdbound/grimoire-model/workflows/Tests/badge.svg)](https://github.com/wyrdbound/grimoire-model/actions)
51
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
52
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
53
+ [![Coverage](https://img.shields.io/badge/coverage-88%25-green.svg)](htmlcov/index.html)
54
+
55
+ **Dict-like model system with schema validation, derived fields, and inheritance for the Grimoire tabletop RPG engine.**
56
+
57
+ Grimoire Model provides a sophisticated, schema-driven model system that combines the familiar dict-like interface with powerful features like automatic field derivation, template-based expressions, model inheritance, and comprehensive validation. Designed to integrate seamlessly with `grimoire-context` for complete game state management.
58
+
59
+ ## ✨ Features
60
+
61
+ - **📚 Dict-like Interface**: Familiar Python dictionary operations with schema validation
62
+ - **🔄 Reactive Derived Fields**: Automatic computation with dependency tracking and batch updates
63
+ - **🧬 Model Inheritance**: Multiple inheritance support with automatic namespace-based resolution
64
+ - **📝 Template Expressions**: Jinja2-powered field templates for dynamic content
65
+ - **🏷️ Namespace Organization**: Global model registry with namespace-based organization
66
+ - **🛡️ Schema Validation**: Pydantic-based type checking and custom validation rules
67
+ - **🔧 Dependency Injection**: Pluggable resolvers for extensibility
68
+ - **⚡ Performance Optimized**: Efficient batch updates and lazy evaluation
69
+ - **🎯 grimoire-context Integration**: Seamless interoperability with context management
70
+
71
+ ## 🚀 Quick Start
72
+
73
+ ### Installation
74
+
75
+ ```bash
76
+ pip install grimoire-model
77
+ ```
78
+
79
+ ### Basic Usage
80
+
81
+ ```python
82
+ from grimoire_model import ModelDefinition, AttributeDefinition, create_model
83
+
84
+ # Define a character model schema
85
+ character_def = ModelDefinition(
86
+ id="character",
87
+ name="Player Character",
88
+ namespace="rpg", # Organize models in namespaces
89
+ attributes={
90
+ "name": AttributeDefinition(type="str", required=True),
91
+ "level": AttributeDefinition(type="int", default=1),
92
+ "hp": AttributeDefinition(type="int", default=100),
93
+ "mp": AttributeDefinition(type="int", default=50),
94
+
95
+ # Derived fields automatically update when dependencies change
96
+ "max_hp": AttributeDefinition(
97
+ type="int",
98
+ derived="{{ level * 8 + hp }}"
99
+ ),
100
+ "character_summary": AttributeDefinition(
101
+ type="str",
102
+ derived="Level {{ level }} {{ name }} ({{ max_hp }} HP, {{ mp }} MP)"
103
+ )
104
+ }
105
+ )
106
+
107
+ # Create a character instance
108
+ character = create_model(character_def, {
109
+ "name": "Aragorn",
110
+ "level": 15,
111
+ "hp": 120,
112
+ "mp": 80
113
+ })
114
+
115
+ # Dict-like interface with automatic derived field updates
116
+ print(character['name']) # "Aragorn"
117
+ print(character['max_hp']) # 240 (15 * 8 + 120)
118
+ print(character['character_summary']) # "Level 15 Aragorn (240 HP, 80 MP)"
119
+
120
+ # Updates trigger automatic recalculation
121
+ character['level'] = 20
122
+ print(character['max_hp']) # 280 (20 * 8 + 120, automatically updated)
123
+ ```
124
+
125
+ ### Global Model Registry
126
+
127
+ Models are automatically registered in a global registry using namespaces:
128
+
129
+ ```python
130
+ from grimoire_model import get_model
131
+
132
+ # Models auto-register when created
133
+ character_def = ModelDefinition(
134
+ id="character",
135
+ namespace="rpg", # Registered in "rpg" namespace
136
+ # ... attributes ...
137
+ )
138
+
139
+ # Retrieve from anywhere in your application
140
+ retrieved_def = get_model("rpg", "character")
141
+ new_character = create_model(retrieved_def, {"name": "Hero"})
142
+
143
+ # Perfect for inheritance - child models automatically find parents
144
+ base_def = ModelDefinition(id="base", namespace="rpg", ...)
145
+ child_def = ModelDefinition(id="child", namespace="rpg", extends=["base"], ...)
146
+ # No manual registry needed - inheritance resolves automatically!
147
+ ```
148
+
149
+ ### Model Inheritance with Namespaces
150
+
151
+ ```python
152
+ from grimoire_model import get_model, clear_registry
153
+
154
+ # Base entity definition (auto-registered in namespace)
155
+ base_entity_def = ModelDefinition(
156
+ id="base_entity",
157
+ name="Base Entity",
158
+ namespace="game", # Registered in "game" namespace
159
+ attributes={
160
+ "id": AttributeDefinition(type="str", required=True),
161
+ "name": AttributeDefinition(type="str", required=True),
162
+ "description": AttributeDefinition(type="str", default="")
163
+ }
164
+ )
165
+
166
+ # Character extends base entity (automatic inheritance resolution)
167
+ character_def = ModelDefinition(
168
+ id="character",
169
+ name="Character",
170
+ namespace="game", # Same namespace enables automatic inheritance
171
+ extends=["base_entity"], # Automatically finds base_entity in namespace
172
+ attributes={
173
+ "level": AttributeDefinition(type="int", default=1),
174
+ "hp": AttributeDefinition(type="int", default=100),
175
+ "max_hp": AttributeDefinition(
176
+ type="int",
177
+ derived="{{ level * 8 + hp }}"
178
+ )
179
+ }
180
+ )
181
+
182
+ # Create character with inherited fields (no registry needed!)
183
+ character = create_model(
184
+ character_def,
185
+ {
186
+ "id": "char_001", # From base_entity
187
+ "name": "Legolas", # From base_entity
188
+ "description": "Elf archer", # From base_entity
189
+ "level": 12, # From character
190
+ "hp": 96 # From character
191
+ }
192
+ )
193
+
194
+ print(character['id']) # "char_001" (inherited)
195
+ print(character['name']) # "Legolas" (inherited)
196
+ print(character['max_hp']) # 192 (derived field)
197
+
198
+ # Retrieve models from global registry
199
+ retrieved_char_def = get_model("game", "character")
200
+ another_character = create_model(retrieved_char_def, {
201
+ "id": "char_002",
202
+ "name": "Gimli"
203
+ })
204
+ ```
205
+
206
+ ### Integration with grimoire-context
207
+
208
+ ```python
209
+ from grimoire_context import GrimoireContext
210
+
211
+ # Create context with character model
212
+ context = GrimoireContext({
213
+ 'party': {
214
+ 'leader': character,
215
+ 'members': 4
216
+ }
217
+ })
218
+
219
+ # Modify character through context - derived fields update automatically
220
+ context = context.set_variable('party.leader.level', 25)
221
+ updated_character = context.get_variable('party.leader')
222
+
223
+ print(updated_character['level']) # 25
224
+ print(updated_character['max_hp']) # 296 (automatically recalculated)
225
+ ```
226
+
227
+ ### Batch Updates for Performance
228
+
229
+ ```python
230
+ # Batch multiple changes for better performance
231
+ character.batch_update({
232
+ 'level': 30,
233
+ 'hp': 150,
234
+ 'mp': 120
235
+ })
236
+
237
+ # All derived fields updated once after batch completion
238
+ print(character['max_hp']) # 390 (30 * 8 + 150)
239
+ ```
240
+
241
+ ## 📚 Documentation
242
+
243
+ - **[Logging Configuration](LOGGING.md)** - Configure library logging output and integration
244
+
245
+ ## 📚 Core Concepts
246
+
247
+ ### Model Definitions
248
+
249
+ Model definitions are schemas that describe the structure, types, and behavior of your data:
250
+
251
+ ```python
252
+ model_def = ModelDefinition(
253
+ id="weapon",
254
+ name="Weapon",
255
+ namespace="combat", # Organize in combat namespace
256
+ description="Combat weapon with damage calculations",
257
+ attributes={
258
+ "name": AttributeDefinition(type="str", required=True),
259
+ "base_damage": AttributeDefinition(type="int", default=1, range="1..50"),
260
+ "enhancement": AttributeDefinition(type="int", default=0, range="0..10"),
261
+
262
+ # Derived field with complex logic
263
+ "total_damage": AttributeDefinition(
264
+ type="int",
265
+ derived="{{ base_damage + enhancement * 2 }}"
266
+ ),
267
+ "damage_category": AttributeDefinition(
268
+ type="str",
269
+ derived="{% if total_damage >= 20 %}High{% elif total_damage >= 10 %}Medium{% else %}Low{% endif %}"
270
+ )
271
+ },
272
+ validations=[
273
+ ValidationRule(
274
+ expression="base_damage > 0",
275
+ message="Base damage must be positive"
276
+ )
277
+ ]
278
+ )
279
+ ```
280
+
281
+ ### Template Expressions
282
+
283
+ Use Jinja2 templates for powerful derived field logic:
284
+
285
+ ```python
286
+ # Simple expression
287
+ "max_hp": "{{ level * 8 + constitution * 2 }}"
288
+
289
+ # Conditional logic
290
+ "damage_bonus": "{% if strength >= 15 %}{{ (strength - 10) // 2 }}{% else %}0{% endif %}"
291
+
292
+ # Complex calculations
293
+ "skill_modifier": "{{ (skill_level + attribute_bonus - 10) // 2 }}"
294
+ ```
295
+
296
+ ### Validation Rules
297
+
298
+ Add custom validation logic to ensure data integrity:
299
+
300
+ ```python
301
+ ValidationRule(
302
+ expression="level >= 1 and level <= 100",
303
+ message="Character level must be between 1 and 100"
304
+ ),
305
+ ValidationRule(
306
+ expression="hp > 0 or status == 'dead'",
307
+ message="Living characters must have positive HP"
308
+ )
309
+ ```
310
+
311
+ ## 🔧 API Reference
312
+
313
+ ### Core Classes
314
+
315
+ #### ModelDefinition
316
+
317
+ ```python
318
+ ModelDefinition(
319
+ id: str, # Unique model identifier
320
+ name: str, # Human-readable name
321
+ namespace: str = "default", # Namespace for organization and inheritance
322
+ description: str = "", # Model description
323
+ attributes: Dict[str, AttributeDefinition], # Field definitions
324
+ extends: List[str] = None, # Parent model IDs (resolved in namespace)
325
+ validations: List[ValidationRule] = None # Validation rules
326
+ )
327
+ ```
328
+
329
+ #### AttributeDefinition
330
+
331
+ ```python
332
+ AttributeDefinition(
333
+ type: str, # Data type (str, int, float, bool, list, dict)
334
+ required: bool = False, # Whether field is required
335
+ default: Any = None, # Default value
336
+ derived: str = None, # Template expression for derived fields
337
+ range: str = None, # Value range constraint (e.g., "1..100")
338
+ enum: List[Any] = None, # Allowed values
339
+ pattern: str = None, # Regex pattern for strings
340
+ description: str = "" # Field description
341
+ )
342
+ ```
343
+
344
+ #### GrimoireModel
345
+
346
+ ```python
347
+ class GrimoireModel(MutableMapping):
348
+ def __init__(
349
+ self,
350
+ model_definition: ModelDefinition,
351
+ data: Dict[str, Any] = None,
352
+ template_resolver: TemplateResolver = None,
353
+ derived_field_resolver: DerivedFieldResolver = None,
354
+ **kwargs
355
+ )
356
+
357
+ # Dict-like interface
358
+ def __getitem__(self, key: str) -> Any
359
+ def __setitem__(self, key: str, value: Any) -> None
360
+ def __delitem__(self, key: str) -> None
361
+ def __iter__(self) -> Iterator[str]
362
+ def __len__(self) -> int
363
+ def keys(), values(), items()
364
+
365
+ # Batch operations
366
+ def batch_update(self, updates: Dict[str, Any]) -> None
367
+
368
+ # Path operations (dot notation)
369
+ def get(self, path: str, default: Any = None) -> Any
370
+ def set(self, path: str, value: Any) -> None
371
+ def has(self, path: str) -> bool
372
+ def delete(self, path: str) -> None
373
+ ```
374
+
375
+ ### Factory Functions
376
+
377
+ #### create_model
378
+
379
+ ```python
380
+ def create_model(
381
+ model_definition: ModelDefinition,
382
+ data: Dict[str, Any] = None,
383
+ template_resolver_type: str = "jinja2",
384
+ derived_field_resolver_type: str = "batched",
385
+ **kwargs
386
+ ) -> GrimoireModel
387
+ ```
388
+
389
+ Creates a model instance with default resolvers. Inheritance is automatically resolved from the global model registry using namespaces.
390
+
391
+ ### Global Registry Functions
392
+
393
+ ```python
394
+ from grimoire_model import register_model, get_model, clear_registry
395
+
396
+ # Register model manually (usually automatic)
397
+ register_model("my_namespace", "my_model", model_definition)
398
+
399
+ # Retrieve model from registry
400
+ model_def = get_model("my_namespace", "my_model")
401
+
402
+ # Clear all models (useful for testing)
403
+ clear_registry()
404
+
405
+ # Access registry directly for advanced operations
406
+ from grimoire_model import get_model_registry
407
+ registry = get_model_registry()
408
+ registry_dict = registry.get_registry_dict()
409
+ all_namespaces = registry.list_namespaces()
410
+ ```
411
+
412
+ ### Template Resolvers
413
+
414
+ - `Jinja2TemplateResolver`: Standard Jinja2 template syntax
415
+ - `ModelContextTemplateResolver`: Simple `$variable` substitution
416
+ - `CachingTemplateResolver`: Cached template compilation for performance
417
+
418
+ ### Derived Field Resolvers
419
+
420
+ - `BatchedDerivedFieldResolver`: Batches updates for performance
421
+ - `DerivedFieldResolver`: Immediate update resolver
422
+
423
+ ## 🧪 Development
424
+
425
+ ### Setup
426
+
427
+ ```bash
428
+ git clone https://github.com/wyrdbound/grimoire-model.git
429
+ cd grimoire-model
430
+ python -m venv .venv
431
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
432
+ pip install -e ".[dev]"
433
+ ```
434
+
435
+ ### Running Tests
436
+
437
+ ```bash
438
+ # Run all tests with coverage
439
+ /Users/justingaylor/src/grimoire-model/.venv/bin/python -m pytest --cov=grimoire_model --cov-report=term
440
+
441
+ # Run specific test file
442
+ /Users/justingaylor/src/grimoire-model/.venv/bin/python -m pytest tests/test_model.py
443
+
444
+ # Run with verbose output
445
+ /Users/justingaylor/src/grimoire-model/.venv/bin/python -m pytest -v
446
+
447
+ # Generate HTML coverage report
448
+ /Users/justingaylor/src/grimoire-model/.venv/bin/python -m pytest --cov=grimoire_model --cov-report=html
449
+ # Open htmlcov/index.html in browser
450
+ ```
451
+
452
+ _Note: Use the virtual environment in the project root as specified in the development guidelines._
453
+
454
+ ### Code Quality
455
+
456
+ ```bash
457
+ # Install development dependencies
458
+ source .venv/bin/activate && pip install ruff mypy
459
+
460
+ # Linting and formatting
461
+ source .venv/bin/activate && ruff check .
462
+ source .venv/bin/activate && ruff format .
463
+
464
+ # Type checking
465
+ source .venv/bin/activate && mypy src/grimoire_model/
466
+
467
+ # Run all quality checks
468
+ source .venv/bin/activate && ruff check . && mypy src/grimoire_model/
469
+ ```
470
+
471
+ ### Running Examples
472
+
473
+ ```bash
474
+ # Basic usage example
475
+ source .venv/bin/activate && python examples/01_basic_usage.py
476
+
477
+ # Advanced features and inheritance
478
+ source .venv/bin/activate && python examples/02_advanced_usage.py
479
+
480
+ # Inheritance and polymorphism
481
+ source .venv/bin/activate && python examples/03_inheritance_polymorphism.py
482
+
483
+ # Performance and integration testing
484
+ source .venv/bin/activate && python examples/04_performance_integration.py
485
+ ```
486
+
487
+ ## 📋 Requirements
488
+
489
+ - Python 3.8+
490
+ - pydantic >= 2.0.0
491
+ - pyrsistent >= 0.19.0
492
+ - jinja2 >= 3.1.0
493
+ - pyyaml >= 6.0
494
+
495
+ ### Development Dependencies
496
+
497
+ - pytest >= 7.0.0
498
+ - pytest-cov >= 4.0.0
499
+ - pytest-mock >= 3.0.0
500
+ - hypothesis >= 6.0.0
501
+ - mypy >= 1.0.0
502
+ - ruff >= 0.1.0
503
+
504
+ ## 🎯 Use Cases
505
+
506
+ Grimoire Model excels in scenarios requiring structured, validated data with complex relationships:
507
+
508
+ - **RPG Character Systems**: Stats, levels, equipment with derived values
509
+ - **Game Item Management**: Equipment, inventory, crafting systems
510
+ - **Rule Engine Data**: Complex game mechanics with interdependent calculations
511
+ - **Configuration Systems**: Hierarchical configs with inheritance and validation
512
+ - **Dynamic Content**: Template-based content generation with context awareness
513
+
514
+ ## 🏗️ Architecture
515
+
516
+ The package follows clean architecture principles with clear separation of concerns:
517
+
518
+ - **Core Layer**: Model definitions, schemas, and the main GrimoireModel class
519
+ - **Resolver Layer**: Pluggable template and derived field resolution systems
520
+ - **Validation Layer**: Type checking, constraints, and custom validation rules
521
+ - **Utils Layer**: Inheritance resolution, path utilities, and helper functions
522
+ - **Integration Layer**: grimoire-context compatibility and factory functions
523
+
524
+ ### Key Design Principles
525
+
526
+ 1. **Dependency Injection**: All major components can be swapped via constructor injection
527
+ 2. **Immutable Operations**: Uses pyrsistent for efficient immutable data structures
528
+ 3. **Template-Driven**: Jinja2 templates provide powerful expression capabilities
529
+ 4. **Performance-Focused**: Batch updates and lazy evaluation minimize overhead
530
+ 5. **Type Safety**: Full type hints and Pydantic integration for runtime validation
531
+ 6. **Explicit Errors**: Prefers explicit errors over fallbacks to maintain system stability
532
+
533
+ ## 📈 Performance
534
+
535
+ Current benchmarks (86% test coverage, 184 tests passing):
536
+
537
+ - **Model Creation**: ~1ms for simple models, ~5ms for complex inheritance
538
+ - **Field Updates**: ~0.1ms for direct fields, ~2ms for derived field cascades
539
+ - **Batch Updates**: 50-80% faster than individual updates for multiple fields
540
+ - **Memory Usage**: ~50KB per model instance (excluding data)
541
+ - **Template Resolution**: Cached compilation provides 10x speed improvement
542
+
543
+ ## 🔄 Integration with grimoire-context
544
+
545
+ Seamless integration is automatically enabled when both packages are installed:
546
+
547
+ ```python
548
+ from grimoire_model import create_model, ModelDefinition, AttributeDefinition
549
+ from grimoire_context import GrimoireContext
550
+
551
+ # Models work naturally in contexts
552
+ character = create_model(character_def, character_data)
553
+ context = GrimoireContext({'player': character})
554
+
555
+ # Context operations automatically handle model updates
556
+ updated_context = context.set_variable('player.level', 25)
557
+ updated_character = updated_context['player']
558
+
559
+ # Derived fields update automatically
560
+ print(updated_character['max_hp']) # Recalculated based on new level
561
+ ```
562
+
563
+ ## 🚨 Error Handling
564
+
565
+ The package provides a comprehensive exception hierarchy:
566
+
567
+ ```python
568
+ from grimoire_model import (
569
+ GrimoireModelError, # Base exception
570
+ ModelValidationError, # Validation failures
571
+ TemplateResolutionError, # Template processing errors
572
+ InheritanceError, # Model inheritance issues
573
+ DependencyError, # Derived field dependency issues
574
+ ConfigurationError # Setup and configuration errors
575
+ )
576
+
577
+ try:
578
+ character = create_model(character_def, invalid_data)
579
+ except ModelValidationError as e:
580
+ print(f"Validation failed: {e}")
581
+ print(f"Field: {e.field_name}")
582
+ print(f"Value: {e.field_value}")
583
+ print(f"Validation rule: {e.validation_rule}")
584
+ ```
585
+
586
+ ## 🔍 Advanced Features
587
+
588
+ ### Custom Template Resolvers
589
+
590
+ ```python
591
+ from grimoire_model.resolvers.template import TemplateResolver
592
+
593
+ class CustomTemplateResolver(TemplateResolver):
594
+ def resolve_template(self, template: str, context: dict) -> str:
595
+ # Custom template logic
596
+ return processed_template
597
+
598
+ # Use custom resolver
599
+ model = GrimoireModel(
600
+ model_def,
601
+ data,
602
+ template_resolver=CustomTemplateResolver()
603
+ )
604
+ ```
605
+
606
+ ### Custom Validators
607
+
608
+ ```python
609
+ from grimoire_model.validation.validators import ValidationEngine
610
+
611
+ def custom_validator(value, rule_params):
612
+ # Custom validation logic
613
+ return is_valid, error_message
614
+
615
+ # Register custom validator
616
+ engine = ValidationEngine()
617
+ engine.register_validator("custom_rule", custom_validator)
618
+ ```
619
+
620
+ ### Multiple Inheritance
621
+
622
+ ```python
623
+ # Multiple parent models (all in same namespace)
624
+ combat_def = ModelDefinition(
625
+ id="character",
626
+ namespace="game", # All parent models must be in same namespace
627
+ extends=["base_entity", "combatant", "spell_caster"],
628
+ attributes={...}
629
+ )
630
+
631
+ # Automatic conflict resolution with left-to-right precedence
632
+ # Parents automatically resolved from "game" namespace
633
+ ```
634
+
635
+ ## 📄 License
636
+
637
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for complete terms and conditions.
638
+
639
+ ## 🤝 Contributing
640
+
641
+ Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
642
+
643
+ If you have questions about the project, please contact: wyrdbound@proton.me
644
+
645
+ ---
646
+
647
+ **Copyright (c) 2025 The Wyrd One**