core-framework 0.12.11__py3-none-any.whl → 0.12.13__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/views.py +37 -0
- core/cli/main.py +37 -3
- core/migrations/operations.py +49 -24
- core/routing.py +15 -2
- core/views.py +12 -2
- {core_framework-0.12.11.dist-info → core_framework-0.12.13.dist-info}/METADATA +1 -1
- {core_framework-0.12.11.dist-info → core_framework-0.12.13.dist-info}/RECORD +10 -10
- {core_framework-0.12.11.dist-info → core_framework-0.12.13.dist-info}/WHEEL +0 -0
- {core_framework-0.12.11.dist-info → core_framework-0.12.13.dist-info}/entry_points.txt +0 -0
core/__init__.py
CHANGED
core/auth/views.py
CHANGED
|
@@ -88,6 +88,43 @@ class AuthViewSet(ViewSet):
|
|
|
88
88
|
# ViewSet config
|
|
89
89
|
tags: list[str] = ["auth"]
|
|
90
90
|
|
|
91
|
+
# Explicitly disable CRUD endpoints that don't make sense for auth
|
|
92
|
+
# These would cause 500 errors if called
|
|
93
|
+
async def list(self, *args, **kwargs):
|
|
94
|
+
"""List is not available on auth endpoint."""
|
|
95
|
+
raise HTTPException(
|
|
96
|
+
status_code=405,
|
|
97
|
+
detail="Method not allowed. Use /auth/me to get current user."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
async def retrieve(self, *args, **kwargs):
|
|
101
|
+
"""Retrieve is not available on auth endpoint."""
|
|
102
|
+
raise HTTPException(
|
|
103
|
+
status_code=405,
|
|
104
|
+
detail="Method not allowed. Use /auth/me to get current user."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
async def create(self, *args, **kwargs):
|
|
108
|
+
"""Use /auth/register instead."""
|
|
109
|
+
raise HTTPException(
|
|
110
|
+
status_code=405,
|
|
111
|
+
detail="Method not allowed. Use /auth/register to create users."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async def update(self, *args, **kwargs):
|
|
115
|
+
"""Update is not available on auth endpoint."""
|
|
116
|
+
raise HTTPException(
|
|
117
|
+
status_code=405,
|
|
118
|
+
detail="Method not allowed."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def destroy(self, *args, **kwargs):
|
|
122
|
+
"""Destroy is not available on auth endpoint."""
|
|
123
|
+
raise HTTPException(
|
|
124
|
+
status_code=405,
|
|
125
|
+
detail="Method not allowed."
|
|
126
|
+
)
|
|
127
|
+
|
|
91
128
|
# Cache for dynamic schema
|
|
92
129
|
_dynamic_register_schema: type | None = None
|
|
93
130
|
|
core/cli/main.py
CHANGED
|
@@ -2266,7 +2266,32 @@ def cmd_check(args: argparse.Namespace) -> int:
|
|
|
2266
2266
|
continue
|
|
2267
2267
|
|
|
2268
2268
|
pending_count += 1
|
|
2269
|
-
|
|
2269
|
+
|
|
2270
|
+
# Try to load migration with error handling for syntax errors
|
|
2271
|
+
try:
|
|
2272
|
+
migration = engine._load_migration(file_path)
|
|
2273
|
+
except SyntaxError as e:
|
|
2274
|
+
print(error(f"\n {migration_name}: SYNTAX ERROR"))
|
|
2275
|
+
print(error(f" File: {file_path}"))
|
|
2276
|
+
print(error(f" Line {e.lineno}: {e.msg}"))
|
|
2277
|
+
if e.text:
|
|
2278
|
+
print(error(f" {e.text.strip()}"))
|
|
2279
|
+
print(warning(f" Fix the syntax error and run check again."))
|
|
2280
|
+
total_issues.append(type('Issue', (), {
|
|
2281
|
+
'severity': Severity.CRITICAL,
|
|
2282
|
+
'message': f"Syntax error in {migration_name}: {e.msg}"
|
|
2283
|
+
})())
|
|
2284
|
+
continue
|
|
2285
|
+
except Exception as e:
|
|
2286
|
+
print(error(f"\n {migration_name}: LOAD ERROR"))
|
|
2287
|
+
print(error(f" {type(e).__name__}: {e}"))
|
|
2288
|
+
print(warning(f" Fix the error and run check again."))
|
|
2289
|
+
total_issues.append(type('Issue', (), {
|
|
2290
|
+
'severity': Severity.CRITICAL,
|
|
2291
|
+
'message': f"Load error in {migration_name}: {e}"
|
|
2292
|
+
})())
|
|
2293
|
+
continue
|
|
2294
|
+
|
|
2270
2295
|
result = await analyzer.analyze(
|
|
2271
2296
|
migration.operations,
|
|
2272
2297
|
conn,
|
|
@@ -2373,13 +2398,22 @@ def cmd_reset_db(args: argparse.Namespace) -> int:
|
|
|
2373
2398
|
elif dialect == "mysql":
|
|
2374
2399
|
await conn.execute(text("SET FOREIGN_KEY_CHECKS = 0"))
|
|
2375
2400
|
|
|
2376
|
-
# Drop all tables
|
|
2401
|
+
# Drop all tables with CASCADE for PostgreSQL
|
|
2377
2402
|
for table in tables:
|
|
2378
2403
|
print(f" Dropping: {table}")
|
|
2379
2404
|
try:
|
|
2380
|
-
|
|
2405
|
+
if dialect == "postgresql":
|
|
2406
|
+
# Use CASCADE to handle foreign key dependencies
|
|
2407
|
+
await conn.execute(text(f'DROP TABLE IF EXISTS "{table}" CASCADE'))
|
|
2408
|
+
else:
|
|
2409
|
+
await conn.execute(text(f'DROP TABLE IF EXISTS "{table}"'))
|
|
2381
2410
|
except Exception as e:
|
|
2382
2411
|
print(warning(f" Warning: {e}"))
|
|
2412
|
+
# Rollback and continue with new transaction
|
|
2413
|
+
try:
|
|
2414
|
+
await conn.rollback()
|
|
2415
|
+
except Exception:
|
|
2416
|
+
pass
|
|
2383
2417
|
|
|
2384
2418
|
# Re-enable foreign key checks
|
|
2385
2419
|
if dialect == "sqlite":
|
core/migrations/operations.py
CHANGED
|
@@ -18,6 +18,51 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def _serialize_default(value: Any) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Serialize a default value for migration file output.
|
|
24
|
+
|
|
25
|
+
Handles:
|
|
26
|
+
- None -> "None"
|
|
27
|
+
- Strings -> repr()
|
|
28
|
+
- Booleans -> "True"/"False"
|
|
29
|
+
- Callables -> "module.function" (importable reference)
|
|
30
|
+
- Other -> repr()
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
value: The default value to serialize
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
String representation suitable for Python code
|
|
37
|
+
"""
|
|
38
|
+
if value is None:
|
|
39
|
+
return "None"
|
|
40
|
+
|
|
41
|
+
if callable(value):
|
|
42
|
+
# Get module and function name for proper serialization
|
|
43
|
+
module = getattr(value, "__module__", "")
|
|
44
|
+
name = getattr(value, "__qualname__", "") or getattr(value, "__name__", "")
|
|
45
|
+
|
|
46
|
+
if module and name:
|
|
47
|
+
# Return importable reference
|
|
48
|
+
return f"{module}.{name}"
|
|
49
|
+
|
|
50
|
+
# Fallback for lambdas or unnamed functions
|
|
51
|
+
return "None"
|
|
52
|
+
|
|
53
|
+
if isinstance(value, bool):
|
|
54
|
+
return str(value)
|
|
55
|
+
|
|
56
|
+
if isinstance(value, str):
|
|
57
|
+
return repr(value)
|
|
58
|
+
|
|
59
|
+
if isinstance(value, (int, float)):
|
|
60
|
+
return str(value)
|
|
61
|
+
|
|
62
|
+
# Default: use repr (may not always be valid Python)
|
|
63
|
+
return repr(value)
|
|
64
|
+
|
|
65
|
+
|
|
21
66
|
@dataclass
|
|
22
67
|
class ColumnDef:
|
|
23
68
|
"""Definição de uma coluna."""
|
|
@@ -215,23 +260,10 @@ class CreateTable(Operation):
|
|
|
215
260
|
return f"Create table '{self.table_name}'"
|
|
216
261
|
|
|
217
262
|
def to_code(self) -> str:
|
|
218
|
-
|
|
219
|
-
"""Serializa valor default para código Python válido."""
|
|
220
|
-
if val is None:
|
|
221
|
-
return "None"
|
|
222
|
-
if callable(val):
|
|
223
|
-
# Funções como datetime.utcnow não podem ser serializadas
|
|
224
|
-
# Usamos None e deixamos o banco lidar com o default
|
|
225
|
-
return "None"
|
|
226
|
-
if isinstance(val, str):
|
|
227
|
-
return f"'{val}'"
|
|
228
|
-
if isinstance(val, bool):
|
|
229
|
-
return str(val)
|
|
230
|
-
return repr(val)
|
|
231
|
-
|
|
263
|
+
# Use global _serialize_default for consistent callable serialization
|
|
232
264
|
cols = ",\n ".join(
|
|
233
265
|
f"ColumnDef(name='{c.name}', type='{c.type}', nullable={c.nullable}, "
|
|
234
|
-
f"default={
|
|
266
|
+
f"default={_serialize_default(c.default)}, primary_key={c.primary_key}, "
|
|
235
267
|
f"autoincrement={c.autoincrement}, unique={c.unique})"
|
|
236
268
|
for c in self.columns
|
|
237
269
|
)
|
|
@@ -300,15 +332,8 @@ class AddColumn(Operation):
|
|
|
300
332
|
|
|
301
333
|
def to_code(self) -> str:
|
|
302
334
|
c = self.column
|
|
303
|
-
# Serializa default de forma segura
|
|
304
|
-
default_val =
|
|
305
|
-
if c.default is not None and not callable(c.default):
|
|
306
|
-
if isinstance(c.default, str):
|
|
307
|
-
default_val = f"'{c.default}'"
|
|
308
|
-
elif isinstance(c.default, bool):
|
|
309
|
-
default_val = str(c.default)
|
|
310
|
-
else:
|
|
311
|
-
default_val = repr(c.default)
|
|
335
|
+
# Serializa default de forma segura usando helper function
|
|
336
|
+
default_val = _serialize_default(c.default)
|
|
312
337
|
|
|
313
338
|
return f"""AddColumn(
|
|
314
339
|
table_name='{self.table_name}',
|
core/routing.py
CHANGED
|
@@ -256,8 +256,14 @@ class Router(APIRouter):
|
|
|
256
256
|
for http_method in action_methods:
|
|
257
257
|
route_name = f"{basename}-{name}"
|
|
258
258
|
|
|
259
|
+
# Get action-specific permission_classes
|
|
260
|
+
action_permission_classes = getattr(method, "permission_classes", None)
|
|
261
|
+
|
|
259
262
|
# Captura method em closure para evitar late binding
|
|
260
|
-
def make_action_endpoint(
|
|
263
|
+
def make_action_endpoint(
|
|
264
|
+
action_method: Callable,
|
|
265
|
+
perm_classes: list | None = None,
|
|
266
|
+
) -> Callable:
|
|
261
267
|
async def action_endpoint(
|
|
262
268
|
request: Request,
|
|
263
269
|
db: AsyncSession = Depends(get_db),
|
|
@@ -265,6 +271,13 @@ class Router(APIRouter):
|
|
|
265
271
|
data: dict[str, Any] | None = Body(None),
|
|
266
272
|
) -> Any:
|
|
267
273
|
vs = viewset_class()
|
|
274
|
+
|
|
275
|
+
# Apply action-specific permissions if defined
|
|
276
|
+
if perm_classes:
|
|
277
|
+
from core.permissions import check_permissions
|
|
278
|
+
perms = [p() if isinstance(p, type) else p for p in perm_classes]
|
|
279
|
+
await check_permissions(perms, request, vs)
|
|
280
|
+
|
|
268
281
|
path_params = request.path_params
|
|
269
282
|
if data is not None:
|
|
270
283
|
return await action_method(vs, request, db, data=data, **path_params)
|
|
@@ -273,7 +286,7 @@ class Router(APIRouter):
|
|
|
273
286
|
|
|
274
287
|
self.add_api_route(
|
|
275
288
|
path,
|
|
276
|
-
make_action_endpoint(method),
|
|
289
|
+
make_action_endpoint(method, action_permission_classes),
|
|
277
290
|
methods=[http_method],
|
|
278
291
|
tags=tags,
|
|
279
292
|
name=route_name,
|
core/views.py
CHANGED
|
@@ -497,15 +497,25 @@ class ViewSet(Generic[ModelT, InputT, OutputT]):
|
|
|
497
497
|
"""
|
|
498
498
|
Levanta erro padronizado para valor de lookup inválido.
|
|
499
499
|
|
|
500
|
+
Returns 422 Validation Error (not 500 Internal Server Error).
|
|
501
|
+
|
|
500
502
|
Args:
|
|
501
503
|
value: Valor recebido
|
|
502
504
|
expected_type: Tipo esperado (para mensagem de erro)
|
|
503
505
|
"""
|
|
504
506
|
raise HTTPException(
|
|
505
|
-
status_code=
|
|
507
|
+
status_code=422,
|
|
506
508
|
detail={
|
|
507
|
-
"error": "
|
|
509
|
+
"error": "validation_error",
|
|
508
510
|
"message": f"Invalid {self.lookup_field} format. Expected {expected_type}.",
|
|
511
|
+
"errors": [
|
|
512
|
+
{
|
|
513
|
+
"loc": ["path", self.lookup_field],
|
|
514
|
+
"msg": f"Invalid {expected_type} format",
|
|
515
|
+
"type": f"{expected_type.lower()}_parsing",
|
|
516
|
+
"input": str(value),
|
|
517
|
+
}
|
|
518
|
+
],
|
|
509
519
|
"field": self.lookup_field,
|
|
510
520
|
"value": str(value),
|
|
511
521
|
"expected_type": expected_type,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-framework
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.13
|
|
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=r4WjmhThQyLBWtdMdRPtmwvc_ksX_W7S5vQjkfYkSFA,12232
|
|
2
2
|
core/app.py,sha256=SsMC5Vlj6PNgACXlfeccOm6CQEKfgh3q3X2p9ubRDlQ,23092
|
|
3
3
|
core/choices.py,sha256=rhcL3p2dB7RK99zIilpmoTFVcibQEIaRpz0CY0kImCE,10502
|
|
4
4
|
core/config.py,sha256=dq4O7QBdrdwj-fZRe2yhX1fKyi_Uetb6sx9-RovJ-9c,14771
|
|
@@ -12,12 +12,12 @@ core/models.py,sha256=jdNdjRPKBZiOBOgg8CmDYBwmuWdD7twCIpqINLGY4Q4,33788
|
|
|
12
12
|
core/permissions.py,sha256=rWfKYHLpWvhWnyHN7QsOmvRkX-23fgSzidFSWOrhQfs,10781
|
|
13
13
|
core/querysets.py,sha256=Z87-U06Un_xA9GKwcjXx0yzw6F_xf_tvG_rBT5UGL9c,22678
|
|
14
14
|
core/relations.py,sha256=UbdRgj0XQGI4lv2FQV1ImSAwu4Pn8yxTkSsdzR3m8cM,21372
|
|
15
|
-
core/routing.py,sha256=
|
|
15
|
+
core/routing.py,sha256=UnlRR8ksbfeXsUhcRoJckoL_f1MxMwK5WVZym2Pi_08,16107
|
|
16
16
|
core/serializers.py,sha256=gR5Y7wTACm1pECkUEpAKBUbPmONGLMDDwej4fyIiOdo,9438
|
|
17
17
|
core/tenancy.py,sha256=R4tNrLcAgRRDSqOvJS2IRXcD2J-zoCE4ng01ip9xWKI,9169
|
|
18
18
|
core/validation.py,sha256=F6sq6g2hzhgQ10nVMc9cdXqK4Og00nCjgdkAi5TlpJw,24533
|
|
19
19
|
core/validators.py,sha256=LCDyvqwIKnMaUEdaVx5kWveZt3XsydknZ_bxBL4ic5U,27895
|
|
20
|
-
core/views.py,sha256=
|
|
20
|
+
core/views.py,sha256=Bs-yFj-9PbiIs-mLz7IdgiitAEB4AvT_Mj-4Ult9NGU,46946
|
|
21
21
|
core/auth/__init__.py,sha256=_yr4rMMvDt_uKujzkKfqlQZ6x9UiQ6jmRppw14hTQNc,4645
|
|
22
22
|
core/auth/backends.py,sha256=PkLk2RQN2rQdtYSiN0mn7cqSp95hnLjO9xTFZqSsPF8,10486
|
|
23
23
|
core/auth/base.py,sha256=Q7vXgwTmgdmyW7G8eJmDket2bKB_8YFnraZ_kK9_gTs,21425
|
|
@@ -29,9 +29,9 @@ core/auth/models.py,sha256=aEE7deQKPS1aH0Btzzh3Z1Bwuqy8zvLZwu4JFEmiUNk,34058
|
|
|
29
29
|
core/auth/permissions.py,sha256=v3ykAgNpq5wJ0NkuC_FuveMctOkDfM9Xp11XEnUAuBg,12461
|
|
30
30
|
core/auth/schemas.py,sha256=L0W96dOD348rJDGeu1K5Rz3aJj-GdwMr2vbwwsYfo2g,3469
|
|
31
31
|
core/auth/tokens.py,sha256=jOF40D5O8WRG8klRwMBuSG-jOhdsp1irXn2aZ2puNSg,9149
|
|
32
|
-
core/auth/views.py,sha256=
|
|
32
|
+
core/auth/views.py,sha256=HQJxTZfXF5IzSf66u-fi4JwP13aSITCb9aPUZOeZYhA,19494
|
|
33
33
|
core/cli/__init__.py,sha256=EOYSATzRugHD2oJ1SPfTIMUygUoNJnO_dRt2yJrkQcU,542
|
|
34
|
-
core/cli/main.py,sha256=
|
|
34
|
+
core/cli/main.py,sha256=qdCdQTQHojAAXpEVi18YzYFvxsjDDtLpb2QRcj1_e4o,126299
|
|
35
35
|
core/deployment/__init__.py,sha256=RNcBRO9oB3WRnhtTTwM6wzVEcUKpKF4XfRkGSbbykIc,794
|
|
36
36
|
core/deployment/docker.py,sha256=ywraIk-ncbHiAX2vXH7jcU9KjhCGPIg7j0xgOTu5Cg8,8681
|
|
37
37
|
core/deployment/kubernetes.py,sha256=IV6gf664EFEyQry2ehgJ2UFhZhWovKpaHXln_0WXCMg,8414
|
|
@@ -65,7 +65,7 @@ core/migrations/analyzer.py,sha256=QiwG_Xf_-Mb-Kp4hstkF8xNJD0Tvxgz20vqvYZ6xEXM,2
|
|
|
65
65
|
core/migrations/cli.py,sha256=mR3lIFTlXSvupFOPVlfuC-urJyDfNFR9nqYZn4TjIco,12019
|
|
66
66
|
core/migrations/engine.py,sha256=jk8-wX8aKNBidUGyQ7ckHcUsukNJYpgSva-Sp-Iu-L4,31590
|
|
67
67
|
core/migrations/migration.py,sha256=Xv5MSNLvGAR9wnuMc4GRwciUSuU22AxWlWZP-hsVliI,2748
|
|
68
|
-
core/migrations/operations.py,sha256=
|
|
68
|
+
core/migrations/operations.py,sha256=K2LuETDJnDFdi2UTa6Z7hTs05ucAPEgHgaBHySY1Z3I,29102
|
|
69
69
|
core/migrations/state.py,sha256=eb_EYTE1tG-xQIwliS_-QTgr0y8-Jj0Va4C3nfpMrd4,15324
|
|
70
70
|
core/tasks/__init__.py,sha256=rDP4PD7Qtw8qbSbOtxMco9w2wBxRJl5uHiLUEDM0DYI,1662
|
|
71
71
|
core/tasks/base.py,sha256=0EWEzWTez0iF6nlI7Aw3stZtBk0Cr7zZ9btI89YdWPU,11762
|
|
@@ -87,7 +87,7 @@ example/auth.py,sha256=zBpLutb8lVKnGfQqQ2wnyygsSutHYZzeJBuhnFhxBaQ,4971
|
|
|
87
87
|
example/models.py,sha256=xKdx0kJ9n0tZ7sCce3KhV3BTvKvsh6m7G69eFm3ukf0,4549
|
|
88
88
|
example/schemas.py,sha256=wJ9QofnuHp4PjtM_IuMMBLVFVDJ4YlwcF6uQm1ooKiY,6139
|
|
89
89
|
example/views.py,sha256=GQwgQcW6yoeUIDbF7-lsaZV7cs8G1S1vGVtiwVpZIQE,14338
|
|
90
|
-
core_framework-0.12.
|
|
91
|
-
core_framework-0.12.
|
|
92
|
-
core_framework-0.12.
|
|
93
|
-
core_framework-0.12.
|
|
90
|
+
core_framework-0.12.13.dist-info/METADATA,sha256=yTAocMiU4__UMmo8YL_t8Uwg7i8_5NsX5J01_imP82M,13021
|
|
91
|
+
core_framework-0.12.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
92
|
+
core_framework-0.12.13.dist-info/entry_points.txt,sha256=MJytamxHbn0CyH3HbxiP-PqOWekjYUo2CA6EWiKWuSI,78
|
|
93
|
+
core_framework-0.12.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|