core-framework 0.12.2__py3-none-any.whl → 0.12.3__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.
- core/__init__.py +1 -1
- core/auth/middleware.py +57 -18
- core/auth/models.py +4 -6
- core/middleware.py +5 -0
- core/migrations/engine.py +68 -2
- {core_framework-0.12.2.dist-info → core_framework-0.12.3.dist-info}/METADATA +1 -1
- {core_framework-0.12.2.dist-info → core_framework-0.12.3.dist-info}/RECORD +9 -9
- {core_framework-0.12.2.dist-info → core_framework-0.12.3.dist-info}/WHEEL +0 -0
- {core_framework-0.12.2.dist-info → core_framework-0.12.3.dist-info}/entry_points.txt +0 -0
core/__init__.py
CHANGED
core/auth/middleware.py
CHANGED
|
@@ -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
|
|
core/auth/models.py
CHANGED
|
@@ -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
|
# =============================================================================
|
core/middleware.py
CHANGED
|
@@ -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
|
|
core/migrations/engine.py
CHANGED
|
@@ -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(
|
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
core/__init__.py,sha256=
|
|
1
|
+
core/__init__.py,sha256=sy9liwZ93LJntl_s54pgi3oAM8vHPC_dgaOeVAydmtY,12058
|
|
2
2
|
core/app.py,sha256=sCA3mJI696i7MIjrPxfOr5zEYt0njarQfHHy3EAajk4,21071
|
|
3
3
|
core/choices.py,sha256=rhcL3p2dB7RK99zIilpmoTFVcibQEIaRpz0CY0kImCE,10502
|
|
4
4
|
core/config.py,sha256=2-MVF9nLoYmxpYYH_Gzn4-Sa3MU87YZskRPtlNyhg6Q,14049
|
|
@@ -7,7 +7,7 @@ core/datetime.py,sha256=bzqlAj3foA-lzbhXjlEiDNR2D-nwXu9mpxpdcUb-Pmw,32730
|
|
|
7
7
|
core/dependencies.py,sha256=LrNLbmQhXoCPRvoRPRMGNo0w6_l4NLGWeeTHzpUU36M,11582
|
|
8
8
|
core/exceptions.py,sha256=cdcffeYnMzCbS4hApOYNmPVNbPUpKcrgJbi3nKhqTuI,22702
|
|
9
9
|
core/fields.py,sha256=F2NdToowkJ_LFvPN9KVyxIFES1AlVDy7WkEp-8UiBpA,9327
|
|
10
|
-
core/middleware.py,sha256=
|
|
10
|
+
core/middleware.py,sha256=MZVw7smJ2MiycYvkaYIC2cNpyqYGk3m-eeoDandqZU4,23506
|
|
11
11
|
core/models.py,sha256=jdNdjRPKBZiOBOgg8CmDYBwmuWdD7twCIpqINLGY4Q4,33788
|
|
12
12
|
core/permissions.py,sha256=HQu_eNBEodJyR50CYcFvdCw9LlEfhI5vJbljV5VIs7M,10162
|
|
13
13
|
core/querysets.py,sha256=Z87-U06Un_xA9GKwcjXx0yzw6F_xf_tvG_rBT5UGL9c,22678
|
|
@@ -22,8 +22,8 @@ core/auth/backends.py,sha256=R-siIE8TrNqDHkCx42zXN1WVvvuWOun1nj8D5elrC9g,10425
|
|
|
22
22
|
core/auth/base.py,sha256=Q7vXgwTmgdmyW7G8eJmDket2bKB_8YFnraZ_kK9_gTs,21425
|
|
23
23
|
core/auth/decorators.py,sha256=tmC7prKUvHuzQ3J872nM6r83DR9d82dCLXKLvUB1Os8,12288
|
|
24
24
|
core/auth/hashers.py,sha256=0gIf67TU0k5H744FADpyh9_ugxA7m3mhYPZxLh_lEtc,12808
|
|
25
|
-
core/auth/middleware.py,sha256=
|
|
26
|
-
core/auth/models.py,sha256=
|
|
25
|
+
core/auth/middleware.py,sha256=r4F3AIb4k9Z7gbTwcyG-MVWCyGikQqP54IHNKmyNtdc,10963
|
|
26
|
+
core/auth/models.py,sha256=3ekHuaiSNhyQ6K1-w-TNmvtC406qhTT8AttA03Zl3pQ,32636
|
|
27
27
|
core/auth/permissions.py,sha256=v3ykAgNpq5wJ0NkuC_FuveMctOkDfM9Xp11XEnUAuBg,12461
|
|
28
28
|
core/auth/schemas.py,sha256=L0W96dOD348rJDGeu1K5Rz3aJj-GdwMr2vbwwsYfo2g,3469
|
|
29
29
|
core/auth/tokens.py,sha256=jk-TnMRdVGPhy6pWqSF2Ef8RTqLrP6Mkuo5GvRQh9no,8489
|
|
@@ -61,7 +61,7 @@ core/messaging/redis/producer.py,sha256=F9NA1GpYvN-wdW5Ilzi49rrAmxfBmicXX3l6sABW
|
|
|
61
61
|
core/migrations/__init__.py,sha256=OF_7XQ9x9V_BWr3d8vDZk8W5QYT0RO3ZXNFnOg8UgDI,1908
|
|
62
62
|
core/migrations/analyzer.py,sha256=QiwG_Xf_-Mb-Kp4hstkF8xNJD0Tvxgz20vqvYZ6xEXM,27287
|
|
63
63
|
core/migrations/cli.py,sha256=mR3lIFTlXSvupFOPVlfuC-urJyDfNFR9nqYZn4TjIco,12019
|
|
64
|
-
core/migrations/engine.py,sha256=
|
|
64
|
+
core/migrations/engine.py,sha256=jk8-wX8aKNBidUGyQ7ckHcUsukNJYpgSva-Sp-Iu-L4,31590
|
|
65
65
|
core/migrations/migration.py,sha256=Xv5MSNLvGAR9wnuMc4GRwciUSuU22AxWlWZP-hsVliI,2748
|
|
66
66
|
core/migrations/operations.py,sha256=wZLui76zU-MDiJfyn3l3NBRGJw1V4XF8tViSV3kvN6A,28651
|
|
67
67
|
core/migrations/state.py,sha256=eb_EYTE1tG-xQIwliS_-QTgr0y8-Jj0Va4C3nfpMrd4,15324
|
|
@@ -78,7 +78,7 @@ example/auth.py,sha256=zBpLutb8lVKnGfQqQ2wnyygsSutHYZzeJBuhnFhxBaQ,4971
|
|
|
78
78
|
example/models.py,sha256=xKdx0kJ9n0tZ7sCce3KhV3BTvKvsh6m7G69eFm3ukf0,4549
|
|
79
79
|
example/schemas.py,sha256=wJ9QofnuHp4PjtM_IuMMBLVFVDJ4YlwcF6uQm1ooKiY,6139
|
|
80
80
|
example/views.py,sha256=GQwgQcW6yoeUIDbF7-lsaZV7cs8G1S1vGVtiwVpZIQE,14338
|
|
81
|
-
core_framework-0.12.
|
|
82
|
-
core_framework-0.12.
|
|
83
|
-
core_framework-0.12.
|
|
84
|
-
core_framework-0.12.
|
|
81
|
+
core_framework-0.12.3.dist-info/METADATA,sha256=XE6KCQJj3FgemixbQXBicIsp3CrsX5al-SzEPH-vtNk,12791
|
|
82
|
+
core_framework-0.12.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
83
|
+
core_framework-0.12.3.dist-info/entry_points.txt,sha256=lQ65IAOpieqU1VcHCUReeyandpyy8IKGix6IkJW_4Is,39
|
|
84
|
+
core_framework-0.12.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|