half-orm-dev 0.17.3a6__py3-none-any.whl → 0.17.3a7__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/scripts/repair-metadata.py +352 -0
- half_orm_dev/version.txt +1 -1
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/METADATA +1 -1
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/RECORD +8 -7
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/WHEEL +0 -0
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/licenses/AUTHORS +0 -0
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/licenses/LICENSE +0 -0
- {half_orm_dev-0.17.3a6.dist-info → half_orm_dev-0.17.3a7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Repair script for half-orm-dev metadata files.
|
|
4
|
+
|
|
5
|
+
This script regenerates all metadata-X.Y.Z.sql files with correct hop_release
|
|
6
|
+
entries by:
|
|
7
|
+
1. Clearing half_orm_meta.hop_release (keeping only 0.0.0)
|
|
8
|
+
2. For each version tag (vX.Y.Z or vX.Y.Z-rcN):
|
|
9
|
+
- Insert the version with the tag's date
|
|
10
|
+
- Generate metadata-X.Y.Z.sql (or metadata-X.Y.Z-rcN.sql)
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
cd /path/to/your/project
|
|
14
|
+
python /path/to/half-orm-dev/scripts/repair-metadata.py [--dry-run]
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--dry-run Show what would be done without modifying anything
|
|
18
|
+
--verbose Show detailed information
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import re
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import List, Tuple, Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_version_tags() -> List[Tuple[str, str, datetime]]:
|
|
31
|
+
"""
|
|
32
|
+
Get all version tags with their dates from git.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of (tag_name, version_string, date) tuples sorted by version.
|
|
36
|
+
tag_name: e.g., "v0.1.0", "v0.1.0-rc1"
|
|
37
|
+
version_string: e.g., "0.1.0", "0.1.0-rc1"
|
|
38
|
+
date: datetime object
|
|
39
|
+
"""
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
['git', 'tag', '--format=%(refname:short) %(creatordate:iso)'],
|
|
42
|
+
capture_output=True, text=True, check=True
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
tags = []
|
|
46
|
+
for line in result.stdout.strip().split('\n'):
|
|
47
|
+
if not line.strip():
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
parts = line.split(' ', 1)
|
|
51
|
+
if len(parts) != 2:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
tag_name, date_str = parts
|
|
55
|
+
|
|
56
|
+
# Match vX.Y.Z or vX.Y.Z-rcN
|
|
57
|
+
match = re.match(r'^v(\d+\.\d+\.\d+(?:-rc\d+)?)$', tag_name)
|
|
58
|
+
if not match:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
version_str = match.group(1)
|
|
62
|
+
|
|
63
|
+
# Parse date (format: "2025-11-21 15:09:14 +0100")
|
|
64
|
+
try:
|
|
65
|
+
# Remove timezone for simpler parsing
|
|
66
|
+
date_part = ' '.join(date_str.split()[:2])
|
|
67
|
+
date = datetime.strptime(date_part, '%Y-%m-%d %H:%M:%S')
|
|
68
|
+
except ValueError:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
tags.append((tag_name, version_str, date))
|
|
72
|
+
|
|
73
|
+
# Sort by version (handle X.Y.Z and X.Y.Z-rcN)
|
|
74
|
+
def version_key(item):
|
|
75
|
+
version_str = item[1]
|
|
76
|
+
# Split base version and rc part
|
|
77
|
+
if '-rc' in version_str:
|
|
78
|
+
base, rc = version_str.split('-rc')
|
|
79
|
+
rc_num = int(rc)
|
|
80
|
+
else:
|
|
81
|
+
base = version_str
|
|
82
|
+
rc_num = 9999 # Production comes after all RCs
|
|
83
|
+
|
|
84
|
+
parts = [int(p) for p in base.split('.')]
|
|
85
|
+
return (*parts, rc_num)
|
|
86
|
+
|
|
87
|
+
return sorted(tags, key=version_key)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_version_string(version_str: str) -> Tuple[int, int, int, str, str]:
|
|
91
|
+
"""
|
|
92
|
+
Parse version string into components.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
version_str: e.g., "0.1.0" or "0.1.0-rc1"
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Tuple of (major, minor, patch, pre_release, pre_release_num)
|
|
99
|
+
"""
|
|
100
|
+
if '-rc' in version_str:
|
|
101
|
+
base, rc = version_str.split('-rc')
|
|
102
|
+
pre_release = 'rc'
|
|
103
|
+
pre_release_num = rc
|
|
104
|
+
else:
|
|
105
|
+
base = version_str
|
|
106
|
+
pre_release = ''
|
|
107
|
+
pre_release_num = ''
|
|
108
|
+
|
|
109
|
+
parts = base.split('.')
|
|
110
|
+
return (int(parts[0]), int(parts[1]), int(parts[2]), pre_release, pre_release_num)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def find_model_dir() -> Optional[Path]:
|
|
114
|
+
"""Find the .hop/model directory in current working directory."""
|
|
115
|
+
cwd = Path.cwd()
|
|
116
|
+
model_dir = cwd / ".hop" / "model"
|
|
117
|
+
|
|
118
|
+
if model_dir.is_dir():
|
|
119
|
+
return model_dir
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_database_name() -> Optional[str]:
|
|
124
|
+
"""Get database name from half_orm config."""
|
|
125
|
+
try:
|
|
126
|
+
from half_orm.model import Model
|
|
127
|
+
model = Model._model
|
|
128
|
+
if model:
|
|
129
|
+
return model._dbname
|
|
130
|
+
except:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
# Try reading from .hop/config
|
|
134
|
+
config_path = Path.cwd() / ".hop" / "config"
|
|
135
|
+
if config_path.exists():
|
|
136
|
+
import configparser
|
|
137
|
+
config = configparser.ConfigParser()
|
|
138
|
+
config.read(config_path)
|
|
139
|
+
if 'database' in config and 'name' in config['database']:
|
|
140
|
+
return config['database']['name']
|
|
141
|
+
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def clear_hop_release_table(db_name: str, dry_run: bool = False) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Clear hop_release table keeping only 0.0.0.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
db_name: Database name
|
|
151
|
+
dry_run: If True, only show what would be done
|
|
152
|
+
"""
|
|
153
|
+
sql = "DELETE FROM half_orm_meta.hop_release WHERE NOT (major = 0 AND minor = 0 AND patch = 0);"
|
|
154
|
+
|
|
155
|
+
if dry_run:
|
|
156
|
+
print(f"Would execute: {sql}")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
subprocess.run(
|
|
160
|
+
['psql', '-d', db_name, '-c', sql],
|
|
161
|
+
check=True, capture_output=True
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def insert_version(db_name: str, major: int, minor: int, patch: int,
|
|
166
|
+
pre_release: str, pre_release_num: str,
|
|
167
|
+
date: datetime, dry_run: bool = False) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Insert a version into hop_release table.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
db_name: Database name
|
|
173
|
+
major, minor, patch: Version components
|
|
174
|
+
pre_release: 'rc' or ''
|
|
175
|
+
pre_release_num: RC number or ''
|
|
176
|
+
date: Release date
|
|
177
|
+
dry_run: If True, only show what would be done
|
|
178
|
+
"""
|
|
179
|
+
date_str = date.strftime('%Y-%m-%d')
|
|
180
|
+
time_str = date.strftime('%H:%M:%S')
|
|
181
|
+
|
|
182
|
+
sql = f"""INSERT INTO half_orm_meta.hop_release
|
|
183
|
+
(major, minor, patch, pre_release, pre_release_num, date, time)
|
|
184
|
+
VALUES ({major}, {minor}, {patch}, '{pre_release}', '{pre_release_num}', '{date_str}', '{time_str}');"""
|
|
185
|
+
|
|
186
|
+
if dry_run:
|
|
187
|
+
print(f"Would execute: {sql}")
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
subprocess.run(
|
|
191
|
+
['psql', '-d', db_name, '-c', sql],
|
|
192
|
+
check=True, capture_output=True
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def generate_metadata_file(db_name: str, version_str: str, model_dir: Path,
|
|
197
|
+
dry_run: bool = False) -> Path:
|
|
198
|
+
"""
|
|
199
|
+
Generate metadata-X.Y.Z.sql file using pg_dump.
|
|
200
|
+
|
|
201
|
+
Only keeps COPY blocks to avoid version-specific SET commands
|
|
202
|
+
and ensure compatibility across PostgreSQL versions.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
db_name: Database name
|
|
206
|
+
version_str: Version string (e.g., "0.1.0" or "0.1.0-rc1")
|
|
207
|
+
model_dir: Path to model directory
|
|
208
|
+
dry_run: If True, only show what would be done
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Path to generated file
|
|
212
|
+
"""
|
|
213
|
+
metadata_file = model_dir / f"metadata-{version_str}.sql"
|
|
214
|
+
|
|
215
|
+
if dry_run:
|
|
216
|
+
print(f"Would generate: {metadata_file}")
|
|
217
|
+
return metadata_file
|
|
218
|
+
|
|
219
|
+
# Dump to stdout
|
|
220
|
+
result = subprocess.run([
|
|
221
|
+
'pg_dump', db_name,
|
|
222
|
+
'--data-only',
|
|
223
|
+
'--table=half_orm_meta.database',
|
|
224
|
+
'--table=half_orm_meta.hop_release',
|
|
225
|
+
'--table=half_orm_meta.hop_release_issue',
|
|
226
|
+
], check=True, capture_output=True, text=True)
|
|
227
|
+
|
|
228
|
+
# Filter to keep only COPY blocks (COPY ... FROM stdin; ... \.)
|
|
229
|
+
filtered_lines = []
|
|
230
|
+
in_copy_block = False
|
|
231
|
+
for line in result.stdout.split('\n'):
|
|
232
|
+
if line.startswith('COPY '):
|
|
233
|
+
in_copy_block = True
|
|
234
|
+
if in_copy_block:
|
|
235
|
+
filtered_lines.append(line)
|
|
236
|
+
if line == '\\.':
|
|
237
|
+
in_copy_block = False
|
|
238
|
+
filtered_lines.append('') # Empty line between blocks
|
|
239
|
+
|
|
240
|
+
metadata_file.write_text('\n'.join(filtered_lines))
|
|
241
|
+
|
|
242
|
+
return metadata_file
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def main():
|
|
246
|
+
parser = argparse.ArgumentParser(
|
|
247
|
+
description="Regenerate half-orm-dev metadata files from git tags.",
|
|
248
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
249
|
+
epilog=__doc__
|
|
250
|
+
)
|
|
251
|
+
parser.add_argument(
|
|
252
|
+
'--dry-run',
|
|
253
|
+
action='store_true',
|
|
254
|
+
help='Show what would be done without modifying anything'
|
|
255
|
+
)
|
|
256
|
+
parser.add_argument(
|
|
257
|
+
'--verbose',
|
|
258
|
+
action='store_true',
|
|
259
|
+
help='Show detailed information'
|
|
260
|
+
)
|
|
261
|
+
parser.add_argument(
|
|
262
|
+
'--database',
|
|
263
|
+
type=str,
|
|
264
|
+
help='Database name (auto-detected if not specified)'
|
|
265
|
+
)
|
|
266
|
+
parser.add_argument(
|
|
267
|
+
'--model-dir',
|
|
268
|
+
type=Path,
|
|
269
|
+
help='Path to model directory (default: .hop/model)'
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
args = parser.parse_args()
|
|
273
|
+
|
|
274
|
+
# Find model directory
|
|
275
|
+
if args.model_dir:
|
|
276
|
+
model_dir = args.model_dir
|
|
277
|
+
else:
|
|
278
|
+
model_dir = find_model_dir()
|
|
279
|
+
|
|
280
|
+
if model_dir is None or not model_dir.is_dir():
|
|
281
|
+
print("Error: Could not find .hop/model directory", file=sys.stderr)
|
|
282
|
+
print("Make sure you're in a half-orm-dev managed project directory", file=sys.stderr)
|
|
283
|
+
sys.exit(1)
|
|
284
|
+
|
|
285
|
+
# Get database name
|
|
286
|
+
db_name = args.database or get_database_name()
|
|
287
|
+
if not db_name:
|
|
288
|
+
print("Error: Could not determine database name", file=sys.stderr)
|
|
289
|
+
print("Use --database option to specify it", file=sys.stderr)
|
|
290
|
+
sys.exit(1)
|
|
291
|
+
|
|
292
|
+
print(f"Model directory: {model_dir}")
|
|
293
|
+
print(f"Database: {db_name}")
|
|
294
|
+
print()
|
|
295
|
+
|
|
296
|
+
if args.dry_run:
|
|
297
|
+
print("=== DRY RUN MODE - No changes will be made ===")
|
|
298
|
+
print()
|
|
299
|
+
|
|
300
|
+
# Get version tags
|
|
301
|
+
tags = get_version_tags()
|
|
302
|
+
|
|
303
|
+
if not tags:
|
|
304
|
+
print("No version tags found (expected format: vX.Y.Z or vX.Y.Z-rcN)")
|
|
305
|
+
sys.exit(0)
|
|
306
|
+
|
|
307
|
+
print(f"Found {len(tags)} version tags")
|
|
308
|
+
print()
|
|
309
|
+
|
|
310
|
+
# Step 1: Clear hop_release table (keep 0.0.0)
|
|
311
|
+
print("Step 1: Clearing hop_release table (keeping 0.0.0)...")
|
|
312
|
+
clear_hop_release_table(db_name, dry_run=args.dry_run)
|
|
313
|
+
print(" Done")
|
|
314
|
+
print()
|
|
315
|
+
|
|
316
|
+
# Step 2: Process each version
|
|
317
|
+
print("Step 2: Processing versions...")
|
|
318
|
+
for tag_name, version_str, date in tags:
|
|
319
|
+
major, minor, patch, pre_release, pre_release_num = parse_version_string(version_str)
|
|
320
|
+
|
|
321
|
+
if args.verbose:
|
|
322
|
+
print(f" {tag_name} -> {version_str} ({date})")
|
|
323
|
+
|
|
324
|
+
# Insert version with correct date
|
|
325
|
+
insert_version(
|
|
326
|
+
db_name, major, minor, patch,
|
|
327
|
+
pre_release, pre_release_num, date,
|
|
328
|
+
dry_run=args.dry_run
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Generate metadata file
|
|
332
|
+
metadata_file = generate_metadata_file(
|
|
333
|
+
db_name, version_str, model_dir,
|
|
334
|
+
dry_run=args.dry_run
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
print(f" ✓ {version_str} ({date.strftime('%Y-%m-%d')})")
|
|
338
|
+
|
|
339
|
+
print()
|
|
340
|
+
print("=" * 50)
|
|
341
|
+
print(f"Summary:")
|
|
342
|
+
print(f" Versions processed: {len(tags)}")
|
|
343
|
+
|
|
344
|
+
if not args.dry_run:
|
|
345
|
+
print()
|
|
346
|
+
print("Next steps:")
|
|
347
|
+
print(" 1. Review the changes: git status")
|
|
348
|
+
print(" 2. Commit: git add .hop/model/metadata-*.sql && git commit -m 'fix: regenerate metadata files'")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
if __name__ == "__main__":
|
|
352
|
+
main()
|
half_orm_dev/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.17.3-
|
|
1
|
+
0.17.3-a7
|
|
@@ -11,7 +11,7 @@ half_orm_dev/release_file.py,sha256=0c9NBhAQ6YpiC3HWj8VtZcfvvZxW2ITk1NEQ60AO0sI,
|
|
|
11
11
|
half_orm_dev/release_manager.py,sha256=AXjjtIWAdv0yy6DkP_eGYKqDDvejmbxnz_QiMRLvwws,125510
|
|
12
12
|
half_orm_dev/repo.py,sha256=h-nsB6z2xph9ortO02g9DcPXeef9Rk2D5WI3Yc3h1M4,98253
|
|
13
13
|
half_orm_dev/utils.py,sha256=M3yViUFfsO7Cp9MYSoUSkCZ6R9w_4jW45UDZUOT8FhI,1493
|
|
14
|
-
half_orm_dev/version.txt,sha256=
|
|
14
|
+
half_orm_dev/version.txt,sha256=3Ff9bgyfOMEi-lwqWRSvH9yY70F6xBUQBQXzA7QjBbI,10
|
|
15
15
|
half_orm_dev/cli/__init__.py,sha256=0CbMj8OIhZmglWakK7NhYPn302erUTEg2VHOdm1hRTQ,163
|
|
16
16
|
half_orm_dev/cli/main.py,sha256=3SVTl5WraNTSY6o7LfvE1dUHKg_RcuVaHHDIn_oINv4,11701
|
|
17
17
|
half_orm_dev/cli/commands/__init__.py,sha256=UhWf0AnWqy4gyFo2SJQv8pL_YJ43pE_c9TgopcjzKDg,1490
|
|
@@ -35,6 +35,7 @@ half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql,sha256=gMZ94YlyrftxcqDn
|
|
|
35
35
|
half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql,sha256=nhRbDi6sUenvVfOnoRuWSbLEC1cEfzrXbxDof2weq04,183
|
|
36
36
|
half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql,sha256=Bd0lXJ6vC0JNe06yqTWYVSrwVDElCI3McSS5pm-Ijlo,306
|
|
37
37
|
half_orm_dev/patches/sql/half_orm_meta.sql,sha256=Vl2YzEWpWdam-tC0ZE8iNMeTRzEHpxtNhdBThbHb2u4,5864
|
|
38
|
+
half_orm_dev/scripts/repair-metadata.py,sha256=qUP4v5I16ONBfI18_mqyrDIOlpTPoa4MozQSy4-N-lg,10248
|
|
38
39
|
half_orm_dev/templates/.gitignore,sha256=RmvQ9D46T9vpRxhYjjY5WUjGVbuyFUMsH059wC7sPBM,140
|
|
39
40
|
half_orm_dev/templates/MANIFEST.in,sha256=53BeBuKi8UtBWB6IG3VQZk9Ow8Iye6Zs14sP-gVyVDA,25
|
|
40
41
|
half_orm_dev/templates/Pipfile,sha256=u3lGJSk5HZwz-EOTrOdBYrkhGV6zgVtrrRPivrO5rmA,182
|
|
@@ -50,9 +51,9 @@ half_orm_dev/templates/sql_adapter,sha256=kAP5y7Qml3DKsbZLUeoVpeXjbQcWltHjkDznED
|
|
|
50
51
|
half_orm_dev/templates/warning,sha256=4hlZ_rRdpmkXxOeRoVd9xnXBARYXn95e-iXrD1f2u7k,490
|
|
51
52
|
half_orm_dev/templates/git-hooks/pre-commit,sha256=Hf084pqeiOebrv4xzA0aiaHbIXswmmNO-dSIXUfzMK0,4707
|
|
52
53
|
half_orm_dev/templates/git-hooks/prepare-commit-msg,sha256=zknOGGoaWKC97zfga2Xl2i_psnNo9MJbrEBuN91eHNw,1070
|
|
53
|
-
half_orm_dev-0.17.
|
|
54
|
-
half_orm_dev-0.17.
|
|
55
|
-
half_orm_dev-0.17.
|
|
56
|
-
half_orm_dev-0.17.
|
|
57
|
-
half_orm_dev-0.17.
|
|
58
|
-
half_orm_dev-0.17.
|
|
54
|
+
half_orm_dev-0.17.3a7.dist-info/licenses/AUTHORS,sha256=eWxqzRdLOt2gX0FMQj_wui03Od3jdlwa8xNe9tl84g0,113
|
|
55
|
+
half_orm_dev-0.17.3a7.dist-info/licenses/LICENSE,sha256=ufhxlSi6mttkGQTsGWrEoB3WA_fCPJ6-k07GSVBgyPw,644
|
|
56
|
+
half_orm_dev-0.17.3a7.dist-info/METADATA,sha256=w85PxMvXeM6dp9I7qkdppzYR_8bVd3O-6hF3dCfTnG8,16149
|
|
57
|
+
half_orm_dev-0.17.3a7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
58
|
+
half_orm_dev-0.17.3a7.dist-info/top_level.txt,sha256=M5hEsWfn5Kw0HL-VnNmS6Jw-3cwRyjims5a8cr18eTM,13
|
|
59
|
+
half_orm_dev-0.17.3a7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|