half-orm-dev 1.0.0a6__tar.gz → 1.0.0a8__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.0a6/half_orm_dev.egg-info → half_orm_dev-1.0.0a8}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migration_manager.py +134 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/modules.py +94 -16
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/repo.py +4 -12
- half_orm_dev-1.0.0a8/half_orm_dev/templates/module_stub_template +8 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/module_template_1 +0 -3
- half_orm_dev-1.0.0a8/half_orm_dev/templates/module_template_3 +3 -0
- half_orm_dev-1.0.0a8/half_orm_dev/version.txt +1 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8/half_orm_dev.egg-info}/PKG-INFO +1 -1
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- half_orm_dev-1.0.0a6/half_orm_dev/templates/module_template_3 +0 -12
- half_orm_dev-1.0.0a6/half_orm_dev/version.txt +0 -1
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/AUTHORS +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/LICENSE +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/README.md +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/bootstrap_manager.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/bootstrap.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/revert_migration.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/set_git_origin.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/database.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/file_executor.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/01_add_bootstrap_table.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/02_move_patches_to_subdirs.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/5/01_update_pyproject_dependency.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/18/0/00_add_async_support.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/18/0/01_update_default_tests.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/release_manager.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/scripts/repair-metadata.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/entry_points.txt +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/requires.txt +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/pyproject.toml +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/setup.cfg +0 -0
- {half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/setup.py +0 -0
|
@@ -313,6 +313,9 @@ class MigrationManager:
|
|
|
313
313
|
f"Continuing with migration attempt."
|
|
314
314
|
)
|
|
315
315
|
|
|
316
|
+
# Ensure all active branches are in sync with origin before touching anything
|
|
317
|
+
self._ensure_active_branches_synced()
|
|
318
|
+
|
|
316
319
|
# Get pending migrations
|
|
317
320
|
pending = self.get_pending_migrations(current_version, target_version)
|
|
318
321
|
|
|
@@ -370,6 +373,16 @@ class MigrationManager:
|
|
|
370
373
|
current_version, target_version, sync_result
|
|
371
374
|
)
|
|
372
375
|
|
|
376
|
+
# Pseudo-patch: regenerate modules and sync to active branches
|
|
377
|
+
try:
|
|
378
|
+
self._regenerate_modules_after_migration(
|
|
379
|
+
current_version, target_version
|
|
380
|
+
)
|
|
381
|
+
except Exception as regen_err:
|
|
382
|
+
result['errors'].append(
|
|
383
|
+
f"Module regeneration after migration failed: {regen_err}"
|
|
384
|
+
)
|
|
385
|
+
|
|
373
386
|
except Exception as e:
|
|
374
387
|
# Don't fail migration if commit fails
|
|
375
388
|
result['errors'].append(f"Failed to create commit: {e}")
|
|
@@ -442,6 +455,127 @@ class MigrationManager:
|
|
|
442
455
|
except Exception:
|
|
443
456
|
pass # remote tag may already be gone
|
|
444
457
|
|
|
458
|
+
def _ensure_active_branches_synced(self) -> None:
|
|
459
|
+
"""Verify all active branches are in sync with origin before migration.
|
|
460
|
+
|
|
461
|
+
Branches that are behind are fast-forwarded automatically (no local commits
|
|
462
|
+
at risk). Branches that are ahead or diverged block the migration — the
|
|
463
|
+
developer must push or resolve before proceeding.
|
|
464
|
+
|
|
465
|
+
Raises:
|
|
466
|
+
MigrationManagerError: if any active branch is ahead or diverged.
|
|
467
|
+
"""
|
|
468
|
+
repo = self._repo
|
|
469
|
+
git_repo = repo.hgit._HGit__git_repo
|
|
470
|
+
current_branch = git_repo.active_branch.name
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
branches_status = repo.hgit.get_active_branches_status()
|
|
474
|
+
except Exception:
|
|
475
|
+
return # can't determine status, proceed cautiously
|
|
476
|
+
|
|
477
|
+
patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
|
|
478
|
+
release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
|
|
479
|
+
staged_branches = [b['name'] for b in branches_status.get('staged_branches', [])]
|
|
480
|
+
active_branches = release_branches + patch_branches + staged_branches
|
|
481
|
+
|
|
482
|
+
blocked = []
|
|
483
|
+
for branch in active_branches:
|
|
484
|
+
try:
|
|
485
|
+
synced, status = repo.hgit.is_branch_synced(branch)
|
|
486
|
+
if synced:
|
|
487
|
+
continue
|
|
488
|
+
if status == 'behind':
|
|
489
|
+
# Fast-forward: no local commits at risk
|
|
490
|
+
repo.hgit.checkout(branch)
|
|
491
|
+
git_repo.git.merge('--ff-only', f'origin/{branch}')
|
|
492
|
+
elif status in ('ahead', 'diverged'):
|
|
493
|
+
blocked.append((branch, status))
|
|
494
|
+
except Exception:
|
|
495
|
+
pass # branch may not exist locally, skip
|
|
496
|
+
|
|
497
|
+
# Return to original branch
|
|
498
|
+
try:
|
|
499
|
+
repo.hgit.checkout(current_branch)
|
|
500
|
+
except Exception:
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
if blocked:
|
|
504
|
+
ahead = [(b, s) for b, s in blocked if s == 'ahead']
|
|
505
|
+
diverged = [(b, s) for b, s in blocked if s == 'diverged']
|
|
506
|
+
parts = []
|
|
507
|
+
if ahead:
|
|
508
|
+
branch_list = ', '.join(b for b, _ in ahead)
|
|
509
|
+
parts.append(
|
|
510
|
+
f" Branches ahead of origin (unpushed commits) — push first:\n"
|
|
511
|
+
+ '\n'.join(f" git push origin {b}" for b, _ in ahead)
|
|
512
|
+
)
|
|
513
|
+
if diverged:
|
|
514
|
+
parts.append(
|
|
515
|
+
f" Branches diverged from origin (local and remote have diverged) "
|
|
516
|
+
f"— rebase or merge to resolve:\n"
|
|
517
|
+
+ '\n'.join(f" {b}" for b, _ in diverged)
|
|
518
|
+
)
|
|
519
|
+
raise MigrationManagerError(
|
|
520
|
+
f"Migration blocked: active branches are not in sync with origin.\n"
|
|
521
|
+
+ '\n'.join(parts)
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
def _regenerate_modules_after_migration(
|
|
525
|
+
self, from_version: str, to_version: str
|
|
526
|
+
) -> None:
|
|
527
|
+
"""Regenerate the project modules after a migration and sync to all active branches.
|
|
528
|
+
|
|
529
|
+
Runs generate(), commits the changed files on ho-prod, then replays those
|
|
530
|
+
changes on every active branch by checking out the package directory from ho-prod.
|
|
531
|
+
Skips silently if generate() produced no changes.
|
|
532
|
+
"""
|
|
533
|
+
from half_orm_dev import modules as _modules
|
|
534
|
+
|
|
535
|
+
repo = self._repo
|
|
536
|
+
git_repo = repo.hgit._HGit__git_repo
|
|
537
|
+
package_name = repo.name
|
|
538
|
+
package_dir = str(Path(repo.base_dir) / package_name)
|
|
539
|
+
|
|
540
|
+
commit_msg = (
|
|
541
|
+
f"[HOP] Regenerate modules (migration {from_version} → {to_version})"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Collect active branches before moving around
|
|
545
|
+
try:
|
|
546
|
+
branches_status = repo.hgit.get_active_branches_status()
|
|
547
|
+
except Exception:
|
|
548
|
+
branches_status = {}
|
|
549
|
+
|
|
550
|
+
patch_branches = [b['name'] for b in branches_status.get('patch_branches', [])]
|
|
551
|
+
release_branches = [b['name'] for b in branches_status.get('release_branches', [])]
|
|
552
|
+
staged_branches = [b['name'] for b in branches_status.get('staged_branches', [])]
|
|
553
|
+
# ho-prod first, then active branches
|
|
554
|
+
all_branches = ['ho-prod'] + release_branches + patch_branches + staged_branches
|
|
555
|
+
|
|
556
|
+
for branch in all_branches:
|
|
557
|
+
try:
|
|
558
|
+
repo.hgit.checkout(branch)
|
|
559
|
+
# generate() reads existing files and preserves developer code sections
|
|
560
|
+
_modules.generate(repo)
|
|
561
|
+
repo.hgit.add(package_dir)
|
|
562
|
+
if not git_repo.git.diff('--cached', '--name-only').strip():
|
|
563
|
+
continue
|
|
564
|
+
repo.hgit.commit('-m', commit_msg)
|
|
565
|
+
try:
|
|
566
|
+
repo.hgit.push_branch(branch)
|
|
567
|
+
except Exception as push_err:
|
|
568
|
+
sys.stderr.write(
|
|
569
|
+
f"Warning: could not push {branch} after module regeneration "
|
|
570
|
+
f"(diverged branch?): {push_err}\n"
|
|
571
|
+
)
|
|
572
|
+
except Exception as e:
|
|
573
|
+
sys.stderr.write(
|
|
574
|
+
f"Warning: could not regenerate modules on {branch}: {e}\n"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
repo.hgit.checkout('ho-prod')
|
|
578
|
+
|
|
445
579
|
def _create_migration_commit_message(
|
|
446
580
|
self,
|
|
447
581
|
from_version: str,
|
|
@@ -20,6 +20,7 @@ history with the last release applied.
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
import importlib
|
|
23
|
+
import inspect
|
|
23
24
|
import os
|
|
24
25
|
import re
|
|
25
26
|
import shutil
|
|
@@ -43,10 +44,7 @@ def read_template(file_name):
|
|
|
43
44
|
return file_.read()
|
|
44
45
|
|
|
45
46
|
NO_APAPTER = {}
|
|
46
|
-
HO_DATACLASSES = [
|
|
47
|
-
'''import dataclasses
|
|
48
|
-
from half_orm.relation import DC_Relation
|
|
49
|
-
from half_orm.field import Field''']
|
|
47
|
+
HO_DATACLASSES = []
|
|
50
48
|
HO_DATACLASSES_IMPORTS = set()
|
|
51
49
|
HO_TYPEDICTS: list = []
|
|
52
50
|
HO_TYPEDICTS_IMPORTS: set = set()
|
|
@@ -54,6 +52,7 @@ INIT_MODULE_TEMPLATE = read_template('init_module_template')
|
|
|
54
52
|
MODULE_TEMPLATE_1 = read_template('module_template_1')
|
|
55
53
|
MODULE_TEMPLATE_2 = read_template('module_template_2')
|
|
56
54
|
MODULE_TEMPLATE_3 = read_template('module_template_3')
|
|
55
|
+
MODULE_STUB_TEMPLATE = read_template('module_stub_template')
|
|
57
56
|
WARNING_TEMPLATE = read_template('warning')
|
|
58
57
|
CONFTEST = read_template('conftest_template')
|
|
59
58
|
TEST = read_template('relation_test')
|
|
@@ -336,6 +335,18 @@ def __gen_typedict(relation, fkeys) -> list:
|
|
|
336
335
|
return extra_classes + [main_class]
|
|
337
336
|
|
|
338
337
|
|
|
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
|
+
|
|
339
350
|
def __gen_typedicts(package_dir: str, package_name: str) -> None:
|
|
340
351
|
with open(os.path.join(package_dir, "ho_typeddicts.py"), "w", encoding='utf-8') as file_:
|
|
341
352
|
file_.write(f"# TypedDicts for {package_name}\n\n")
|
|
@@ -545,7 +556,9 @@ def __update_this_module(
|
|
|
545
556
|
HO_DATACLASSES.append(__gen_dataclass(rel, existing_fkeys))
|
|
546
557
|
HO_TYPEDICTS.extend(__gen_typedict(rel, existing_fkeys))
|
|
547
558
|
|
|
548
|
-
|
|
559
|
+
stub_path = __gen_stub_file(module_path, class_name, dict_class_name, package_name)
|
|
560
|
+
|
|
561
|
+
return [module_path, stub_path]
|
|
549
562
|
|
|
550
563
|
|
|
551
564
|
def __reset_dataclasses(repo, package_dir):
|
|
@@ -557,20 +570,86 @@ def __reset_dataclasses(repo, package_dir):
|
|
|
557
570
|
file_.write(f'class DC_{__get_full_class_name(*t_qrn)}: ...\n')
|
|
558
571
|
|
|
559
572
|
|
|
573
|
+
_TYPING_NAMES = frozenset(('Any', 'Dict', 'Iterator', 'List', 'Optional', 'Tuple', 'Union'))
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def __gen_dc_relation() -> tuple:
|
|
577
|
+
"""Introspect Relation to generate DC_Relation with signatures and docstrings.
|
|
578
|
+
|
|
579
|
+
Returns (class_str, frozenset of typing names needed).
|
|
580
|
+
"""
|
|
581
|
+
from half_orm.relation import Relation
|
|
582
|
+
|
|
583
|
+
def _fmt_doc(doc: str) -> str:
|
|
584
|
+
lines = doc.splitlines()
|
|
585
|
+
if len(lines) == 1:
|
|
586
|
+
return f' """{doc}"""'
|
|
587
|
+
parts = [f' """{lines[0]}']
|
|
588
|
+
for line in lines[1:]:
|
|
589
|
+
parts.append(f' {line}' if line.strip() else '')
|
|
590
|
+
parts.append(' """')
|
|
591
|
+
return '\n'.join(parts)
|
|
592
|
+
|
|
593
|
+
method_blocks = []
|
|
594
|
+
for name in sorted(dir(Relation)):
|
|
595
|
+
if not name.startswith('ho_'):
|
|
596
|
+
continue
|
|
597
|
+
raw = inspect.getattr_static(Relation, name)
|
|
598
|
+
is_classmethod = isinstance(raw, classmethod)
|
|
599
|
+
underlying = raw.__func__ if isinstance(raw, (classmethod, staticmethod)) else raw
|
|
600
|
+
if not callable(underlying):
|
|
601
|
+
continue
|
|
602
|
+
is_async = inspect.iscoroutinefunction(underlying)
|
|
603
|
+
try:
|
|
604
|
+
sig_str = str(inspect.signature(underlying))
|
|
605
|
+
except (ValueError, TypeError):
|
|
606
|
+
continue
|
|
607
|
+
doc = inspect.getdoc(underlying)
|
|
608
|
+
block = []
|
|
609
|
+
if is_classmethod:
|
|
610
|
+
block.append(' @classmethod')
|
|
611
|
+
prefix = ' async def' if is_async else ' def'
|
|
612
|
+
block.append(f'{prefix} {name}{sig_str}:')
|
|
613
|
+
if doc:
|
|
614
|
+
block.append(_fmt_doc(doc))
|
|
615
|
+
block.append(' ...')
|
|
616
|
+
method_blocks.append('\n'.join(block))
|
|
617
|
+
|
|
618
|
+
full_body = '\n\n'.join(method_blocks)
|
|
619
|
+
needed_typing = frozenset(t for t in _TYPING_NAMES if t in full_body)
|
|
620
|
+
class_str = (
|
|
621
|
+
'class DC_Relation:\n'
|
|
622
|
+
' # auto-generated by half-orm-dev — do not edit\n\n'
|
|
623
|
+
+ full_body + '\n'
|
|
624
|
+
)
|
|
625
|
+
return class_str, needed_typing
|
|
626
|
+
|
|
627
|
+
|
|
560
628
|
def __gen_dataclasses(package_dir, package_name):
|
|
629
|
+
dc_relation_str, dc_typing = __gen_dc_relation()
|
|
561
630
|
with open(os.path.join(package_dir, "ho_dataclasses.py"), "w", encoding='utf-8') as file_:
|
|
562
|
-
file_.write(f"#
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
file_.write(f"import {
|
|
567
|
-
|
|
631
|
+
file_.write(f"# DO NOT EDIT — auto-generated by half-orm-dev\n\n")
|
|
632
|
+
file_.write("import dataclasses\n")
|
|
633
|
+
file_.write("from half_orm.field import Field\n")
|
|
634
|
+
if dc_typing:
|
|
635
|
+
file_.write(f"from typing import {', '.join(sorted(dc_typing))}\n")
|
|
636
|
+
for mod in sorted(HO_DATACLASSES_IMPORTS):
|
|
637
|
+
file_.write(f"import {mod}\n")
|
|
638
|
+
file_.write("\n\n")
|
|
639
|
+
file_.write(dc_relation_str)
|
|
568
640
|
for dc in HO_DATACLASSES:
|
|
569
|
-
file_.write(f"\n{dc}\n")
|
|
641
|
+
file_.write(f"\n\n{dc}\n")
|
|
570
642
|
|
|
571
643
|
|
|
572
644
|
def generate(repo):
|
|
573
645
|
"""Synchronize the modules with the structure of the relation in PG."""
|
|
646
|
+
# Reset accumulators — allows safe repeated calls in the same process
|
|
647
|
+
HO_DATACLASSES.clear()
|
|
648
|
+
HO_DATACLASSES_IMPORTS.clear()
|
|
649
|
+
HO_TYPEDICTS.clear()
|
|
650
|
+
HO_TYPEDICTS_IMPORTS.clear()
|
|
651
|
+
NO_APAPTER.clear()
|
|
652
|
+
|
|
574
653
|
package_name = repo.name
|
|
575
654
|
base_dir = Path(repo.base_dir)
|
|
576
655
|
package_dir = base_dir / package_name
|
|
@@ -613,10 +692,9 @@ def generate(repo):
|
|
|
613
692
|
|
|
614
693
|
# Generate modules for each relation
|
|
615
694
|
for relation in repo.database.model._relations():
|
|
616
|
-
|
|
617
|
-
if
|
|
618
|
-
files_list.
|
|
619
|
-
# Tests are no longer added to files_list (they live in tests/ directory)
|
|
695
|
+
paths = __update_this_module(repo, relation, str(package_dir), package_name)
|
|
696
|
+
if paths:
|
|
697
|
+
files_list.extend(paths)
|
|
620
698
|
|
|
621
699
|
__gen_dataclasses(str(package_dir), package_name)
|
|
622
700
|
__gen_typedicts(str(package_dir), package_name)
|
|
@@ -690,18 +690,10 @@ class Repo:
|
|
|
690
690
|
remote_ref = f"origin/{branch}"
|
|
691
691
|
try:
|
|
692
692
|
synced, status = self.hgit.is_branch_synced(branch)
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
file=sys.stderr
|
|
698
|
-
)
|
|
699
|
-
# Only reset when the remote is ahead of local ("behind") or
|
|
700
|
-
# branches have diverged. Never reset an "ahead" branch —
|
|
701
|
-
# that would orphan local commits that have not been pushed
|
|
702
|
-
# yet (e.g. the "Create patch directory" commit from
|
|
703
|
-
# `hop patch create` before the first push).
|
|
704
|
-
if not synced and status in ("behind", "diverged"):
|
|
693
|
+
# Only fast-forward when origin is strictly ahead (no local commits
|
|
694
|
+
# at risk). Never reset on diverged branches — that would destroy
|
|
695
|
+
# unmerged local work.
|
|
696
|
+
if not synced and status == "behind":
|
|
705
697
|
self.hgit._HGit__git_repo.git.reset('--hard', remote_ref)
|
|
706
698
|
except GitCommandError:
|
|
707
699
|
# Remote branch may not exist yet, continue without reset
|
|
@@ -0,0 +1,8 @@
|
|
|
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}: ...
|
|
@@ -6,10 +6,7 @@ WARNING!
|
|
|
6
6
|
|
|
7
7
|
{warning}
|
|
8
8
|
"""
|
|
9
|
-
from typing import TYPE_CHECKING, Iterator
|
|
10
9
|
from half_orm.model import register
|
|
11
10
|
from {package_name} import MODEL, ho_dataclasses
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from {package_name}.ho_typeddicts import {dict_class_name}
|
|
14
11
|
fields_aliases=None
|
|
15
12
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a8
|
|
@@ -64,6 +64,7 @@ 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
|
|
67
68
|
half_orm_dev/templates/module_template_1
|
|
68
69
|
half_orm_dev/templates/module_template_2
|
|
69
70
|
half_orm_dev/templates/module_template_3
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#pylint: disable=line-too-long, too-many-arguments, redefined-builtin, too-many-positional-arguments
|
|
2
|
-
def __init__(self, {kwargs}):
|
|
3
|
-
super().__init__({arg_names}, **kwargs)
|
|
4
|
-
|
|
5
|
-
def __iter__(self) -> 'Iterator[{dict_class_name}]':
|
|
6
|
-
return super().__iter__()
|
|
7
|
-
|
|
8
|
-
def ho_get(self, *args) -> '{dict_class_name}':
|
|
9
|
-
return super().ho_get(*args)
|
|
10
|
-
|
|
11
|
-
async def ho_aget(self, *args) -> '{dict_class_name}':
|
|
12
|
-
return await super().ho_aget(*args)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.0-a6
|
|
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
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py
RENAMED
|
File without changes
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/17/4/00_toml_dict_format.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/0/18/0/00_add_async_support.py
RENAMED
|
File without changes
|
|
File without changes
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/half_orm_dev/migrations/hop/BREAKING_CHANGES-1.0.0.md
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
|
{half_orm_dev-1.0.0a6 → half_orm_dev-1.0.0a8}/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
|