half-orm-dev 1.0.0a6__tar.gz → 1.0.0a8__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 (80) hide show
  1. {half_orm_dev-1.0.0a6/half_orm_dev.egg-info → half_orm_dev-1.0.0a8}/PKG-INFO +1 -1
  2. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migration_manager.py +134 -0
  3. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/modules.py +94 -16
  4. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/repo.py +4 -12
  5. half_orm_dev-1.0.0a8/half_orm_dev/templates/module_stub_template +8 -0
  6. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/module_template_1 +0 -3
  7. half_orm_dev-1.0.0a8/half_orm_dev/templates/module_template_3 +3 -0
  8. half_orm_dev-1.0.0a8/half_orm_dev/version.txt +1 -0
  9. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8/half_orm_dev.egg-info}/PKG-INFO +1 -1
  10. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/SOURCES.txt +1 -0
  11. half_orm_dev-1.0.0a6/half_orm_dev/templates/module_template_3 +0 -12
  12. half_orm_dev-1.0.0a6/half_orm_dev/version.txt +0 -1
  13. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/AUTHORS +0 -0
  14. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/LICENSE +0 -0
  15. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/README.md +0 -0
  16. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/__init__.py +0 -0
  17. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/bootstrap_manager.py +0 -0
  18. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/__init__.py +0 -0
  19. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/__init__.py +0 -0
  20. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/apply.py +0 -0
  21. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/bootstrap.py +0 -0
  22. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/check.py +0 -0
  23. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/clone.py +0 -0
  24. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/init.py +0 -0
  25. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/migrate.py +0 -0
  26. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/patch.py +0 -0
  27. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/release.py +0 -0
  28. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/restore.py +0 -0
  29. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/revert_migration.py +0 -0
  30. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
  31. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/sync.py +0 -0
  32. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/todo.py +0 -0
  33. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/undo.py +0 -0
  34. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/update.py +0 -0
  35. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/upgrade.py +0 -0
  36. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/main.py +0 -0
  37. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli_extension.py +0 -0
  38. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/database.py +0 -0
  39. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/decorators.py +0 -0
  40. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/file_executor.py +0 -0
  41. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/hgit.py +0 -0
  42. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  43. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  44. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
  45. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
  46. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
  47. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
  48. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
  49. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
  50. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
  51. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patch_manager.py +0 -0
  52. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patch_validator.py +0 -0
  53. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  54. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  55. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  56. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/log +0 -0
  57. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  58. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/release_file.py +0 -0
  59. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/release_manager.py +0 -0
  60. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/scripts/repair-metadata.py +0 -0
  61. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/.gitignore +0 -0
  62. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/MANIFEST.in +0 -0
  63. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/README +0 -0
  64. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/conftest_template +0 -0
  65. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
  66. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  67. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/init_module_template +0 -0
  68. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/module_template_2 +0 -0
  69. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/pyproject.toml +0 -0
  70. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/relation_test +0 -0
  71. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/sql_adapter +0 -0
  72. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/warning +0 -0
  73. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/utils.py +0 -0
  74. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  75. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/entry_points.txt +0 -0
  76. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/requires.txt +0 -0
  77. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/top_level.txt +0 -0
  78. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/pyproject.toml +0 -0
  79. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/setup.cfg +0 -0
  80. {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a6
3
+ Version: 1.0.0a8
4
4
  Summary: half_orm development Framework.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -313,6 +313,9 @@ class MigrationManager:
313
313
  f"Continuing with migration attempt."
314
314
  )
315
315
 
316
+ # Ensure all active branches are in sync with origin before touching anything
317
+ self._ensure_active_branches_synced()
318
+
316
319
  # Get pending migrations
317
320
  pending = self.get_pending_migrations(current_version, target_version)
318
321
 
@@ -370,6 +373,16 @@ class MigrationManager:
370
373
  current_version, target_version, sync_result
371
374
  )
372
375
 
376
+ # Pseudo-patch: regenerate modules and sync to active branches
377
+ try:
378
+ self._regenerate_modules_after_migration(
379
+ current_version, target_version
380
+ )
381
+ except Exception as regen_err:
382
+ result['errors'].append(
383
+ f"Module regeneration after migration failed: {regen_err}"
384
+ )
385
+
373
386
  except Exception as e:
374
387
  # Don't fail migration if commit fails
375
388
  result['errors'].append(f"Failed to create commit: {e}")
@@ -442,6 +455,127 @@ class MigrationManager:
442
455
  except Exception:
443
456
  pass # remote tag may already be gone
444
457
 
458
+ def _ensure_active_branches_synced(self) -> None:
459
+ """Verify all active branches are in sync with origin before migration.
460
+
461
+ Branches that are behind are fast-forwarded automatically (no local commits
462
+ at risk). Branches that are ahead or diverged block the migration — the
463
+ developer must push or resolve before proceeding.
464
+
465
+ Raises:
466
+ MigrationManagerError: if any active branch is ahead or diverged.
467
+ """
468
+ repo = self._repo
469
+ git_repo = repo.hgit._HGit__git_repo
470
+ current_branch = git_repo.active_branch.name
471
+
472
+ try:
473
+ branches_status = repo.hgit.get_active_branches_status()
474
+ except Exception:
475
+ return # can't determine status, proceed cautiously
476
+
477
+ patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
478
+ release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
479
+ staged_branches = [b['name'] for b in branches_status.get('staged_branches', [])]
480
+ active_branches = release_branches + patch_branches + staged_branches
481
+
482
+ blocked = []
483
+ for branch in active_branches:
484
+ try:
485
+ synced, status = repo.hgit.is_branch_synced(branch)
486
+ if synced:
487
+ continue
488
+ if status == 'behind':
489
+ # Fast-forward: no local commits at risk
490
+ repo.hgit.checkout(branch)
491
+ git_repo.git.merge('--ff-only', f'origin/{branch}')
492
+ elif status in ('ahead', 'diverged'):
493
+ blocked.append((branch, status))
494
+ except Exception:
495
+ pass # branch may not exist locally, skip
496
+
497
+ # Return to original branch
498
+ try:
499
+ repo.hgit.checkout(current_branch)
500
+ except Exception:
501
+ pass
502
+
503
+ if blocked:
504
+ ahead = [(b, s) for b, s in blocked if s == 'ahead']
505
+ diverged = [(b, s) for b, s in blocked if s == 'diverged']
506
+ parts = []
507
+ if ahead:
508
+ branch_list = ', '.join(b for b, _ in ahead)
509
+ parts.append(
510
+ f" Branches ahead of origin (unpushed commits) — push first:\n"
511
+ + '\n'.join(f" git push origin {b}" for b, _ in ahead)
512
+ )
513
+ if diverged:
514
+ parts.append(
515
+ f" Branches diverged from origin (local and remote have diverged) "
516
+ f"— rebase or merge to resolve:\n"
517
+ + '\n'.join(f" {b}" for b, _ in diverged)
518
+ )
519
+ raise MigrationManagerError(
520
+ f"Migration blocked: active branches are not in sync with origin.\n"
521
+ + '\n'.join(parts)
522
+ )
523
+
524
+ def _regenerate_modules_after_migration(
525
+ self, from_version: str, to_version: str
526
+ ) -> None:
527
+ """Regenerate the project modules after a migration and sync to all active branches.
528
+
529
+ Runs generate(), commits the changed files on ho-prod, then replays those
530
+ changes on every active branch by checking out the package directory from ho-prod.
531
+ Skips silently if generate() produced no changes.
532
+ """
533
+ from half_orm_dev import modules as _modules
534
+
535
+ repo = self._repo
536
+ git_repo = repo.hgit._HGit__git_repo
537
+ package_name = repo.name
538
+ package_dir = str(Path(repo.base_dir) / package_name)
539
+
540
+ commit_msg = (
541
+ f"[HOP] Regenerate modules (migration {from_version} → {to_version})"
542
+ )
543
+
544
+ # Collect active branches before moving around
545
+ try:
546
+ branches_status = repo.hgit.get_active_branches_status()
547
+ except Exception:
548
+ branches_status = {}
549
+
550
+ patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
551
+ release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
552
+ staged_branches = [b['name'] for b in branches_status.get('staged_branches', [])]
553
+ # ho-prod first, then active branches
554
+ all_branches = ['ho-prod'] + release_branches + patch_branches + staged_branches
555
+
556
+ for branch in all_branches:
557
+ try:
558
+ repo.hgit.checkout(branch)
559
+ # generate() reads existing files and preserves developer code sections
560
+ _modules.generate(repo)
561
+ repo.hgit.add(package_dir)
562
+ if not git_repo.git.diff('--cached', '--name-only').strip():
563
+ continue
564
+ repo.hgit.commit('-m', commit_msg)
565
+ try:
566
+ repo.hgit.push_branch(branch)
567
+ except Exception as push_err:
568
+ sys.stderr.write(
569
+ f"Warning: could not push {branch} after module regeneration "
570
+ f"(diverged branch?): {push_err}\n"
571
+ )
572
+ except Exception as e:
573
+ sys.stderr.write(
574
+ f"Warning: could not regenerate modules on {branch}: {e}\n"
575
+ )
576
+
577
+ repo.hgit.checkout('ho-prod')
578
+
445
579
  def _create_migration_commit_message(
446
580
  self,
447
581
  from_version: str,
@@ -20,6 +20,7 @@ history with the last release applied.
20
20
  """
21
21
 
22
22
  import importlib
23
+ import inspect
23
24
  import os
24
25
  import re
25
26
  import shutil
@@ -43,10 +44,7 @@ def read_template(file_name):
43
44
  return file_.read()
44
45
 
45
46
  NO_APAPTER = {}
46
- HO_DATACLASSES = [
47
- '''import dataclasses
48
- from half_orm.relation import DC_Relation
49
- from half_orm.field import Field''']
47
+ HO_DATACLASSES = []
50
48
  HO_DATACLASSES_IMPORTS = set()
51
49
  HO_TYPEDICTS: list = []
52
50
  HO_TYPEDICTS_IMPORTS: set = set()
@@ -54,6 +52,7 @@ INIT_MODULE_TEMPLATE = read_template('init_module_template')
54
52
  MODULE_TEMPLATE_1 = read_template('module_template_1')
55
53
  MODULE_TEMPLATE_2 = read_template('module_template_2')
56
54
  MODULE_TEMPLATE_3 = read_template('module_template_3')
55
+ MODULE_STUB_TEMPLATE = read_template('module_stub_template')
57
56
  WARNING_TEMPLATE = read_template('warning')
58
57
  CONFTEST = read_template('conftest_template')
59
58
  TEST = read_template('relation_test')
@@ -336,6 +335,18 @@ def __gen_typedict(relation, fkeys) -> list:
336
335
  return extra_classes + [main_class]
337
336
 
338
337
 
338
+ def __gen_stub_file(module_path: str, class_name: str, dict_class_name: str, package_name: str) -> str:
339
+ """Generate a .pyi stub alongside the module for IDE type inference."""
340
+ stub_path = module_path[:-3] + '.pyi'
341
+ with open(stub_path, 'w', encoding='utf-8') as f:
342
+ f.write(MODULE_STUB_TEMPLATE.format(
343
+ package_name=package_name,
344
+ dict_class_name=dict_class_name,
345
+ class_name=class_name,
346
+ ))
347
+ return stub_path
348
+
349
+
339
350
  def __gen_typedicts(package_dir: str, package_name: str) -> None:
340
351
  with open(os.path.join(package_dir, "ho_typeddicts.py"), "w", encoding='utf-8') as file_:
341
352
  file_.write(f"# TypedDicts for {package_name}\n\n")
@@ -545,7 +556,9 @@ def __update_this_module(
545
556
  HO_DATACLASSES.append(__gen_dataclass(rel, existing_fkeys))
546
557
  HO_TYPEDICTS.extend(__gen_typedict(rel, existing_fkeys))
547
558
 
548
- return module_path
559
+ stub_path = __gen_stub_file(module_path, class_name, dict_class_name, package_name)
560
+
561
+ return [module_path, stub_path]
549
562
 
550
563
 
551
564
  def __reset_dataclasses(repo, package_dir):
@@ -557,20 +570,86 @@ def __reset_dataclasses(repo, package_dir):
557
570
  file_.write(f'class DC_{__get_full_class_name(*t_qrn)}: ...\n')
558
571
 
559
572
 
573
+ _TYPING_NAMES = frozenset(('Any', 'Dict', 'Iterator', 'List', 'Optional', 'Tuple', 'Union'))
574
+
575
+
576
+ def __gen_dc_relation() -> tuple:
577
+ """Introspect Relation to generate DC_Relation with signatures and docstrings.
578
+
579
+ Returns (class_str, frozenset of typing names needed).
580
+ """
581
+ from half_orm.relation import Relation
582
+
583
+ def _fmt_doc(doc: str) -> str:
584
+ lines = doc.splitlines()
585
+ if len(lines) == 1:
586
+ return f' """{doc}"""'
587
+ parts = [f' """{lines[0]}']
588
+ for line in lines[1:]:
589
+ parts.append(f' {line}' if line.strip() else '')
590
+ parts.append(' """')
591
+ return '\n'.join(parts)
592
+
593
+ method_blocks = []
594
+ for name in sorted(dir(Relation)):
595
+ if not name.startswith('ho_'):
596
+ continue
597
+ raw = inspect.getattr_static(Relation, name)
598
+ is_classmethod = isinstance(raw, classmethod)
599
+ underlying = raw.__func__ if isinstance(raw, (classmethod, staticmethod)) else raw
600
+ if not callable(underlying):
601
+ continue
602
+ is_async = inspect.iscoroutinefunction(underlying)
603
+ try:
604
+ sig_str = str(inspect.signature(underlying))
605
+ except (ValueError, TypeError):
606
+ continue
607
+ doc = inspect.getdoc(underlying)
608
+ block = []
609
+ if is_classmethod:
610
+ block.append(' @classmethod')
611
+ prefix = ' async def' if is_async else ' def'
612
+ block.append(f'{prefix} {name}{sig_str}:')
613
+ if doc:
614
+ block.append(_fmt_doc(doc))
615
+ block.append(' ...')
616
+ method_blocks.append('\n'.join(block))
617
+
618
+ full_body = '\n\n'.join(method_blocks)
619
+ needed_typing = frozenset(t for t in _TYPING_NAMES if t in full_body)
620
+ class_str = (
621
+ 'class DC_Relation:\n'
622
+ ' # auto-generated by half-orm-dev — do not edit\n\n'
623
+ + full_body + '\n'
624
+ )
625
+ return class_str, needed_typing
626
+
627
+
560
628
  def __gen_dataclasses(package_dir, package_name):
629
+ dc_relation_str, dc_typing = __gen_dc_relation()
561
630
  with open(os.path.join(package_dir, "ho_dataclasses.py"), "w", encoding='utf-8') as file_:
562
- file_.write(f"# dataclasses for {package_name}\n\n")
563
- hd_imports = list(HO_DATACLASSES_IMPORTS)
564
- hd_imports.sort()
565
- for to_import in hd_imports:
566
- file_.write(f"import {to_import}\n")
567
- file_.write("\n")
631
+ file_.write(f"# DO NOT EDIT — auto-generated by half-orm-dev\n\n")
632
+ file_.write("import dataclasses\n")
633
+ file_.write("from half_orm.field import Field\n")
634
+ if dc_typing:
635
+ file_.write(f"from typing import {', '.join(sorted(dc_typing))}\n")
636
+ for mod in sorted(HO_DATACLASSES_IMPORTS):
637
+ file_.write(f"import {mod}\n")
638
+ file_.write("\n\n")
639
+ file_.write(dc_relation_str)
568
640
  for dc in HO_DATACLASSES:
569
- file_.write(f"\n{dc}\n")
641
+ file_.write(f"\n\n{dc}\n")
570
642
 
571
643
 
572
644
  def generate(repo):
573
645
  """Synchronize the modules with the structure of the relation in PG."""
646
+ # Reset accumulators — allows safe repeated calls in the same process
647
+ HO_DATACLASSES.clear()
648
+ HO_DATACLASSES_IMPORTS.clear()
649
+ HO_TYPEDICTS.clear()
650
+ HO_TYPEDICTS_IMPORTS.clear()
651
+ NO_APAPTER.clear()
652
+
574
653
  package_name = repo.name
575
654
  base_dir = Path(repo.base_dir)
576
655
  package_dir = base_dir / package_name
@@ -613,10 +692,9 @@ def generate(repo):
613
692
 
614
693
  # Generate modules for each relation
615
694
  for relation in repo.database.model._relations():
616
- module_path = __update_this_module(repo, relation, str(package_dir), package_name)
617
- if module_path:
618
- files_list.append(module_path)
619
- # Tests are no longer added to files_list (they live in tests/ directory)
695
+ paths = __update_this_module(repo, relation, str(package_dir), package_name)
696
+ if paths:
697
+ files_list.extend(paths)
620
698
 
621
699
  __gen_dataclasses(str(package_dir), package_name)
622
700
  __gen_typedicts(str(package_dir), package_name)
@@ -690,18 +690,10 @@ class Repo:
690
690
  remote_ref = f"origin/{branch}"
691
691
  try:
692
692
  synced, status = self.hgit.is_branch_synced(branch)
693
- if not synced and status == "diverged":
694
- print(
695
- f"Warning: branch {branch} has diverged from origin. "
696
- f"Resetting to origin (source of truth).",
697
- file=sys.stderr
698
- )
699
- # Only reset when the remote is ahead of local ("behind") or
700
- # branches have diverged. Never reset an "ahead" branch —
701
- # that would orphan local commits that have not been pushed
702
- # yet (e.g. the "Create patch directory" commit from
703
- # `hop patch create` before the first push).
704
- if not synced and status in ("behind", "diverged"):
693
+ # Only fast-forward when origin is strictly ahead (no local commits
694
+ # at risk). Never reset on diverged branches — that would destroy
695
+ # unmerged local work.
696
+ if not synced and status == "behind":
705
697
  self.hgit._HGit__git_repo.git.reset('--hard', remote_ref)
706
698
  except GitCommandError:
707
699
  # Remote branch may not exist yet, continue without reset
@@ -0,0 +1,8 @@
1
+ # DO NOT EDIT — auto-generated by half-orm-dev
2
+ from typing import Iterator
3
+ from {package_name}.ho_typeddicts import {dict_class_name}
4
+
5
+ class {class_name}:
6
+ def __iter__(self) -> Iterator[{dict_class_name}]: ...
7
+ def ho_get(self, *args) -> {dict_class_name}: ...
8
+ async def ho_aget(self, *args) -> {dict_class_name}: ...
@@ -6,10 +6,7 @@ WARNING!
6
6
 
7
7
  {warning}
8
8
  """
9
- from typing import TYPE_CHECKING, Iterator
10
9
  from half_orm.model import register
11
10
  from {package_name} import MODEL, ho_dataclasses
12
- if TYPE_CHECKING:
13
- from {package_name}.ho_typeddicts import {dict_class_name}
14
11
  fields_aliases=None
15
12
 
@@ -0,0 +1,3 @@
1
+ #pylint: disable=line-too-long, too-many-arguments, redefined-builtin, too-many-positional-arguments
2
+ def __init__(self, {kwargs}):
3
+ super().__init__({arg_names}, **kwargs)
@@ -0,0 +1 @@
1
+ 1.0.0-a8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a6
3
+ Version: 1.0.0a8
4
4
  Summary: half_orm development Framework.
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -64,6 +64,7 @@ half_orm_dev/templates/MANIFEST.in
64
64
  half_orm_dev/templates/README
65
65
  half_orm_dev/templates/conftest_template
66
66
  half_orm_dev/templates/init_module_template
67
+ half_orm_dev/templates/module_stub_template
67
68
  half_orm_dev/templates/module_template_1
68
69
  half_orm_dev/templates/module_template_2
69
70
  half_orm_dev/templates/module_template_3
@@ -1,12 +0,0 @@
1
- #pylint: disable=line-too-long, too-many-arguments, redefined-builtin, too-many-positional-arguments
2
- def __init__(self, {kwargs}):
3
- super().__init__({arg_names}, **kwargs)
4
-
5
- def __iter__(self) -> 'Iterator[{dict_class_name}]':
6
- return super().__iter__()
7
-
8
- def ho_get(self, *args) -> '{dict_class_name}':
9
- return super().ho_get(*args)
10
-
11
- async def ho_aget(self, *args) -> '{dict_class_name}':
12
- return await super().ho_aget(*args)
@@ -1 +0,0 @@
1
- 1.0.0-a6
File without changes
File without changes
File without changes
File without changes
File without changes