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.
- {half_orm_dev-1.0.0a31/half_orm_dev.egg-info → half_orm_dev-1.0.0a33}/PKG-INFO +23 -19
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/README.md +19 -17
- half_orm_dev-1.0.0a33/half_orm_dev/_bootstrap_runner.py +39 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/__init__.py +3 -5
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/check.py +1 -1
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/clone.py +2 -2
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/init.py +20 -4
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/recover.py +1 -1
- half_orm_dev-1.0.0a33/half_orm_dev/cli/commands/restore.py +62 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/todo.py +0 -2
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/main.py +2 -2
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/database.py +124 -104
- half_orm_dev-1.0.0a33/half_orm_dev/file_executor.py +336 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/hgit.py +69 -2
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/migration_manager.py +23 -13
- {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
- half_orm_dev-1.0.0a33/half_orm_dev/migrations/1/0/0/a33/00_metadata_to_data_sql.py +61 -0
- half_orm_dev-1.0.0a33/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +54 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/modules.py +16 -29
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patch_manager.py +230 -183
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/release_file.py +3 -4
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/release_manager.py +28 -72
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/repo.py +318 -229
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/.gitignore +3 -1
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/init_module_template +1 -1
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/module_template_1 +0 -1
- half_orm_dev-1.0.0a33/half_orm_dev/templates/module_template_2 +7 -0
- half_orm_dev-1.0.0a33/half_orm_dev/venv_setup.py +71 -0
- half_orm_dev-1.0.0a33/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33/half_orm_dev.egg-info}/PKG-INFO +23 -19
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/SOURCES.txt +5 -4
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/requires.txt +3 -1
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/setup.py +4 -1
- half_orm_dev-1.0.0a33/tests/test_bootstrap_runner.py +65 -0
- half_orm_dev-1.0.0a31/half_orm_dev/bootstrap_manager.py +0 -392
- half_orm_dev-1.0.0a31/half_orm_dev/cli/commands/bootstrap.py +0 -139
- half_orm_dev-1.0.0a31/half_orm_dev/cli/commands/restore.py +0 -14
- half_orm_dev-1.0.0a31/half_orm_dev/file_executor.py +0 -219
- half_orm_dev-1.0.0a31/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -103
- half_orm_dev-1.0.0a31/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -18
- half_orm_dev-1.0.0a31/half_orm_dev/templates/module_template_2 +0 -7
- half_orm_dev-1.0.0a31/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/LICENSE +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/rollback.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/decorators.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patch_validator.py +0 -0
- {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
- {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
- {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
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/py.typed +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/pre-push +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/git-hooks/reference-transaction +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a31 → half_orm_dev-1.0.0a33}/pyproject.toml +0 -0
- {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.
|
|
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:
|
|
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
|
|
389
|
+
### Bootstrap - Data Initialization
|
|
388
390
|
|
|
389
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
404
|
-
-
|
|
405
|
-
-
|
|
406
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
|
356
|
+
### Bootstrap - Data Initialization
|
|
357
357
|
|
|
358
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
|
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=
|
|
18
|
-
@click.option('--port', default=
|
|
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=
|
|
29
|
-
@click.option('--port', default=
|
|
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
|
-
|
|
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.
|
|
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', '
|
|
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):
|