half-orm-dev 0.16.0a9__py3-none-any.whl
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/__init__.py +1 -0
- half_orm_dev/cli/__init__.py +9 -0
- half_orm_dev/cli/commands/__init__.py +56 -0
- half_orm_dev/cli/commands/apply.py +13 -0
- half_orm_dev/cli/commands/clone.py +102 -0
- half_orm_dev/cli/commands/init.py +331 -0
- half_orm_dev/cli/commands/new.py +15 -0
- half_orm_dev/cli/commands/patch.py +317 -0
- half_orm_dev/cli/commands/prepare.py +21 -0
- half_orm_dev/cli/commands/prepare_release.py +119 -0
- half_orm_dev/cli/commands/promote_to.py +127 -0
- half_orm_dev/cli/commands/release.py +344 -0
- half_orm_dev/cli/commands/restore.py +14 -0
- half_orm_dev/cli/commands/sync.py +13 -0
- half_orm_dev/cli/commands/todo.py +73 -0
- half_orm_dev/cli/commands/undo.py +17 -0
- half_orm_dev/cli/commands/update.py +73 -0
- half_orm_dev/cli/commands/upgrade.py +191 -0
- half_orm_dev/cli/main.py +103 -0
- half_orm_dev/cli_extension.py +38 -0
- half_orm_dev/database.py +1389 -0
- half_orm_dev/hgit.py +1025 -0
- half_orm_dev/hop.py +167 -0
- half_orm_dev/manifest.py +43 -0
- half_orm_dev/modules.py +456 -0
- half_orm_dev/patch.py +281 -0
- half_orm_dev/patch_manager.py +1694 -0
- half_orm_dev/patch_validator.py +335 -0
- half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
- half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
- half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
- half_orm_dev/patches/log +2 -0
- half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
- half_orm_dev/release_manager.py +2841 -0
- half_orm_dev/repo.py +1562 -0
- half_orm_dev/templates/.gitignore +15 -0
- half_orm_dev/templates/MANIFEST.in +1 -0
- half_orm_dev/templates/Pipfile +13 -0
- half_orm_dev/templates/README +25 -0
- half_orm_dev/templates/conftest_template +42 -0
- half_orm_dev/templates/init_module_template +10 -0
- half_orm_dev/templates/module_template_1 +12 -0
- half_orm_dev/templates/module_template_2 +6 -0
- half_orm_dev/templates/module_template_3 +3 -0
- half_orm_dev/templates/relation_test +23 -0
- half_orm_dev/templates/setup.py +81 -0
- half_orm_dev/templates/sql_adapter +9 -0
- half_orm_dev/templates/warning +12 -0
- half_orm_dev/utils.py +49 -0
- half_orm_dev/version.txt +1 -0
- half_orm_dev-0.16.0a9.dist-info/METADATA +935 -0
- half_orm_dev-0.16.0a9.dist-info/RECORD +58 -0
- half_orm_dev-0.16.0a9.dist-info/WHEEL +5 -0
- half_orm_dev-0.16.0a9.dist-info/licenses/AUTHORS +3 -0
- half_orm_dev-0.16.0a9.dist-info/licenses/LICENSE +14 -0
- half_orm_dev-0.16.0a9.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +329 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Upgrade command - Apply releases sequentially to production database.
|
|
3
|
+
|
|
4
|
+
Equivalent to 'apt upgrade' - applies available releases incrementally
|
|
5
|
+
to existing production database without data destruction.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from half_orm_dev.repo import Repo
|
|
11
|
+
from half_orm_dev.release_manager import ReleaseManagerError
|
|
12
|
+
from half_orm import utils
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.option(
|
|
17
|
+
'--to-release', '-t',
|
|
18
|
+
type=str,
|
|
19
|
+
default=None,
|
|
20
|
+
help='Stop at specific version (e.g., 1.3.7). Default: upgrade to latest'
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
'--dry-run', '-d',
|
|
24
|
+
is_flag=True,
|
|
25
|
+
help='Simulate upgrade without making changes'
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
'--force',
|
|
29
|
+
is_flag=True,
|
|
30
|
+
help='Overwrite existing backup without confirmation'
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
'--skip-backup',
|
|
34
|
+
is_flag=True,
|
|
35
|
+
help='Skip backup creation (DANGEROUS - for testing only)'
|
|
36
|
+
)
|
|
37
|
+
def upgrade(to_release, dry_run, force, skip_backup):
|
|
38
|
+
"""
|
|
39
|
+
Apply releases sequentially to production database.
|
|
40
|
+
|
|
41
|
+
Upgrades production database by applying releases incrementally
|
|
42
|
+
to existing data. NEVER destroys or recreates the database.
|
|
43
|
+
Creates automatic backup before any changes.
|
|
44
|
+
|
|
45
|
+
CRITICAL: This command works on EXISTING production database.
|
|
46
|
+
It does NOT use restore operations that would destroy data.
|
|
47
|
+
|
|
48
|
+
Must be run from ho-prod branch.
|
|
49
|
+
|
|
50
|
+
Workflow:
|
|
51
|
+
1. CREATE BACKUP (backups/{version}.sql) - FIRST ACTION
|
|
52
|
+
2. Validate environment (ho-prod branch, clean repo)
|
|
53
|
+
3. Apply releases sequentially on existing database
|
|
54
|
+
4. Update database version after each release
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
# Upgrade to latest (all available releases)
|
|
58
|
+
half_orm dev upgrade
|
|
59
|
+
|
|
60
|
+
# Upgrade to specific version
|
|
61
|
+
half_orm dev upgrade --to-release=1.3.7
|
|
62
|
+
|
|
63
|
+
# Simulate upgrade (no changes)
|
|
64
|
+
half_orm dev upgrade --dry-run
|
|
65
|
+
|
|
66
|
+
# Force overwrite existing backup
|
|
67
|
+
half_orm dev upgrade --force
|
|
68
|
+
|
|
69
|
+
Options:
|
|
70
|
+
--to-release=VERSION Stop at specific version
|
|
71
|
+
--dry-run Simulate without changes
|
|
72
|
+
--force Overwrite existing backup
|
|
73
|
+
--skip-backup Skip backup (DANGEROUS)
|
|
74
|
+
|
|
75
|
+
Requires:
|
|
76
|
+
- Current branch: ho-prod
|
|
77
|
+
- Repository: clean (no uncommitted changes)
|
|
78
|
+
- Permissions: Database write access
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
# Get repository instance
|
|
82
|
+
repo = Repo()
|
|
83
|
+
|
|
84
|
+
# Delegate to ReleaseManager
|
|
85
|
+
click.echo("🔄 Starting production upgrade...\n")
|
|
86
|
+
|
|
87
|
+
result = repo.release_manager.upgrade_production(
|
|
88
|
+
to_version=to_release,
|
|
89
|
+
dry_run=dry_run,
|
|
90
|
+
force_backup=force,
|
|
91
|
+
skip_backup=skip_backup
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Display results
|
|
95
|
+
_display_upgrade_results(result)
|
|
96
|
+
|
|
97
|
+
except ReleaseManagerError as e:
|
|
98
|
+
click.echo(f"\n❌ {utils.Color.red('Upgrade failed:')}")
|
|
99
|
+
click.echo(f" {str(e)}\n")
|
|
100
|
+
raise click.Abort()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _display_upgrade_results(result):
|
|
104
|
+
"""
|
|
105
|
+
Format and display upgrade results to user.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
result: Dict from ReleaseManager.upgrade_production()
|
|
109
|
+
"""
|
|
110
|
+
# === DRY RUN MODE ===
|
|
111
|
+
if result.get('dry_run'):
|
|
112
|
+
click.echo(f"{utils.Color.bold('DRY RUN')} - Simulation only, no changes made\n")
|
|
113
|
+
|
|
114
|
+
current = result['current_version']
|
|
115
|
+
click.echo(f"Current version: {utils.Color.bold(current)}")
|
|
116
|
+
|
|
117
|
+
if not result['releases_would_apply']:
|
|
118
|
+
click.echo(f"\n✓ {utils.Color.green('Already at latest version')}")
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Show what would happen
|
|
122
|
+
click.echo(f"\nWould create backup: {utils.Color.bold(result['backup_would_be_created'])}")
|
|
123
|
+
|
|
124
|
+
click.echo(f"\nWould apply releases:")
|
|
125
|
+
for version in result['releases_would_apply']:
|
|
126
|
+
patches = result['patches_would_apply'][version]
|
|
127
|
+
patch_count = len(patches)
|
|
128
|
+
click.echo(f" → {utils.Color.bold(version)} - {patch_count} patches")
|
|
129
|
+
for patch_id in patches:
|
|
130
|
+
click.echo(f" • {patch_id}")
|
|
131
|
+
|
|
132
|
+
final = result['final_version']
|
|
133
|
+
click.echo(f"\nWould upgrade: {current} → {utils.Color.green(final)}")
|
|
134
|
+
|
|
135
|
+
click.echo(f"\n{utils.Color.yellow('To apply this upgrade, run without --dry-run')}")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# === ACTUAL UPGRADE ===
|
|
139
|
+
|
|
140
|
+
current = result['current_version']
|
|
141
|
+
|
|
142
|
+
# Backup confirmation
|
|
143
|
+
if result['backup_created']:
|
|
144
|
+
backup_path = result['backup_created']
|
|
145
|
+
click.echo(f"✓ Backup created: {utils.Color.bold(backup_path)}")
|
|
146
|
+
elif result.get('message') and 'already at latest' in result['message'].lower():
|
|
147
|
+
# Up to date scenario
|
|
148
|
+
pass
|
|
149
|
+
else:
|
|
150
|
+
click.echo(f"⚠️ {utils.Color.yellow('No backup created (--skip-backup used)')}")
|
|
151
|
+
|
|
152
|
+
click.echo(f"\nCurrent version: {utils.Color.bold(current)}")
|
|
153
|
+
|
|
154
|
+
# Check if already up to date
|
|
155
|
+
if not result['releases_applied']:
|
|
156
|
+
click.echo(f"\n✓ {utils.Color.green('Production already at latest version')}")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
# Show applied releases
|
|
160
|
+
click.echo(f"\n{utils.Color.green('Applied releases:')}")
|
|
161
|
+
|
|
162
|
+
for version in result['releases_applied']:
|
|
163
|
+
patches = result['patches_applied'][version]
|
|
164
|
+
patch_count = len(patches)
|
|
165
|
+
|
|
166
|
+
if patch_count == 0:
|
|
167
|
+
click.echo(f" ✓ {utils.Color.bold(version)} - (empty release)")
|
|
168
|
+
else:
|
|
169
|
+
click.echo(f" ✓ {utils.Color.bold(version)} - {patch_count} patches")
|
|
170
|
+
for patch_id in patches:
|
|
171
|
+
click.echo(f" • {patch_id}")
|
|
172
|
+
|
|
173
|
+
# Final status
|
|
174
|
+
final = result['final_version']
|
|
175
|
+
click.echo(f"\n{utils.Color.green('✓ Upgrade complete!')}")
|
|
176
|
+
click.echo(f" {current} → {utils.Color.bold(utils.Color.green(final))}")
|
|
177
|
+
|
|
178
|
+
# Next steps
|
|
179
|
+
if result['target_version']:
|
|
180
|
+
# Partial upgrade
|
|
181
|
+
click.echo(f"\n📝 Partial upgrade to {result['target_version']} complete.")
|
|
182
|
+
click.echo(f" To upgrade further, run: half_orm dev upgrade")
|
|
183
|
+
else:
|
|
184
|
+
# Full upgrade
|
|
185
|
+
click.echo(f"\n📝 Production is now at latest version.")
|
|
186
|
+
|
|
187
|
+
# Rollback information
|
|
188
|
+
if result['backup_created']:
|
|
189
|
+
backup_path = result['backup_created']
|
|
190
|
+
click.echo(f"\n💡 To rollback if needed:")
|
|
191
|
+
click.echo(f" psql -d {result.get('db_name', 'DATABASE')} -f {backup_path}")
|
half_orm_dev/cli/main.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI module - Creates and configures the CLI group
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from half_orm_dev.repo import Repo
|
|
7
|
+
from half_orm import utils
|
|
8
|
+
from .commands import ALL_COMMANDS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Hop:
|
|
12
|
+
"""Sets the options available to the hop command"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.__repo: Repo = Repo() # Utilise le singleton
|
|
16
|
+
self.__available_cmds = self._determine_available_commands()
|
|
17
|
+
|
|
18
|
+
def _determine_available_commands(self):
|
|
19
|
+
"""
|
|
20
|
+
Determine which commands are available based on context.
|
|
21
|
+
|
|
22
|
+
Returns different command sets based on:
|
|
23
|
+
- Repository status (checked/unchecked)
|
|
24
|
+
- Development mode (devel flag - metadata presence)
|
|
25
|
+
- Environment (production flag)
|
|
26
|
+
"""
|
|
27
|
+
if not self.repo_checked:
|
|
28
|
+
# Outside hop repository - commands for project initialization
|
|
29
|
+
return ['init', 'clone']
|
|
30
|
+
|
|
31
|
+
# Inside hop repository
|
|
32
|
+
if not self.__repo.devel:
|
|
33
|
+
# Sync-only mode (no metadata)
|
|
34
|
+
return ['sync-package']
|
|
35
|
+
|
|
36
|
+
# Development mode (metadata present)
|
|
37
|
+
if self.__repo.database.production:
|
|
38
|
+
# PRODUCTION ENVIRONMENT - Release deployment only
|
|
39
|
+
return ['update', 'upgrade']
|
|
40
|
+
else:
|
|
41
|
+
# DEVELOPMENT ENVIRONMENT - Patch development
|
|
42
|
+
return ['patch', 'release']
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def repo_checked(self):
|
|
46
|
+
"""Returns whether we are in a repo or not."""
|
|
47
|
+
return self.__repo.checked
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def state(self):
|
|
51
|
+
"""Returns the state of the repo."""
|
|
52
|
+
return self.__repo.state
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def available_commands(self):
|
|
56
|
+
"""Returns the list of available commands."""
|
|
57
|
+
return self.__available_cmds
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_cli_group():
|
|
61
|
+
"""
|
|
62
|
+
Creates and returns the CLI group with appropriate commands.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
click.Group: Configured CLI group
|
|
66
|
+
"""
|
|
67
|
+
hop = Hop()
|
|
68
|
+
|
|
69
|
+
@click.group(invoke_without_command=True)
|
|
70
|
+
@click.pass_context
|
|
71
|
+
def dev(ctx):
|
|
72
|
+
"""halfORM development tools - Git-centric patch management and database synchronization"""
|
|
73
|
+
if ctx.invoked_subcommand is None:
|
|
74
|
+
# Show repo state when no subcommand is provided
|
|
75
|
+
if hop.repo_checked:
|
|
76
|
+
click.echo(hop.state)
|
|
77
|
+
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
78
|
+
|
|
79
|
+
# Adapt displayed commands based on environment
|
|
80
|
+
if hop.__repo.database.production:
|
|
81
|
+
# Production commands
|
|
82
|
+
click.echo(f" • {utils.Color.bold('update')} - Fetch and list available releases")
|
|
83
|
+
click.echo(f" • {utils.Color.bold('upgrade [--to-release=X.Y.Z]')} - Apply releases to production")
|
|
84
|
+
else:
|
|
85
|
+
# Development commands
|
|
86
|
+
click.echo(f" • {utils.Color.bold('patch')}")
|
|
87
|
+
click.echo(f" • {utils.Color.bold('prepare-release <level>')} - Prepare next release stage file (patch/minor/major)")
|
|
88
|
+
click.echo(f" • {utils.Color.bold('promote-to <target>')} - Promote stage to rc or prod")
|
|
89
|
+
|
|
90
|
+
click.echo(f"\nTry {utils.Color.bold('half_orm dev <command> --help')} for more information.\n")
|
|
91
|
+
else:
|
|
92
|
+
click.echo(hop.state)
|
|
93
|
+
click.echo("\nNot in a hop repository.")
|
|
94
|
+
click.echo(f"\n{utils.Color.bold('Available commands:')}")
|
|
95
|
+
click.echo(f"\n • {utils.Color.bold('init <package_name>')} - Create new halfORM project.")
|
|
96
|
+
click.echo(f"\n • {utils.Color.bold('clone <git origin>')} - Clone an existing halfORM project.\n")
|
|
97
|
+
|
|
98
|
+
# Add only available commands to the group
|
|
99
|
+
for cmd_name in hop.available_commands:
|
|
100
|
+
if cmd_name in ALL_COMMANDS:
|
|
101
|
+
dev.add_command(ALL_COMMANDS[cmd_name])
|
|
102
|
+
|
|
103
|
+
return dev
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
CLI extension integration for half-orm-dev
|
|
6
|
+
|
|
7
|
+
Provides the halfORM development tools through the unified half_orm CLI interface.
|
|
8
|
+
Generates/Patches/Synchronizes a hop Python package with a PostgreSQL database.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from half_orm.cli_utils import create_and_register_extension
|
|
13
|
+
from .cli import create_cli_group
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_commands(main_group):
|
|
17
|
+
"""
|
|
18
|
+
Required entry point for halfORM extensions.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
main_group: The main Click group for the half_orm command
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Create the dev CLI group with all commands
|
|
25
|
+
dev_group = create_cli_group()
|
|
26
|
+
|
|
27
|
+
# Register it as an extension
|
|
28
|
+
@create_and_register_extension(main_group, sys.modules[__name__])
|
|
29
|
+
def dev():
|
|
30
|
+
"""halfORM development tools - project management, patches, and database synchronization"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# Copy all commands from the created group to the registered extension
|
|
34
|
+
for name, command in dev_group.commands.items():
|
|
35
|
+
dev.add_command(command)
|
|
36
|
+
|
|
37
|
+
# Copy the callback from the created group
|
|
38
|
+
dev.callback = dev_group.callback
|