half-orm-gen 1.0.0a1__tar.gz → 1.0.0a2__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 (36) hide show
  1. {half_orm_gen-1.0.0a1/half_orm_gen.egg-info → half_orm_gen-1.0.0a2}/PKG-INFO +1 -1
  2. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/crud_routes.py +32 -9
  3. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_app/angular.py +355 -89
  4. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_app/svelte.py +263 -52
  5. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_store/base.py +2 -0
  6. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_store/svelte.py +59 -7
  7. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/templates.py +119 -10
  8. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/templates_fastapi.py +73 -10
  9. half_orm_gen-1.0.0a2/half_orm_gen/version.txt +1 -0
  10. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2/half_orm_gen.egg-info}/PKG-INFO +1 -1
  11. half_orm_gen-1.0.0a1/half_orm_gen/version.txt +0 -1
  12. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/AUTHORS +0 -0
  13. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/LICENSE +0 -0
  14. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/README.md +0 -0
  15. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/__init__.py +0 -0
  16. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/api_routes.py +0 -0
  17. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/cli_extension.py +0 -0
  18. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_app/__init__.py +0 -0
  19. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/gen_store/__init__.py +0 -0
  20. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/generate.py +0 -0
  21. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffold.py +0 -0
  22. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/api_init.py +0 -0
  23. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/custom_authorization.py +0 -0
  24. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/custom_init.py +0 -0
  25. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/custom_middlewares_init.py +0 -0
  26. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/custom_routes.py +0 -0
  27. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/guards.py +0 -0
  28. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/scaffolding/roles_core.py +0 -0
  29. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen/tools.py +0 -0
  30. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen.egg-info/SOURCES.txt +0 -0
  31. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen.egg-info/dependency_links.txt +0 -0
  32. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen.egg-info/requires.txt +0 -0
  33. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/half_orm_gen.egg-info/top_level.txt +0 -0
  34. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/pyproject.toml +0 -0
  35. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/setup.cfg +0 -0
  36. {half_orm_gen-1.0.0a1 → half_orm_gen-1.0.0a2}/tests/test_extension.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_gen
3
- Version: 1.0.0a1
3
+ Version: 1.0.0a2
4
4
  Summary: API and frontend backoffice generation for halfORM projects.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -58,13 +58,18 @@ def _simple_pk(relation) -> Tuple[str, str, str] | None:
58
58
 
59
59
 
60
60
  def _filter_params_str(all_fields: dict) -> Tuple[str, str]:
61
- """Return (filter_params_block, filter_dict_str) for query-param filters."""
61
+ """Return (filter_params_block, filter_dict_str) for query-param filters.
62
+
63
+ All column filters are prefixed with 'ho_col_' to avoid conflicts with
64
+ custom business logic parameters in @api_* decorated methods.
65
+ """
62
66
  lines = []
63
67
  dict_items = []
64
68
  for fname, fobj in all_fields.items():
65
69
  type_str = _py_type_str(fobj.py_type)
66
- lines.append(f' {fname}: Optional[{type_str}] = None,\n')
67
- dict_items.append(f"'{fname}': {fname}")
70
+ param_name = f'ho_col_{fname}'
71
+ lines.append(f' {param_name}: Optional[{type_str}] = None,\n')
72
+ dict_items.append(f"'{fname}': {param_name}")
68
73
  return ''.join(lines), ', '.join(dict_items)
69
74
 
70
75
 
@@ -309,15 +314,18 @@ def generate_crud_routes(
309
314
  pk_field, pk_path_type, pk_py_type = pk_cols[0]
310
315
  pk_instance_filter = f'{pk_field}=id'
311
316
  pk_broadcast_expr = f'result.get("{pk_field}")'
317
+ pk_is_composite = False
312
318
  elif len(pk_cols) > 1:
313
319
  pk_field = pk_cols[0][0] # first field; used in WS cascade map
314
320
  pk_path_type = 'str'
315
321
  pk_py_type = 'str'
316
322
  _pk_names = [f for f, _, _ in pk_cols]
317
- pk_instance_filter = f"**dict(zip({_pk_names!r}, id.split('::')))"
318
- pk_broadcast_expr = f"'::'.join(str(result.get(f, '')) for f in {_pk_names!r})"
323
+ # New format: col1:val1::col2:val2 (parsed and validated by _parse_composite_pk)
324
+ pk_instance_filter = f"**_parse_composite_pk(id, {_pk_names!r})"
325
+ pk_broadcast_expr = f"_format_composite_pk(result, {_pk_names!r})"
326
+ pk_is_composite = True
319
327
  else:
320
- pk_field = pk_path_type = pk_py_type = pk_instance_filter = pk_broadcast_expr = None
328
+ pk_field = pk_path_type = pk_py_type = pk_instance_filter = pk_broadcast_expr = pk_is_composite = None
321
329
 
322
330
  instance = _instance(relation)
323
331
  all_fields = getattr(instance, '_ho_fields', {})
@@ -357,12 +365,17 @@ def generate_crud_routes(
357
365
  # GET /{pk}
358
366
  if pk_info and (module_str, 'GET') not in covered and 'GET' in crud_access:
359
367
  handler_name = f'{handler_prefix}_get'
368
+ if pk_is_composite:
369
+ param_type = 'Path' if templates.FRAMEWORK == 'fastapi' else 'Parameter'
370
+ pk_type_annotation = f'Annotated[str, {param_type}(pattern=_COMPOSITE_PK_PATTERN)]'
371
+ else:
372
+ pk_type_annotation = pk_py_type
360
373
  handler_blocks.append(templates.CRUD_GET_ONE.format(
361
374
  path=base_path,
362
375
  handler_name=handler_prefix,
363
376
  pk_instance_filter=pk_instance_filter,
364
377
  pk_path_type=pk_path_type,
365
- pk_py_type=pk_py_type,
378
+ pk_type_annotation=pk_type_annotation,
366
379
  module_alias=module_alias,
367
380
  class_name=relation.__name__,
368
381
  out_typedict=out_class,
@@ -405,12 +418,17 @@ def generate_crud_routes(
405
418
  put_in_names = [f for f in all_names if f != pk_field and f not in api_excluded]
406
419
  decl_blocks.append('\n' + templates.typedict_block(put_in_class, put_in_names, all_fields) + '\n')
407
420
  handler_name = f'{handler_prefix}_update'
421
+ if pk_is_composite:
422
+ param_type = 'Path' if templates.FRAMEWORK == 'fastapi' else 'Parameter'
423
+ pk_type_annotation = f'Annotated[str, {param_type}(pattern=_COMPOSITE_PK_PATTERN)]'
424
+ else:
425
+ pk_type_annotation = pk_py_type
408
426
  handler_blocks.append(templates.CRUD_PUT.format(
409
427
  path=base_path,
410
428
  handler_name=handler_prefix,
411
429
  pk_instance_filter=pk_instance_filter,
412
430
  pk_path_type=pk_path_type,
413
- pk_py_type=pk_py_type,
431
+ pk_type_annotation=pk_type_annotation,
414
432
  module_alias=module_alias,
415
433
  class_name=relation.__name__,
416
434
  in_typedict=put_in_class,
@@ -422,12 +440,17 @@ def generate_crud_routes(
422
440
 
423
441
  if (module_str, 'DELETE') not in covered and 'DELETE' in crud_access:
424
442
  handler_name = f'{handler_prefix}_delete'
443
+ if pk_is_composite:
444
+ param_type = 'Path' if templates.FRAMEWORK == 'fastapi' else 'Parameter'
445
+ pk_type_annotation = f'Annotated[str, {param_type}(pattern=_COMPOSITE_PK_PATTERN)]'
446
+ else:
447
+ pk_type_annotation = pk_py_type
425
448
  handler_blocks.append(templates.CRUD_DELETE.format(
426
449
  path=base_path,
427
450
  handler_name=handler_prefix,
428
451
  pk_instance_filter=pk_instance_filter,
429
452
  pk_path_type=pk_path_type,
430
- pk_py_type=pk_py_type,
453
+ pk_type_annotation=pk_type_annotation,
431
454
  module_alias=module_alias,
432
455
  class_name=relation.__name__,
433
456
  access_description=_access_description(crud_access, 'DELETE'),