core-framework 0.12.8__tar.gz → 0.12.10__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 (141) hide show
  1. {core_framework-0.12.8 → core_framework-0.12.10}/PKG-INFO +1 -1
  2. {core_framework-0.12.8 → core_framework-0.12.10}/core/__init__.py +10 -1
  3. {core_framework-0.12.8 → core_framework-0.12.10}/core/app.py +51 -0
  4. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/views.py +99 -23
  5. {core_framework-0.12.8 → core_framework-0.12.10}/core/config.py +20 -0
  6. core_framework-0.12.10/core/validation.py +768 -0
  7. {core_framework-0.12.8 → core_framework-0.12.10}/core/views.py +108 -0
  8. core_framework-0.12.10/docs/32-testing.md +1342 -0
  9. {core_framework-0.12.8 → core_framework-0.12.10}/pyproject.toml +1 -1
  10. core_framework-0.12.10/tests/test_validation.py +307 -0
  11. {core_framework-0.12.8 → core_framework-0.12.10}/.gitignore +0 -0
  12. {core_framework-0.12.8 → core_framework-0.12.10}/README.md +0 -0
  13. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/__init__.py +0 -0
  14. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/backends.py +0 -0
  15. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/base.py +0 -0
  16. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/decorators.py +0 -0
  17. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/hashers.py +0 -0
  18. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/helpers.py +0 -0
  19. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/middleware.py +0 -0
  20. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/models.py +0 -0
  21. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/permissions.py +0 -0
  22. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/schemas.py +0 -0
  23. {core_framework-0.12.8 → core_framework-0.12.10}/core/auth/tokens.py +0 -0
  24. {core_framework-0.12.8 → core_framework-0.12.10}/core/choices.py +0 -0
  25. {core_framework-0.12.8 → core_framework-0.12.10}/core/cli/__init__.py +0 -0
  26. {core_framework-0.12.8 → core_framework-0.12.10}/core/cli/main.py +0 -0
  27. {core_framework-0.12.8 → core_framework-0.12.10}/core/database.py +0 -0
  28. {core_framework-0.12.8 → core_framework-0.12.10}/core/datetime.py +0 -0
  29. {core_framework-0.12.8 → core_framework-0.12.10}/core/dependencies.py +0 -0
  30. {core_framework-0.12.8 → core_framework-0.12.10}/core/deployment/__init__.py +0 -0
  31. {core_framework-0.12.8 → core_framework-0.12.10}/core/deployment/docker.py +0 -0
  32. {core_framework-0.12.8 → core_framework-0.12.10}/core/deployment/kubernetes.py +0 -0
  33. {core_framework-0.12.8 → core_framework-0.12.10}/core/deployment/pm2.py +0 -0
  34. {core_framework-0.12.8 → core_framework-0.12.10}/core/exceptions.py +0 -0
  35. {core_framework-0.12.8 → core_framework-0.12.10}/core/fields.py +0 -0
  36. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/__init__.py +0 -0
  37. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/avro.py +0 -0
  38. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/base.py +0 -0
  39. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/config.py +0 -0
  40. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/confluent/__init__.py +0 -0
  41. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/confluent/consumer.py +0 -0
  42. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/confluent/producer.py +0 -0
  43. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/decorators.py +0 -0
  44. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/kafka/__init__.py +0 -0
  45. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/kafka/admin.py +0 -0
  46. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/kafka/broker.py +0 -0
  47. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/kafka/consumer.py +0 -0
  48. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/kafka/producer.py +0 -0
  49. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/rabbitmq/__init__.py +0 -0
  50. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/rabbitmq/broker.py +0 -0
  51. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/rabbitmq/consumer.py +0 -0
  52. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/rabbitmq/producer.py +0 -0
  53. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/redis/__init__.py +0 -0
  54. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/redis/broker.py +0 -0
  55. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/redis/consumer.py +0 -0
  56. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/redis/producer.py +0 -0
  57. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/registry.py +0 -0
  58. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/topics.py +0 -0
  59. {core_framework-0.12.8 → core_framework-0.12.10}/core/messaging/workers.py +0 -0
  60. {core_framework-0.12.8 → core_framework-0.12.10}/core/middleware.py +0 -0
  61. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/__init__.py +0 -0
  62. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/analyzer.py +0 -0
  63. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/cli.py +0 -0
  64. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/engine.py +0 -0
  65. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/migration.py +0 -0
  66. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/operations.py +0 -0
  67. {core_framework-0.12.8 → core_framework-0.12.10}/core/migrations/state.py +0 -0
  68. {core_framework-0.12.8 → core_framework-0.12.10}/core/models.py +0 -0
  69. {core_framework-0.12.8 → core_framework-0.12.10}/core/permissions.py +0 -0
  70. {core_framework-0.12.8 → core_framework-0.12.10}/core/querysets.py +0 -0
  71. {core_framework-0.12.8 → core_framework-0.12.10}/core/relations.py +0 -0
  72. {core_framework-0.12.8 → core_framework-0.12.10}/core/routing.py +0 -0
  73. {core_framework-0.12.8 → core_framework-0.12.10}/core/serializers.py +0 -0
  74. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/__init__.py +0 -0
  75. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/base.py +0 -0
  76. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/config.py +0 -0
  77. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/decorators.py +0 -0
  78. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/registry.py +0 -0
  79. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/scheduler.py +0 -0
  80. {core_framework-0.12.8 → core_framework-0.12.10}/core/tasks/worker.py +0 -0
  81. {core_framework-0.12.8 → core_framework-0.12.10}/core/tenancy.py +0 -0
  82. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/__init__.py +0 -0
  83. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/assertions.py +0 -0
  84. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/client.py +0 -0
  85. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/database.py +0 -0
  86. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/factories.py +0 -0
  87. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/mocks.py +0 -0
  88. {core_framework-0.12.8 → core_framework-0.12.10}/core/testing/plugin.py +0 -0
  89. {core_framework-0.12.8 → core_framework-0.12.10}/core/validators.py +0 -0
  90. {core_framework-0.12.8 → core_framework-0.12.10}/docs/01-quickstart.md +0 -0
  91. {core_framework-0.12.8 → core_framework-0.12.10}/docs/02-viewsets.md +0 -0
  92. {core_framework-0.12.8 → core_framework-0.12.10}/docs/03-authentication.md +0 -0
  93. {core_framework-0.12.8 → core_framework-0.12.10}/docs/04-messaging.md +0 -0
  94. {core_framework-0.12.8 → core_framework-0.12.10}/docs/05-multi-service.md +0 -0
  95. {core_framework-0.12.8 → core_framework-0.12.10}/docs/06-tasks.md +0 -0
  96. {core_framework-0.12.8 → core_framework-0.12.10}/docs/07-deployment.md +0 -0
  97. {core_framework-0.12.8 → core_framework-0.12.10}/docs/08-complete-example.md +0 -0
  98. {core_framework-0.12.8 → core_framework-0.12.10}/docs/09-settings.md +0 -0
  99. {core_framework-0.12.8 → core_framework-0.12.10}/docs/10-migrations.md +0 -0
  100. {core_framework-0.12.8 → core_framework-0.12.10}/docs/11-permissions.md +0 -0
  101. {core_framework-0.12.8 → core_framework-0.12.10}/docs/12-auth-backends.md +0 -0
  102. {core_framework-0.12.8 → core_framework-0.12.10}/docs/13-validators.md +0 -0
  103. {core_framework-0.12.8 → core_framework-0.12.10}/docs/14-querysets.md +0 -0
  104. {core_framework-0.12.8 → core_framework-0.12.10}/docs/15-routing.md +0 -0
  105. {core_framework-0.12.8 → core_framework-0.12.10}/docs/16-serializers.md +0 -0
  106. {core_framework-0.12.8 → core_framework-0.12.10}/docs/17-datetime.md +0 -0
  107. {core_framework-0.12.8 → core_framework-0.12.10}/docs/18-dependencies.md +0 -0
  108. {core_framework-0.12.8 → core_framework-0.12.10}/docs/19-views.md +0 -0
  109. {core_framework-0.12.8 → core_framework-0.12.10}/docs/20-fields.md +0 -0
  110. {core_framework-0.12.8 → core_framework-0.12.10}/docs/21-tenancy.md +0 -0
  111. {core_framework-0.12.8 → core_framework-0.12.10}/docs/22-replicas.md +0 -0
  112. {core_framework-0.12.8 → core_framework-0.12.10}/docs/23-soft-delete.md +0 -0
  113. {core_framework-0.12.8 → core_framework-0.12.10}/docs/24-relations.md +0 -0
  114. {core_framework-0.12.8 → core_framework-0.12.10}/docs/25-exceptions.md +0 -0
  115. {core_framework-0.12.8 → core_framework-0.12.10}/docs/26-choices.md +0 -0
  116. {core_framework-0.12.8 → core_framework-0.12.10}/docs/27-workers.md +0 -0
  117. {core_framework-0.12.8 → core_framework-0.12.10}/docs/28-avro.md +0 -0
  118. {core_framework-0.12.8 → core_framework-0.12.10}/docs/29-topics.md +0 -0
  119. {core_framework-0.12.8 → core_framework-0.12.10}/docs/30-changelog-0.12.2.md +0 -0
  120. {core_framework-0.12.8 → core_framework-0.12.10}/docs/31-middleware.md +0 -0
  121. {core_framework-0.12.8 → core_framework-0.12.10}/docs/32-migration-guide-0.12.2.md +0 -0
  122. {core_framework-0.12.8 → core_framework-0.12.10}/docs/33-changelog-0.12.3.md +0 -0
  123. {core_framework-0.12.8 → core_framework-0.12.10}/docs/99-faq-troubleshooting.md +0 -0
  124. {core_framework-0.12.8 → core_framework-0.12.10}/docs/GUIDE.md +0 -0
  125. {core_framework-0.12.8 → core_framework-0.12.10}/docs/README.md +0 -0
  126. {core_framework-0.12.8 → core_framework-0.12.10}/example/__init__.py +0 -0
  127. {core_framework-0.12.8 → core_framework-0.12.10}/example/app.py +0 -0
  128. {core_framework-0.12.8 → core_framework-0.12.10}/example/auth.py +0 -0
  129. {core_framework-0.12.8 → core_framework-0.12.10}/example/models.py +0 -0
  130. {core_framework-0.12.8 → core_framework-0.12.10}/example/schemas.py +0 -0
  131. {core_framework-0.12.8 → core_framework-0.12.10}/example/views.py +0 -0
  132. {core_framework-0.12.8 → core_framework-0.12.10}/libs/__init__.py +0 -0
  133. {core_framework-0.12.8 → core_framework-0.12.10}/main.py +0 -0
  134. {core_framework-0.12.8 → core_framework-0.12.10}/tests/__init__.py +0 -0
  135. {core_framework-0.12.8 → core_framework-0.12.10}/tests/conftest.py +0 -0
  136. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_auth_helpers.py +0 -0
  137. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_imports.py +0 -0
  138. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_models.py +0 -0
  139. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_permissions.py +0 -0
  140. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_querysets.py +0 -0
  141. {core_framework-0.12.8 → core_framework-0.12.10}/tests/test_serializers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-framework
3
- Version: 0.12.8
3
+ Version: 0.12.10
4
4
  Summary: Core Framework - Django-inspired, FastAPI-powered. Alta performance, baixo acoplamento, produtividade extrema.
5
5
  Project-URL: Homepage, https://github.com/SorPuti/core-framework
6
6
  Project-URL: Documentation, https://github.com/SorPuti/core-framework#readme
@@ -41,6 +41,15 @@ from core.dependencies import Depends, get_db, get_current_user
41
41
  from core.config import Settings, get_settings
42
42
  from core.app import CoreApp
43
43
 
44
+ # Validation
45
+ from core.validation import (
46
+ SchemaModelValidator,
47
+ SchemaModelMismatchError,
48
+ ValidationWarning,
49
+ validate_schema,
50
+ validate_all_viewsets,
51
+ )
52
+
44
53
  # Advanced Fields (UUID7, JSON, etc.)
45
54
  from core.fields import (
46
55
  uuid7,
@@ -278,7 +287,7 @@ from core.exceptions import (
278
287
  MissingDependency,
279
288
  )
280
289
 
281
- __version__ = "0.12.8"
290
+ __version__ = "0.12.10"
282
291
  __all__ = [
283
292
  # Models
284
293
  "Model",
@@ -158,6 +158,13 @@ class CoreApp:
158
158
 
159
159
  async def _startup(self) -> None:
160
160
  """Executa tarefas de startup."""
161
+ import logging
162
+ logger = logging.getLogger("core.app")
163
+
164
+ # Schema/Model validation (before database init for fail-fast)
165
+ if getattr(self.settings, "strict_validation", self.settings.debug):
166
+ await self._validate_schemas()
167
+
161
168
  # Verifica se deve usar replicas
162
169
  if self.settings.has_read_replica:
163
170
  # Inicializa com read/write replicas
@@ -195,6 +202,50 @@ class CoreApp:
195
202
  if hasattr(result, "__await__"):
196
203
  await result
197
204
 
205
+ async def _validate_schemas(self) -> None:
206
+ """
207
+ Validate all ViewSet schemas against their models.
208
+
209
+ Called during startup if strict_validation is enabled.
210
+ In DEBUG mode, raises SchemaModelMismatchError on critical issues.
211
+ In production, logs errors but continues.
212
+ """
213
+ import logging
214
+ logger = logging.getLogger("core.app")
215
+
216
+ try:
217
+ from core.views import validate_pending_viewsets
218
+ from core.validation import SchemaModelMismatchError
219
+
220
+ logger.info("Running schema/model validations...")
221
+
222
+ # In debug mode, fail fast on critical issues
223
+ strict = self.settings.debug
224
+
225
+ try:
226
+ issues = validate_pending_viewsets(strict=strict)
227
+
228
+ if issues:
229
+ logger.warning(
230
+ f"Schema validation completed with {len(issues)} issues"
231
+ )
232
+ else:
233
+ logger.info("Schema validation passed")
234
+
235
+ except SchemaModelMismatchError as e:
236
+ if self.settings.debug:
237
+ logger.error(f"Schema validation failed: {e}")
238
+ raise RuntimeError(
239
+ f"Schema validation errors (set DEBUG=False to skip):\n{e}"
240
+ ) from e
241
+ else:
242
+ logger.error(f"Schema validation errors (ignored): {e}")
243
+
244
+ except ImportError:
245
+ logger.debug("Validation module not available, skipping")
246
+ except Exception as e:
247
+ logger.warning(f"Could not validate schemas: {e}")
248
+
198
249
  async def _shutdown(self) -> None:
199
250
  """Executa tarefas de shutdown."""
200
251
  # Executa callbacks customizados
@@ -108,14 +108,22 @@ class AuthViewSet(ViewSet):
108
108
 
109
109
  def _get_register_schema(self) -> type:
110
110
  """
111
- Bug #5 Fix: Get registration schema with extra fields support.
111
+ Get registration schema with STRICT extra fields support.
112
112
 
113
- If extra_register_fields is set, creates a dynamic schema that
114
- accepts those additional fields.
113
+ This method validates against the model to determine if fields are
114
+ required (NOT NULL) or optional (nullable).
115
+
116
+ Rules:
117
+ - If model column is NOT NULL and has no default -> REQUIRED in schema
118
+ - If model column is nullable or has default -> OPTIONAL in schema
115
119
 
116
120
  Returns:
117
121
  Pydantic schema class for registration
118
122
  """
123
+ import logging
124
+ import warnings
125
+ logger = logging.getLogger("core.auth")
126
+
119
127
  # If register_schema was explicitly overridden, use it
120
128
  if self.register_schema != BaseRegisterInput:
121
129
  return self.register_schema
@@ -124,32 +132,53 @@ class AuthViewSet(ViewSet):
124
132
  if not self.extra_register_fields:
125
133
  return BaseRegisterInput
126
134
 
127
- # Create dynamic schema with extra fields
135
+ # Return cached schema if available
128
136
  if self._dynamic_register_schema is not None:
129
137
  return self._dynamic_register_schema
130
138
 
131
- # Build extra fields - all as optional strings by default
132
- # Users can provide type hints via annotations in User model
133
- extra_fields = {}
134
139
  User = self._get_user_model()
135
- user_annotations = getattr(User, "__annotations__", {})
140
+
141
+ # Get model column info for nullable/required check
142
+ model_columns = {}
143
+ try:
144
+ from sqlalchemy import inspect
145
+ mapper = inspect(User)
146
+ model_columns = {col.name: col for col in mapper.columns}
147
+ except Exception as e:
148
+ logger.debug(f"Could not inspect User model: {e}")
149
+
150
+ extra_fields = {}
136
151
 
137
152
  for field_name in self.extra_register_fields:
138
- # Try to get type from User model
139
- field_type = user_annotations.get(field_name, str)
140
- # Extract actual type from Mapped[...] if needed
141
- field_type_str = str(field_type)
142
- if "Mapped[" in field_type_str:
143
- # It's a Mapped type, try to extract inner type
144
- if "str" in field_type_str:
145
- extra_fields[field_name] = (str | None, None)
146
- elif "int" in field_type_str:
147
- extra_fields[field_name] = (int | None, None)
148
- elif "bool" in field_type_str:
149
- extra_fields[field_name] = (bool | None, None)
153
+ col = model_columns.get(field_name)
154
+
155
+ if col is not None:
156
+ # Determine type from model
157
+ python_type = self._get_python_type_from_column(col.type)
158
+
159
+ # Check if field is required
160
+ is_nullable = col.nullable
161
+ has_default = col.default is not None or col.server_default is not None
162
+
163
+ if not is_nullable and not has_default:
164
+ # REQUIRED field - use ... (Ellipsis) as default
165
+ extra_fields[field_name] = (python_type, ...)
166
+ logger.info(
167
+ f"Field '{field_name}' is NOT NULL in model, "
168
+ f"adding as REQUIRED to register schema"
169
+ )
150
170
  else:
151
- extra_fields[field_name] = (str | None, None)
171
+ # Optional field
172
+ extra_fields[field_name] = (python_type | None, None)
152
173
  else:
174
+ # Field not in model columns, warn and make optional
175
+ warnings.warn(
176
+ f"Field '{field_name}' in extra_register_fields "
177
+ f"not found in {User.__name__} model columns. "
178
+ f"Adding as optional str.",
179
+ UserWarning,
180
+ stacklevel=2,
181
+ )
153
182
  extra_fields[field_name] = (str | None, None)
154
183
 
155
184
  # Create dynamic model
@@ -160,14 +189,61 @@ class AuthViewSet(ViewSet):
160
189
  **extra_fields,
161
190
  )
162
191
 
163
- # Allow extra fields
192
+ # Allow extra fields (ignore unknown)
164
193
  self._dynamic_register_schema.model_config = {
165
194
  **BaseRegisterInput.model_config,
166
- "extra": "ignore", # Ignore unknown fields instead of forbidding
195
+ "extra": "ignore",
167
196
  }
168
197
 
169
198
  return self._dynamic_register_schema
170
199
 
200
+ def _get_python_type_from_column(self, sa_type) -> type:
201
+ """
202
+ Convert SQLAlchemy column type to Python type.
203
+
204
+ Args:
205
+ sa_type: SQLAlchemy type instance
206
+
207
+ Returns:
208
+ Corresponding Python type
209
+ """
210
+ from sqlalchemy import String, Integer, Boolean, Float, Text, DateTime, Date
211
+
212
+ type_map = {
213
+ String: str,
214
+ Text: str,
215
+ Integer: int,
216
+ Boolean: bool,
217
+ Float: float,
218
+ }
219
+
220
+ for sa_cls, py_type in type_map.items():
221
+ if isinstance(sa_type, sa_cls):
222
+ return py_type
223
+
224
+ # Check type name for dialect-specific types
225
+ type_name = type(sa_type).__name__
226
+ if "String" in type_name or "Text" in type_name or "VARCHAR" in type_name:
227
+ return str
228
+ if "Integer" in type_name or "INT" in type_name:
229
+ return int
230
+ if "Boolean" in type_name or "BOOL" in type_name:
231
+ return bool
232
+ if "Float" in type_name or "Numeric" in type_name or "Decimal" in type_name:
233
+ return float
234
+ if "DateTime" in type_name or "Timestamp" in type_name:
235
+ from datetime import datetime
236
+ return datetime
237
+ if "Date" in type_name:
238
+ from datetime import date
239
+ return date
240
+ if "UUID" in type_name:
241
+ from uuid import UUID
242
+ return UUID
243
+
244
+ # Default to str
245
+ return str
246
+
171
247
  def _create_tokens(self, user) -> dict:
172
248
  """
173
249
  Bug #6 Fix: Create access and refresh tokens using current API.
@@ -76,6 +76,26 @@ class Settings(BaseSettings):
76
76
  description="Chave secreta para criptografia e tokens",
77
77
  )
78
78
 
79
+ # =========================================================================
80
+ # Validation
81
+ # =========================================================================
82
+
83
+ strict_validation: bool = PydanticField(
84
+ default=True,
85
+ description=(
86
+ "Habilita validação rigorosa de schemas contra models. "
87
+ "Em modo strict, erros críticos (campo NOT NULL opcional no schema) "
88
+ "causam falha no startup em DEBUG mode."
89
+ ),
90
+ )
91
+ validation_fail_fast: bool | None = PydanticField(
92
+ default=None,
93
+ description=(
94
+ "Se True, falha no primeiro erro de validação. "
95
+ "Se None, usa valor de DEBUG."
96
+ ),
97
+ )
98
+
79
99
  # =========================================================================
80
100
  # Database
81
101
  # =========================================================================