half-orm-dev 1.0.0a12__tar.gz → 1.0.0a15__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.0a12/half_orm_dev.egg-info → half_orm_dev-1.0.0a15}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/modules.py +85 -36
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patch_manager.py +19 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/repo.py +76 -61
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/module_template_1 +1 -2
- half_orm_dev-1.0.0a15/half_orm_dev/templates/module_template_2 +7 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/module_template_3 +1 -1
- half_orm_dev-1.0.0a15/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev.egg-info/SOURCES.txt +0 -1
- half_orm_dev-1.0.0a12/half_orm_dev/templates/module_stub_template +0 -8
- half_orm_dev-1.0.0a12/half_orm_dev/templates/module_template_2 +0 -7
- half_orm_dev-1.0.0a12/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/LICENSE +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/README.md +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/bootstrap_manager.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/database.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/file_executor.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migration_manager.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/release_manager.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/setup.py +0 -0
|
@@ -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")
|
|
@@ -2002,6 +2002,25 @@ class PatchManager:
|
|
|
2002
2002
|
result = patch_mgr.merge_patch()
|
|
2003
2003
|
# Merges into ho-release/0.17.0, moves to stage
|
|
2004
2004
|
"""
|
|
2005
|
+
# 0. Remove stale local ho-* branches before any state change.
|
|
2006
|
+
# A stale branch (local but no longer on remote) causes
|
|
2007
|
+
# sync_hop_to_active_branches to leave staged-but-uncommitted
|
|
2008
|
+
# .hop/ changes that block subsequent checkouts, silently
|
|
2009
|
+
# skipping branches that should receive the TOML update.
|
|
2010
|
+
try:
|
|
2011
|
+
stale_result = self._repo.hgit.prune_local_branches(
|
|
2012
|
+
pattern="ho-*",
|
|
2013
|
+
dry_run=False,
|
|
2014
|
+
exclude_current=True,
|
|
2015
|
+
)
|
|
2016
|
+
if stale_result.get('deleted'):
|
|
2017
|
+
click.echo(
|
|
2018
|
+
f" ℹ Removed {len(stale_result['deleted'])} stale local "
|
|
2019
|
+
f"branch(es): {', '.join(stale_result['deleted'])}"
|
|
2020
|
+
)
|
|
2021
|
+
except Exception as e:
|
|
2022
|
+
raise PatchManagerError(f"Failed to clean up stale branches: {e}")
|
|
2023
|
+
|
|
2005
2024
|
# 1. Extract patch_id from current branch
|
|
2006
2025
|
current_branch = self._repo.hgit.branch
|
|
2007
2026
|
if not current_branch.startswith('ho-patch/'):
|
|
@@ -643,10 +643,19 @@ class Repo:
|
|
|
643
643
|
# Get all active branches (including ho-prod, release branches, and patch branches)
|
|
644
644
|
try:
|
|
645
645
|
branches_status = self.hgit.get_active_branches_status()
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
#
|
|
646
|
+
# Only sync branches that still exist on remote — stale local
|
|
647
|
+
# branches (e.g. ho-patch/X renamed to ho-staged/X) leave
|
|
648
|
+
# staged-but-uncommitted .hop/ changes after a failed pre-commit
|
|
649
|
+
# hook, blocking checkout of subsequent branches in the loop.
|
|
650
|
+
patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])
|
|
651
|
+
if b.get('exists_on_remote', True)]
|
|
652
|
+
release_branches = [b['name'] for b in branches_status.get('release_branches', [])
|
|
653
|
+
if b.get('exists_on_remote', True)]
|
|
654
|
+
|
|
655
|
+
# ho-staged/* branches are frozen after merge — excluded from sync.
|
|
656
|
+
# ho-prod is included but may have a protective hook (e.g. blocking
|
|
657
|
+
# direct commits); a commit failure on it is treated as a soft skip
|
|
658
|
+
# rather than aborting the whole sync (see Phase 1 loop below).
|
|
650
659
|
all_branches = ['ho-prod'] + release_branches + patch_branches
|
|
651
660
|
|
|
652
661
|
# Filter release branches to avoid syncing to future versions
|
|
@@ -685,140 +694,146 @@ class Repo:
|
|
|
685
694
|
result['errors'].append(f"Failed to get active branches: {e}")
|
|
686
695
|
return result
|
|
687
696
|
|
|
688
|
-
#
|
|
697
|
+
# Phase 1 — local commits only (no push yet).
|
|
698
|
+
# All commits are made locally first so that a failure on any branch
|
|
699
|
+
# can be fully rolled back via git reset --hard, leaving a clean state.
|
|
700
|
+
original_shas = {} # branch → SHA recorded after checkout/fast-forward
|
|
701
|
+
committed_branches = [] # branches that received a new local commit
|
|
702
|
+
commit_msg = f"[HOP] Sync .hop/ from {source_branch} ({reason})"
|
|
703
|
+
|
|
704
|
+
def _rollback_phase1():
|
|
705
|
+
"""Undo all local commits made during Phase 1."""
|
|
706
|
+
for rb, sha in original_shas.items():
|
|
707
|
+
try:
|
|
708
|
+
self.hgit.checkout(rb)
|
|
709
|
+
self.hgit._HGit__git_repo.git.reset('--hard', sha)
|
|
710
|
+
except Exception:
|
|
711
|
+
pass
|
|
712
|
+
try:
|
|
713
|
+
self.hgit.checkout(source_branch)
|
|
714
|
+
self.__config = Config(self.base_dir)
|
|
715
|
+
except Exception:
|
|
716
|
+
pass
|
|
717
|
+
|
|
689
718
|
for branch in target_branches:
|
|
690
719
|
try:
|
|
691
|
-
# Checkout to target branch
|
|
692
720
|
self.hgit.checkout(branch)
|
|
693
721
|
|
|
694
|
-
#
|
|
695
|
-
# This avoids non-fast-forward push failures when another
|
|
696
|
-
# actor has already synced this branch.
|
|
722
|
+
# Fast-forward if behind remote (safe — no local commits lost)
|
|
697
723
|
remote_ref = f"origin/{branch}"
|
|
698
724
|
try:
|
|
699
725
|
synced, status = self.hgit.is_branch_synced(branch)
|
|
700
|
-
# Only fast-forward when origin is strictly ahead (no local commits
|
|
701
|
-
# at risk). Never reset on diverged branches — that would destroy
|
|
702
|
-
# unmerged local work.
|
|
703
726
|
if not synced and status == "behind":
|
|
704
727
|
self.hgit._HGit__git_repo.git.reset('--hard', remote_ref)
|
|
705
728
|
except GitCommandError:
|
|
706
|
-
# Remote branch may not exist yet, continue without reset
|
|
707
729
|
pass
|
|
708
730
|
|
|
709
|
-
#
|
|
731
|
+
# Record SHA after fast-forward: this is the rollback target
|
|
732
|
+
original_shas[branch] = self.hgit._HGit__git_repo.head.commit.hexsha
|
|
733
|
+
|
|
710
734
|
self.__config = Config(self.base_dir)
|
|
711
735
|
|
|
712
|
-
#
|
|
713
|
-
# This updates/adds files but doesn't remove deleted files
|
|
736
|
+
# Copy .hop/ from source branch
|
|
714
737
|
self.hgit._HGit__git_repo.git.checkout(source_branch, '--', '.hop/')
|
|
715
738
|
|
|
716
|
-
# Also checkout additional files if specified
|
|
717
739
|
if additional_files:
|
|
718
740
|
for file_path in additional_files:
|
|
719
741
|
try:
|
|
720
742
|
self.hgit._HGit__git_repo.git.checkout(source_branch, '--', file_path)
|
|
721
743
|
except GitCommandError:
|
|
722
|
-
# File might not exist in source branch, skip
|
|
723
744
|
pass
|
|
724
745
|
|
|
725
|
-
#
|
|
726
|
-
# IMPORTANT: Only remove files for versions that exist in source
|
|
727
|
-
# Don't remove release files (*-patches.toml, *.txt) for other versions
|
|
746
|
+
# Remove files that exist in target but no longer in source
|
|
728
747
|
try:
|
|
729
|
-
# Get list of files in .hop/ on current branch (target)
|
|
730
748
|
target_files_output = self.hgit._HGit__git_repo.git.ls_files('.hop/')
|
|
731
749
|
target_files = set(f for f in target_files_output.split('\n') if f.strip())
|
|
732
|
-
|
|
733
|
-
# Get list of files in .hop/ on source branch
|
|
734
750
|
source_files_output = self.hgit._HGit__git_repo.git.ls_tree('-r', '--name-only', source_branch, '.hop/')
|
|
735
751
|
source_files = set(f for f in source_files_output.split('\n') if f.strip())
|
|
736
|
-
|
|
737
|
-
# Get versions present in source (from .toml and .txt release files)
|
|
738
752
|
source_versions = set()
|
|
739
753
|
for file_path in source_files:
|
|
740
|
-
# Extract version from release files
|
|
741
754
|
if file_path.startswith('.hop/releases/'):
|
|
742
755
|
filename = file_path.replace('.hop/releases/', '')
|
|
743
|
-
# Match patterns: X.Y.Z-patches.toml, X.Y.Z.txt, X.Y.Z-rcN.txt, X.Y.Z-hotfixN.txt
|
|
744
756
|
match = re.match(r'^(\d+\.\d+\.\d+)[-.]', filename)
|
|
745
757
|
if match:
|
|
746
758
|
source_versions.add(match.group(1))
|
|
747
|
-
|
|
748
|
-
# Files to delete = in target but not in source
|
|
749
|
-
files_to_delete = target_files - source_files
|
|
750
|
-
|
|
751
|
-
# Filter: only delete files for versions present in source
|
|
752
|
-
# This prevents deleting release files for unrelated versions
|
|
753
759
|
safe_to_delete = []
|
|
754
|
-
for file_path in
|
|
760
|
+
for file_path in target_files - source_files:
|
|
755
761
|
if not file_path:
|
|
756
762
|
continue
|
|
757
|
-
# Check if it's a release file
|
|
758
763
|
if file_path.startswith('.hop/releases/'):
|
|
759
764
|
filename = file_path.replace('.hop/releases/', '')
|
|
760
765
|
match = re.match(r'^(\d+\.\d+\.\d+)[-.]', filename)
|
|
761
766
|
if match:
|
|
762
|
-
|
|
763
|
-
# Only delete if this version exists in source
|
|
764
|
-
if file_version in source_versions:
|
|
767
|
+
if match.group(1) in source_versions:
|
|
765
768
|
safe_to_delete.append(file_path)
|
|
766
769
|
else:
|
|
767
|
-
# Not a versioned file, safe to delete
|
|
768
770
|
safe_to_delete.append(file_path)
|
|
769
771
|
else:
|
|
770
|
-
# Not in releases/, safe to delete
|
|
771
772
|
safe_to_delete.append(file_path)
|
|
772
|
-
|
|
773
|
-
# Remove files
|
|
774
773
|
for file_path in safe_to_delete:
|
|
775
774
|
self.hgit._HGit__git_repo.git.rm(file_path)
|
|
776
|
-
except Exception
|
|
777
|
-
# If something fails in deletion detection, log but continue
|
|
778
|
-
# The checkout already happened, so we have the updates
|
|
775
|
+
except Exception:
|
|
779
776
|
pass
|
|
780
777
|
|
|
781
|
-
# Stage all changes
|
|
782
778
|
self.hgit.add('.hop/')
|
|
783
779
|
if additional_files:
|
|
784
780
|
for file_path in additional_files:
|
|
785
781
|
try:
|
|
786
782
|
self.hgit.add(file_path)
|
|
787
783
|
except GitCommandError:
|
|
788
|
-
pass
|
|
784
|
+
pass
|
|
789
785
|
|
|
790
|
-
# Check if there are changes
|
|
791
786
|
status = self.hgit._HGit__git_repo.git.status('--porcelain')
|
|
792
787
|
if not status.strip():
|
|
793
|
-
# No changes, skip
|
|
794
788
|
result['skipped_branches'].append(branch)
|
|
789
|
+
del original_shas[branch] # nothing to roll back for this branch
|
|
795
790
|
continue
|
|
796
791
|
|
|
797
|
-
# Commit changes
|
|
798
|
-
commit_msg = f"[HOP] Sync .hop/ from {source_branch} ({reason})"
|
|
799
792
|
self.hgit.commit('-m', commit_msg)
|
|
800
793
|
|
|
801
|
-
# Record the SHA of the sync commit for potential revert
|
|
802
794
|
result['branch_commits'][branch] = (
|
|
803
795
|
self.hgit._HGit__git_repo.head.commit.hexsha
|
|
804
796
|
)
|
|
805
|
-
|
|
806
|
-
# Push to remote
|
|
807
|
-
self.hgit.push_branch(branch)
|
|
808
|
-
|
|
809
|
-
result['synced_branches'].append(branch)
|
|
797
|
+
committed_branches.append(branch)
|
|
810
798
|
|
|
811
799
|
except Exception as e:
|
|
812
|
-
|
|
800
|
+
if branch == 'ho-prod':
|
|
801
|
+
# ho-prod may have a protective hook that blocks direct
|
|
802
|
+
# commits. Clean up staged state so the next checkout
|
|
803
|
+
# succeeds, then skip gracefully.
|
|
804
|
+
try:
|
|
805
|
+
self.hgit._HGit__git_repo.git.reset('HEAD')
|
|
806
|
+
self.hgit._HGit__git_repo.git.checkout('--', '.hop/')
|
|
807
|
+
except Exception:
|
|
808
|
+
pass
|
|
809
|
+
original_shas.pop('ho-prod', None)
|
|
810
|
+
result['errors'].append(f"ho-prod: sync skipped (commit blocked): {e}")
|
|
811
|
+
else:
|
|
812
|
+
# Any other branch failure aborts and rolls back all local commits.
|
|
813
|
+
_rollback_phase1()
|
|
814
|
+
raise RepoError(
|
|
815
|
+
f"Sync commit failed on '{branch}': {e}\n"
|
|
816
|
+
f"All local sync commits have been rolled back.\n"
|
|
817
|
+
f"Run 'hop check' to diagnose."
|
|
818
|
+
) from e
|
|
813
819
|
|
|
814
|
-
# Return to source branch
|
|
820
|
+
# Return to source branch before pushing
|
|
815
821
|
try:
|
|
816
822
|
self.hgit.checkout(source_branch)
|
|
817
|
-
# Reload config for source branch
|
|
818
823
|
self.__config = Config(self.base_dir)
|
|
819
824
|
except Exception as e:
|
|
820
825
|
result['errors'].append(f"Failed to return to {source_branch}: {e}")
|
|
821
826
|
|
|
827
|
+
# Phase 2 — push all committed branches.
|
|
828
|
+
# The distributed lock ensures no conflicting pushes; errors here are
|
|
829
|
+
# unexpected but collected rather than raised.
|
|
830
|
+
for branch in committed_branches:
|
|
831
|
+
try:
|
|
832
|
+
self.hgit.push_branch(branch)
|
|
833
|
+
result['synced_branches'].append(branch)
|
|
834
|
+
except Exception as e:
|
|
835
|
+
result['errors'].append(f"{branch}: push failed: {e}")
|
|
836
|
+
|
|
822
837
|
return result
|
|
823
838
|
|
|
824
839
|
def sync_and_validate_ho_prod(self):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a15
|
|
@@ -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-a12
|
|
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.0a12 → half_orm_dev-1.0.0a15}/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
|
|
File without changes
|
{half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a12 → half_orm_dev-1.0.0a15}/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.0a12 → half_orm_dev-1.0.0a15}/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
|