fast-clean-architecture 1.0.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.
Files changed (30) hide show
  1. fast_clean_architecture/__init__.py +24 -0
  2. fast_clean_architecture/cli.py +480 -0
  3. fast_clean_architecture/config.py +506 -0
  4. fast_clean_architecture/exceptions.py +63 -0
  5. fast_clean_architecture/generators/__init__.py +11 -0
  6. fast_clean_architecture/generators/component_generator.py +1039 -0
  7. fast_clean_architecture/generators/config_updater.py +308 -0
  8. fast_clean_architecture/generators/package_generator.py +174 -0
  9. fast_clean_architecture/generators/template_validator.py +546 -0
  10. fast_clean_architecture/generators/validation_config.py +75 -0
  11. fast_clean_architecture/generators/validation_metrics.py +193 -0
  12. fast_clean_architecture/templates/__init__.py +7 -0
  13. fast_clean_architecture/templates/__init__.py.j2 +26 -0
  14. fast_clean_architecture/templates/api.py.j2 +65 -0
  15. fast_clean_architecture/templates/command.py.j2 +26 -0
  16. fast_clean_architecture/templates/entity.py.j2 +49 -0
  17. fast_clean_architecture/templates/external.py.j2 +61 -0
  18. fast_clean_architecture/templates/infrastructure_repository.py.j2 +69 -0
  19. fast_clean_architecture/templates/model.py.j2 +38 -0
  20. fast_clean_architecture/templates/query.py.j2 +26 -0
  21. fast_clean_architecture/templates/repository.py.j2 +57 -0
  22. fast_clean_architecture/templates/schemas.py.j2 +32 -0
  23. fast_clean_architecture/templates/service.py.j2 +109 -0
  24. fast_clean_architecture/templates/value_object.py.j2 +34 -0
  25. fast_clean_architecture/utils.py +553 -0
  26. fast_clean_architecture-1.0.0.dist-info/METADATA +541 -0
  27. fast_clean_architecture-1.0.0.dist-info/RECORD +30 -0
  28. fast_clean_architecture-1.0.0.dist-info/WHEEL +4 -0
  29. fast_clean_architecture-1.0.0.dist-info/entry_points.txt +2 -0
  30. fast_clean_architecture-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,546 @@
1
+ """Template validation module with simplified architecture."""
2
+
3
+ import logging
4
+ import time
5
+ from abc import ABC, abstractmethod
6
+ from typing import Dict, Any, Set, Optional, List, Protocol, TypeVar
7
+ from pathlib import Path
8
+
9
+ import jinja2
10
+ from jinja2 import meta, Environment, TemplateSyntaxError, UndefinedError
11
+ from jinja2.sandbox import SandboxedEnvironment
12
+
13
+ from ..exceptions import (
14
+ TemplateError,
15
+ TemplateValidationError,
16
+ TemplateMissingVariablesError,
17
+ TemplateUndefinedVariableError,
18
+ )
19
+ from .validation_config import ValidationConfig
20
+ from .validation_metrics import (
21
+ timed_validation,
22
+ get_metrics_collector,
23
+ ValidationMetrics,
24
+ validation_timeout,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Type variables for enhanced type safety
30
+ T = TypeVar("T", bound="TemplateValidationStrategy")
31
+
32
+
33
+ class ValidatorFactory(Protocol):
34
+ """Protocol for validator factory implementations."""
35
+
36
+ def create(self, config: ValidationConfig) -> "TemplateValidator":
37
+ """Create a validator with the given configuration."""
38
+ ...
39
+
40
+ def create_default(self) -> "TemplateValidator":
41
+ """Create a validator with default configuration."""
42
+ ...
43
+
44
+
45
+ class TemplateValidationStrategy(ABC):
46
+ """Abstract base class for template validation strategies."""
47
+
48
+ @abstractmethod
49
+ def validate(self, template_source: str, template_vars: Dict[str, Any]) -> None:
50
+ """Validate template with given variables.
51
+
52
+ Args:
53
+ template_source: The template source code
54
+ template_vars: Variables to validate against
55
+
56
+ Raises:
57
+ TemplateError: If validation fails
58
+ """
59
+ pass
60
+
61
+
62
+ class SimpleTemplateValidator:
63
+ """Simplified template validator with essential security features.
64
+
65
+ This replaces the complex validation strategies with a single, secure approach.
66
+ """
67
+
68
+ def __init__(
69
+ self, template_env: Environment, config: Optional[ValidationConfig] = None
70
+ ):
71
+ self.template_env = template_env
72
+ self.config = config or ValidationConfig()
73
+ self._metrics_collector = (
74
+ get_metrics_collector() if config and config.enable_metrics else None
75
+ )
76
+
77
+ def validate(self, template_source: str, template_vars: Dict[str, Any]) -> None:
78
+ """Validate template with essential security checks.
79
+
80
+ Args:
81
+ template_source: The template source code
82
+ template_vars: Variables to validate against
83
+
84
+ Raises:
85
+ TemplateSyntaxError: If template has syntax errors
86
+ TemplateMissingVariablesError: If required variables are missing
87
+ TemplateValidationError: For other validation errors
88
+ """
89
+ template_size = len(template_source.encode("utf-8"))
90
+
91
+ # Check template size limits (security)
92
+ if template_size > self.config.max_template_size_bytes:
93
+ raise TemplateValidationError(
94
+ f"Template size ({template_size} bytes) exceeds limit "
95
+ f"({self.config.max_template_size_bytes} bytes)"
96
+ )
97
+
98
+ try:
99
+ # Parse template to check for syntax errors
100
+ try:
101
+ parsed = self.template_env.parse(template_source)
102
+ except TemplateSyntaxError as e:
103
+ if self._metrics_collector:
104
+ self._metrics_collector.record_validation(
105
+ False, "TemplateSyntaxError"
106
+ )
107
+ raise
108
+
109
+ # Find undefined variables
110
+ undefined_vars = meta.find_undeclared_variables(parsed)
111
+ missing_vars = undefined_vars - set(template_vars.keys())
112
+
113
+ # Filter out variables that are optional (have defaults or in conditionals)
114
+ if missing_vars:
115
+ optional_vars = self._find_optional_variables(
116
+ template_source, missing_vars
117
+ )
118
+ truly_missing = missing_vars - optional_vars
119
+
120
+ if truly_missing:
121
+ error = TemplateMissingVariablesError(truly_missing)
122
+ if self._metrics_collector:
123
+ self._metrics_collector.record_validation(
124
+ False, "TemplateMissingVariablesError"
125
+ )
126
+ raise error
127
+
128
+ # Record successful validation
129
+ if self._metrics_collector:
130
+ self._metrics_collector.record_validation(True)
131
+
132
+ except (
133
+ TemplateSyntaxError,
134
+ TemplateValidationError,
135
+ TemplateMissingVariablesError,
136
+ ):
137
+ # These exceptions are already handled above or should propagate
138
+ raise
139
+ except Exception as e:
140
+ logger.error(f"Unexpected error during validation: {e}")
141
+ if self._metrics_collector:
142
+ self._metrics_collector.record_validation(False, type(e).__name__)
143
+ raise TemplateValidationError(f"Template validation error: {e}")
144
+
145
+ def _find_optional_variables(self, template_source: str, missing_vars: set) -> set:
146
+ """Find variables that are optional (have default filters or are in conditionals)."""
147
+ import re
148
+
149
+ optional_vars = set()
150
+
151
+ # Simplified and secure patterns to prevent ReDoS attacks
152
+ # Only match simple variable names with basic default filters
153
+ default_pattern = re.compile(
154
+ r"{{\s*([a-zA-Z_][a-zA-Z0-9_]{0,50})\s*\|\s*default\b", re.MULTILINE
155
+ )
156
+ # Simple conditional pattern without nested matching
157
+ conditional_pattern = re.compile(
158
+ r"{%\s*if\s+([a-zA-Z_][a-zA-Z0-9_]{0,50})(?:\s+is\s+defined)?\s*%}",
159
+ re.MULTILINE,
160
+ )
161
+
162
+ # Find variables with default filters
163
+ for match in default_pattern.finditer(template_source):
164
+ var_name = match.group(1).strip()
165
+ if var_name in missing_vars:
166
+ optional_vars.add(var_name)
167
+
168
+ # Find variables in conditionals (simplified detection)
169
+ for match in conditional_pattern.finditer(template_source):
170
+ var_name = match.group(1).strip()
171
+ if var_name in missing_vars:
172
+ optional_vars.add(var_name)
173
+
174
+ return optional_vars
175
+
176
+
177
+ class RuntimeValidator(TemplateValidationStrategy):
178
+ """Validates templates by attempting to render them.
179
+
180
+ Examples:
181
+ >>> env = Environment()
182
+ >>> validator = RuntimeValidator(env)
183
+ >>> validator.validate("Hello {{ name }}!", {"name": "World"})
184
+ >>> # Raises TemplateUndefinedVariableError for undefined variables
185
+ >>> validator.validate("Hello {{ missing }}!", {"name": "World"})
186
+ """
187
+
188
+ def __init__(
189
+ self, template_env: Environment, config: Optional[ValidationConfig] = None
190
+ ):
191
+ self.template_env = template_env
192
+ self.config = config or ValidationConfig()
193
+ self._metrics_collector = get_metrics_collector()
194
+
195
+ def validate(self, template_source: str, template_vars: Dict[str, Any]) -> None:
196
+ """Validate by attempting to render the template.
197
+
198
+ Args:
199
+ template_source: The template source code
200
+ template_vars: Variables to use for rendering
201
+
202
+ Raises:
203
+ TemplateSyntaxError: If template has syntax errors
204
+ TemplateUndefinedVariableError: If undefined variables are encountered
205
+ TemplateValidationError: For other validation errors
206
+ """
207
+ template_size = len(template_source.encode("utf-8"))
208
+
209
+ with timed_validation(
210
+ "RuntimeValidator",
211
+ template_size,
212
+ len(template_vars),
213
+ True, # Always enable timing for simplified approach
214
+ ) as metrics:
215
+ try:
216
+ logger.debug(
217
+ f"Starting runtime validation for template of size {template_size} bytes"
218
+ )
219
+
220
+ template = self.template_env.from_string(template_source)
221
+
222
+ # Attempt to render with timeout if configured
223
+ if self.config.render_timeout_seconds > 0:
224
+ with validation_timeout(self.config.render_timeout_seconds):
225
+ rendered = template.render(**template_vars)
226
+ else:
227
+ rendered = template.render(**template_vars)
228
+
229
+ rendered_size = len(rendered.encode("utf-8"))
230
+
231
+ logger.debug(
232
+ f"Runtime validation successful: rendered {rendered_size} bytes"
233
+ )
234
+ self._metrics_collector.record_validation(True)
235
+
236
+ except TemplateSyntaxError as e:
237
+ logger.error(f"Template syntax error during runtime validation: {e}")
238
+ metrics.errors_encountered = 1
239
+ self._metrics_collector.record_validation(False, "TemplateSyntaxError")
240
+ # Let TemplateSyntaxError propagate for proper error handling
241
+ raise
242
+ except UndefinedError as e:
243
+ logger.error(f"Undefined variable during runtime validation: {e}")
244
+ metrics.errors_encountered = 1
245
+ metrics.undefined_variables_found = 1
246
+ error = TemplateUndefinedVariableError(str(e))
247
+ self._metrics_collector.record_validation(
248
+ False, "TemplateUndefinedVariableError"
249
+ )
250
+ raise error
251
+ except Exception as e:
252
+ logger.error(f"Unexpected error during runtime validation: {e}")
253
+ metrics.errors_encountered = 1
254
+ self._metrics_collector.record_validation(False, type(e).__name__)
255
+ raise TemplateValidationError(f"Template rendering error: {e}")
256
+
257
+
258
+ class TemplateSourceResolver:
259
+ """Resolves template source from various inputs."""
260
+
261
+ def __init__(self, template_env: Environment):
262
+ self.template_env = template_env
263
+
264
+ def resolve_source(self, template_input: str) -> str:
265
+ """Resolve template source from input (content or filename).
266
+
267
+ Args:
268
+ template_input: Either template content or template filename
269
+
270
+ Returns:
271
+ str: The template source code
272
+
273
+ Raises:
274
+ TemplateError: If template cannot be resolved
275
+ """
276
+ try:
277
+ # Check if it's template content or template name
278
+ if self._is_template_content(template_input):
279
+ return template_input
280
+ else:
281
+ # It's a template name, load from file
282
+ template = self.template_env.get_template(template_input)
283
+ if template.environment.loader is None:
284
+ raise TemplateError(
285
+ f"No loader available for template: {template_input}"
286
+ )
287
+ return template.environment.loader.get_source(
288
+ template.environment, template_input
289
+ )[0]
290
+ except jinja2.TemplateNotFound:
291
+ raise TemplateError(f"Template not found: {template_input}")
292
+ except Exception as e:
293
+ if isinstance(e, TemplateError):
294
+ raise
295
+ raise TemplateError(f"Error resolving template source: {e}")
296
+
297
+ def _is_template_content(self, template_input: str) -> bool:
298
+ """Check if input is template content or filename."""
299
+ return (
300
+ "{{" in template_input
301
+ or "{%" in template_input
302
+ or not template_input.endswith(".j2")
303
+ )
304
+
305
+
306
+ class TemplateValidator:
307
+ """Main template validator that orchestrates different validation strategies.
308
+
309
+ This class provides a unified interface for template validation using
310
+ configurable strategies and comprehensive error handling.
311
+
312
+ Examples:
313
+ >>> env = Environment()
314
+ >>> config = ValidationConfig(sandbox_mode=True)
315
+ >>> validator = TemplateValidator(env, config=config)
316
+ >>> validator.validate("Hello {{ name }}!", {"name": "World"})
317
+
318
+ >>> # With custom validators
319
+ >>> static = SimpleTemplateValidator(env)
320
+ >>> runtime = RuntimeValidator(env)
321
+ >>> validator = TemplateValidator(env, static_validator=static, runtime_validator=runtime)
322
+ """
323
+
324
+ def __init__(
325
+ self,
326
+ template_env: Environment,
327
+ config: Optional[ValidationConfig] = None,
328
+ static_validator: Optional[TemplateValidationStrategy] = None,
329
+ runtime_validator: Optional[TemplateValidationStrategy] = None,
330
+ fallback_strategy: Optional[RuntimeValidator] = None,
331
+ ):
332
+ """Initialize the template validator.
333
+
334
+ Args:
335
+ template_env: Jinja2 environment for template processing
336
+ config: Validation configuration settings
337
+ static_validator: Custom static analysis validator
338
+ runtime_validator: Custom runtime validator
339
+ fallback_strategy: Fallback validator for error recovery
340
+ """
341
+ self.template_env = template_env
342
+ self.config = config or ValidationConfig()
343
+ self._metrics_collector = get_metrics_collector()
344
+
345
+ # Initialize validators with configuration
346
+ self.static_validator = static_validator or SimpleTemplateValidator(
347
+ template_env, self.config
348
+ )
349
+ self.runtime_validator = runtime_validator or RuntimeValidator(
350
+ template_env, self.config
351
+ )
352
+ self.fallback_strategy = fallback_strategy
353
+
354
+ # Logging is configured at application level
355
+
356
+ def validate(
357
+ self, template_source: str, template_vars: Dict[str, Any]
358
+ ) -> ValidationMetrics:
359
+ """Validate template using configured strategies.
360
+
361
+ Args:
362
+ template_source: The template source code to validate
363
+ template_vars: Variables to validate against
364
+
365
+ Returns:
366
+ ValidationMetrics: Metrics from the validation process
367
+
368
+ Raises:
369
+ TemplateSyntaxError: If template has syntax errors
370
+ TemplateMissingVariablesError: If required variables are missing
371
+ TemplateUndefinedVariableError: If undefined variables are encountered
372
+ TemplateValidationError: For other validation errors
373
+ """
374
+ validation_start = time.time()
375
+ template_size = len(template_source.encode("utf-8"))
376
+
377
+ logger.info("Starting template validation")
378
+
379
+ try:
380
+ # Use simplified validation approach
381
+ self.static_validator.validate(template_source, template_vars)
382
+
383
+ # Create success metrics
384
+ validation_time = time.time() - validation_start
385
+ metrics = ValidationMetrics(
386
+ strategy_used="simple",
387
+ validation_time_ms=validation_time * 1000,
388
+ template_size_bytes=template_size,
389
+ variables_count=len(template_vars),
390
+ undefined_variables_found=0,
391
+ fallback_used=False,
392
+ )
393
+
394
+ logger.info(f"Template validation successful in {validation_time:.3f}s")
395
+ if self._metrics_collector:
396
+ self._metrics_collector.record_validation(True)
397
+ return metrics
398
+
399
+ except (
400
+ TemplateSyntaxError,
401
+ TemplateValidationError,
402
+ TemplateMissingVariablesError,
403
+ TemplateUndefinedVariableError,
404
+ ) as e:
405
+ validation_time = time.time() - validation_start
406
+ metrics = ValidationMetrics(
407
+ strategy_used="simple",
408
+ validation_time_ms=validation_time * 1000,
409
+ template_size_bytes=template_size,
410
+ variables_count=len(template_vars),
411
+ undefined_variables_found=0,
412
+ fallback_used=False,
413
+ errors_encountered=1,
414
+ )
415
+
416
+ logger.error(f"Template validation failed in {validation_time:.3f}s: {e}")
417
+ if self._metrics_collector:
418
+ self._metrics_collector.record_validation(False, type(e).__name__)
419
+ raise
420
+
421
+ def get_metrics(self) -> Dict[str, Any]:
422
+ """Get validation metrics from the metrics collector.
423
+
424
+ Returns:
425
+ Dict containing validation statistics and metrics
426
+ """
427
+ return self._metrics_collector.get_stats().to_dict()
428
+
429
+ def reset_metrics(self) -> None:
430
+ """Reset validation metrics."""
431
+ self._metrics_collector.reset()
432
+
433
+
434
+ class TemplateValidatorFactory:
435
+ """Factory for creating template validators with different configurations.
436
+
437
+ This factory implements the ValidatorFactory protocol and provides
438
+ convenient methods for creating validators with common configurations.
439
+
440
+ Examples:
441
+ >>> env = Environment()
442
+ >>> factory = TemplateValidatorFactory()
443
+ >>>
444
+ >>> # Create with default configuration
445
+ >>> validator = factory.create_default(env)
446
+ >>>
447
+ >>> # Create with custom configuration
448
+ >>> config = ValidationConfig(sandbox_mode=True)
449
+ >>> validator = factory.create(env, config)
450
+ >>>
451
+ >>> # Create with predefined configurations
452
+ >>> static_validator = factory.create_static_only(env)
453
+ >>> runtime_validator = factory.create_runtime_only(env)
454
+ """
455
+
456
+ def create(
457
+ self, template_env: Environment, config: ValidationConfig
458
+ ) -> TemplateValidator:
459
+ """Create a validator with the given configuration.
460
+
461
+ Args:
462
+ template_env: Jinja2 environment for template processing
463
+ config: Validation configuration settings
464
+
465
+ Returns:
466
+ TemplateValidator: Configured validator instance
467
+ """
468
+ return TemplateValidator(template_env=template_env, config=config)
469
+
470
+ def create_default(self, template_env: Environment) -> TemplateValidator:
471
+ """Create a validator with default configuration.
472
+
473
+ Args:
474
+ template_env: Jinja2 environment for template processing
475
+
476
+ Returns:
477
+ TemplateValidator: Validator with default settings
478
+ """
479
+ return TemplateValidator(template_env=template_env)
480
+
481
+ @staticmethod
482
+ def create_static_only(
483
+ template_env: Environment, strict_mode: bool = False
484
+ ) -> TemplateValidator:
485
+ """Create a validator that only uses static analysis.
486
+
487
+ Args:
488
+ template_env: Jinja2 environment for template processing
489
+ strict_mode: Whether to enable strict validation mode
490
+
491
+ Returns:
492
+ TemplateValidator: Static-only validator
493
+ """
494
+ config = ValidationConfig(sandbox_mode=True)
495
+ return TemplateValidator(template_env=template_env, config=config)
496
+
497
+ @staticmethod
498
+ def create_runtime_only(
499
+ template_env: Environment, timeout_seconds: int = 30
500
+ ) -> TemplateValidator:
501
+ """Create a validator that only uses runtime validation.
502
+
503
+ Args:
504
+ template_env: Jinja2 environment for template processing
505
+ timeout_seconds: Timeout for template rendering
506
+
507
+ Returns:
508
+ TemplateValidator: Runtime-only validator
509
+ """
510
+ config = ValidationConfig(
511
+ sandbox_mode=True, render_timeout_seconds=timeout_seconds
512
+ )
513
+ return TemplateValidator(template_env=template_env, config=config)
514
+
515
+ @staticmethod
516
+ def create_with_fallback(
517
+ template_env: Environment, enable_metrics: bool = True
518
+ ) -> TemplateValidator:
519
+ """Create a validator with static analysis and runtime fallback.
520
+
521
+ Args:
522
+ template_env: Jinja2 environment for template processing
523
+ enable_metrics: Whether to enable performance metrics collection
524
+
525
+ Returns:
526
+ TemplateValidator: Validator with fallback strategy
527
+ """
528
+ config = ValidationConfig(sandbox_mode=True)
529
+ return TemplateValidator(
530
+ template_env=template_env,
531
+ config=config,
532
+ fallback_strategy=RuntimeValidator(template_env, config),
533
+ )
534
+
535
+ @staticmethod
536
+ def create_comprehensive(template_env: Environment) -> TemplateValidator:
537
+ """Create a validator that runs both static and runtime validation.
538
+
539
+ Args:
540
+ template_env: Jinja2 environment for template processing
541
+
542
+ Returns:
543
+ TemplateValidator: Comprehensive validator
544
+ """
545
+ config = ValidationConfig(sandbox_mode=True)
546
+ return TemplateValidator(template_env=template_env, config=config)
@@ -0,0 +1,75 @@
1
+ """Configuration classes for template validation."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional, Dict, Any
5
+ from enum import Enum
6
+
7
+
8
+ class ValidationStrategy(Enum):
9
+ """Available validation strategies."""
10
+
11
+ STATIC_ONLY = "static_only"
12
+ RUNTIME_ONLY = "runtime_only"
13
+ BOTH = "both"
14
+ STATIC_WITH_RUNTIME_FALLBACK = "static_with_runtime_fallback"
15
+
16
+
17
+ class LogLevel(Enum):
18
+ """Logging levels for validation."""
19
+
20
+ DEBUG = "debug"
21
+ INFO = "info"
22
+ WARNING = "warning"
23
+ ERROR = "error"
24
+
25
+
26
+ @dataclass
27
+ class ValidationConfig:
28
+ """Simplified configuration for template validation with essential security."""
29
+
30
+ # Core validation settings (simplified)
31
+ strict_mode: bool = False
32
+ allow_undefined: bool = True
33
+
34
+ # Security settings (essential only)
35
+ sandbox_mode: bool = True
36
+ max_template_size_bytes: int = 64 * 1024 # 64KB
37
+ render_timeout_seconds: int = 10
38
+ max_variable_nesting_depth: int = 10 # Prevent deep recursion DoS
39
+
40
+ # Basic monitoring
41
+ enable_metrics: bool = False # Disabled by default to reduce complexity
42
+ log_level: str = "WARNING" # Reduced logging
43
+
44
+ def __post_init__(self):
45
+ """Validate configuration after initialization."""
46
+ if self.max_variable_nesting_depth < 1:
47
+ raise ValueError("max_variable_nesting_depth must be at least 1")
48
+
49
+ if self.max_template_size_bytes <= 0:
50
+ raise ValueError("max_template_size_bytes must be positive")
51
+
52
+
53
+ @dataclass
54
+ class ValidationMetrics:
55
+ """Metrics collected during template validation."""
56
+
57
+ strategy_used: str
58
+ validation_time_ms: float
59
+ template_size_bytes: int
60
+ variables_count: int
61
+ undefined_variables_found: int
62
+ fallback_used: bool = False
63
+ errors_encountered: int = 0
64
+
65
+ def to_dict(self) -> Dict[str, Any]:
66
+ """Convert metrics to dictionary for logging."""
67
+ return {
68
+ "strategy_used": self.strategy_used,
69
+ "validation_time_ms": round(self.validation_time_ms, 3),
70
+ "template_size_bytes": self.template_size_bytes,
71
+ "variables_count": self.variables_count,
72
+ "undefined_variables_found": self.undefined_variables_found,
73
+ "fallback_used": self.fallback_used,
74
+ "errors_encountered": self.errors_encountered,
75
+ }