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 CHANGED
@@ -287,7 +287,7 @@ from core.exceptions import (
287
287
  MissingDependency,
288
288
  )
289
289
 
290
- __version__ = "0.12.11"
290
+ __version__ = "0.12.13"
291
291
  __all__ = [
292
292
  # Models
293
293
  "Model",
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
- migration = engine._load_migration(file_path)
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
- await conn.execute(text(f'DROP TABLE IF EXISTS "{table}"'))
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":
@@ -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
- def serialize_default(val):
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={serialize_default(c.default)}, primary_key={c.primary_key}, "
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 = "None"
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(action_method: Callable) -> Callable:
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=400,
507
+ status_code=422,
506
508
  detail={
507
- "error": "invalid_lookup_value",
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.11
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=tvZLlVpXoyS3BPdF0hivmXeVRomd2awBRg-ZeAxvyMg,12232
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=vIiJN8bQ2836WW2zUKTJVBTC8RpjtDYgEGdz7mldnGc,15422
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=jP2HypxplVP5nHJfmhQ2d4pegnVYhl8KXVydc25l7V4,46541
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=psL4g2Fe1saXn1eCnm6b18E2JCbALixVJahr2OIaLvI,18211
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=0WAO1deegd2Yja19ggDbX4j6p5S_p2KrmyAmale9HZs,124490
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=wZLui76zU-MDiJfyn3l3NBRGJw1V4XF8tViSV3kvN6A,28651
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.11.dist-info/METADATA,sha256=VpC4knI1g1olWe8cq5H-A1M8W84N6ONhq9pLIp4wrrg,13021
91
- core_framework-0.12.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
92
- core_framework-0.12.11.dist-info/entry_points.txt,sha256=MJytamxHbn0CyH3HbxiP-PqOWekjYUo2CA6EWiKWuSI,78
93
- core_framework-0.12.11.dist-info/RECORD,,
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,,