half-orm-dev 1.0.0a30__tar.gz → 1.0.0a31__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.0a30/half_orm_dev.egg-info → half_orm_dev-1.0.0a31}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/bootstrap_manager.py +6 -2
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/file_executor.py +68 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/release_manager.py +69 -69
- half_orm_dev-1.0.0a31/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31/half_orm_dev.egg-info}/PKG-INFO +1 -1
- half_orm_dev-1.0.0a30/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/LICENSE +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/README.md +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/recover.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/rollback.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/database.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migration_manager.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/1/0/0/a20/01_update_gitignore.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/py.typed +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/repo.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/git-hooks/pre-push +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/git-hooks/reference-transaction +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev.egg-info/SOURCES.txt +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/setup.py +0 -0
|
@@ -18,7 +18,7 @@ from pathlib import Path
|
|
|
18
18
|
from typing import List, Set, Tuple, Optional, TYPE_CHECKING
|
|
19
19
|
|
|
20
20
|
from half_orm_dev.file_executor import (
|
|
21
|
-
execute_sql_file,
|
|
21
|
+
execute_sql_file, execute_python_bootstrap, FileExecutionError
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
@@ -180,7 +180,11 @@ class BootstrapManager:
|
|
|
180
180
|
if file_path.suffix == '.sql':
|
|
181
181
|
execute_sql_file(file_path, self._repo.database.model)
|
|
182
182
|
elif file_path.suffix == '.py':
|
|
183
|
-
output =
|
|
183
|
+
output = execute_python_bootstrap(
|
|
184
|
+
file_path,
|
|
185
|
+
model=self._repo.database.model,
|
|
186
|
+
cwd=self._bootstrap_dir
|
|
187
|
+
)
|
|
184
188
|
if output:
|
|
185
189
|
click.echo(f" Output: {output}")
|
|
186
190
|
else:
|
|
@@ -5,6 +5,8 @@ This module provides common file execution functionality used by both
|
|
|
5
5
|
PatchManager and BootstrapManager.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import ast
|
|
9
|
+
import importlib.util
|
|
8
10
|
import re
|
|
9
11
|
import subprocess
|
|
10
12
|
import sys
|
|
@@ -98,6 +100,72 @@ def execute_python_file(file_path: Path, cwd: Optional[Path] = None) -> str:
|
|
|
98
100
|
raise FileExecutionError(f"Failed to execute Python file {file_path.name}: {e}") from e
|
|
99
101
|
|
|
100
102
|
|
|
103
|
+
def _has_run_entrypoint(file_path: Path) -> bool:
|
|
104
|
+
"""Return True if the file defines a top-level run() function."""
|
|
105
|
+
try:
|
|
106
|
+
tree = ast.parse(file_path.read_text(encoding='utf-8'))
|
|
107
|
+
except (OSError, SyntaxError):
|
|
108
|
+
return False
|
|
109
|
+
return any(
|
|
110
|
+
isinstance(node, ast.FunctionDef) and node.name == 'run'
|
|
111
|
+
for node in tree.body
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def execute_python_bootstrap(file_path: Path, model, cwd: Optional[Path] = None) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Execute a Python bootstrap script.
|
|
118
|
+
|
|
119
|
+
Fast path — if the script defines a top-level run(model) function it is
|
|
120
|
+
loaded in-process via importlib and called with the live database model,
|
|
121
|
+
sharing the existing connection.
|
|
122
|
+
|
|
123
|
+
Slow path — scripts without run(model) are executed as a subprocess
|
|
124
|
+
(backwards-compatible with pre-API scripts).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
file_path: Path to Python bootstrap script
|
|
128
|
+
model: halfORM Model instance (shared database connection)
|
|
129
|
+
cwd: Working directory for execution (default: file's parent)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Return value of run() converted to str, or subprocess stdout.
|
|
133
|
+
Empty string if run() returns None.
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
FileExecutionError: If execution fails
|
|
137
|
+
"""
|
|
138
|
+
if cwd is None:
|
|
139
|
+
cwd = file_path.parent
|
|
140
|
+
|
|
141
|
+
if not _has_run_entrypoint(file_path):
|
|
142
|
+
return execute_python_file(file_path, cwd)
|
|
143
|
+
|
|
144
|
+
module_name = f"_hop_bootstrap_{file_path.stem.replace('-', '_').replace('.', '_')}"
|
|
145
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
146
|
+
module = importlib.util.module_from_spec(spec)
|
|
147
|
+
|
|
148
|
+
cwd_str = str(cwd)
|
|
149
|
+
inserted = cwd_str not in sys.path
|
|
150
|
+
if inserted:
|
|
151
|
+
sys.path.insert(0, cwd_str)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
spec.loader.exec_module(module)
|
|
155
|
+
result = module.run(model)
|
|
156
|
+
return str(result) if result is not None else ''
|
|
157
|
+
except FileExecutionError:
|
|
158
|
+
raise
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise FileExecutionError(
|
|
161
|
+
f"Python execution failed in {file_path.name}: {e}"
|
|
162
|
+
) from e
|
|
163
|
+
finally:
|
|
164
|
+
if inserted and cwd_str in sys.path:
|
|
165
|
+
sys.path.remove(cwd_str)
|
|
166
|
+
sys.modules.pop(module_name, None)
|
|
167
|
+
|
|
168
|
+
|
|
101
169
|
_HOP_MARKER = re.compile(r"(--|#)\s*@hop:(bootstrap|data)")
|
|
102
170
|
|
|
103
171
|
|
|
@@ -101,15 +101,7 @@ class ReleaseManager:
|
|
|
101
101
|
"""
|
|
102
102
|
schema_path = Path(self._base_dir) / ".hop" / "model" / "schema.sql"
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
version_from_file = self._parse_version_from_symlink(schema_path)
|
|
106
|
-
|
|
107
|
-
# Optional validation against database
|
|
108
|
-
version_from_db = self._repo.database.last_release_s
|
|
109
|
-
if version_from_file != version_from_db:
|
|
110
|
-
self._repo.restore_database_from_schema()
|
|
111
|
-
|
|
112
|
-
return version_from_file
|
|
104
|
+
return self._parse_version_from_symlink(schema_path)
|
|
113
105
|
|
|
114
106
|
def _parse_version_from_symlink(self, schema_path: Path) -> str:
|
|
115
107
|
"""
|
|
@@ -1138,11 +1130,8 @@ class ReleaseManager:
|
|
|
1138
1130
|
"""
|
|
1139
1131
|
Fetch tags and list available releases for production upgrade (read-only).
|
|
1140
1132
|
|
|
1141
|
-
Equivalent to 'apt update' - synchronizes with origin and shows available
|
|
1142
|
-
releases but makes NO modifications to database or repository.
|
|
1143
|
-
|
|
1144
1133
|
Workflow:
|
|
1145
|
-
1. Fetch
|
|
1134
|
+
1. Fetch all refs from origin (git fetch --prune)
|
|
1146
1135
|
2. Read current production version from database (hop_last_release)
|
|
1147
1136
|
3. List available release tags (v1.3.6, v1.3.6-rc1, v1.4.0)
|
|
1148
1137
|
4. Calculate sequential upgrade path
|
|
@@ -1181,7 +1170,11 @@ class ReleaseManager:
|
|
|
1181
1170
|
"""
|
|
1182
1171
|
allow_rc = self._repo.allow_rc
|
|
1183
1172
|
|
|
1184
|
-
# 1.
|
|
1173
|
+
# 1. Sync with remote then read available release tags
|
|
1174
|
+
try:
|
|
1175
|
+
self._repo.hgit.fetch_from_origin()
|
|
1176
|
+
except Exception as e:
|
|
1177
|
+
raise ReleaseManagerError(f"Failed to fetch from origin: {e}")
|
|
1185
1178
|
available_tags = self._get_available_release_tags(allow_rc=allow_rc)
|
|
1186
1179
|
|
|
1187
1180
|
# 2. Read current production version from database
|
|
@@ -1248,7 +1241,9 @@ class ReleaseManager:
|
|
|
1248
1241
|
if production_versions:
|
|
1249
1242
|
# Use last production version as target
|
|
1250
1243
|
target_version = production_versions[-1]
|
|
1251
|
-
upgrade_path = self._calculate_upgrade_path(
|
|
1244
|
+
upgrade_path = self._calculate_upgrade_path(
|
|
1245
|
+
current_version, target_version, available_tags=available_tags
|
|
1246
|
+
)
|
|
1252
1247
|
|
|
1253
1248
|
# 5. Return results
|
|
1254
1249
|
return {
|
|
@@ -1260,10 +1255,11 @@ class ReleaseManager:
|
|
|
1260
1255
|
|
|
1261
1256
|
def _get_available_release_tags(self, allow_rc: bool = False) -> List[str]:
|
|
1262
1257
|
"""
|
|
1263
|
-
Get available release tags from Git repository.
|
|
1258
|
+
Get available release tags from local Git repository.
|
|
1264
1259
|
|
|
1265
|
-
|
|
1260
|
+
Filters local tags for release tags (v*.*.*).
|
|
1266
1261
|
Excludes RC tags unless allow_rc=True.
|
|
1262
|
+
Caller is responsible for fetching from origin before calling this.
|
|
1267
1263
|
|
|
1268
1264
|
Args:
|
|
1269
1265
|
allow_rc: If True, include RC tags (v1.3.6-rc1)
|
|
@@ -1283,12 +1279,6 @@ class ReleaseManager:
|
|
|
1283
1279
|
tags = mgr._get_available_release_tags(allow_rc=True)
|
|
1284
1280
|
# → ["v1.3.6-rc1", "v1.3.6", "v1.4.0"]
|
|
1285
1281
|
"""
|
|
1286
|
-
try:
|
|
1287
|
-
# Fetch tags from origin
|
|
1288
|
-
self._repo.hgit.fetch_tags()
|
|
1289
|
-
except Exception as e:
|
|
1290
|
-
raise ReleaseManagerError(f"Failed to fetch tags from origin: {e}")
|
|
1291
|
-
|
|
1292
1282
|
# Get all tags from repository
|
|
1293
1283
|
try:
|
|
1294
1284
|
all_tags = self._repo.hgit._HGit__git_repo.tags
|
|
@@ -1314,7 +1304,8 @@ class ReleaseManager:
|
|
|
1314
1304
|
def _calculate_upgrade_path(
|
|
1315
1305
|
self,
|
|
1316
1306
|
current: str,
|
|
1317
|
-
target: str
|
|
1307
|
+
target: str,
|
|
1308
|
+
available_tags: Optional[List[str]] = None,
|
|
1318
1309
|
) -> List[str]:
|
|
1319
1310
|
"""
|
|
1320
1311
|
Calculate sequential upgrade path between two versions.
|
|
@@ -1348,7 +1339,8 @@ class ReleaseManager:
|
|
|
1348
1339
|
if current == target:
|
|
1349
1340
|
return []
|
|
1350
1341
|
|
|
1351
|
-
available_tags
|
|
1342
|
+
if available_tags is None:
|
|
1343
|
+
available_tags = self._get_available_release_tags(allow_rc=False)
|
|
1352
1344
|
|
|
1353
1345
|
available_versions = []
|
|
1354
1346
|
for tag in available_tags:
|
|
@@ -1386,10 +1378,10 @@ class ReleaseManager:
|
|
|
1386
1378
|
It does NOT use restore_database_from_schema() which would destroy data.
|
|
1387
1379
|
|
|
1388
1380
|
Workflow:
|
|
1389
|
-
1.
|
|
1381
|
+
1. Fetch available releases via update_production()
|
|
1390
1382
|
2. Validate production environment (ho-prod branch, clean repo)
|
|
1391
|
-
3.
|
|
1392
|
-
4.
|
|
1383
|
+
3. Calculate upgrade path (all or to specific version)
|
|
1384
|
+
4. CREATE BACKUP (last action before any destructive operation)
|
|
1393
1385
|
5. Apply each release sequentially on existing database
|
|
1394
1386
|
6. Update database version after each release
|
|
1395
1387
|
|
|
@@ -1488,36 +1480,10 @@ class ReleaseManager:
|
|
|
1488
1480
|
'message': 'Production already at latest version'
|
|
1489
1481
|
}
|
|
1490
1482
|
|
|
1491
|
-
# === 2.
|
|
1492
|
-
# Preferred: instant snapshot via CREATE DATABASE ... TEMPLATE (requires CREATEDB).
|
|
1493
|
-
# Fallback: full pg_dump.
|
|
1494
|
-
# Connections are terminated before the snapshot — this is intentional:
|
|
1495
|
-
# we want no application traffic during the schema migration anyway.
|
|
1496
|
-
snapshot_name = None
|
|
1497
|
-
backup_path = None
|
|
1498
|
-
if not dry_run and not skip_backup:
|
|
1499
|
-
db = self._repo.database
|
|
1500
|
-
version_slug = current_version.replace('.', '_').replace('-', '_')
|
|
1501
|
-
snap_name = f"{db.name}_hop_snap_{version_slug}"
|
|
1502
|
-
|
|
1503
|
-
if db.has_createdb_privilege():
|
|
1504
|
-
if force_backup:
|
|
1505
|
-
db.drop_snapshot(snap_name)
|
|
1506
|
-
db.terminate_active_connections()
|
|
1507
|
-
db.create_snapshot(snap_name)
|
|
1508
|
-
snapshot_name = snap_name
|
|
1509
|
-
# Our psycopg connection was terminated above — reconnect.
|
|
1510
|
-
db._Database__model.reconnect(reload=True)
|
|
1511
|
-
else:
|
|
1512
|
-
backup_path = self._create_production_backup(
|
|
1513
|
-
current_version,
|
|
1514
|
-
force=force_backup
|
|
1515
|
-
)
|
|
1516
|
-
|
|
1517
|
-
# === 3. Validate environment ===
|
|
1483
|
+
# === 2. Validate environment ===
|
|
1518
1484
|
self._validate_production_upgrade()
|
|
1519
1485
|
|
|
1520
|
-
# ===
|
|
1486
|
+
# === 3. Calculate upgrade path ===
|
|
1521
1487
|
if to_version:
|
|
1522
1488
|
# Upgrade to specific version
|
|
1523
1489
|
full_path = update_info['upgrade_path']
|
|
@@ -1558,19 +1524,48 @@ class ReleaseManager:
|
|
|
1558
1524
|
'final_version': upgrade_path[-1] if upgrade_path else current_version
|
|
1559
1525
|
}
|
|
1560
1526
|
|
|
1561
|
-
# ===
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1527
|
+
# === 4. SNAPSHOT OR BACKUP (last step before any destructive operation) ===
|
|
1528
|
+
# Preferred: instant snapshot via CREATE DATABASE ... TEMPLATE (requires CREATEDB).
|
|
1529
|
+
# Fallback: full pg_dump.
|
|
1530
|
+
# Connections are terminated before the snapshot — this is intentional:
|
|
1531
|
+
# we want no application traffic during the schema migration anyway.
|
|
1532
|
+
snapshot_name = None
|
|
1533
|
+
backup_path = None
|
|
1534
|
+
if not skip_backup:
|
|
1535
|
+
db = self._repo.database
|
|
1536
|
+
version_slug = current_version.replace('.', '_').replace('-', '_')
|
|
1537
|
+
snap_name = f"{db.name}_hop_snap_{version_slug}"
|
|
1538
|
+
|
|
1539
|
+
if db.has_createdb_privilege():
|
|
1540
|
+
if force_backup:
|
|
1541
|
+
db.drop_snapshot(snap_name)
|
|
1542
|
+
db.terminate_active_connections()
|
|
1543
|
+
db.create_snapshot(snap_name)
|
|
1544
|
+
snapshot_name = snap_name
|
|
1545
|
+
# Our psycopg connection was terminated above — reconnect.
|
|
1546
|
+
db._Database__model.reconnect(reload=True)
|
|
1547
|
+
else:
|
|
1548
|
+
backup_path = self._create_production_backup(
|
|
1549
|
+
current_version,
|
|
1550
|
+
force=force_backup
|
|
1551
|
+
)
|
|
1568
1552
|
|
|
1553
|
+
# === 5. Apply releases ===
|
|
1554
|
+
git_repo = self._repo.hgit._HGit__git_repo
|
|
1569
1555
|
patches_applied = {}
|
|
1570
1556
|
try:
|
|
1571
1557
|
for version in upgrade_path:
|
|
1572
|
-
# Checkout immutable ho-prod-X.Y.Z branch (created at promote time)
|
|
1558
|
+
# Checkout immutable ho-prod-X.Y.Z branch (created at promote time).
|
|
1559
|
+
# Fetch explicitly: production servers cloned with --single-branch only
|
|
1560
|
+
# track ho-prod, so fetch_from_origin() won't fetch ho-prod-X.Y.Z.
|
|
1573
1561
|
prod_branch = f"ho-prod-{version}"
|
|
1562
|
+
try:
|
|
1563
|
+
git_repo.git.fetch(
|
|
1564
|
+
'origin',
|
|
1565
|
+
f'{prod_branch}:refs/remotes/origin/{prod_branch}'
|
|
1566
|
+
)
|
|
1567
|
+
except Exception:
|
|
1568
|
+
pass # already present locally or will fail at checkout below
|
|
1574
1569
|
local_heads = {h.name: h for h in git_repo.heads}
|
|
1575
1570
|
if prod_branch in local_heads:
|
|
1576
1571
|
local_heads[prod_branch].checkout()
|
|
@@ -1736,13 +1731,18 @@ class ReleaseManager:
|
|
|
1736
1731
|
mgr._validate_production_upgrade()
|
|
1737
1732
|
# → Raises: "Repository has uncommitted changes"
|
|
1738
1733
|
"""
|
|
1739
|
-
# Check branch: must be on a ho-prod-X.Y.Z
|
|
1734
|
+
# Check branch: must be on a production branch (ho-prod, ho-prod-X.Y.Z, or ho-current)
|
|
1740
1735
|
current_branch = self._repo.hgit.branch
|
|
1741
|
-
|
|
1736
|
+
valid = (
|
|
1737
|
+
current_branch == "ho-prod"
|
|
1738
|
+
or current_branch == "ho-current"
|
|
1739
|
+
or current_branch.startswith("ho-prod-")
|
|
1740
|
+
)
|
|
1741
|
+
if not valid:
|
|
1742
1742
|
raise ReleaseManagerError(
|
|
1743
|
-
f"Must be on a ho-prod-X.Y.Z
|
|
1744
|
-
f"
|
|
1745
|
-
f"
|
|
1743
|
+
f"Must be on a production branch (ho-prod, ho-prod-X.Y.Z, or ho-current) "
|
|
1744
|
+
f"for production upgrade.\n"
|
|
1745
|
+
f"Current branch: {current_branch}"
|
|
1746
1746
|
)
|
|
1747
1747
|
|
|
1748
1748
|
# Check repo is clean
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a31
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.0-a30
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/cli/commands/revert_migration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a30 → half_orm_dev-1.0.0a31}/half_orm_dev/templates/git-hooks/prepare-commit-msg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|