core-framework 0.12.2__tar.gz → 0.12.3__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.
- {core_framework-0.12.2 → core_framework-0.12.3}/PKG-INFO +1 -1
- {core_framework-0.12.2 → core_framework-0.12.3}/core/__init__.py +1 -1
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/middleware.py +57 -18
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/models.py +4 -6
- {core_framework-0.12.2 → core_framework-0.12.3}/core/middleware.py +5 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/engine.py +68 -2
- core_framework-0.12.3/docs/33-changelog-0.12.3.md +181 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/pyproject.toml +1 -1
- {core_framework-0.12.2 → core_framework-0.12.3}/.gitignore +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/README.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/app.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/backends.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/base.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/decorators.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/hashers.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/permissions.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/schemas.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/tokens.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/auth/views.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/choices.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/cli/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/cli/main.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/config.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/database.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/datetime.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/dependencies.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/deployment/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/deployment/docker.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/deployment/kubernetes.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/deployment/pm2.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/exceptions.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/fields.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/avro.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/base.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/config.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/confluent/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/confluent/consumer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/confluent/producer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/decorators.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/kafka/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/kafka/admin.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/kafka/broker.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/kafka/consumer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/kafka/producer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/rabbitmq/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/rabbitmq/broker.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/rabbitmq/consumer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/rabbitmq/producer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/redis/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/redis/broker.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/redis/consumer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/redis/producer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/registry.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/topics.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/messaging/workers.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/analyzer.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/cli.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/migration.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/operations.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/migrations/state.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/models.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/permissions.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/querysets.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/relations.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/routing.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/serializers.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/base.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/config.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/decorators.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/registry.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/scheduler.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tasks/worker.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/tenancy.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/validators.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/core/views.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/01-quickstart.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/02-viewsets.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/03-authentication.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/04-messaging.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/05-multi-service.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/06-tasks.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/07-deployment.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/08-complete-example.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/09-settings.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/10-migrations.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/11-permissions.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/12-auth-backends.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/13-validators.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/14-querysets.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/15-routing.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/16-serializers.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/17-datetime.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/18-dependencies.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/19-views.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/20-fields.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/21-tenancy.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/22-replicas.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/23-soft-delete.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/24-relations.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/25-exceptions.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/26-choices.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/27-workers.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/28-avro.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/29-topics.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/30-changelog-0.12.2.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/31-middleware.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/32-migration-guide-0.12.2.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/99-faq-troubleshooting.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/GUIDE.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/docs/README.md +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/app.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/auth.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/models.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/schemas.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/example/views.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/libs/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/main.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/tests/__init__.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/tests/conftest.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/tests/test_models.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/tests/test_querysets.py +0 -0
- {core_framework-0.12.2 → core_framework-0.12.3}/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.
|
|
3
|
+
Version: 0.12.3
|
|
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
|
|
@@ -182,26 +182,65 @@ class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
|
182
182
|
if User is None:
|
|
183
183
|
return None
|
|
184
184
|
|
|
185
|
-
# Get database session
|
|
186
|
-
|
|
185
|
+
# Bug #3 & #4 Fix: Get database session correctly
|
|
186
|
+
# The middleware runs outside FastAPI DI context, so we need to
|
|
187
|
+
# handle database session creation carefully
|
|
188
|
+
db = await self._get_db_session()
|
|
189
|
+
if db is None:
|
|
190
|
+
return None
|
|
187
191
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if user is None:
|
|
196
|
-
return None
|
|
197
|
-
|
|
198
|
-
# Check if user is active
|
|
199
|
-
if hasattr(user, "is_active") and not user.is_active:
|
|
200
|
-
return None
|
|
201
|
-
|
|
202
|
-
return user
|
|
203
|
-
except Exception:
|
|
192
|
+
try:
|
|
193
|
+
# Convert user_id to correct type
|
|
194
|
+
user_id_converted = self._convert_user_id(user_id, User)
|
|
195
|
+
|
|
196
|
+
user = await User.objects.using(db).filter(id=user_id_converted).first()
|
|
197
|
+
|
|
198
|
+
if user is None:
|
|
204
199
|
return None
|
|
200
|
+
|
|
201
|
+
# Check if user is active
|
|
202
|
+
if hasattr(user, "is_active") and not user.is_active:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
return user
|
|
206
|
+
except Exception:
|
|
207
|
+
return None
|
|
208
|
+
finally:
|
|
209
|
+
await db.close()
|
|
210
|
+
|
|
211
|
+
async def _get_db_session(self) -> Any | None:
|
|
212
|
+
"""
|
|
213
|
+
Get a database session for authentication.
|
|
214
|
+
|
|
215
|
+
Bug #3 & #4 Fix: Handles both initialized and uninitialized database states.
|
|
216
|
+
Creates session directly from engine if normal path fails.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
AsyncSession or None if database not available
|
|
220
|
+
"""
|
|
221
|
+
# Try 1: Use the standard get_read_session (if database is initialized)
|
|
222
|
+
try:
|
|
223
|
+
from core.database import get_read_session, _read_session_factory
|
|
224
|
+
|
|
225
|
+
if _read_session_factory is not None:
|
|
226
|
+
return _read_session_factory()
|
|
227
|
+
except (RuntimeError, ImportError):
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
# Try 2: Create session from settings (lazy initialization)
|
|
231
|
+
try:
|
|
232
|
+
from core.config import get_settings
|
|
233
|
+
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
|
234
|
+
|
|
235
|
+
settings = get_settings()
|
|
236
|
+
db_url = getattr(settings, 'database_read_url', None) or getattr(settings, 'database_url', None)
|
|
237
|
+
|
|
238
|
+
if db_url:
|
|
239
|
+
engine = create_async_engine(db_url, echo=False)
|
|
240
|
+
session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
241
|
+
return session_factory()
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
205
244
|
|
|
206
245
|
return None
|
|
207
246
|
|
|
@@ -27,12 +27,14 @@ Uso:
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
29
|
from typing import Any, ClassVar, TYPE_CHECKING
|
|
30
|
+
from uuid import UUID
|
|
30
31
|
|
|
31
32
|
from sqlalchemy import Table, Column, Integer, ForeignKey, inspect
|
|
32
33
|
from sqlalchemy.orm import Mapped, relationship, declared_attr
|
|
33
34
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
|
34
35
|
|
|
35
36
|
from core.models import Model, Field
|
|
37
|
+
from core.fields import AdvancedField
|
|
36
38
|
from core.auth.base import get_password_hasher, get_auth_config
|
|
37
39
|
from core.datetime import timezone, DateTime
|
|
38
40
|
|
|
@@ -69,7 +71,6 @@ def _get_pk_column_type(model_class: type) -> type:
|
|
|
69
71
|
from sqlalchemy import Integer, BigInteger, String
|
|
70
72
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
|
71
73
|
from sqlalchemy import Uuid
|
|
72
|
-
import uuid
|
|
73
74
|
|
|
74
75
|
# Verifica cache primeiro
|
|
75
76
|
cache_key = f"{model_class.__module__}.{model_class.__name__}"
|
|
@@ -790,12 +791,9 @@ class AbstractUUIDUser(AbstractUser):
|
|
|
790
791
|
|
|
791
792
|
__abstract__ = True
|
|
792
793
|
|
|
793
|
-
# Importa aqui para evitar circular import
|
|
794
|
-
from core.fields import AdvancedField
|
|
795
|
-
from uuid import UUID as UUIDType
|
|
796
|
-
|
|
797
794
|
# Override: usa UUID como PK
|
|
798
|
-
|
|
795
|
+
# UUID importado no topo do módulo para resolução correta de tipos
|
|
796
|
+
id: Mapped[UUID] = AdvancedField.uuid_pk()
|
|
799
797
|
|
|
800
798
|
|
|
801
799
|
# =============================================================================
|
|
@@ -736,11 +736,16 @@ class SecurityHeadersMiddleware(BaseMiddleware):
|
|
|
736
736
|
# =============================================================================
|
|
737
737
|
|
|
738
738
|
_builtin_middlewares.update({
|
|
739
|
+
# Pre-built middlewares from this module
|
|
739
740
|
"timing": "core.middleware.TimingMiddleware",
|
|
740
741
|
"request_id": "core.middleware.RequestIDMiddleware",
|
|
741
742
|
"logging": "core.middleware.LoggingMiddleware",
|
|
742
743
|
"maintenance": "core.middleware.MaintenanceModeMiddleware",
|
|
743
744
|
"security_headers": "core.middleware.SecurityHeadersMiddleware",
|
|
745
|
+
# Auth middlewares (ensure they're registered even if initial dict failed)
|
|
746
|
+
"auth": "core.auth.middleware.AuthenticationMiddleware",
|
|
747
|
+
"authentication": "core.auth.middleware.AuthenticationMiddleware",
|
|
748
|
+
"optional_auth": "core.auth.middleware.OptionalAuthenticationMiddleware",
|
|
744
749
|
})
|
|
745
750
|
|
|
746
751
|
|
|
@@ -255,6 +255,71 @@ class MigrationEngine:
|
|
|
255
255
|
index=col.index,
|
|
256
256
|
)
|
|
257
257
|
|
|
258
|
+
def _topological_sort_tables(self, tables: list[TableState]) -> list[TableState]:
|
|
259
|
+
"""
|
|
260
|
+
Bug #5 Fix: Ordena tabelas em ordem topológica baseada em FKs.
|
|
261
|
+
|
|
262
|
+
Tabelas referenciadas por FKs são criadas ANTES das que as referenciam.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
tables: Lista de TableState a ordenar
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Lista ordenada topologicamente
|
|
269
|
+
"""
|
|
270
|
+
if not tables:
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
# Mapeia nome -> tabela
|
|
274
|
+
table_map = {t.name: t for t in tables}
|
|
275
|
+
|
|
276
|
+
# Constrói grafo de dependências
|
|
277
|
+
# dependencias[A] = {B, C} significa A depende de B e C (A tem FK para B e C)
|
|
278
|
+
dependencies: dict[str, set[str]] = {t.name: set() for t in tables}
|
|
279
|
+
|
|
280
|
+
for table in tables:
|
|
281
|
+
for fk in table.foreign_keys:
|
|
282
|
+
ref_table = fk.references_table
|
|
283
|
+
# Só adiciona dependência se a tabela referenciada também está sendo criada
|
|
284
|
+
# (tabelas já existentes não precisam ser consideradas)
|
|
285
|
+
if ref_table in table_map:
|
|
286
|
+
dependencies[table.name].add(ref_table)
|
|
287
|
+
|
|
288
|
+
# Algoritmo de Kahn para ordenação topológica
|
|
289
|
+
result = []
|
|
290
|
+
|
|
291
|
+
# Encontra tabelas sem dependências (grau de entrada 0)
|
|
292
|
+
no_deps = [name for name, deps in dependencies.items() if not deps]
|
|
293
|
+
|
|
294
|
+
while no_deps:
|
|
295
|
+
# Remove uma tabela sem dependências
|
|
296
|
+
name = no_deps.pop(0)
|
|
297
|
+
result.append(table_map[name])
|
|
298
|
+
|
|
299
|
+
# Remove esta tabela das dependências de outras
|
|
300
|
+
for other_name, deps in dependencies.items():
|
|
301
|
+
if name in deps:
|
|
302
|
+
deps.remove(name)
|
|
303
|
+
# Se não tem mais dependências, adiciona à fila
|
|
304
|
+
if not deps and other_name not in [t.name for t in result]:
|
|
305
|
+
no_deps.append(other_name)
|
|
306
|
+
|
|
307
|
+
# Verifica ciclo (se sobrou alguma tabela com dependências)
|
|
308
|
+
remaining = [t for t in tables if t not in result]
|
|
309
|
+
if remaining:
|
|
310
|
+
# Há um ciclo - adiciona as tabelas restantes no final
|
|
311
|
+
# (o banco vai falhar, mas pelo menos o erro será claro)
|
|
312
|
+
import warnings
|
|
313
|
+
cycle_tables = [t.name for t in remaining]
|
|
314
|
+
warnings.warn(
|
|
315
|
+
f"Circular FK dependency detected involving tables: {cycle_tables}. "
|
|
316
|
+
"Migration may fail. Consider breaking the cycle with nullable FKs.",
|
|
317
|
+
RuntimeWarning,
|
|
318
|
+
)
|
|
319
|
+
result.extend(remaining)
|
|
320
|
+
|
|
321
|
+
return result
|
|
322
|
+
|
|
258
323
|
def _diff_to_operations(self, diff: SchemaDiff) -> list[Operation]:
|
|
259
324
|
"""Converte SchemaDiff em lista de operações."""
|
|
260
325
|
from core.migrations.operations import CreateEnum, DropEnum, AlterEnum
|
|
@@ -284,8 +349,9 @@ class MigrationEngine:
|
|
|
284
349
|
new_values=new_enum.values,
|
|
285
350
|
))
|
|
286
351
|
|
|
287
|
-
# 3. Criar tabelas
|
|
288
|
-
|
|
352
|
+
# 3. Criar tabelas (BUG #5 FIX: em ordem topológica)
|
|
353
|
+
sorted_tables = self._topological_sort_tables(diff.tables_to_create)
|
|
354
|
+
for table in sorted_tables:
|
|
289
355
|
columns = [self._column_state_to_def(col) for col in table.columns.values()]
|
|
290
356
|
foreign_keys = [
|
|
291
357
|
ForeignKeyDef(
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Changelog v0.12.3
|
|
2
|
+
|
|
3
|
+
**Data de Release:** 03/02/2026
|
|
4
|
+
|
|
5
|
+
Esta versão corrige 5 bugs críticos encontrados na v0.12.2 que impediam o funcionamento do `AuthenticationMiddleware` e causavam erros em models com UUID.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Bugs Corrigidos
|
|
10
|
+
|
|
11
|
+
### Bug #1: `AbstractUUIDUser` usa tipo não resolvível
|
|
12
|
+
**Arquivo:** `core/auth/models.py`
|
|
13
|
+
|
|
14
|
+
**Problema:** A classe `AbstractUUIDUser` definia o campo `id` com um tipo `UUIDType` que não era importado corretamente no escopo do módulo, causando erro de resolução de tipo pelo SQLAlchemy.
|
|
15
|
+
|
|
16
|
+
**Antes:**
|
|
17
|
+
```python
|
|
18
|
+
class AbstractUUIDUser(AbstractUser):
|
|
19
|
+
# Import dentro da classe (problemático)
|
|
20
|
+
from uuid import UUID as UUIDType
|
|
21
|
+
id: Mapped[UUIDType] = AdvancedField.uuid_pk()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Erro:**
|
|
25
|
+
```
|
|
26
|
+
MappedAnnotationError: Could not resolve all types within mapped annotation: "Mapped[UUIDType]"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Depois:**
|
|
30
|
+
```python
|
|
31
|
+
# Imports no topo do módulo
|
|
32
|
+
from uuid import UUID
|
|
33
|
+
from core.fields import AdvancedField
|
|
34
|
+
|
|
35
|
+
class AbstractUUIDUser(AbstractUser):
|
|
36
|
+
id: Mapped[UUID] = AdvancedField.uuid_pk()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Bug #2: Shortcut `"auth"` não registrado nos middleware shortcuts
|
|
42
|
+
**Arquivo:** `core/middleware.py`
|
|
43
|
+
|
|
44
|
+
**Problema:** O shortcut `"auth"` era definido no início do arquivo, mas podia falhar dependendo da ordem de carregamento dos módulos.
|
|
45
|
+
|
|
46
|
+
**Correção:** Adicionados os shortcuts de auth também no `_builtin_middlewares.update()` para garantir registro mesmo em casos de edge.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
_builtin_middlewares.update({
|
|
50
|
+
# ... outros middlewares ...
|
|
51
|
+
"auth": "core.auth.middleware.AuthenticationMiddleware",
|
|
52
|
+
"authentication": "core.auth.middleware.AuthenticationMiddleware",
|
|
53
|
+
"optional_auth": "core.auth.middleware.OptionalAuthenticationMiddleware",
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Bug #3: `AuthenticationMiddleware` usa `async for` incorretamente
|
|
60
|
+
**Arquivo:** `core/auth/middleware.py`
|
|
61
|
+
|
|
62
|
+
**Problema:** O middleware usava `async for db in get_read_session()` mas `get_read_session()` é uma coroutine que retorna `AsyncSession` diretamente, não um async generator.
|
|
63
|
+
|
|
64
|
+
**Antes:**
|
|
65
|
+
```python
|
|
66
|
+
async for db in get_read_session(): # ERRADO!
|
|
67
|
+
# ...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Erro:**
|
|
71
|
+
```
|
|
72
|
+
RuntimeWarning: coroutine 'get_read_session' was never awaited
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Depois:**
|
|
76
|
+
```python
|
|
77
|
+
db = await self._get_db_session()
|
|
78
|
+
try:
|
|
79
|
+
# ... usar db ...
|
|
80
|
+
finally:
|
|
81
|
+
await db.close()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Bug #4: `get_read_session()` falha fora do contexto FastAPI
|
|
87
|
+
**Arquivo:** `core/auth/middleware.py`
|
|
88
|
+
|
|
89
|
+
**Problema:** O middleware executa antes do lifecycle de dependency injection do FastAPI, então `init_replicas()` pode não ter sido chamado, causando erro de "Database not initialized".
|
|
90
|
+
|
|
91
|
+
**Erro:**
|
|
92
|
+
```
|
|
93
|
+
RuntimeError: Database not initialized. Call init_replicas() first.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Correção:** Implementado método `_get_db_session()` no middleware que:
|
|
97
|
+
1. Tenta usar o session factory padrão se já inicializado
|
|
98
|
+
2. Como fallback, cria uma sessão diretamente a partir das settings
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
async def _get_db_session(self) -> AsyncSession | None:
|
|
102
|
+
# Try 1: Use standard factory if initialized
|
|
103
|
+
try:
|
|
104
|
+
if _read_session_factory is not None:
|
|
105
|
+
return _read_session_factory()
|
|
106
|
+
except RuntimeError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Try 2: Create from settings (lazy init)
|
|
110
|
+
try:
|
|
111
|
+
settings = get_settings()
|
|
112
|
+
db_url = settings.database_url
|
|
113
|
+
engine = create_async_engine(db_url)
|
|
114
|
+
session_factory = async_sessionmaker(engine)
|
|
115
|
+
return session_factory()
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Bug #5: Ordenação topológica incorreta nas migrations
|
|
125
|
+
**Arquivo:** `core/migrations/engine.py`
|
|
126
|
+
|
|
127
|
+
**Problema:** Tabelas com foreign keys eram criadas antes das tabelas que elas referenciam, causando erro de "relation does not exist".
|
|
128
|
+
|
|
129
|
+
**Erro:**
|
|
130
|
+
```
|
|
131
|
+
asyncpg.exceptions.UndefinedTableError: relation "workspaces" does not exist
|
|
132
|
+
[SQL: CREATE TABLE "domains" (..., FOREIGN KEY ("workspace_id") REFERENCES "workspaces" ...)]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Correção:** Implementado algoritmo de ordenação topológica (Kahn's algorithm) no método `_topological_sort_tables()` que:
|
|
136
|
+
1. Constrói grafo de dependências baseado em FKs
|
|
137
|
+
2. Ordena tabelas garantindo que referenciadas sejam criadas primeiro
|
|
138
|
+
3. Detecta e alerta sobre dependências circulares
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
def _topological_sort_tables(self, tables: list[TableState]) -> list[TableState]:
|
|
142
|
+
# Constrói grafo de dependências
|
|
143
|
+
dependencies = {t.name: set() for t in tables}
|
|
144
|
+
for table in tables:
|
|
145
|
+
for fk in table.foreign_keys:
|
|
146
|
+
if fk.references_table in table_map:
|
|
147
|
+
dependencies[table.name].add(fk.references_table)
|
|
148
|
+
|
|
149
|
+
# Kahn's algorithm para ordenação topológica
|
|
150
|
+
# ... ordena tabelas por dependências ...
|
|
151
|
+
return sorted_tables
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Resumo das Mudanças
|
|
157
|
+
|
|
158
|
+
| Bug | Arquivo | Correção |
|
|
159
|
+
|-----|---------|----------|
|
|
160
|
+
| #1 | `core/auth/models.py` | Import de `UUID` movido para nível do módulo |
|
|
161
|
+
| #2 | `core/middleware.py` | Shortcuts de auth adicionados no `.update()` |
|
|
162
|
+
| #3 | `core/auth/middleware.py` | Removido `async for`, usa `await` corretamente |
|
|
163
|
+
| #4 | `core/auth/middleware.py` | Novo método `_get_db_session()` com lazy init |
|
|
164
|
+
| #5 | `core/migrations/engine.py` | Implementada ordenação topológica de tabelas |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Como Atualizar
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install --upgrade core-framework==0.12.3
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Impacto
|
|
175
|
+
|
|
176
|
+
Com estas correções:
|
|
177
|
+
- `AbstractUUIDUser` funciona corretamente para modelos com UUID PK
|
|
178
|
+
- `AuthenticationMiddleware` funciona out-of-the-box
|
|
179
|
+
- `request.state.user` é populado corretamente
|
|
180
|
+
- Endpoint `/auth/me` funciona sem workarounds
|
|
181
|
+
- Migrations criam tabelas na ordem correta de dependências
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|