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.
Files changed (58) hide show
  1. half_orm_dev/__init__.py +1 -0
  2. half_orm_dev/cli/__init__.py +9 -0
  3. half_orm_dev/cli/commands/__init__.py +56 -0
  4. half_orm_dev/cli/commands/apply.py +13 -0
  5. half_orm_dev/cli/commands/clone.py +102 -0
  6. half_orm_dev/cli/commands/init.py +331 -0
  7. half_orm_dev/cli/commands/new.py +15 -0
  8. half_orm_dev/cli/commands/patch.py +317 -0
  9. half_orm_dev/cli/commands/prepare.py +21 -0
  10. half_orm_dev/cli/commands/prepare_release.py +119 -0
  11. half_orm_dev/cli/commands/promote_to.py +127 -0
  12. half_orm_dev/cli/commands/release.py +344 -0
  13. half_orm_dev/cli/commands/restore.py +14 -0
  14. half_orm_dev/cli/commands/sync.py +13 -0
  15. half_orm_dev/cli/commands/todo.py +73 -0
  16. half_orm_dev/cli/commands/undo.py +17 -0
  17. half_orm_dev/cli/commands/update.py +73 -0
  18. half_orm_dev/cli/commands/upgrade.py +191 -0
  19. half_orm_dev/cli/main.py +103 -0
  20. half_orm_dev/cli_extension.py +38 -0
  21. half_orm_dev/database.py +1389 -0
  22. half_orm_dev/hgit.py +1025 -0
  23. half_orm_dev/hop.py +167 -0
  24. half_orm_dev/manifest.py +43 -0
  25. half_orm_dev/modules.py +456 -0
  26. half_orm_dev/patch.py +281 -0
  27. half_orm_dev/patch_manager.py +1694 -0
  28. half_orm_dev/patch_validator.py +335 -0
  29. half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +34 -0
  30. half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +2 -0
  31. half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +3 -0
  32. half_orm_dev/patches/log +2 -0
  33. half_orm_dev/patches/sql/half_orm_meta.sql +208 -0
  34. half_orm_dev/release_manager.py +2841 -0
  35. half_orm_dev/repo.py +1562 -0
  36. half_orm_dev/templates/.gitignore +15 -0
  37. half_orm_dev/templates/MANIFEST.in +1 -0
  38. half_orm_dev/templates/Pipfile +13 -0
  39. half_orm_dev/templates/README +25 -0
  40. half_orm_dev/templates/conftest_template +42 -0
  41. half_orm_dev/templates/init_module_template +10 -0
  42. half_orm_dev/templates/module_template_1 +12 -0
  43. half_orm_dev/templates/module_template_2 +6 -0
  44. half_orm_dev/templates/module_template_3 +3 -0
  45. half_orm_dev/templates/relation_test +23 -0
  46. half_orm_dev/templates/setup.py +81 -0
  47. half_orm_dev/templates/sql_adapter +9 -0
  48. half_orm_dev/templates/warning +12 -0
  49. half_orm_dev/utils.py +49 -0
  50. half_orm_dev/version.txt +1 -0
  51. half_orm_dev-0.16.0a9.dist-info/METADATA +935 -0
  52. half_orm_dev-0.16.0a9.dist-info/RECORD +58 -0
  53. half_orm_dev-0.16.0a9.dist-info/WHEEL +5 -0
  54. half_orm_dev-0.16.0a9.dist-info/licenses/AUTHORS +3 -0
  55. half_orm_dev-0.16.0a9.dist-info/licenses/LICENSE +14 -0
  56. half_orm_dev-0.16.0a9.dist-info/top_level.txt +2 -0
  57. tests/__init__.py +0 -0
  58. tests/conftest.py +329 -0
@@ -0,0 +1 @@
1
+ __all__ = ['cli']
@@ -0,0 +1,9 @@
1
+ """
2
+ CLI module for half-orm-dev
3
+
4
+ Provides modular command structure for development tools.
5
+ """
6
+
7
+ from .main import create_cli_group
8
+
9
+ __all__ = ['create_cli_group']
@@ -0,0 +1,56 @@
1
+ """
2
+ Commands module for half-orm-dev CLI
3
+
4
+ Provides all individual command implementations.
5
+ REFACTORED in v0.16.0 - Git-centric patch workflow
6
+ """
7
+
8
+ # ✅ New Git-centric commands (stubs for now)
9
+ from .init import init
10
+ from .clone import clone
11
+ from .patch import patch
12
+ from .release import release
13
+ from .update import update
14
+ from .upgrade import upgrade
15
+ from .todo import apply_release
16
+ from .todo import create_hotfix
17
+ from .todo import rollback
18
+
19
+ # ♻️ Adapted existing commands
20
+ from .todo import sync_package # Unchanged
21
+ from .todo import restore # Adapted for new architecture
22
+
23
+ # Registry of all available commands - Git-centric architecture
24
+ ALL_COMMANDS = {
25
+ # Core workflow
26
+ 'init': init,
27
+ 'clone': clone,
28
+ 'patch': patch,
29
+ 'release': release,
30
+ 'update': update, # Adapted for production
31
+ 'upgrade': upgrade, # Adapted for production
32
+ # 🚧 (stubs)
33
+ 'apply_release': apply_release,
34
+
35
+ # 🚧 Emergency workflow (stubs)
36
+ 'create-hotfix': create_hotfix,
37
+ 'rollback': rollback,
38
+
39
+ # ♻️ Adapted commands
40
+ 'sync-package': sync_package, # Unchanged
41
+ 'restore': restore, # Adapted
42
+ }
43
+
44
+ __all__ = [
45
+ # New commands
46
+ 'init',
47
+ 'clone',
48
+ 'patch',
49
+ 'release',
50
+ 'upgrade',
51
+ 'create_hotfix',
52
+ 'rollback',
53
+ # Adapted commands
54
+ 'sync_package',
55
+ 'ALL_COMMANDS'
56
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ Apply command - Apply the current release
3
+ """
4
+
5
+ import click
6
+ from half_orm_dev.repo import Repo
7
+
8
+
9
+ @click.command()
10
+ def apply():
11
+ """Apply the current release."""
12
+ repo = Repo()
13
+ repo.apply_release()
@@ -0,0 +1,102 @@
1
+ """
2
+ Clone command - Clone existing half_orm_dev project from Git repository
3
+
4
+ Clones a Git repository, checks out ho-prod branch, sets up local database,
5
+ and restores schema to production version.
6
+ """
7
+
8
+ import click
9
+ from pathlib import Path
10
+ from half_orm_dev.repo import Repo, RepoError
11
+
12
+
13
+ @click.command('clone')
14
+ @click.argument('git_origin')
15
+ @click.option('--database-name', default=None, help='Custom local database name (default: use project name)')
16
+ @click.option('--dest-dir', default=None, help='Destination directory name (default: infer from git URL)')
17
+ @click.option('--production', is_flag=True, help='Production mode (default: False)')
18
+ @click.option('--no-create-db', is_flag=True, help='Skip database creation (database must exist)')
19
+ def clone(git_origin, database_name, dest_dir, production, no_create_db):
20
+ """
21
+ Clone existing half_orm_dev project and setup local database.
22
+
23
+ Clones a Git repository, checks out the ho-prod branch, creates/configures
24
+ the local database, and restores the schema to production version.
25
+
26
+ ARGUMENTS:
27
+ git_origin: Git repository URL (HTTPS, SSH, file://)
28
+
29
+ WORKFLOW:
30
+ 1. Clone repository from git_origin
31
+ 2. Checkout ho-prod branch
32
+ 3. Create .hop/alt_config if --database-name provided
33
+ 4. Setup database (create + metadata if needed)
34
+ 5. Restore database from model/schema.sql
35
+
36
+ EXAMPLES:
37
+ # Basic clone (prompts for database connection params)
38
+ half_orm dev clone https://github.com/user/project.git
39
+
40
+ # Clone with custom database name
41
+ half_orm dev clone https://github.com/user/project.git --database-name my_local_db
42
+
43
+ # Clone to specific directory
44
+ half_orm dev clone https://github.com/user/project.git --dest-dir my_project
45
+
46
+ # Production mode (database must already exist)
47
+ half_orm dev clone https://github.com/user/project.git --production --no-create-db
48
+
49
+ NOTES:
50
+ - Changes current directory to cloned project
51
+ - Interactive prompts for missing connection parameters
52
+ - Requires model/schema.sql in repository for schema restoration
53
+ """
54
+ try:
55
+ click.echo(f"🔄 Cloning half_orm project from {git_origin}...")
56
+ click.echo()
57
+
58
+ # Execute clone
59
+ Repo.clone_repo(
60
+ git_origin=git_origin,
61
+ database_name=database_name,
62
+ dest_dir=dest_dir,
63
+ production=production,
64
+ create_db=not no_create_db
65
+ )
66
+
67
+ # Determine project name for success message
68
+ if dest_dir:
69
+ project_name = dest_dir
70
+ else:
71
+ project_name = git_origin.rstrip('/').split('/')[-1]
72
+ if project_name.endswith('.git'):
73
+ project_name = project_name[:-4]
74
+
75
+ click.echo()
76
+ click.echo(f"✅ Project '{project_name}' cloned and configured successfully!")
77
+ click.echo(f" 📁 Project directory: {Path.cwd()}")
78
+ click.echo(f" 🗄️ Database restored to production version")
79
+ click.echo()
80
+ click.echo(f"Next steps:")
81
+ click.echo(f" • cd {project_name}")
82
+ click.echo(f" • half_orm dev create-patch <patch_id> # Start developing")
83
+ click.echo()
84
+
85
+ except FileExistsError as e:
86
+ click.echo(f"❌ Error: {e}", err=True)
87
+ click.echo(f" Remove the existing directory or choose a different destination.", err=True)
88
+ raise click.Abort()
89
+
90
+ except RepoError as e:
91
+ click.echo(f"❌ Clone failed: {e}", err=True)
92
+ click.echo()
93
+ click.echo(f"Common issues:")
94
+ click.echo(f" • Invalid or inaccessible Git URL")
95
+ click.echo(f" • Missing 'ho-prod' branch in repository")
96
+ click.echo(f" • Database connection or permission issues")
97
+ click.echo(f" • Missing model/schema.sql in repository")
98
+ raise click.Abort()
99
+
100
+ except Exception as e:
101
+ click.echo(f"❌ Unexpected error: {e}", err=True)
102
+ raise click.Abort()
@@ -0,0 +1,331 @@
1
+ """
2
+ Init command - Initialize new half_orm_dev project (unified workflow)
3
+
4
+ Orchestrates database setup and project creation in a single command.
5
+ This is a convenience wrapper around the existing init-database and init-project logic.
6
+ """
7
+
8
+ import click
9
+ from pathlib import Path
10
+ from half_orm import utils
11
+ from half_orm.model import Model
12
+ from half_orm_dev.database import Database
13
+ from half_orm_dev.repo import Repo
14
+
15
+
16
+ class DatabaseExistsWithMetadataError(Exception):
17
+ """Raised when database already has metadata (existing project)."""
18
+ pass
19
+
20
+
21
+ class ProjectDirectoryExistsError(Exception):
22
+ """Raised when project directory already exists."""
23
+ pass
24
+
25
+
26
+ @click.command('init')
27
+ @click.argument('project_name')
28
+ @click.option('--host', default='localhost', help='PostgreSQL host (default: localhost)')
29
+ @click.option('--port', default=5432, type=int, help='PostgreSQL port (default: 5432)')
30
+ @click.option('--user', default=None, help='Database user (default: $USER)')
31
+ @click.option('--password', default=None, help='Database password (prompts if missing)')
32
+ @click.option('--git-origin', default=None, help='Git remote origin URL (prompts if missing)')
33
+ @click.option('--production', is_flag=True, help='Mark as production environment (default: False)')
34
+ @click.option('--force-sync-only', is_flag=True, help='Skip metadata installation, force sync-only mode')
35
+ @click.option('--create-db', is_flag=False, default=True)
36
+ @click.option('--docker', default=None, help='Docker container name for PostgreSQL')
37
+ def init(project_name, host, port, user, password, git_origin, production, force_sync_only, create_db, docker):
38
+ """
39
+ Initialize a new half_orm_dev project with database and code structure.
40
+
41
+ Creates both database (with metadata) and project structure in a single command.
42
+
43
+ \b
44
+ ARGUMENTS:
45
+ project_name: Name of the project (= database name = Python package name)
46
+
47
+ \b
48
+ WORKFLOW:
49
+ 1. Check if database exists and has metadata
50
+ 2. Create/configure database with half_orm_dev metadata
51
+ 3. Create project directory structure with Git repository
52
+ 4. Generate Python package from database schema
53
+
54
+ \b
55
+ EXAMPLES:
56
+ # Create new project with new database (interactive)
57
+ half_orm dev init my_blog
58
+
59
+ # Create with explicit connection parameters
60
+ half_orm dev init my_blog --host=localhost --user=dev --git-origin=https://github.com/user/my_blog.git
61
+
62
+ # Force sync-only mode (no metadata, limited functionality)
63
+ half_orm dev init legacy_project --force-sync-only
64
+
65
+ \b
66
+ PROJECT STRUCTURE CREATED:
67
+ my_blog/
68
+ ├── .git/ (ho-prod branch)
69
+ ├── .hop/config (project configuration)
70
+ ├── Patches/ (schema patches)
71
+ ├── releases/ (release management)
72
+ ├── model/ (schema snapshots)
73
+ ├── backups/ (database backups)
74
+ ├── my_blog/ (Python package)
75
+ ├── tests/ (test directory)
76
+ ├── README.md
77
+ ├── .gitignore
78
+ └── pyproject.toml
79
+
80
+ \b
81
+ MODES:
82
+ - Full development mode (default): Database with half_orm_dev metadata
83
+ → Enables: create-patch, apply-patch, prepare-release, etc.
84
+
85
+ - Sync-only mode (--force-sync-only or user declines metadata):
86
+ → Only enables: sync-package (code generation from schema)
87
+ → Limited functionality, no patch management
88
+
89
+ \b
90
+ ERROR CASES:
91
+ - Database exists with metadata:
92
+ → Error: "Use 'half_orm dev clone <git-url>' to work on existing project"
93
+
94
+ - Project directory already exists:
95
+ → Error: "Directory already exists, choose a different name"
96
+ """
97
+ try:
98
+ database_name = project_name
99
+ package_name = project_name
100
+
101
+ click.echo(f"🚀 Initializing half_orm project '{project_name}'...")
102
+ click.echo()
103
+
104
+ # ============================================================
105
+ # STEP 1: PRE-FLIGHT CHECKS
106
+ # ============================================================
107
+
108
+ # Check 1: Project directory must not exist
109
+ project_dir = Path.cwd() / project_name
110
+ if project_dir.exists():
111
+ raise ProjectDirectoryExistsError(
112
+ f"Directory '{project_name}' already exists in current directory.\n"
113
+ f"Choose a different project name or remove the existing directory."
114
+ )
115
+
116
+ # Prepare connection options (will be used by both check and setup)
117
+ connection_options = {
118
+ 'host': host,
119
+ 'port': port,
120
+ 'user': user,
121
+ 'password': password,
122
+ 'production': production,
123
+ 'docker_container': docker
124
+ }
125
+
126
+ # Check 2: Database status (exists? has metadata?)
127
+ click.echo(f"🔍 Checking database status...")
128
+ db_exists, has_metadata = _check_database_status(
129
+ database_name, connection_options
130
+ )
131
+
132
+ # Check 3: Existing project detection
133
+ if has_metadata:
134
+ raise DatabaseExistsWithMetadataError(
135
+ f"Database '{database_name}' already has half_orm_dev metadata (existing project).\n"
136
+ f"To work on this project, use: half_orm dev clone <git-url>"
137
+ )
138
+
139
+ # ============================================================
140
+ # STEP 2: DATABASE SETUP
141
+ # ============================================================
142
+
143
+ # Determine metadata installation strategy
144
+ install_metadata = True
145
+ create_db = not db_exists
146
+
147
+ if db_exists:
148
+ # Database exists without metadata
149
+ click.echo(f"ℹ️ Database '{database_name}' exists without half_orm_dev metadata.")
150
+
151
+ if force_sync_only:
152
+ install_metadata = False
153
+ click.echo("⚠️ Sync-only mode forced: metadata installation skipped.")
154
+ else:
155
+ # Interactive prompt
156
+ install_metadata = click.confirm(
157
+ "Install half_orm_dev metadata for full development mode?",
158
+ default=True
159
+ )
160
+ if not install_metadata:
161
+ click.echo("⚠️ Continuing in sync-only mode (limited functionality).")
162
+
163
+ click.echo()
164
+
165
+ # Execute database setup (reuses existing Database.setup_database)
166
+ click.echo(f"🗄️ Setting up database '{database_name}'...")
167
+ Database.setup_database(
168
+ database_name=database_name,
169
+ connection_options=connection_options,
170
+ create_db=create_db,
171
+ add_metadata=install_metadata
172
+ )
173
+
174
+ click.echo(f"✅ Database '{database_name}' configured successfully.")
175
+ click.echo()
176
+
177
+ # ============================================================
178
+ # STEP 3: PROJECT STRUCTURE CREATION
179
+ # ============================================================
180
+
181
+ # Prompt for git_origin if not provided
182
+ if not git_origin:
183
+ git_origin = _prompt_for_git_origin(project_name)
184
+
185
+ click.echo(f"📁 Creating project structure...")
186
+
187
+ # Now safe to instantiate Repo (database is configured)
188
+ repo = Repo()
189
+ repo.init_git_centric_project(
190
+ package_name=package_name,
191
+ git_origin=git_origin
192
+ )
193
+
194
+ # ============================================================
195
+ # STEP 4: SUCCESS MESSAGE
196
+ # ============================================================
197
+
198
+ click.echo()
199
+ click.echo(f"✅ Project '{project_name}' initialized successfully!")
200
+ click.echo()
201
+ click.echo("📂 Project structure:")
202
+ click.echo(f" {project_name}/")
203
+ click.echo(f" ├── .git/ (ho-prod branch)")
204
+ click.echo(f" ├── .hop/config")
205
+ click.echo(f" ├── Patches/")
206
+ click.echo(f" ├── releases/")
207
+ click.echo(f" ├── model/")
208
+ click.echo(f" ├── {package_name}/ (Python package)")
209
+ click.echo(f" └── tests/")
210
+ click.echo()
211
+ click.echo("🚀 Next steps:")
212
+ click.echo(f" cd {project_name}/")
213
+
214
+ if install_metadata:
215
+ click.echo(" half_orm dev create-patch <patch-name> # Start developing")
216
+ else:
217
+ click.echo(" half_orm dev sync-package # Sync Python code with schema")
218
+ click.echo()
219
+ click.echo(" ⚠️ Note: Sync-only mode has limited functionality.")
220
+ click.echo(" To enable full development mode later, you'll need to:")
221
+ click.echo(" 1. Install metadata manually in the database")
222
+ click.echo(" 2. Reconfigure the project")
223
+
224
+ except DatabaseExistsWithMetadataError as e:
225
+ click.echo()
226
+ utils.error(str(e), exit_code=1)
227
+
228
+ except ProjectDirectoryExistsError as e:
229
+ click.echo()
230
+ utils.error(str(e), exit_code=1)
231
+
232
+ except Exception as e:
233
+ click.echo()
234
+ utils.error(f"Project initialization failed: {e}", exit_code=1)
235
+
236
+
237
+ def _check_database_status(database_name, connection_options):
238
+ """
239
+ Check if database exists and has half_orm_dev metadata.
240
+
241
+ Args:
242
+ database_name (str): Name of the database to check
243
+ connection_options (dict): Already collected connection parameters
244
+ - host (str): PostgreSQL host
245
+ - port (int): PostgreSQL port
246
+ - user (str): Database user
247
+ - password (str): Database password
248
+ - production (bool): Production flag
249
+
250
+ Returns:
251
+ tuple: (db_exists: bool, has_metadata: bool)
252
+ - (False, False): Database doesn't exist
253
+ - (True, False): Database exists without metadata
254
+ - (True, True): Database exists with metadata (existing project)
255
+
256
+ Implementation:
257
+ Attempts connection using Model. If successful, checks for
258
+ half_orm_meta.hop_release table. Uses already-collected parameters
259
+ to avoid re-prompting the user.
260
+ """
261
+ try:
262
+ # Try to create Model instance
263
+ # Model will attempt to read from ~/.half_orm/<database_name> config
264
+ # If config doesn't exist yet, connection will fail and we return (False, False)
265
+ model = Model(database_name)
266
+
267
+ # Database exists and is accessible, check for metadata
268
+ has_metadata = model.has_relation('half_orm_meta.hop_release')
269
+
270
+ # Clean up
271
+ model.disconnect()
272
+
273
+ return (True, has_metadata)
274
+
275
+ except Exception as e:
276
+ # Database doesn't exist or not accessible yet
277
+ # This is expected for new databases before setup_database runs
278
+ error_msg = str(e).lower()
279
+ if 'does not exist' in error_msg or 'database' in error_msg or 'connection' in error_msg:
280
+ return (False, False)
281
+ else:
282
+ # Unexpected error, re-raise
283
+ raise
284
+
285
+
286
+ def _prompt_for_git_origin(project_name):
287
+ """
288
+ Interactively prompt user for git origin URL.
289
+
290
+ Args:
291
+ project_name (str): Project name (used for suggestion)
292
+
293
+ Returns:
294
+ str: Git origin URL provided by user
295
+
296
+ Prompts:
297
+ "Git remote origin URL (e.g., https://github.com/user/my_blog.git): "
298
+
299
+ Validation:
300
+ - Must not be empty
301
+ - Basic URL format check (starts with http/git/ssh)
302
+ """
303
+ click.echo("🔗 Git repository configuration:")
304
+ click.echo(f" Example: https://github.com/<user>/{project_name}.git")
305
+ click.echo()
306
+
307
+ while True:
308
+ git_origin = click.prompt(
309
+ "Git remote origin URL",
310
+ type=str,
311
+ default=""
312
+ )
313
+
314
+ # Validation
315
+ if not git_origin or git_origin.strip() == "":
316
+ click.echo("❌ Git origin URL cannot be empty. Please provide a valid URL.")
317
+ continue
318
+
319
+ git_origin = git_origin.strip()
320
+
321
+ # Basic format validation
322
+ valid_prefixes = ('http://', 'https://', 'git://', 'git@', 'ssh://')
323
+ if not any(git_origin.startswith(prefix) for prefix in valid_prefixes):
324
+ click.echo(
325
+ "⚠️ Warning: Git URL should start with http://, https://, git://, git@, or ssh://\n"
326
+ " Example: https://github.com/user/repo.git"
327
+ )
328
+ if not click.confirm("Use this URL anyway?", default=False):
329
+ continue
330
+
331
+ return git_origin
@@ -0,0 +1,15 @@
1
+ """
2
+ New command - Creates a new hop project
3
+ """
4
+
5
+ import click
6
+ from half_orm_dev.repo import Repo
7
+
8
+
9
+ @click.command()
10
+ @click.argument('package_name')
11
+ @click.option('-d', '--devel', is_flag=True, help="Development mode")
12
+ def new(package_name, devel=False):
13
+ """Creates a new hop project named <package_name>."""
14
+ repo = Repo()
15
+ repo.init(package_name, devel)