half-orm-dev 1.0.0a14__tar.gz → 1.0.0a16__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.0a14/half_orm_dev.egg-info → half_orm_dev-1.0.0a16}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/hgit.py +22 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migration_manager.py +34 -2
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/modules.py +85 -36
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/release_manager.py +55 -15
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/repo.py +35 -14
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/module_template_1 +1 -2
- half_orm_dev-1.0.0a16/half_orm_dev/templates/module_template_2 +7 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/module_template_3 +1 -1
- half_orm_dev-1.0.0a16/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev.egg-info/SOURCES.txt +0 -1
- half_orm_dev-1.0.0a14/half_orm_dev/templates/module_stub_template +0 -8
- half_orm_dev-1.0.0a14/half_orm_dev/templates/module_template_2 +0 -7
- half_orm_dev-1.0.0a14/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/LICENSE +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/README.md +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/bootstrap_manager.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/database.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/file_executor.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/setup.py +0 -0
|
@@ -931,6 +931,28 @@ class HGit:
|
|
|
931
931
|
print(f"⚠️ Warning: Failed to delete local lock tag {lock_tag}: {e}")
|
|
932
932
|
|
|
933
933
|
|
|
934
|
+
def is_branch_locked(self, branch_name: str, timeout_minutes: int = 30) -> bool:
|
|
935
|
+
"""Return True if a valid (non-stale) lock tag for branch_name exists on origin."""
|
|
936
|
+
self.fetch_tags()
|
|
937
|
+
safe_branch_name = branch_name.replace('/', '-')
|
|
938
|
+
lock_pattern = f"lock-{safe_branch_name}-*"
|
|
939
|
+
existing_locks = self.list_tags(pattern=lock_pattern)
|
|
940
|
+
if not existing_locks:
|
|
941
|
+
return False
|
|
942
|
+
lock_name = existing_locks[0]
|
|
943
|
+
remote_ref = self.__git_repo.git.ls_remote(
|
|
944
|
+
'--tags', 'origin', f'refs/tags/{lock_name}'
|
|
945
|
+
).strip()
|
|
946
|
+
if not remote_ref:
|
|
947
|
+
return False
|
|
948
|
+
match = re.search(r'-(\d+)$', lock_name)
|
|
949
|
+
if not match:
|
|
950
|
+
return True
|
|
951
|
+
lock_timestamp_ms = int(match.group(1))
|
|
952
|
+
lock_time = datetime.fromtimestamp(lock_timestamp_ms / 1000.0, tz=timezone.utc)
|
|
953
|
+
age_minutes = (datetime.now(timezone.utc) - lock_time).total_seconds() / 60
|
|
954
|
+
return age_minutes <= timeout_minutes
|
|
955
|
+
|
|
934
956
|
def list_tags(self, pattern: Optional[str] = None) -> List[str]:
|
|
935
957
|
"""
|
|
936
958
|
List tags matching glob pattern.
|
|
@@ -25,7 +25,9 @@ import re
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import sys
|
|
27
27
|
import importlib.util
|
|
28
|
+
from configparser import ConfigParser
|
|
28
29
|
from packaging import version
|
|
30
|
+
import click
|
|
29
31
|
from pathlib import Path
|
|
30
32
|
from typing import List, Dict, Optional, Tuple
|
|
31
33
|
from half_orm import utils
|
|
@@ -283,14 +285,44 @@ class MigrationManager:
|
|
|
283
285
|
)
|
|
284
286
|
ahead, behind = map(int, result_check.stdout.strip().split())
|
|
285
287
|
if behind > 0:
|
|
288
|
+
# Check whether the migration was already applied on origin/ho-prod.
|
|
289
|
+
already_done = False
|
|
290
|
+
try:
|
|
291
|
+
remote_config_text = self._repo.hgit._HGit__git_repo.git.show(
|
|
292
|
+
'origin/ho-prod:.hop/config'
|
|
293
|
+
)
|
|
294
|
+
_cp = ConfigParser()
|
|
295
|
+
_cp.read_string(remote_config_text)
|
|
296
|
+
remote_version = _cp['halfORM'].get('hop_version', '')
|
|
297
|
+
if remote_version == target_version:
|
|
298
|
+
already_done = True
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
if already_done:
|
|
303
|
+
# Another developer already ran the migration.
|
|
304
|
+
# Pull and sync all active branches, then return.
|
|
305
|
+
click.echo(
|
|
306
|
+
f" ℹ Migration to {target_version} already applied by another developer.\n"
|
|
307
|
+
f" Pulling and syncing all active branches..."
|
|
308
|
+
)
|
|
309
|
+
self._repo.hgit._HGit__git_repo.remotes.origin.pull('ho-prod')
|
|
310
|
+
self._repo.hgit.sync_active_branches(pattern="ho-*")
|
|
311
|
+
# Reload config so the rest of the tool sees the new version
|
|
312
|
+
self._repo._Repo__config.read()
|
|
313
|
+
result['already_synced'] = True
|
|
314
|
+
return result
|
|
315
|
+
|
|
286
316
|
raise MigrationManagerError(
|
|
287
317
|
f"ho-prod is {behind} commits behind origin/ho-prod. "
|
|
288
318
|
f"Please pull changes first: git pull origin ho-prod"
|
|
289
319
|
)
|
|
290
320
|
if ahead > 0:
|
|
291
321
|
result['errors'].append(f"Warning: ho-prod is {ahead} commits ahead of origin/ho-prod")
|
|
292
|
-
except subprocess.CalledProcessError
|
|
293
|
-
|
|
322
|
+
except (MigrationManagerError, subprocess.CalledProcessError):
|
|
323
|
+
raise
|
|
324
|
+
except Exception:
|
|
325
|
+
# Could not compare — maybe origin/ho-prod doesn't exist yet
|
|
294
326
|
pass
|
|
295
327
|
|
|
296
328
|
# Get current version from .hop/config
|
|
@@ -48,11 +48,12 @@ HO_DATACLASSES = []
|
|
|
48
48
|
HO_DATACLASSES_IMPORTS = set()
|
|
49
49
|
HO_TYPEDICTS: list = []
|
|
50
50
|
HO_TYPEDICTS_IMPORTS: set = set()
|
|
51
|
+
HO_BASECLASSES: list = []
|
|
52
|
+
HO_BASECLASSES_DICT_NAMES: set = set()
|
|
51
53
|
INIT_MODULE_TEMPLATE = read_template('init_module_template')
|
|
52
54
|
MODULE_TEMPLATE_1 = read_template('module_template_1')
|
|
53
55
|
MODULE_TEMPLATE_2 = read_template('module_template_2')
|
|
54
56
|
MODULE_TEMPLATE_3 = read_template('module_template_3')
|
|
55
|
-
MODULE_STUB_TEMPLATE = read_template('module_stub_template')
|
|
56
57
|
WARNING_TEMPLATE = read_template('warning')
|
|
57
58
|
CONFTEST = read_template('conftest_template')
|
|
58
59
|
TEST = read_template('relation_test')
|
|
@@ -335,18 +336,6 @@ def __gen_typedict(relation, fkeys) -> list:
|
|
|
335
336
|
return extra_classes + [main_class]
|
|
336
337
|
|
|
337
338
|
|
|
338
|
-
def __gen_stub_file(module_path: str, class_name: str, dict_class_name: str, package_name: str) -> str:
|
|
339
|
-
"""Generate a .pyi stub alongside the module for IDE type inference."""
|
|
340
|
-
stub_path = module_path[:-3] + '.pyi'
|
|
341
|
-
with open(stub_path, 'w', encoding='utf-8') as f:
|
|
342
|
-
f.write(MODULE_STUB_TEMPLATE.format(
|
|
343
|
-
package_name=package_name,
|
|
344
|
-
dict_class_name=dict_class_name,
|
|
345
|
-
class_name=class_name,
|
|
346
|
-
))
|
|
347
|
-
return stub_path
|
|
348
|
-
|
|
349
|
-
|
|
350
339
|
def __gen_typedicts(package_dir: str, package_name: str) -> None:
|
|
351
340
|
with open(os.path.join(package_dir, "ho_typeddicts.py"), "w", encoding='utf-8') as file_:
|
|
352
341
|
file_.write(f"# TypedDicts for {package_name}\n\n")
|
|
@@ -563,7 +552,9 @@ def __update_this_module(
|
|
|
563
552
|
rel, package_name)
|
|
564
553
|
|
|
565
554
|
t_qrn = list(rel._t_fqrn)[1:]
|
|
566
|
-
|
|
555
|
+
full_name = __get_full_class_name(*t_qrn)
|
|
556
|
+
dict_class_name = f'{full_name}Dict'
|
|
557
|
+
bc_name = f'BC_{full_name}'
|
|
567
558
|
|
|
568
559
|
# Generate Python module
|
|
569
560
|
with open(module_path, 'w', encoding='utf-8') as file_:
|
|
@@ -580,6 +571,7 @@ def __update_this_module(
|
|
|
580
571
|
class_name=class_name,
|
|
581
572
|
dc_name=rel._ho_dataclass_name(),
|
|
582
573
|
dict_class_name=dict_class_name,
|
|
574
|
+
bc_name=bc_name,
|
|
583
575
|
fqtn=fqtn,
|
|
584
576
|
kwargs=kwargs,
|
|
585
577
|
arg_names=arg_names,
|
|
@@ -605,19 +597,9 @@ def __update_this_module(
|
|
|
605
597
|
|
|
606
598
|
HO_DATACLASSES.append(__gen_dataclass(rel, existing_fkeys))
|
|
607
599
|
HO_TYPEDICTS.extend(__gen_typedict(rel, existing_fkeys))
|
|
600
|
+
HO_BASECLASSES.append(__gen_baseclass(rel, existing_fkeys))
|
|
608
601
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
return [module_path, stub_path]
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
def __reset_dataclasses(repo, package_dir):
|
|
615
|
-
with open(os.path.join(package_dir, "ho_dataclasses.py"), "w", encoding='utf-8') as file_:
|
|
616
|
-
for relation in repo.database.model._relations():
|
|
617
|
-
t_qrn = relation[1][1:]
|
|
618
|
-
if t_qrn[0].find('half_orm') == 0:
|
|
619
|
-
continue
|
|
620
|
-
file_.write(f'class DC_{__get_full_class_name(*t_qrn)}: ...\n')
|
|
602
|
+
return module_path
|
|
621
603
|
|
|
622
604
|
|
|
623
605
|
_TYPING_NAMES = frozenset(('Any', 'Dict', 'Iterator', 'List', 'Optional', 'Tuple', 'Union'))
|
|
@@ -675,20 +657,80 @@ def __gen_dc_relation() -> tuple:
|
|
|
675
657
|
return class_str, needed_typing
|
|
676
658
|
|
|
677
659
|
|
|
678
|
-
def
|
|
660
|
+
def __gen_baseclass(relation, fkeys) -> str:
|
|
661
|
+
"""Generate a BC_* base class string with TypedDict-typed method overrides."""
|
|
662
|
+
rel = relation()
|
|
663
|
+
t_qrn = list(rel._t_fqrn)[1:]
|
|
664
|
+
full_name = __get_full_class_name(*t_qrn)
|
|
665
|
+
fqtn = '.'.join(t_qrn)
|
|
666
|
+
bc_name = f'BC_{full_name}'
|
|
667
|
+
dc_name = rel._ho_dataclass_name()
|
|
668
|
+
d = f'{full_name}Dict'
|
|
669
|
+
HO_BASECLASSES_DICT_NAMES.add(d)
|
|
670
|
+
lines = [
|
|
671
|
+
f"class {bc_name}(",
|
|
672
|
+
f" MODEL.get_relation_class('{fqtn}', fields_aliases=None),",
|
|
673
|
+
f" {dc_name}",
|
|
674
|
+
f"):",
|
|
675
|
+
f" def __iter__(self) -> Iterator[{d}]:",
|
|
676
|
+
f" return super().__iter__()",
|
|
677
|
+
f"",
|
|
678
|
+
f" def ho_select(self, *args, distinct: bool = False, order_by: str = None, limit: int = None, offset: int = None, json_agg=None) -> Iterator[{d}]:",
|
|
679
|
+
f" return super().ho_select(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset, json_agg=json_agg)",
|
|
680
|
+
f"",
|
|
681
|
+
f" def ho_get(self, *args) -> {d}:",
|
|
682
|
+
f" return super().ho_get(*args)",
|
|
683
|
+
f"",
|
|
684
|
+
f" def ho_insert(self, *args, upsert: Optional[bool] = False) -> {d}:",
|
|
685
|
+
f" return super().ho_insert(*args, upsert=upsert)",
|
|
686
|
+
f"",
|
|
687
|
+
f" async def ho_aselect(self, *args, distinct: bool = False, order_by: str = None, limit: int = None, offset: int = None) -> List[{d}]:",
|
|
688
|
+
f" return await super().ho_aselect(*args, distinct=distinct, order_by=order_by, limit=limit, offset=offset)",
|
|
689
|
+
f"",
|
|
690
|
+
f" async def ho_aget(self, *args) -> {d}:",
|
|
691
|
+
f" return await super().ho_aget(*args)",
|
|
692
|
+
f"",
|
|
693
|
+
f" async def ho_ainsert(self, *args, upsert: bool = False) -> {d}:",
|
|
694
|
+
f" return await super().ho_ainsert(*args, upsert=upsert)",
|
|
695
|
+
]
|
|
696
|
+
return '\n'.join(lines)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def __reset_baseclasses(repo, package_dir):
|
|
700
|
+
with open(os.path.join(package_dir, "ho_baseclasses.py"), "w", encoding='utf-8') as file_:
|
|
701
|
+
for relation in repo.database.model._relations():
|
|
702
|
+
t_qrn = relation[1][1:]
|
|
703
|
+
if t_qrn[0].find('half_orm') == 0:
|
|
704
|
+
continue
|
|
705
|
+
full_name = __get_full_class_name(*t_qrn)
|
|
706
|
+
file_.write(f'class DC_{full_name}: ...\n')
|
|
707
|
+
file_.write(f'class BC_{full_name}: ...\n')
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def __gen_baseclasses(package_dir, package_name):
|
|
679
711
|
dc_relation_str, dc_typing = __gen_dc_relation()
|
|
680
|
-
with open(os.path.join(package_dir, "
|
|
681
|
-
file_.write(
|
|
712
|
+
with open(os.path.join(package_dir, "ho_baseclasses.py"), "w", encoding='utf-8') as file_:
|
|
713
|
+
file_.write("# DO NOT EDIT — auto-generated by half-orm-dev\n\n")
|
|
714
|
+
file_.write("from __future__ import annotations\n")
|
|
715
|
+
typing_names = sorted(dc_typing | {'Iterator', 'List', 'Optional', 'TYPE_CHECKING'})
|
|
716
|
+
file_.write(f"from typing import {', '.join(typing_names)}\n")
|
|
682
717
|
file_.write("import dataclasses\n")
|
|
683
718
|
file_.write("from half_orm.field import Field\n")
|
|
684
|
-
if dc_typing:
|
|
685
|
-
file_.write(f"from typing import {', '.join(sorted(dc_typing))}\n")
|
|
686
719
|
for mod in sorted(HO_DATACLASSES_IMPORTS):
|
|
687
720
|
file_.write(f"import {mod}\n")
|
|
721
|
+
file_.write(f"from {package_name} import MODEL\n")
|
|
722
|
+
if HO_BASECLASSES_DICT_NAMES:
|
|
723
|
+
file_.write("if TYPE_CHECKING:\n")
|
|
724
|
+
file_.write(f" from {package_name}.ho_typeddicts import (\n")
|
|
725
|
+
for name in sorted(HO_BASECLASSES_DICT_NAMES):
|
|
726
|
+
file_.write(f" {name},\n")
|
|
727
|
+
file_.write(" )\n")
|
|
688
728
|
file_.write("\n\n")
|
|
689
729
|
file_.write(dc_relation_str)
|
|
690
730
|
for dc in HO_DATACLASSES:
|
|
691
731
|
file_.write(f"\n\n{dc}\n")
|
|
732
|
+
for bc in HO_BASECLASSES:
|
|
733
|
+
file_.write(f"\n\n{bc}\n")
|
|
692
734
|
|
|
693
735
|
|
|
694
736
|
def generate(repo):
|
|
@@ -698,6 +740,8 @@ def generate(repo):
|
|
|
698
740
|
HO_DATACLASSES_IMPORTS.clear()
|
|
699
741
|
HO_TYPEDICTS.clear()
|
|
700
742
|
HO_TYPEDICTS_IMPORTS.clear()
|
|
743
|
+
HO_BASECLASSES.clear()
|
|
744
|
+
HO_BASECLASSES_DICT_NAMES.clear()
|
|
701
745
|
NO_APAPTER.clear()
|
|
702
746
|
|
|
703
747
|
package_name = repo.name
|
|
@@ -721,7 +765,11 @@ def generate(repo):
|
|
|
721
765
|
if not package_dir.exists():
|
|
722
766
|
package_dir.mkdir(parents=True)
|
|
723
767
|
|
|
724
|
-
|
|
768
|
+
__reset_baseclasses(repo, str(package_dir))
|
|
769
|
+
# Remove legacy ho_dataclasses.py if present (merged into ho_baseclasses.py)
|
|
770
|
+
legacy = Path(package_dir) / "ho_dataclasses.py"
|
|
771
|
+
if legacy.exists():
|
|
772
|
+
legacy.unlink()
|
|
725
773
|
|
|
726
774
|
# Generate package __init__.py
|
|
727
775
|
with open(package_dir / INIT_PY, 'w', encoding='utf-8') as file_:
|
|
@@ -742,12 +790,13 @@ def generate(repo):
|
|
|
742
790
|
|
|
743
791
|
# Generate modules for each relation
|
|
744
792
|
for relation in repo.database.model._relations():
|
|
745
|
-
|
|
746
|
-
if
|
|
747
|
-
files_list.
|
|
793
|
+
module_path = __update_this_module(repo, relation, str(package_dir), package_name)
|
|
794
|
+
if module_path:
|
|
795
|
+
files_list.append(module_path)
|
|
796
|
+
# Tests are no longer added to files_list (they live in tests/ directory)
|
|
748
797
|
|
|
749
|
-
__gen_dataclasses(str(package_dir), package_name)
|
|
750
798
|
__gen_typedicts(str(package_dir), package_name)
|
|
799
|
+
__gen_baseclasses(str(package_dir), package_name)
|
|
751
800
|
|
|
752
801
|
if len(NO_APAPTER):
|
|
753
802
|
print("MISSING ADAPTER FOR SQL TYPE")
|
|
@@ -1430,6 +1430,31 @@ class ReleaseManager:
|
|
|
1430
1430
|
f"Cannot read current production version from database: {e}"
|
|
1431
1431
|
)
|
|
1432
1432
|
|
|
1433
|
+
# Migration: production servers that still track ho-prod instead of
|
|
1434
|
+
# ho-current. Create ho-current from the current version's immutable tag
|
|
1435
|
+
# and switch to it — local-only, no push to origin.
|
|
1436
|
+
if (
|
|
1437
|
+
self._repo.production
|
|
1438
|
+
and self._repo.hgit.branch == 'ho-prod'
|
|
1439
|
+
and not self._repo.hgit.branch_exists('ho-current')
|
|
1440
|
+
):
|
|
1441
|
+
current_tag = f'v{current_version}'
|
|
1442
|
+
tag_exists = any(
|
|
1443
|
+
t.name == current_tag
|
|
1444
|
+
for t in self._repo.hgit._HGit__git_repo.tags
|
|
1445
|
+
)
|
|
1446
|
+
if not tag_exists:
|
|
1447
|
+
raise ReleaseManagerError(
|
|
1448
|
+
f"Cannot migrate to ho-current: tag {current_tag} not found locally.\n"
|
|
1449
|
+
f"Run 'hop update' again after ensuring tags are fetched."
|
|
1450
|
+
)
|
|
1451
|
+
self._repo.hgit.create_branch_from_tag('ho-current', current_tag)
|
|
1452
|
+
self._repo.hgit._HGit__git_repo.heads['ho-current'].checkout()
|
|
1453
|
+
click.echo(
|
|
1454
|
+
f" ℹ Migrated to ho-current (created from {current_tag}, local only).\n"
|
|
1455
|
+
f" Production servers should now use ho-current instead of ho-prod."
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1433
1458
|
# 3. Build list of available releases with details
|
|
1434
1459
|
available_releases = []
|
|
1435
1460
|
|
|
@@ -1855,25 +1880,31 @@ class ReleaseManager:
|
|
|
1855
1880
|
'final_version': upgrade_path[-1] if upgrade_path else current_version
|
|
1856
1881
|
}
|
|
1857
1882
|
|
|
1858
|
-
# === 5.
|
|
1859
|
-
#
|
|
1860
|
-
#
|
|
1861
|
-
# pushing files in several steps).
|
|
1883
|
+
# === 5. Get release files and apply releases ===
|
|
1884
|
+
# ho-prod: acquire lock + pull (rolling branch, needs lock against partial pushes)
|
|
1885
|
+
# ho-current: fetch + reset --hard vX.Y.Z per version (immutable tags, no lock needed)
|
|
1862
1886
|
lock_tag = None
|
|
1863
1887
|
patches_applied = {}
|
|
1888
|
+
on_ho_current = self._repo.hgit.branch == "ho-current"
|
|
1864
1889
|
try:
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1890
|
+
if on_ho_current:
|
|
1891
|
+
try:
|
|
1892
|
+
self._repo.hgit.fetch_tags()
|
|
1893
|
+
self._repo.hgit._HGit__git_repo.remotes.origin.fetch()
|
|
1894
|
+
except Exception as e:
|
|
1895
|
+
raise ReleaseManagerError(f"Failed to fetch from origin: {e}")
|
|
1896
|
+
else:
|
|
1897
|
+
lock_tag = self._repo.hgit.acquire_branch_lock('ho-prod')
|
|
1898
|
+
try:
|
|
1899
|
+
self._repo.hgit.pull('origin', 'ho-prod')
|
|
1900
|
+
except Exception as e:
|
|
1901
|
+
raise ReleaseManagerError(f"Failed to pull ho-prod branch: {e}")
|
|
1873
1902
|
|
|
1874
1903
|
# Apply releases sequentially
|
|
1875
1904
|
try:
|
|
1876
1905
|
for version in upgrade_path:
|
|
1906
|
+
if on_ho_current:
|
|
1907
|
+
self._repo.hgit._HGit__git_repo.git.reset('--hard', f'v{version}')
|
|
1877
1908
|
applied_patches = self._apply_release_to_production(version)
|
|
1878
1909
|
patches_applied[version] = applied_patches
|
|
1879
1910
|
except Exception as e:
|
|
@@ -2033,12 +2064,21 @@ class ReleaseManager:
|
|
|
2033
2064
|
# → Raises: "Repository has uncommitted changes"
|
|
2034
2065
|
"""
|
|
2035
2066
|
# Check branch
|
|
2036
|
-
|
|
2067
|
+
current_branch = self._repo.hgit.branch
|
|
2068
|
+
if current_branch not in ("ho-prod", "ho-current"):
|
|
2037
2069
|
raise ReleaseManagerError(
|
|
2038
|
-
f"Must be on ho-prod branch for production upgrade. "
|
|
2039
|
-
f"Current branch: {
|
|
2070
|
+
f"Must be on ho-prod or ho-current branch for production upgrade. "
|
|
2071
|
+
f"Current branch: {current_branch}"
|
|
2040
2072
|
)
|
|
2041
2073
|
|
|
2074
|
+
# For ho-current: reject if a lock is held on ho-prod (dev operation in progress)
|
|
2075
|
+
if current_branch == "ho-current":
|
|
2076
|
+
if self._repo.hgit.is_branch_locked("ho-prod"):
|
|
2077
|
+
raise ReleaseManagerError(
|
|
2078
|
+
"Cannot upgrade: ho-prod is currently locked by a development operation.\n"
|
|
2079
|
+
"Wait for the operation to complete and retry."
|
|
2080
|
+
)
|
|
2081
|
+
|
|
2042
2082
|
# Check repo is clean
|
|
2043
2083
|
if not self._repo.hgit.repos_is_clean():
|
|
2044
2084
|
raise ReleaseManagerError(
|
|
@@ -541,23 +541,31 @@ class Repo:
|
|
|
541
541
|
create_commit=True
|
|
542
542
|
)
|
|
543
543
|
|
|
544
|
-
result['migration_run'] = True
|
|
545
544
|
result['errors'] = migration_result.get('errors', [])
|
|
546
545
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
546
|
+
if migration_result.get('already_synced'):
|
|
547
|
+
# Migration was already done by another developer; branches are now synced.
|
|
548
|
+
result['migration_run'] = False
|
|
549
|
+
result['already_synced'] = True
|
|
550
|
+
if not silent:
|
|
551
|
+
print(f"✓ Branches synced to migration {current_version} (applied by another developer)")
|
|
552
|
+
else:
|
|
553
|
+
result['migration_run'] = True
|
|
554
554
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
555
|
+
# Clean up orphaned ho-staged/* branches (patch IDs in .txt = already in production)
|
|
556
|
+
if hasattr(self, 'release_manager'):
|
|
557
|
+
try:
|
|
558
|
+
deleted = self.release_manager.cleanup_orphaned_staged_branches()
|
|
559
|
+
result['orphaned_staged_deleted'] = deleted
|
|
560
|
+
except Exception:
|
|
561
|
+
result['orphaned_staged_deleted'] = []
|
|
562
|
+
|
|
563
|
+
# Log success if not silent
|
|
564
|
+
if not silent:
|
|
565
|
+
if migration_result.get('migrations_applied'):
|
|
566
|
+
print(f"✓ Applied {len(migration_result['migrations_applied'])} migration(s)")
|
|
567
|
+
else:
|
|
568
|
+
print(f"✓ Updated repository version to {current_version}")
|
|
561
569
|
|
|
562
570
|
finally:
|
|
563
571
|
# Always try to return to original branch if we switched
|
|
@@ -1600,6 +1608,19 @@ class Repo:
|
|
|
1600
1608
|
|
|
1601
1609
|
result['branch_sync'] = sync_result
|
|
1602
1610
|
|
|
1611
|
+
# 0c. Reload config and validate version after pull/sync.
|
|
1612
|
+
# Another developer may have run the migration and pushed a new hop_version.
|
|
1613
|
+
# The pull + sync above may have updated .hop/config on the current branch;
|
|
1614
|
+
# we must detect that before any further operation.
|
|
1615
|
+
if self.hgit:
|
|
1616
|
+
try:
|
|
1617
|
+
self.__config = Config(self.__base_dir)
|
|
1618
|
+
self._validate_version()
|
|
1619
|
+
except OutdatedHalfORMDevError:
|
|
1620
|
+
raise
|
|
1621
|
+
except Exception:
|
|
1622
|
+
pass # offline or version parsing error — don't block
|
|
1623
|
+
|
|
1603
1624
|
# 1. Check and update Git hooks
|
|
1604
1625
|
if not dry_run:
|
|
1605
1626
|
result['hooks'] = self.install_git_hooks()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a16
|
|
@@ -64,7 +64,6 @@ half_orm_dev/templates/MANIFEST.in
|
|
|
64
64
|
half_orm_dev/templates/README
|
|
65
65
|
half_orm_dev/templates/conftest_template
|
|
66
66
|
half_orm_dev/templates/init_module_template
|
|
67
|
-
half_orm_dev/templates/module_stub_template
|
|
68
67
|
half_orm_dev/templates/module_template_1
|
|
69
68
|
half_orm_dev/templates/module_template_2
|
|
70
69
|
half_orm_dev/templates/module_template_3
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# DO NOT EDIT — auto-generated by half-orm-dev
|
|
2
|
-
from typing import Iterator
|
|
3
|
-
from {package_name}.ho_typeddicts import {dict_class_name}
|
|
4
|
-
|
|
5
|
-
class {class_name}:
|
|
6
|
-
def __iter__(self) -> Iterator[{dict_class_name}]: ...
|
|
7
|
-
def ho_get(self, *args) -> {dict_class_name}: ...
|
|
8
|
-
async def ho_aget(self, *args) -> {dict_class_name}: ...
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.0-a14
|
|
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.0a14 → half_orm_dev-1.0.0a16}/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
|
{half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/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
|
{half_orm_dev-1.0.0a14 → half_orm_dev-1.0.0a16}/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
|