half-orm-dev 1.0.0a31__tar.gz → 1.0.0a33__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 (91) hide show
  1. {half_orm_dev-1.0.0a31/half_orm_dev.egg-info → half_orm_dev-1.0.0a33}/PKG-INFO +23 -19
  2. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/README.md +19 -17
  3. half_orm_dev-1.0.0a33/half_orm_dev/_bootstrap_runner.py +39 -0
  4. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/__init__.py +3 -5
  5. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/check.py +1 -1
  6. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/clone.py +2 -2
  7. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/init.py +20 -4
  8. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/recover.py +1 -1
  9. half_orm_dev-1.0.0a33/half_orm_dev/cli/commands/restore.py +62 -0
  10. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/todo.py +0 -2
  11. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/main.py +2 -2
  12. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/database.py +124 -104
  13. half_orm_dev-1.0.0a33/half_orm_dev/file_executor.py +336 -0
  14. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/hgit.py +69 -2
  15. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migration_manager.py +23 -13
  16. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +3 -3
  17. half_orm_dev-1.0.0a33/half_orm_dev/migrations/1/0/0/a33/00_metadata_to_data_sql.py +61 -0
  18. half_orm_dev-1.0.0a33/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +54 -0
  19. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/modules.py +16 -29
  20. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patch_manager.py +230 -183
  21. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/release_file.py +3 -4
  22. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/release_manager.py +28 -72
  23. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/repo.py +318 -229
  24. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/.gitignore +3 -1
  25. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/init_module_template +1 -1
  26. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/module_template_1 +0 -1
  27. half_orm_dev-1.0.0a33/half_orm_dev/templates/module_template_2 +7 -0
  28. half_orm_dev-1.0.0a33/half_orm_dev/venv_setup.py +71 -0
  29. half_orm_dev-1.0.0a33/half_orm_dev/version.txt +1 -0
  30. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33/half_orm_dev.egg-info}/PKG-INFO +23 -19
  31. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/SOURCES.txt +5 -4
  32. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/requires.txt +3 -1
  33. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/setup.py +4 -1
  34. half_orm_dev-1.0.0a33/tests/test_bootstrap_runner.py +65 -0
  35. half_orm_dev-1.0.0a31/half_orm_dev/bootstrap_manager.py +0 -392
  36. half_orm_dev-1.0.0a31/half_orm_dev/cli/commands/bootstrap.py +0 -139
  37. half_orm_dev-1.0.0a31/half_orm_dev/cli/commands/restore.py +0 -14
  38. half_orm_dev-1.0.0a31/half_orm_dev/file_executor.py +0 -219
  39. half_orm_dev-1.0.0a31/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -103
  40. half_orm_dev-1.0.0a31/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -18
  41. half_orm_dev-1.0.0a31/half_orm_dev/templates/module_template_2 +0 -7
  42. half_orm_dev-1.0.0a31/half_orm_dev/version.txt +0 -1
  43. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/AUTHORS +0 -0
  44. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/LICENSE +0 -0
  45. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/__init__.py +0 -0
  46. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/__init__.py +0 -0
  47. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/apply.py +0 -0
  48. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/migrate.py +0 -0
  49. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/patch.py +0 -0
  50. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/release.py +0 -0
  51. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/revert_migration.py +0 -0
  52. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/rollback.py +0 -0
  53. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
  54. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/sync.py +0 -0
  55. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/undo.py +0 -0
  56. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/upgrade.py +0 -0
  57. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli_extension.py +0 -0
  58. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/decorators.py +0 -0
  59. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
  60. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
  61. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
  62. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
  63. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
  64. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
  65. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migrations/1/0/0/a20/01_update_gitignore.py +0 -0
  66. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patch_validator.py +0 -0
  67. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
  68. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
  69. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
  70. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/log +0 -0
  71. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
  72. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/py.typed +0 -0
  73. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/scripts/repair-metadata.py +0 -0
  74. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/MANIFEST.in +0 -0
  75. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/README +0 -0
  76. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/conftest_template +0 -0
  77. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
  78. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/pre-push +0 -0
  79. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
  80. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/reference-transaction +0 -0
  81. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/module_template_3 +0 -0
  82. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/pyproject.toml +0 -0
  83. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/relation_test +0 -0
  84. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/sql_adapter +0 -0
  85. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/warning +0 -0
  86. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/utils.py +0 -0
  87. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/dependency_links.txt +0 -0
  88. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/entry_points.txt +0 -0
  89. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/top_level.txt +0 -0
  90. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/pyproject.toml +0 -0
  91. {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm_dev
3
- Version: 1.0.0a31
3
+ Version: 1.0.0a33
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
@@ -21,9 +21,11 @@ License-File: LICENSE
21
21
  License-File: AUTHORS
22
22
  Requires-Dist: GitPython
23
23
  Requires-Dist: click
24
+ Requires-Dist: packaging
24
25
  Requires-Dist: pydash
25
26
  Requires-Dist: pytest
26
- Requires-Dist: half_orm<1.1.0,>=1.0.0a1
27
+ Requires-Dist: pytest-asyncio
28
+ Requires-Dist: half_orm<1.1.0,>=1.0.0rc16
27
29
  Requires-Dist: tomli>=2.0.0; python_version < "3.11"
28
30
  Requires-Dist: tomli_w>=1.0.0
29
31
  Dynamic: license-file
@@ -384,29 +386,31 @@ half_orm dev upgrade [--to-release X.Y.Z]
384
386
  half_orm dev upgrade --dry-run
385
387
  ```
386
388
 
387
- ### Data Bootstrap
389
+ ### Bootstrap - Data Initialization
388
390
 
389
- Mark patch files with `-- @HOP:bootstrap` (SQL) or `# @HOP:bootstrap` (Python) to declare reference data.
390
- The marker **must be on the first line** of the file:
391
+ Bootstrap scripts initialize application data on empty databases. Place SQL and Python files in the `bootstrap/` directory:
391
392
 
392
- ```sql
393
- -- @HOP:bootstrap
394
- INSERT INTO roles (name) VALUES ('admin'), ('user') ON CONFLICT DO NOTHING;
395
393
  ```
396
-
397
- ```python
398
- # @HOP:bootstrap
399
- # (no shebang — the file is executed directly by half-orm-dev)
400
- MyModel(field='value').ho_insert()
394
+ bootstrap/
395
+ ├── 01-init-roles.sql
396
+ ├── 02-seed-config.py
397
+ └── 03-reference-data.sql
401
398
  ```
402
399
 
403
- These files are automatically:
404
- - Copied to `bootstrap/` during `patch merge`
405
- - Executed during production `upgrade`
406
- - Tracked in database (each script runs once)
400
+ Files are executed **alphabetically** during:
401
+ - **Development**: Each `patch apply` (allows iteration on bootstrap scripts)
402
+ - **Production**: Initial `clone` only (one-time initialization)
403
+
404
+ For production data changes, use **patches** (not bootstrap).
407
405
 
408
- > **Note:** If the marker is not on the first line it is silently ignored.
409
- > A warning is displayed during `patch apply` to help catch this mistake.
406
+ **Python files** can define a `run(model)` function to share the database connection:
407
+
408
+ ```python
409
+ def run(model):
410
+ # model is the halfORM Model instance with active connection
411
+ MyModel = model.get_relation_class('schema.table')
412
+ MyModel(field='value').ho_insert()
413
+ ```
410
414
 
411
415
  **Note:** Use `half_orm dev <command> --help` for detailed help on each command.
412
416
 
@@ -353,29 +353,31 @@ half_orm dev upgrade [--to-release X.Y.Z]
353
353
  half_orm dev upgrade --dry-run
354
354
  ```
355
355
 
356
- ### Data Bootstrap
356
+ ### Bootstrap - Data Initialization
357
357
 
358
- Mark patch files with `-- @HOP:bootstrap` (SQL) or `# @HOP:bootstrap` (Python) to declare reference data.
359
- The marker **must be on the first line** of the file:
358
+ Bootstrap scripts initialize application data on empty databases. Place SQL and Python files in the `bootstrap/` directory:
360
359
 
361
- ```sql
362
- -- @HOP:bootstrap
363
- INSERT INTO roles (name) VALUES ('admin'), ('user') ON CONFLICT DO NOTHING;
364
360
  ```
365
-
366
- ```python
367
- # @HOP:bootstrap
368
- # (no shebang — the file is executed directly by half-orm-dev)
369
- MyModel(field='value').ho_insert()
361
+ bootstrap/
362
+ ├── 01-init-roles.sql
363
+ ├── 02-seed-config.py
364
+ └── 03-reference-data.sql
370
365
  ```
371
366
 
372
- These files are automatically:
373
- - Copied to `bootstrap/` during `patch merge`
374
- - Executed during production `upgrade`
375
- - Tracked in database (each script runs once)
367
+ Files are executed **alphabetically** during:
368
+ - **Development**: Each `patch apply` (allows iteration on bootstrap scripts)
369
+ - **Production**: Initial `clone` only (one-time initialization)
370
+
371
+ For production data changes, use **patches** (not bootstrap).
376
372
 
377
- > **Note:** If the marker is not on the first line it is silently ignored.
378
- > A warning is displayed during `patch apply` to help catch this mistake.
373
+ **Python files** can define a `run(model)` function to share the database connection:
374
+
375
+ ```python
376
+ def run(model):
377
+ # model is the halfORM Model instance with active connection
378
+ MyModel = model.get_relation_class('schema.table')
379
+ MyModel(field='value').ho_insert()
380
+ ```
379
381
 
380
382
  **Note:** Use `half_orm dev <command> --help` for detailed help on each command.
381
383
 
@@ -0,0 +1,39 @@
1
+ """
2
+ Internal entrypoint for running a single bootstrap script in a project's
3
+ own virtual environment, as a subprocess.
4
+
5
+ Invoked as:
6
+ <venv_python> -m half_orm_dev._bootstrap_runner <script_path> <database_name>
7
+
8
+ Not a public API - used exclusively by
9
+ half_orm_dev.file_executor.execute_python_bootstrap() when a venv_python
10
+ is provided (i.e. the project declared its own dependencies via
11
+ requirements.txt). Running via -m rather than a bare script path avoids
12
+ needing to locate this file on disk from inside the target venv; it only
13
+ needs half_orm_dev importable there, which pip install -e . already
14
+ guarantees (pyproject.toml always depends on half_orm_dev).
15
+ """
16
+
17
+ import importlib.util
18
+ import sys
19
+
20
+ from half_orm.model import Model
21
+
22
+
23
+ def main() -> None:
24
+ script_path, database_name = sys.argv[1], sys.argv[2]
25
+
26
+ spec = importlib.util.spec_from_file_location('_bootstrap_script', script_path)
27
+ module = importlib.util.module_from_spec(spec)
28
+ spec.loader.exec_module(module)
29
+
30
+ run = getattr(module, 'run', None)
31
+ if run is not None:
32
+ model = Model(database_name)
33
+ result = run(model)
34
+ if result is not None:
35
+ print(result)
36
+
37
+
38
+ if __name__ == '__main__':
39
+ main()
@@ -15,14 +15,13 @@ from .check import check
15
15
  from .set_git_origin import set_git_origin
16
16
  from .migrate import migrate
17
17
  from .revert_migration import revert_migration
18
- from .bootstrap import bootstrap
19
18
  from .rollback import rollback
20
19
  from .recover import recover
20
+ from .restore import restore
21
21
  from .todo import apply_release
22
22
 
23
23
  # ♻️ Adapted existing commands
24
24
  from .todo import sync_package # Unchanged
25
- from .todo import restore # Adapted for new architecture
26
25
 
27
26
  # Registry of all available commands - Git-centric architecture
28
27
  ALL_COMMANDS = {
@@ -36,17 +35,16 @@ ALL_COMMANDS = {
36
35
  'set-git-origin': set_git_origin, # Update git remote origin URL
37
36
  'migrate': migrate, # Repository migration after upgrade
38
37
  'revert-migration': revert_migration, # Revert last migration
39
- 'bootstrap': bootstrap, # Execute data initialization scripts
40
38
  # 🚧 (stubs)
41
39
  'apply_release': apply_release,
42
40
 
43
41
  # 🚧 Emergency workflow (stubs)
44
42
  'rollback': rollback,
45
43
  'recover': recover,
44
+ 'restore': restore,
46
45
 
47
46
  # ♻️ Adapted commands
48
47
  'sync-package': sync_package, # Unchanged
49
- 'restore': restore, # Adapted
50
48
  }
51
49
 
52
50
  __all__ = [
@@ -58,9 +56,9 @@ __all__ = [
58
56
  'upgrade',
59
57
  'check',
60
58
  'migrate',
61
- 'bootstrap',
62
59
  'rollback',
63
60
  'recover',
61
+ 'restore',
64
62
  # Adapted commands
65
63
  'sync_package',
66
64
  'ALL_COMMANDS'
@@ -162,7 +162,7 @@ def _display_check_results(repo, result: dict, dry_run: bool, verbose: bool):
162
162
  click.echo(f"\n🔧 {utils.Color.bold('Orphaned patches')} ({len(orphaned_patches)}):")
163
163
  for patch_id in sorted(orphaned_patches):
164
164
  click.echo(f" • {patch_id}")
165
- click.echo(f" {utils.Color.blue('(Use \"half_orm dev release attach-patch <id>\" to reattach)')}")
165
+ click.echo(f""" {utils.Color.blue('(Use "half_orm dev release attach-patch <id>" to reattach)')}""")
166
166
 
167
167
  # Show standalone patch branches (not in candidates/stage)
168
168
  standalone_patches = [b for b in patch_branches
@@ -14,8 +14,8 @@ from half_orm_dev.repo import Repo, RepoError
14
14
  @click.argument('git_origin')
15
15
  @click.option('--database-name', default=None, help='Custom local database name (default: use project name)')
16
16
  @click.option('--dest-dir', default=None, help='Destination directory name (default: infer from git URL)')
17
- @click.option('--host', default='localhost', help='PostgreSQL host (default: localhost)')
18
- @click.option('--port', default=5432, type=int, help='PostgreSQL port (default: 5432)')
17
+ @click.option('--host', default=None, help='PostgreSQL host (default: $PGHOST, then localhost)')
18
+ @click.option('--port', default=None, type=int, help='PostgreSQL port (default: $PGPORT, then 5432)')
19
19
  @click.option('--user', default=None, help='Database user (default: $USER)')
20
20
  @click.option('--password', default=None, help='Database password (prompts if missing)')
21
21
  @click.option('--production', is_flag=True, help='Production mode (default: False)')
@@ -25,8 +25,8 @@ class ProjectDirectoryExistsError(Exception):
25
25
 
26
26
  @click.command('init')
27
27
  @click.argument('project_name')
28
- @click.option('--host', default='localhost', help='PostgreSQL host (default: localhost)')
29
- @click.option('--port', default=5432, type=int, help='PostgreSQL port (default: 5432)')
28
+ @click.option('--host', default=None, help='PostgreSQL host (default: $PGHOST, then localhost)')
29
+ @click.option('--port', default=None, type=int, help='PostgreSQL port (default: $PGPORT, then 5432)')
30
30
  @click.option('--user', default=None, help='Database user (default: $USER)')
31
31
  @click.option('--password', default=None, help='Database password (prompts if missing)')
32
32
  @click.option('--git-origin', default=None, help='Git remote origin URL (prompts if missing)')
@@ -34,7 +34,15 @@ class ProjectDirectoryExistsError(Exception):
34
34
  @click.option('--force-sync-only', is_flag=True, help='Skip metadata installation, force sync-only mode')
35
35
  @click.option('--create-db', is_flag=False, default=True)
36
36
  @click.option('--docker', default='', help='Docker container name for PostgreSQL')
37
- def init(project_name, host, port, user, password, git_origin, production, force_sync_only, create_db, docker):
37
+ @click.option(
38
+ '--with-meta', is_flag=False, flag_value='__ALL__', default=None,
39
+ metavar='[NAMES]',
40
+ help='Expose half_orm_meta data on the generated Model. Bare flag '
41
+ 'exposes everything; pass a comma-separated allowlist of '
42
+ 'fully-qualified relation names to expose only those, e.g. '
43
+ '--with-meta=half_orm_meta.identity.user'
44
+ )
45
+ def init(project_name, host, port, user, password, git_origin, production, force_sync_only, create_db, docker, with_meta):
38
46
  """
39
47
  Initialize a new half_orm_dev project with database and code structure.
40
48
 
@@ -187,10 +195,18 @@ def init(project_name, host, port, user, password, git_origin, production, force
187
195
  click.echo(f"📁 Creating project structure...")
188
196
 
189
197
  # Now safe to instantiate Repo (database is configured)
198
+ if with_meta is None:
199
+ with_half_orm_meta = False
200
+ elif with_meta == '__ALL__':
201
+ with_half_orm_meta = True
202
+ else:
203
+ with_half_orm_meta = with_meta
204
+
190
205
  repo = Repo()
191
206
  repo.init_git_centric_project(
192
207
  package_name=package_name,
193
- git_origin=git_origin
208
+ git_origin=git_origin,
209
+ with_half_orm_meta=with_half_orm_meta
194
210
  )
195
211
 
196
212
  # ============================================================
@@ -41,6 +41,6 @@ def recover() -> None:
41
41
 
42
42
  if result['errors']:
43
43
  for error in result['errors']:
44
- click.echo(utils.Color.yellow(f"Warning: {error}"), err=True)
44
+ click.echo(utils.Color.bold(f"Warning: {error}"), err=True)
45
45
 
46
46
  click.echo(utils.Color.green("Recovery complete."))
@@ -0,0 +1,62 @@
1
+ """
2
+ Restore command - Restore the development database to a given release.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+ from half_orm_dev.repo import Repo, RepoError
9
+ from half_orm import utils
10
+
11
+
12
+ @click.command()
13
+ @click.argument('release')
14
+ def restore(release: str) -> None:
15
+ """
16
+ Restore the database to the state of RELEASE.
17
+
18
+ Drops all user schemas and reloads them, picking the most precise
19
+ snapshot available for RELEASE:
20
+
21
+ \b
22
+ 1. .hop/model/release-RELEASE.sql (production + patches staged
23
+ for that release, still in development)
24
+ 2. .hop/model/schema-RELEASE.sql (published snapshot of that
25
+ exact released version)
26
+
27
+ Fails with an error, rather than silently loading a different
28
+ version, if neither file exists.
29
+
30
+ \b
31
+ Examples:
32
+ # Restore to a release currently in development
33
+ half_orm dev restore 0.17.1
34
+
35
+ # Restore to an already-published version
36
+ half_orm dev restore 0.3.5
37
+ """
38
+ try:
39
+ repo = Repo()
40
+ click.echo(f"Restoring database to release {utils.Color.bold(release)}...")
41
+
42
+ release_schema_path = repo.get_release_schema_path(release)
43
+ schema_path = Path(repo.model_dir) / f"schema-{release}.sql"
44
+
45
+ if release_schema_path.exists():
46
+ repo.restore_database_from_release_schema(release)
47
+ elif schema_path.exists():
48
+ repo.restore_database_from_version_schema(release)
49
+ else:
50
+ raise RepoError(
51
+ f"No schema found for release '{release}': neither "
52
+ f"{release_schema_path.name} nor {schema_path.name} "
53
+ f"exists in {repo.model_dir}."
54
+ )
55
+
56
+ click.echo(f"✓ {utils.Color.green('Database restored to')} {utils.Color.bold(release)}")
57
+ except RepoError as e:
58
+ click.echo(utils.Color.red(f"\n❌ {e}"), err=True)
59
+ raise click.Abort()
60
+ except Exception as e:
61
+ click.echo(utils.Color.red(f"\n❌ Unexpected error: {e}"), err=True)
62
+ raise click.Abort()
@@ -42,7 +42,6 @@ def todo(ctx):
42
42
  'create-hotfix': 'Create emergency hotfix bypassing normal workflow',
43
43
  'rollback': 'Rollback database to previous version using backups/',
44
44
  'sync-package': 'Synchronize Python package with database model',
45
- 'restore': 'Restore database to specific version (adapt for new backups)',
46
45
  'list-patches': 'List all patches in Patches/ directory',
47
46
  'status': 'Show development status with patch/release information',
48
47
  'apply-release': 'Apply next release',
@@ -70,4 +69,3 @@ status = todo
70
69
 
71
70
  # ♻️ Commands to adapt (also in todo for now)
72
71
  sync_package = todo # Keep functionality, adapt to new architecture
73
- restore = todo # Adapt for new backup/restore logic
@@ -60,8 +60,8 @@ class Hop:
60
60
  return ['sync-package', 'check']
61
61
 
62
62
  # DEVELOPMENT ENVIRONMENT - Patch development
63
- return ['patch', 'release', 'check', 'bootstrap', 'set-git-origin',
64
- 'revert-migration', 'recover']
63
+ return ['patch', 'release', 'check', 'set-git-origin',
64
+ 'revert-migration', 'recover', 'restore']
65
65
 
66
66
  @property
67
67
  def repo_checked(self):