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.
- grimoire_model-0.2.0/LICENSE +21 -0
- grimoire_model-0.2.0/PKG-INFO +647 -0
- grimoire_model-0.2.0/README.md +600 -0
- grimoire_model-0.2.0/pyproject.toml +130 -0
- grimoire_model-0.2.0/setup.cfg +4 -0
- grimoire_model-0.2.0/src/grimoire_model/__init__.py +260 -0
- grimoire_model-0.2.0/src/grimoire_model/core/__init__.py +1 -0
- grimoire_model-0.2.0/src/grimoire_model/core/exceptions.py +287 -0
- grimoire_model-0.2.0/src/grimoire_model/core/model.py +473 -0
- grimoire_model-0.2.0/src/grimoire_model/core/registry.py +380 -0
- grimoire_model-0.2.0/src/grimoire_model/core/schema.py +367 -0
- grimoire_model-0.2.0/src/grimoire_model/logging.py +39 -0
- grimoire_model-0.2.0/src/grimoire_model/resolvers/__init__.py +1 -0
- grimoire_model-0.2.0/src/grimoire_model/resolvers/derived.py +528 -0
- grimoire_model-0.2.0/src/grimoire_model/resolvers/template.py +402 -0
- grimoire_model-0.2.0/src/grimoire_model/utils/__init__.py +1 -0
- grimoire_model-0.2.0/src/grimoire_model/utils/inheritance.py +446 -0
- grimoire_model-0.2.0/src/grimoire_model/utils/paths.py +332 -0
- grimoire_model-0.2.0/src/grimoire_model/validation/__init__.py +1 -0
- grimoire_model-0.2.0/src/grimoire_model/validation/validators.py +440 -0
- grimoire_model-0.2.0/src/grimoire_model.egg-info/PKG-INFO +647 -0
- grimoire_model-0.2.0/src/grimoire_model.egg-info/SOURCES.txt +32 -0
- grimoire_model-0.2.0/src/grimoire_model.egg-info/dependency_links.txt +1 -0
- grimoire_model-0.2.0/src/grimoire_model.egg-info/requires.txt +24 -0
- grimoire_model-0.2.0/src/grimoire_model.egg-info/top_level.txt +1 -0
- grimoire_model-0.2.0/tests/test_derived_resolver.py +420 -0
- grimoire_model-0.2.0/tests/test_exceptions.py +169 -0
- grimoire_model-0.2.0/tests/test_logging.py +732 -0
- grimoire_model-0.2.0/tests/test_model.py +1183 -0
- grimoire_model-0.2.0/tests/test_registry.py +456 -0
- grimoire_model-0.2.0/tests/test_schema.py +282 -0
- grimoire_model-0.2.0/tests/test_template_resolver.py +254 -0
- grimoire_model-0.2.0/tests/test_utils.py +553 -0
- 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
|
+
[](https://github.com/wyrdbound/grimoire-model/actions)
|
|
51
|
+
[](https://www.python.org/downloads/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
[](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**
|