grimoire-model 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,260 @@
1
+ """
2
+ Grimoire Model Package
3
+
4
+ A dict-like model system with schema validation, derived fields, and inheritance
5
+ designed for integration with grimoire-context.
6
+
7
+ Key Features:
8
+ - Dict-like interface (MutableMapping)
9
+ - Schema validation with Pydantic
10
+ - Reactive derived fields with dependency tracking
11
+ - Model inheritance support
12
+ - Template-based field expressions using Jinja2
13
+ - Immutable operations with pyrsistent
14
+ - Dependency injection for extensibility
15
+
16
+ Example Usage:
17
+ from grimoire_model import GrimoireModel, ModelDefinition, create_model
18
+
19
+ # Define model schema
20
+ character_def = ModelDefinition(
21
+ id="character",
22
+ name="Player Character",
23
+ attributes={
24
+ "name": {"type": "str", "required": True},
25
+ "level": {"type": "int", "default": 1},
26
+ "hp": {"type": "int", "default": 8},
27
+ "max_hp": {"type": "int", "derived": "{{ level * 8 }}"}
28
+ }
29
+ )
30
+
31
+ # Create model instance
32
+ character = create_model(character_def, {"name": "Aragorn", "level": 5})
33
+
34
+ # Use as dict
35
+ character['level'] = 6 # Automatically updates max_hp derived field
36
+ print(character['max_hp']) # 48
37
+
38
+ # Works with grimoire-context
39
+ from grimoire_context import GrimoireContext
40
+ context = GrimoireContext({'character': character})
41
+ context.set_variable('character.level', 7)
42
+ """
43
+
44
+ __version__ = "0.2.0"
45
+ __author__ = "The Wyrd One"
46
+ __email__ = "wyrdbound@proton.me"
47
+
48
+ # Core exports
49
+ from .core.exceptions import (
50
+ ConfigurationError,
51
+ DependencyError,
52
+ GrimoireModelError,
53
+ InheritanceError,
54
+ ModelValidationError,
55
+ TemplateResolutionError,
56
+ )
57
+ from .core.model import (
58
+ GrimoireModel,
59
+ create_model,
60
+ )
61
+ from .core.registry import (
62
+ ModelRegistry,
63
+ clear_registry,
64
+ get_default_registry,
65
+ get_model,
66
+ get_model_registry, # Kept for backward compatibility
67
+ register_model,
68
+ )
69
+ from .core.schema import (
70
+ AttributeDefinition,
71
+ ModelDefinition,
72
+ ValidationRule,
73
+ )
74
+
75
+ # Logging configuration
76
+ from .logging import clear_logger_injection, get_logger, inject_logger, logger
77
+ from .resolvers.derived import (
78
+ BatchedDerivedFieldResolver,
79
+ DependencyInfo,
80
+ DerivedFieldResolver,
81
+ ObservableValue,
82
+ create_derived_field_resolver,
83
+ )
84
+
85
+ # Resolver exports
86
+ from .resolvers.template import (
87
+ CachingTemplateResolver,
88
+ Jinja2TemplateResolver,
89
+ ModelContextTemplateResolver,
90
+ TemplateResolver,
91
+ create_template_resolver,
92
+ )
93
+
94
+ # Utility exports
95
+ from .utils.inheritance import resolve_model_inheritance
96
+ from .utils.paths import (
97
+ delete_nested_value,
98
+ flatten_dict,
99
+ get_nested_value,
100
+ has_nested_value,
101
+ set_nested_value,
102
+ unflatten_dict,
103
+ )
104
+
105
+ # Validation exports
106
+ from .validation.validators import (
107
+ EnumValidator,
108
+ LengthValidator,
109
+ PatternValidator,
110
+ RangeValidator,
111
+ RequiredValidator,
112
+ TypeValidator,
113
+ ValidationEngine,
114
+ get_validation_engine,
115
+ validate_field_value,
116
+ validate_model_data,
117
+ )
118
+
119
+ __all__ = [
120
+ # Core classes
121
+ "GrimoireModel",
122
+ "ModelDefinition",
123
+ "AttributeDefinition",
124
+ "ValidationRule",
125
+ # Factory functions
126
+ "create_model",
127
+ "create_template_resolver",
128
+ "create_derived_field_resolver",
129
+ # Registry
130
+ "ModelRegistry",
131
+ "get_default_registry",
132
+ "get_model_registry", # Backward compatibility
133
+ "clear_registry",
134
+ "register_model",
135
+ "get_model",
136
+ # Logging
137
+ "logger",
138
+ "get_logger",
139
+ "inject_logger",
140
+ "clear_logger_injection",
141
+ # Exceptions
142
+ "GrimoireModelError",
143
+ "ModelValidationError",
144
+ "TemplateResolutionError",
145
+ "InheritanceError",
146
+ "DependencyError",
147
+ "ConfigurationError",
148
+ # Resolvers
149
+ "TemplateResolver",
150
+ "Jinja2TemplateResolver",
151
+ "ModelContextTemplateResolver",
152
+ "CachingTemplateResolver",
153
+ "DerivedFieldResolver",
154
+ "BatchedDerivedFieldResolver",
155
+ "ObservableValue",
156
+ "DependencyInfo",
157
+ # Utilities
158
+ "resolve_model_inheritance",
159
+ "get_nested_value",
160
+ "set_nested_value",
161
+ "has_nested_value",
162
+ "delete_nested_value",
163
+ "flatten_dict",
164
+ "unflatten_dict",
165
+ # Validators
166
+ "TypeValidator",
167
+ "RangeValidator",
168
+ "EnumValidator",
169
+ "RequiredValidator",
170
+ "PatternValidator",
171
+ "LengthValidator",
172
+ "ValidationEngine",
173
+ "validate_field_value",
174
+ "validate_model_data",
175
+ "get_validation_engine",
176
+ ]
177
+
178
+ # Package metadata
179
+ __meta__ = {
180
+ "name": "grimoire-model",
181
+ "version": __version__,
182
+ "description": (
183
+ "Dict-like model system with validation and derived fields for Grimoire"
184
+ ),
185
+ "long_description": __doc__,
186
+ "author": __author__,
187
+ "author_email": __email__,
188
+ "license": "MIT",
189
+ "url": "https://github.com/wyrdbound/grimoire-model",
190
+ "classifiers": [
191
+ "Development Status :: 3 - Alpha",
192
+ "Intended Audience :: Developers",
193
+ "License :: OSI Approved :: MIT License",
194
+ "Programming Language :: Python :: 3",
195
+ "Programming Language :: Python :: 3.8",
196
+ "Programming Language :: Python :: 3.9",
197
+ "Programming Language :: Python :: 3.10",
198
+ "Programming Language :: Python :: 3.11",
199
+ "Programming Language :: Python :: 3.12",
200
+ "Topic :: Games/Entertainment :: Role-Playing",
201
+ "Topic :: Software Development :: Libraries :: Python Modules",
202
+ ],
203
+ "keywords": "gaming rpg tabletop model validation schema",
204
+ "python_requires": ">=3.8",
205
+ "install_requires": [
206
+ "pydantic>=2.0.0",
207
+ "pyrsistent>=0.19.0",
208
+ "jinja2>=3.1.0",
209
+ "pyyaml>=6.0",
210
+ ],
211
+ "extras_require": {
212
+ "dev": [
213
+ "pytest>=7.0.0",
214
+ "pytest-cov>=4.0.0",
215
+ "ruff>=0.1.0",
216
+ "mypy>=1.0.0",
217
+ "black>=22.0.0",
218
+ ],
219
+ "docs": [
220
+ "sphinx>=4.0.0",
221
+ "sphinx-rtd-theme>=1.0.0",
222
+ "sphinxcontrib-napoleon>=0.7",
223
+ ],
224
+ "test": [
225
+ "pytest>=7.0.0",
226
+ "pytest-cov>=4.0.0",
227
+ "pytest-mock>=3.0.0",
228
+ "hypothesis>=6.0.0",
229
+ ],
230
+ },
231
+ }
232
+
233
+
234
+ # Integration helpers for grimoire-context
235
+ def register_with_grimoire_context():
236
+ """Register GrimoireModel as a compatible value type with grimoire-context.
237
+
238
+ This function should be called if you want seamless integration between
239
+ grimoire-model and grimoire-context packages.
240
+ """
241
+ try:
242
+ from grimoire_context import GrimoireContext # type: ignore[import-not-found]
243
+
244
+ # Register our model as a compatible dict-like type
245
+ if hasattr(GrimoireContext, "register_dict_like_type"):
246
+ GrimoireContext.register_dict_like_type(GrimoireModel) # type: ignore
247
+
248
+ return True
249
+ except ImportError:
250
+ # grimoire-context not available
251
+ return False
252
+
253
+
254
+ # Optional auto-registration
255
+ try:
256
+ # Try to register automatically if grimoire-context is available
257
+ register_with_grimoire_context()
258
+ except Exception:
259
+ # Silently ignore registration failures
260
+ pass
@@ -0,0 +1 @@
1
+ """Core components of grimoire-model package."""
@@ -0,0 +1,287 @@
1
+ """
2
+ Core exceptions for grimoire-model package.
3
+
4
+ Provides a hierarchy of exceptions for different error conditions that can occur
5
+ during model operations, validation, template resolution, and inheritance processing.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional
9
+
10
+
11
+ class GrimoireModelError(Exception):
12
+ """Base exception for grimoire-model package.
13
+
14
+ All other exceptions in this package inherit from this base class.
15
+ """
16
+
17
+ def __init__(self, message: str, context: Optional[Dict[str, Any]] = None):
18
+ """Initialize with message and optional context information.
19
+
20
+ Args:
21
+ message: Human-readable error description
22
+ context: Additional context information for debugging
23
+ """
24
+ super().__init__(message)
25
+ self.message = message
26
+ self.context = context or {}
27
+
28
+ def __str__(self) -> str:
29
+ """Return string representation with context if available."""
30
+ if self.context:
31
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
32
+ return f"{self.message} (context: {context_str})"
33
+ return self.message
34
+
35
+
36
+ class ModelValidationError(GrimoireModelError):
37
+ """Raised when model validation fails.
38
+
39
+ This exception is raised when:
40
+ - Required fields are missing
41
+ - Field values don't match their defined types
42
+ - Value constraints are violated (range, enum, etc.)
43
+ - Cross-field validation rules fail
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ message: str,
49
+ field_name: Optional[str] = None,
50
+ field_value: Any = None,
51
+ validation_errors: Optional[List[str]] = None,
52
+ context: Optional[Dict[str, Any]] = None,
53
+ ):
54
+ """Initialize validation error with field-specific information.
55
+
56
+ Args:
57
+ message: Primary error message
58
+ field_name: Name of the field that failed validation
59
+ field_value: Value that caused the validation failure
60
+ validation_errors: List of specific validation error messages
61
+ context: Additional context information
62
+ """
63
+ super().__init__(message, context)
64
+ self.field_name = field_name
65
+ self.field_value = field_value
66
+ self.validation_errors = validation_errors or []
67
+
68
+ def __str__(self) -> str:
69
+ """Return detailed string representation including field information."""
70
+ parts = [self.message]
71
+
72
+ if self.field_name:
73
+ parts.append(f"field: {self.field_name}")
74
+
75
+ if self.field_value is not None:
76
+ parts.append(f"value: {self.field_value}")
77
+
78
+ if self.validation_errors:
79
+ error_list = ", ".join(self.validation_errors)
80
+ parts.append(f"errors: [{error_list}]")
81
+
82
+ if self.context:
83
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
84
+ parts.append(f"context: {context_str}")
85
+
86
+ return " | ".join(parts)
87
+
88
+
89
+ class TemplateResolutionError(GrimoireModelError):
90
+ """Raised when template resolution fails.
91
+
92
+ This exception is raised when:
93
+ - Template syntax is invalid
94
+ - Referenced variables are undefined
95
+ - Template evaluation produces an error
96
+ - Circular template dependencies are detected
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ message: str,
102
+ template_str: Optional[str] = None,
103
+ template_variables: Optional[List[str]] = None,
104
+ context: Optional[Dict[str, Any]] = None,
105
+ ):
106
+ """Initialize template resolution error.
107
+
108
+ Args:
109
+ message: Primary error message
110
+ template_str: The template string that failed to resolve
111
+ template_variables: Variables referenced in the template
112
+ context: Additional context information
113
+ """
114
+ super().__init__(message, context)
115
+ self.template_str = template_str
116
+ self.template_variables = template_variables or []
117
+
118
+ def __str__(self) -> str:
119
+ """Return detailed string representation including template information."""
120
+ parts = [self.message]
121
+
122
+ if self.template_str:
123
+ parts.append(f"template: {self.template_str}")
124
+
125
+ if self.template_variables:
126
+ vars_str = ", ".join(self.template_variables)
127
+ parts.append(f"variables: [{vars_str}]")
128
+
129
+ if self.context:
130
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
131
+ parts.append(f"context: {context_str}")
132
+
133
+ return " | ".join(parts)
134
+
135
+
136
+ class InheritanceError(GrimoireModelError):
137
+ """Raised when model inheritance resolution fails.
138
+
139
+ This exception is raised when:
140
+ - Parent model definitions are not found
141
+ - Circular inheritance dependencies are detected
142
+ - Inheritance conflicts cannot be resolved
143
+ - Model registry is incomplete or corrupted
144
+ """
145
+
146
+ def __init__(
147
+ self,
148
+ message: str,
149
+ model_id: Optional[str] = None,
150
+ parent_ids: Optional[List[str]] = None,
151
+ inheritance_chain: Optional[List[str]] = None,
152
+ context: Optional[Dict[str, Any]] = None,
153
+ ):
154
+ """Initialize inheritance error.
155
+
156
+ Args:
157
+ message: Primary error message
158
+ model_id: ID of the model that failed inheritance resolution
159
+ parent_ids: List of parent model IDs
160
+ inheritance_chain: The inheritance chain being resolved
161
+ context: Additional context information
162
+ """
163
+ super().__init__(message, context)
164
+ self.model_id = model_id
165
+ self.parent_ids = parent_ids or []
166
+ self.inheritance_chain = inheritance_chain or []
167
+
168
+ def __str__(self) -> str:
169
+ """Return detailed string representation including inheritance information."""
170
+ parts = [self.message]
171
+
172
+ if self.model_id:
173
+ parts.append(f"model: {self.model_id}")
174
+
175
+ if self.parent_ids:
176
+ parents_str = ", ".join(self.parent_ids)
177
+ parts.append(f"parents: [{parents_str}]")
178
+
179
+ if self.inheritance_chain:
180
+ chain_str = " -> ".join(self.inheritance_chain)
181
+ parts.append(f"chain: {chain_str}")
182
+
183
+ if self.context:
184
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
185
+ parts.append(f"context: {context_str}")
186
+
187
+ return " | ".join(parts)
188
+
189
+
190
+ class DependencyError(GrimoireModelError):
191
+ """Raised when derived field dependencies cannot be resolved.
192
+
193
+ This exception is raised when:
194
+ - Circular dependencies are detected between derived fields
195
+ - Required dependencies are missing or undefined
196
+ - Dependency graph cannot be topologically sorted
197
+ - Dependency computation fails
198
+ """
199
+
200
+ def __init__(
201
+ self,
202
+ message: str,
203
+ field_name: Optional[str] = None,
204
+ dependencies: Optional[List[str]] = None,
205
+ dependency_chain: Optional[List[str]] = None,
206
+ context: Optional[Dict[str, Any]] = None,
207
+ ):
208
+ """Initialize dependency error.
209
+
210
+ Args:
211
+ message: Primary error message
212
+ field_name: Name of the field with dependency issues
213
+ dependencies: List of field dependencies
214
+ dependency_chain: The dependency chain that failed
215
+ context: Additional context information
216
+ """
217
+ super().__init__(message, context)
218
+ self.field_name = field_name
219
+ self.dependencies = dependencies or []
220
+ self.dependency_chain = dependency_chain or []
221
+
222
+ def __str__(self) -> str:
223
+ """Return detailed string representation including dependency information."""
224
+ parts = [self.message]
225
+
226
+ if self.field_name:
227
+ parts.append(f"field: {self.field_name}")
228
+
229
+ if self.dependencies:
230
+ deps_str = ", ".join(self.dependencies)
231
+ parts.append(f"deps: [{deps_str}]")
232
+
233
+ if self.dependency_chain:
234
+ chain_str = " -> ".join(self.dependency_chain)
235
+ parts.append(f"chain: {chain_str}")
236
+
237
+ if self.context:
238
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
239
+ parts.append(f"context: {context_str}")
240
+
241
+ return " | ".join(parts)
242
+
243
+
244
+ class ConfigurationError(GrimoireModelError):
245
+ """Raised when configuration is invalid or incomplete.
246
+
247
+ This exception is raised when:
248
+ - Required configuration parameters are missing
249
+ - Configuration values are invalid
250
+ - Dependency injection setup fails
251
+ - Resolver configuration is incomplete
252
+ """
253
+
254
+ def __init__(
255
+ self,
256
+ message: str,
257
+ config_key: Optional[str] = None,
258
+ config_value: Any = None,
259
+ context: Optional[Dict[str, Any]] = None,
260
+ ):
261
+ """Initialize configuration error.
262
+
263
+ Args:
264
+ message: Primary error message
265
+ config_key: Configuration key that caused the error
266
+ config_value: Invalid configuration value
267
+ context: Additional context information
268
+ """
269
+ super().__init__(message, context)
270
+ self.config_key = config_key
271
+ self.config_value = config_value
272
+
273
+ def __str__(self) -> str:
274
+ """Return detailed string representation including configuration information."""
275
+ parts = [self.message]
276
+
277
+ if self.config_key:
278
+ parts.append(f"key: {self.config_key}")
279
+
280
+ if self.config_value is not None:
281
+ parts.append(f"value: {self.config_value}")
282
+
283
+ if self.context:
284
+ context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
285
+ parts.append(f"context: {context_str}")
286
+
287
+ return " | ".join(parts)