half-orm-dev 0.17.2a3__tar.gz → 0.17.2a4__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 (66) hide show
  1. {half_orm_dev-0.17.2a3/half_orm_dev.egg-info → half_orm_dev-0.17.2a4}/PKG-INFO +1 -1
  2. half_orm_dev-0.17.2a4/half_orm_dev/decorators.py +133 -0
  3. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/hgit.py +4 -0
  4. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/migration_manager.py +43 -130
  5. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patch_manager.py +148 -168
  6. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/release_manager.py +55 -583
  7. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/repo.py +272 -6
  8. half_orm_dev-0.17.2a4/half_orm_dev/version.txt +1 -0
  9. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4/half_orm_dev.egg-info}/PKG-INFO +1 -1
  10. half_orm_dev-0.17.2a3/half_orm_dev/decorators.py +0 -54
  11. half_orm_dev-0.17.2a3/half_orm_dev/version.txt +0 -1
  12. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/AUTHORS +0 -0
  13. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/LICENSE +0 -0
  14. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/README.md +0 -0
  15. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/__init__.py +0 -0
  16. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/__init__.py +0 -0
  17. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/__init__.py +0 -0
  18. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/apply.py +0 -0
  19. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/check.py +0 -0
  20. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/clone.py +0 -0
  21. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/init.py +0 -0
  22. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/new.py +0 -0
  23. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/patch.py +0 -0
  24. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/release.py +0 -0
  25. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/restore.py +0 -0
  26. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/sync.py +0 -0
  27. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/todo.py +0 -0
  28. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/undo.py +0 -0
  29. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/update.py +0 -0
  30. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/commands/upgrade.py +0 -0
  31. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli/main.py +0 -0
  32. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/cli_extension.py +0 -0
  33. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/database.py +0 -0
  34. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  35. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  36. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/modules.py +0 -0
  37. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patch_validator.py +0 -0
  38. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  39. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  40. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  41. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patches/log +0 -0
  42. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  43. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/release_file.py +0 -0
  44. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/.gitignore +0 -0
  45. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/MANIFEST.in +0 -0
  46. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/Pipfile +0 -0
  47. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/README +0 -0
  48. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/conftest_template +0 -0
  49. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
  50. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  51. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/init_module_template +0 -0
  52. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/module_template_1 +0 -0
  53. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/module_template_2 +0 -0
  54. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/module_template_3 +0 -0
  55. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/pyproject.toml +0 -0
  56. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/relation_test +0 -0
  57. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/sql_adapter +0 -0
  58. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/templates/warning +0 -0
  59. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev/utils.py +0 -0
  60. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev.egg-info/SOURCES.txt +0 -0
  61. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  62. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev.egg-info/requires.txt +0 -0
  63. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/half_orm_dev.egg-info/top_level.txt +0 -0
  64. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/pyproject.toml +0 -0
  65. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/setup.cfg +0 -0
  66. {half_orm_dev-0.17.2a3 → half_orm_dev-0.17.2a4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 0.17.2a3
3
+ Version: 0.17.2a4
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
@@ -0,0 +1,133 @@
1
+ """
2
+ Decorators for half-orm-dev.
3
+
4
+ Provides common decorators for ReleaseManager and PatchManager.
5
+ """
6
+
7
+ import sys
8
+ import inspect
9
+ from functools import wraps
10
+
11
+
12
+ def with_dynamic_branch_lock(branch_getter, timeout_minutes: int = 30):
13
+ """
14
+ Decorator to protect methods with a dynamic branch lock.
15
+
16
+ Unlike with_branch_lock which uses a static branch name, this decorator
17
+ calls a function to determine the branch name at runtime.
18
+
19
+ IMPORTANT: Automatically syncs .hop/ directory to all other active branches
20
+ after the decorated method completes (from locked branch to all others).
21
+
22
+ Args:
23
+ branch_getter: Callable that takes (self, *args, **kwargs) and returns branch name
24
+ timeout_minutes: Lock timeout in minutes (default: 30)
25
+
26
+ Usage:
27
+ def _get_release_branch(self, patch_id, *args, **kwargs):
28
+ # Logic to determine release branch from patch_id
29
+ return f"ho-release/{version}"
30
+
31
+ @with_dynamic_branch_lock(_get_release_branch)
32
+ def close_patch(self, patch_id):
33
+ # Will lock the release branch determined by _get_release_branch
34
+ ...
35
+
36
+ Notes:
37
+ - branch_getter is called with the same arguments as the decorated function
38
+ - The lock is ALWAYS released in the finally block, even on error
39
+ - After success, .hop/ is automatically synced from locked branch to all others
40
+ """
41
+ def decorator(func):
42
+ @wraps(func)
43
+ def wrapper(self, *args, **kwargs):
44
+ lock_tag = None
45
+ locked_branch = None
46
+ try:
47
+ # Determine branch name dynamically
48
+ locked_branch = branch_getter(self, *args, **kwargs)
49
+
50
+ # Acquire lock
51
+ lock_tag = self._repo.hgit.acquire_branch_lock(locked_branch, timeout_minutes=timeout_minutes)
52
+
53
+ # Execute the method
54
+ result = func(self, *args, **kwargs)
55
+
56
+ # After success, sync .hop/ from current branch to all other active branches
57
+ try:
58
+ sync_result = self._repo.sync_hop_to_active_branches(
59
+ reason=f"{func.__name__}"
60
+ )
61
+ # Log sync errors but don't fail the operation
62
+ if sync_result.get('errors'):
63
+ for error in sync_result['errors']:
64
+ print(f"Warning: .hop/ sync error: {error}", file=sys.stderr)
65
+ except Exception as e:
66
+ # Don't fail the decorated method if sync fails
67
+ print(f"Warning: Failed to sync .hop/ to active branches: {e}", file=sys.stderr)
68
+
69
+ return result
70
+ finally:
71
+ # Always release lock (even on error)
72
+ if lock_tag:
73
+ self._repo.hgit.release_branch_lock(lock_tag)
74
+
75
+ return wrapper
76
+ return decorator
77
+
78
+ class Node:
79
+ def __init__(self, name):
80
+ self.name = name
81
+ self.children = []
82
+
83
+ class Node:
84
+ def __init__(self, name):
85
+ self.name = name
86
+ self.children = []
87
+
88
+ def print_tree(node, depth=0):
89
+ print(" " * depth + node.name)
90
+ for child in node.children:
91
+ print_tree(child, depth + 1)
92
+
93
+
94
+ def trace_package(package_root: str):
95
+ def decorator(func):
96
+
97
+ def wrapper(*args, **kwargs):
98
+ root = Node(func.__qualname__)
99
+ stack = [root]
100
+
101
+ def tracer(frame, event, arg):
102
+ filename = frame.f_code.co_filename
103
+
104
+ # On garde uniquement les appels venant du package
105
+ if package_root not in filename:
106
+ return tracer
107
+
108
+ if event == 'call':
109
+ name = frame.f_code.co_qualname
110
+ node = Node(name)
111
+ stack[-1].children.append(node)
112
+ stack.append(node)
113
+
114
+ elif event == 'return':
115
+ if len(stack) > 1:
116
+ stack.pop()
117
+
118
+ return tracer
119
+
120
+ sys.settrace(tracer)
121
+ try:
122
+ result = func(*args, **kwargs)
123
+ finally:
124
+ sys.settrace(None)
125
+
126
+ print("\n=== Arbre d'exécution ===")
127
+ print_tree(root)
128
+ print("=========================\n")
129
+
130
+ return result # 🔥 retour normal, aucune modification
131
+
132
+ return wrapper
133
+ return decorator
@@ -247,6 +247,10 @@ class HGit:
247
247
  "Proxy to git.add method"
248
248
  return self.__git_repo.git.add(*args, **kwargs)
249
249
 
250
+ def rm(self, *args, **kwargs):
251
+ "Proxy to git.rm method"
252
+ return self.__git_repo.git.rm(*args, **kwargs)
253
+
250
254
  def commit(self, *args, **kwargs):
251
255
  "Proxy to git.commit method"
252
256
  return self.__git_repo.git.commit(*args, **kwargs)
@@ -234,23 +234,24 @@ class MigrationManager:
234
234
  return result
235
235
 
236
236
  @with_dynamic_branch_lock(lambda self, *args, **kwargs: 'ho-prod')
237
- def run_migrations(self, target_version: str, create_commit: bool = True, notify_branches: bool = True) -> Dict:
237
+ def run_migrations(self, target_version: str, create_commit: bool = True) -> Dict:
238
238
  """
239
239
  Run all pending migrations up to target version.
240
240
 
241
241
  IMPORTANT: This method acquires a lock on ho-prod branch via decorator.
242
242
  Should only be called when on ho-prod branch.
243
243
 
244
+ After successful completion, the decorator automatically syncs .hop/
245
+ directory to all active branches (ho-patch/*, ho-release/*).
246
+
244
247
  Args:
245
248
  target_version: Target version string (e.g., "0.17.1")
246
249
  create_commit: Whether to create Git commit after migration
247
- notify_branches: Whether to create empty commits on active branches
248
250
 
249
251
  Returns:
250
252
  Dict with migration results including:
251
253
  - migrations_applied: List of applied migrations
252
254
  - commit_created: Whether migration commit was created
253
- - notified_branches: List of branches that were notified
254
255
  """
255
256
  result = {
256
257
  'target_version': target_version,
@@ -296,28 +297,30 @@ class MigrationManager:
296
297
  self._repo, '_Repo__config'
297
298
  ) else "0.0.0"
298
299
 
300
+ # If already at target version, nothing to do
301
+ if current_version == target_version:
302
+ return result
303
+
299
304
  # Get pending migrations
300
305
  pending = self.get_pending_migrations(current_version, target_version)
301
306
 
302
- if not pending:
303
- # No migrations to apply
304
- return result
305
-
306
- # Apply each migration
307
- for version_str, migration_dir in pending:
308
- try:
309
- migration_result = self.apply_migration(version_str, migration_dir)
310
- result['migrations_applied'].append(migration_result)
307
+ # Apply each migration if there are any
308
+ if pending:
309
+ for version_str, migration_dir in pending:
310
+ try:
311
+ migration_result = self.apply_migration(version_str, migration_dir)
312
+ result['migrations_applied'].append(migration_result)
311
313
 
312
- except MigrationManagerError as e:
313
- result['errors'].append(str(e))
314
- raise
314
+ except MigrationManagerError as e:
315
+ result['errors'].append(str(e))
316
+ raise
315
317
 
316
- # Update hop_version in .hop/config
318
+ # Update hop_version in .hop/config (current_version != target_version)
319
+ # This ensures the version is updated even when upgrading between versions
320
+ # that have no migration scripts (e.g., 0.17.1-a2 → 0.17.2-a3)
317
321
  if hasattr(self._repo, '_Repo__config'):
318
322
  self._repo._Repo__config.hop_version = target_version
319
323
  self._repo._Repo__config.write()
320
- self._repo.hgit.add(str(Path('.hop') / 'config'))
321
324
 
322
325
  # Create Git commit if requested
323
326
  if create_commit and self._repo.hgit:
@@ -328,32 +331,25 @@ class MigrationManager:
328
331
  result['migrations_applied']
329
332
  )
330
333
 
331
- # Commit the changes added by the script
332
- self._repo.hgit.commit('-m', commit_msg)
334
+ # Commit and sync to active branches
335
+ sync_result = self._repo.commit_and_sync_to_active_branches(
336
+ message=commit_msg,
337
+ reason=f"migration {current_version} → {target_version}"
338
+ )
333
339
 
334
340
  result['commit_created'] = True
335
341
  result['commit_message'] = commit_msg
336
-
337
- # Push to remote
338
- try:
339
- self._repo.hgit.push()
340
- result['commit_pushed'] = True
341
- except Exception as e:
342
- result['errors'].append(f"Failed to push commit: {e}")
343
- result['commit_pushed'] = False
342
+ result['commit_pushed'] = True
343
+ result['sync_result'] = sync_result
344
344
 
345
345
  except Exception as e:
346
346
  # Don't fail migration if commit fails
347
347
  result['errors'].append(f"Failed to create commit: {e}")
348
348
 
349
- # Notify active branches if requested
350
- if notify_branches and create_commit and result['commit_created']:
351
- try:
352
- notified = self._notify_active_branches(current_version, target_version)
353
- result['notified_branches'] = notified
354
- except Exception as e:
355
- # Don't fail migration if notification fails
356
- result['errors'].append(f"Failed to notify branches: {e}")
349
+ # Note: Branch synchronization is now handled automatically by the
350
+ # @with_dynamic_branch_lock decorator when the method completes.
351
+ # The decorator calls repo.sync_hop_to_active_branches() for all
352
+ # operations on ho-prod, ensuring .hop/ is always synced.
357
353
 
358
354
  return result
359
355
 
@@ -369,111 +365,28 @@ class MigrationManager:
369
365
  Args:
370
366
  from_version: Starting version
371
367
  to_version: Target version
372
- migrations: List of migration result dicts
368
+ migrations: List of migration result dicts (can be empty)
373
369
 
374
370
  Returns:
375
371
  Commit message string
376
372
  """
377
373
  lines = [
378
- f"[HOP] Migration from {from_version} to {to_version}",
379
- "",
380
- "Applied migrations:"
374
+ f"[HOP] Migration from {from_version} to {to_version}"
381
375
  ]
382
376
 
383
- for migration in migrations:
384
- version = migration['version']
385
- files = migration['applied_files']
386
- lines.append(f" - {version}: {', '.join(files)}")
377
+ if migrations:
378
+ lines.append("")
379
+ lines.append("Applied migrations:")
380
+ for migration in migrations:
381
+ version = migration['version']
382
+ files = migration['applied_files']
383
+ lines.append(f" - {version}: {', '.join(files)}")
384
+ else:
385
+ lines.append("")
386
+ lines.append("No migration scripts needed (version update only)")
387
387
 
388
388
  return '\n'.join(lines)
389
389
 
390
- def _notify_active_branches(self, from_version: str, to_version: str) -> List[str]:
391
- """
392
- Apply migration to active branches (ho-patch/*, ho-release/*).
393
-
394
- Applies the same migrations on all active branches, creates commits,
395
- and pushes them to origin. This prevents merge conflicts when branches
396
- merge ho-prod later.
397
-
398
- Args:
399
- from_version: Starting version
400
- to_version: Target version
401
-
402
- Returns:
403
- List of branch names that were migrated
404
- """
405
- migrated_branches = []
406
-
407
- # Get active branches from origin using hgit method
408
- branches_status = self._repo.hgit.get_active_branches_status()
409
-
410
- # Extract branch names from patch_branches and release_branches
411
- patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
412
- release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
413
-
414
- # Combine all active branches (excluding ho-prod)
415
- active_branches = [b for b in patch_branches + release_branches if b != 'ho-prod']
416
-
417
- # Current branch (should be ho-prod)
418
- current_branch = self._repo.hgit.branch
419
-
420
- # Apply migration on each active branch
421
- for branch in active_branches:
422
- try:
423
- # Checkout branch
424
- self._repo.hgit.checkout(branch)
425
-
426
- # Reload config for this branch
427
- from half_orm_dev.repo import Config
428
- self._repo._Repo__config = Config(self._repo.base_dir)
429
-
430
- # Get pending migrations for this branch
431
- branch_version = self._repo._Repo__config.hop_version
432
- pending = self.get_pending_migrations(branch_version, to_version)
433
-
434
- if not pending:
435
- # Already migrated or no migration needed
436
- continue
437
-
438
- # Apply each migration
439
- for version_str, migration_dir in pending:
440
- self.apply_migration(version_str, migration_dir)
441
-
442
- # Update hop_version in .hop/config
443
- self._repo._Repo__config.hop_version = to_version
444
- self._repo._Repo__config.write()
445
- self._repo.hgit.add(Path('.hop') / 'config')
446
-
447
- # Create commit message
448
- commit_msg = self._create_migration_commit_message(
449
- branch_version,
450
- to_version,
451
- [{'version': v, 'applied_files': []} for v, _ in pending]
452
- )
453
-
454
- # commit the changes added by the script
455
- self._repo.hgit.commit('-m', commit_msg)
456
-
457
- # Push the commit
458
- self._repo.hgit.push_branch(branch)
459
-
460
- migrated_branches.append(branch)
461
-
462
- except Exception as e:
463
- print(f"Warning: Failed to migrate branch {branch}: {e}", file=sys.stderr)
464
-
465
- # Return to original branch (ho-prod)
466
- if current_branch:
467
- try:
468
- self._repo.hgit.checkout(current_branch)
469
- # Reload config for original branch
470
- from half_orm_dev.repo import Config
471
- self._repo._Repo__config = Config(self._repo.base_dir)
472
- except Exception:
473
- pass
474
-
475
- return migrated_branches
476
-
477
390
  def check_migration_needed(self, current_tool_version: str) -> bool:
478
391
  """
479
392
  Check if migration is needed.