half-orm-dev 0.17.3a6__tar.gz → 0.17.3a8__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-0.17.3a6/half_orm_dev.egg-info → half_orm_dev-0.17.3a8}/PKG-INFO +2 -2
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/database.py +21 -157
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/repo.py +1 -54
- half_orm_dev-0.17.3a8/half_orm_dev/scripts/repair-metadata.py +352 -0
- half_orm_dev-0.17.3a8/half_orm_dev/version.txt +1 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8/half_orm_dev.egg-info}/PKG-INFO +2 -2
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev.egg-info/SOURCES.txt +1 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev.egg-info/requires.txt +1 -1
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/pyproject.toml +1 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/setup.py +1 -1
- half_orm_dev-0.17.3a6/half_orm_dev/version.txt +0 -1
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/AUTHORS +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/LICENSE +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/README.md +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/__init__.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/__init__.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/__init__.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/apply.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/check.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/clone.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/init.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/migrate.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/patch.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/release.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/restore.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/sync.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/todo.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/undo.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/update.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/commands/upgrade.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli/main.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/cli_extension.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/decorators.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/hgit.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/migration_manager.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/migrations/0/17/1/01_txt_to_toml.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/modules.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patch_manager.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patch_validator.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patches/log +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/patches/sql/half_orm_meta.sql +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/release_file.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/release_manager.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/.gitignore +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/MANIFEST.in +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/Pipfile +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/README +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/conftest_template +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/git-hooks/pre-commit +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/git-hooks/prepare-commit-msg +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/init_module_template +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/module_template_1 +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/module_template_2 +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/module_template_3 +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/pyproject.toml +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/relation_test +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/sql_adapter +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/templates/warning +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/utils.py +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev.egg-info/dependency_links.txt +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev.egg-info/top_level.txt +0 -0
- {half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: half_orm_dev
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.3a8
|
|
4
4
|
Summary: half_orm development Framework.
|
|
5
5
|
Author-email: Joël Maïzi <joel.maizi@collorg.org>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -23,7 +23,7 @@ Requires-Dist: GitPython
|
|
|
23
23
|
Requires-Dist: click
|
|
24
24
|
Requires-Dist: pydash
|
|
25
25
|
Requires-Dist: pytest
|
|
26
|
-
Requires-Dist: half_orm<0.18.0,>=0.17.
|
|
26
|
+
Requires-Dist: half_orm<0.18.0,>=0.17.3
|
|
27
27
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
28
28
|
Requires-Dist: tomli_w>=1.0.0
|
|
29
29
|
Dynamic: license-file
|
|
@@ -1224,126 +1224,12 @@ class Database:
|
|
|
1224
1224
|
|
|
1225
1225
|
return complete_params
|
|
1226
1226
|
|
|
1227
|
-
@classmethod
|
|
1228
|
-
def _load_configuration(cls, database_name):
|
|
1229
|
-
"""
|
|
1230
|
-
Load existing database configuration file, replacing DbConn functionality.
|
|
1231
|
-
|
|
1232
|
-
Reads halfORM configuration file and returns connection parameters as a dictionary.
|
|
1233
|
-
This method completely replaces DbConn.__init() logic, supporting both minimal
|
|
1234
|
-
configurations (PostgreSQL trust mode) and complete parameter sets.
|
|
1235
|
-
|
|
1236
|
-
Args:
|
|
1237
|
-
database_name (str): Name of the database to load configuration for
|
|
1238
|
-
|
|
1239
|
-
Returns:
|
|
1240
|
-
dict | None: Connection parameters dictionary with standardized keys:
|
|
1241
|
-
- name (str): Database name (always present)
|
|
1242
|
-
- user (str): Database user (defaults to $USER environment variable)
|
|
1243
|
-
- password (str): Database password (empty string if not set)
|
|
1244
|
-
- host (str): Database host (empty string for Unix socket, 'localhost' otherwise)
|
|
1245
|
-
- port (int): Database port (5432 if not specified)
|
|
1246
|
-
- production (bool): Production environment flag (defaults to False)
|
|
1247
|
-
Returns None if configuration file doesn't exist.
|
|
1248
|
-
|
|
1249
|
-
Raises:
|
|
1250
|
-
FileNotFoundError: If CONF_DIR doesn't exist or isn't accessible
|
|
1251
|
-
PermissionError: If configuration file exists but isn't readable
|
|
1252
|
-
ValueError: If configuration file format is invalid or corrupted
|
|
1253
|
-
|
|
1254
|
-
Examples:
|
|
1255
|
-
# Complete configuration file
|
|
1256
|
-
config = Database._load_configuration("production_db")
|
|
1257
|
-
# Returns: {'name': 'production_db', 'user': 'app_user', 'password': 'secret',
|
|
1258
|
-
# 'host': 'db.company.com', 'port': 5432, 'production': True}
|
|
1259
|
-
|
|
1260
|
-
# Minimal trust mode configuration (only name=database_name)
|
|
1261
|
-
config = Database._load_configuration("local_dev")
|
|
1262
|
-
# Returns: {'name': 'local_dev', 'user': 'joel', 'password': '',
|
|
1263
|
-
# 'host': '', 'port': 5432, 'production': False}
|
|
1264
|
-
|
|
1265
|
-
# Non-existent configuration
|
|
1266
|
-
config = Database._load_configuration("unknown_db")
|
|
1267
|
-
# Returns: None
|
|
1268
|
-
|
|
1269
|
-
Migration Notes:
|
|
1270
|
-
- Completely replaces DbConn.__init() and DbConn.__init logic
|
|
1271
|
-
- Maintains backward compatibility with existing config files
|
|
1272
|
-
- Standardizes return format (int for port, bool for production)
|
|
1273
|
-
- Integrates PostgreSQL trust mode defaults directly into Database class
|
|
1274
|
-
- Eliminates external DbConn dependency while preserving all functionality
|
|
1275
|
-
"""
|
|
1276
|
-
from half_orm.model import CONF_DIR
|
|
1277
|
-
|
|
1278
|
-
# Check if configuration directory exists
|
|
1279
|
-
if not os.path.exists(CONF_DIR):
|
|
1280
|
-
raise FileNotFoundError(f"Configuration directory {CONF_DIR} doesn't exist")
|
|
1281
|
-
|
|
1282
|
-
# Build configuration file path
|
|
1283
|
-
config_file = os.path.join(CONF_DIR, database_name)
|
|
1284
|
-
|
|
1285
|
-
# Return None if configuration file doesn't exist
|
|
1286
|
-
if not os.path.exists(config_file):
|
|
1287
|
-
return None
|
|
1288
|
-
|
|
1289
|
-
# Check if file is readable before attempting to parse
|
|
1290
|
-
if not os.access(config_file, os.R_OK):
|
|
1291
|
-
raise PermissionError(f"Configuration file {config_file} is not readable")
|
|
1292
|
-
|
|
1293
|
-
# Read configuration file
|
|
1294
|
-
config = ConfigParser()
|
|
1295
|
-
try:
|
|
1296
|
-
config.read(config_file)
|
|
1297
|
-
except Exception as e:
|
|
1298
|
-
raise ValueError(f"Configuration file format is invalid: {e}")
|
|
1299
|
-
|
|
1300
|
-
# Check if [database] section exists
|
|
1301
|
-
if not config.has_section('database'):
|
|
1302
|
-
raise ValueError("Configuration file format is invalid: missing [database] section")
|
|
1303
|
-
|
|
1304
|
-
# Extract configuration values with PostgreSQL defaults
|
|
1305
|
-
try:
|
|
1306
|
-
name = config.get('database', 'name')
|
|
1307
|
-
user = config.get('database', 'user', fallback=os.environ.get('USER', ''))
|
|
1308
|
-
password = config.get('database', 'password', fallback='')
|
|
1309
|
-
host = config.get('database', 'host', fallback='')
|
|
1310
|
-
port_str = config.get('database', 'port', fallback='')
|
|
1311
|
-
production_str = config.get('database', 'production', fallback='False')
|
|
1312
|
-
docker_container = config.get('database', 'docker_container', fallback='')
|
|
1313
|
-
|
|
1314
|
-
# Convert port to int (default 5432 if empty)
|
|
1315
|
-
if port_str == '':
|
|
1316
|
-
port = 5432
|
|
1317
|
-
else:
|
|
1318
|
-
port = int(port_str)
|
|
1319
|
-
|
|
1320
|
-
# Convert production to bool
|
|
1321
|
-
production = config.getboolean('database', 'production', fallback=False)
|
|
1322
|
-
|
|
1323
|
-
return {
|
|
1324
|
-
'name': name,
|
|
1325
|
-
'user': user,
|
|
1326
|
-
'password': password,
|
|
1327
|
-
'host': host,
|
|
1328
|
-
'port': port,
|
|
1329
|
-
'production': production,
|
|
1330
|
-
'docker_container': docker_container
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
except (ValueError, TypeError) as e:
|
|
1334
|
-
raise ValueError(f"Configuration file format is invalid: {e}")
|
|
1335
|
-
|
|
1336
1227
|
def _get_connection_params(self):
|
|
1337
1228
|
"""
|
|
1338
1229
|
Get current connection parameters for this database instance.
|
|
1339
1230
|
|
|
1340
|
-
Returns the connection parameters dictionary
|
|
1341
|
-
|
|
1342
|
-
unified interface for accessing connection parameters during the migration
|
|
1343
|
-
from DbConn to integrated Database functionality.
|
|
1344
|
-
|
|
1345
|
-
Uses instance-level caching to avoid repeated file reads within the same
|
|
1346
|
-
Database instance lifecycle.
|
|
1231
|
+
Returns the connection parameters dictionary using Model._dbinfo,
|
|
1232
|
+
which is already loaded by half_orm from the configuration file.
|
|
1347
1233
|
|
|
1348
1234
|
Returns:
|
|
1349
1235
|
dict: Connection parameters dictionary with standardized keys:
|
|
@@ -1353,59 +1239,37 @@ class Database:
|
|
|
1353
1239
|
- host (str): Database host (empty string for Unix socket)
|
|
1354
1240
|
- port (int): Database port (5432 default)
|
|
1355
1241
|
- production (bool): Production environment flag
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
Examples:
|
|
1359
|
-
# Get connection parameters for existing database instance
|
|
1360
|
-
db = Database(repo)
|
|
1361
|
-
params = db._get_connection_params()
|
|
1362
|
-
# Returns: {'name': 'my_db', 'user': 'dev', 'password': '',
|
|
1363
|
-
# 'host': 'localhost', 'port': 5432, 'production': False}
|
|
1364
|
-
|
|
1365
|
-
# Access specific parameters (replaces DbConn.property access)
|
|
1366
|
-
user = db._get_connection_params()['user'] # replaces self.__connection_params.user
|
|
1367
|
-
host = db._get_connection_params()['host'] # replaces self.__connection_params.host
|
|
1368
|
-
prod = db._get_connection_params()['production'] # replaces self.__connection_params.production
|
|
1369
|
-
|
|
1370
|
-
Implementation Notes:
|
|
1371
|
-
- Uses _load_configuration() internally but handles all exceptions
|
|
1372
|
-
- Provides stable interface - never raises exceptions
|
|
1373
|
-
- Returns sensible defaults if configuration is missing/invalid
|
|
1374
|
-
- Serves as protective wrapper around _load_configuration()
|
|
1375
|
-
- Exceptions from _load_configuration() are caught and handled gracefully
|
|
1376
|
-
- Uses instance-level cache to avoid repeated file reads
|
|
1377
|
-
|
|
1378
|
-
Migration Notes:
|
|
1379
|
-
- Replaces self.__connection_params.user, .host, .port, .production access
|
|
1380
|
-
- Serves as transition method during DbConn elimination
|
|
1381
|
-
- Maintains compatibility with existing Database instance usage patterns
|
|
1382
|
-
- Will be used by state, production, and execute_pg_command properties
|
|
1242
|
+
- docker_container (str): Docker container name (if configured)
|
|
1383
1243
|
"""
|
|
1384
1244
|
# Return cached parameters if already loaded
|
|
1385
1245
|
if hasattr(self, '_Database__connection_params_cache') and self.__connection_params_cache is not None:
|
|
1386
1246
|
return self.__connection_params_cache
|
|
1387
1247
|
|
|
1388
|
-
#
|
|
1248
|
+
# Use connection info from Model._dbinfo (already loaded by half_orm)
|
|
1249
|
+
if self.__model is not None and hasattr(self.__model, '_dbinfo'):
|
|
1250
|
+
dbinfo = self.__model._dbinfo
|
|
1251
|
+
config = {
|
|
1252
|
+
'name': self.__repo.name,
|
|
1253
|
+
'user': dbinfo.get('user', os.environ.get('USER', '')),
|
|
1254
|
+
'password': dbinfo.get('password', ''),
|
|
1255
|
+
'host': dbinfo.get('host', ''),
|
|
1256
|
+
'port': int(dbinfo.get('port', 5432) or 5432),
|
|
1257
|
+
'production': not self.__model._production_mode, # devel=False means production
|
|
1258
|
+
'docker_container': dbinfo.get('docker_container', ''),
|
|
1259
|
+
}
|
|
1260
|
+
self.__connection_params_cache = config
|
|
1261
|
+
return config
|
|
1262
|
+
|
|
1263
|
+
# Fallback: defaults (should not happen in normal usage)
|
|
1389
1264
|
config = {
|
|
1390
1265
|
'name': self.__repo.name,
|
|
1391
1266
|
'user': os.environ.get('USER', ''),
|
|
1392
1267
|
'password': '',
|
|
1393
1268
|
'host': '',
|
|
1394
1269
|
'port': 5432,
|
|
1395
|
-
'production': False
|
|
1270
|
+
'production': False,
|
|
1271
|
+
'docker_container': '',
|
|
1396
1272
|
}
|
|
1397
|
-
|
|
1398
|
-
try:
|
|
1399
|
-
# Try to load configuration for this database
|
|
1400
|
-
loaded_config = self._load_configuration(self.__repo.name)
|
|
1401
|
-
if loaded_config is not None:
|
|
1402
|
-
config = loaded_config
|
|
1403
|
-
except (FileNotFoundError, PermissionError, ValueError):
|
|
1404
|
-
# Handle all possible exceptions from _load_configuration gracefully
|
|
1405
|
-
# Return sensible defaults to maintain stable interface
|
|
1406
|
-
pass
|
|
1407
|
-
|
|
1408
|
-
# Cache the result for subsequent calls
|
|
1409
1273
|
self.__connection_params_cache = config
|
|
1410
1274
|
return config
|
|
1411
1275
|
|
|
@@ -1199,10 +1199,7 @@ class Repo:
|
|
|
1199
1199
|
# Step 1b: Validate git origin URL (EARLY validation)
|
|
1200
1200
|
self._validate_git_origin_url(git_origin)
|
|
1201
1201
|
|
|
1202
|
-
# Step 2:
|
|
1203
|
-
self._verify_database_configured(package_name)
|
|
1204
|
-
|
|
1205
|
-
# Step 3: Connect to database and detect mode
|
|
1202
|
+
# Step 2: Connect to database and detect mode
|
|
1206
1203
|
devel_mode = self._detect_development_mode(package_name)
|
|
1207
1204
|
|
|
1208
1205
|
# Step 4: Setup project directory
|
|
@@ -1645,56 +1642,6 @@ class Repo:
|
|
|
1645
1642
|
# Store normalized name for later use
|
|
1646
1643
|
return normalized_name
|
|
1647
1644
|
|
|
1648
|
-
|
|
1649
|
-
def _verify_database_configured(self, package_name):
|
|
1650
|
-
"""
|
|
1651
|
-
Verify database is configured via init-database command.
|
|
1652
|
-
|
|
1653
|
-
Checks that database configuration file exists and is accessible.
|
|
1654
|
-
Does NOT create the database - assumes init-database was run first.
|
|
1655
|
-
|
|
1656
|
-
Args:
|
|
1657
|
-
package_name (str): Database name to verify
|
|
1658
|
-
|
|
1659
|
-
Raises:
|
|
1660
|
-
DatabaseNotConfiguredError: If configuration file doesn't exist
|
|
1661
|
-
DatabaseConnectionError: If cannot connect to configured database
|
|
1662
|
-
|
|
1663
|
-
Process:
|
|
1664
|
-
1. Check ~/.half_orm/<package_name> exists
|
|
1665
|
-
2. Attempt connection to verify database is accessible
|
|
1666
|
-
3. Store connection for later use
|
|
1667
|
-
|
|
1668
|
-
Examples:
|
|
1669
|
-
# Database configured
|
|
1670
|
-
_verify_database_configured("my_blog") # Success
|
|
1671
|
-
|
|
1672
|
-
# Database not configured
|
|
1673
|
-
_verify_database_configured("unconfigured_db")
|
|
1674
|
-
# Raises: DatabaseNotConfiguredError with helpful message
|
|
1675
|
-
"""
|
|
1676
|
-
# Try to load database configuration
|
|
1677
|
-
config = Database._load_configuration(package_name)
|
|
1678
|
-
|
|
1679
|
-
if config is None:
|
|
1680
|
-
raise ValueError(
|
|
1681
|
-
f"Database '{package_name}' is not configured.\n"
|
|
1682
|
-
f"Please run: half_orm dev init-database {package_name} [OPTIONS]\n"
|
|
1683
|
-
f"See 'half_orm dev init-database --help' for more information."
|
|
1684
|
-
)
|
|
1685
|
-
|
|
1686
|
-
# Try to connect to verify database is accessible
|
|
1687
|
-
try:
|
|
1688
|
-
model = Model(package_name)
|
|
1689
|
-
# Store model for later use
|
|
1690
|
-
return model
|
|
1691
|
-
except OperationalError as e:
|
|
1692
|
-
raise OperationalError(
|
|
1693
|
-
f"Cannot connect to database '{package_name}'.\n"
|
|
1694
|
-
f"Database may not exist or connection parameters may be incorrect.\n"
|
|
1695
|
-
f"Original error: {e}"
|
|
1696
|
-
)
|
|
1697
|
-
|
|
1698
1645
|
def _detect_development_mode(self, package_name):
|
|
1699
1646
|
"""
|
|
1700
1647
|
Detect development mode based on metadata presence in database.
|
|
@@ -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()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.17.3-a8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: half_orm_dev
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.3a8
|
|
4
4
|
Summary: half_orm development Framework.
|
|
5
5
|
Author-email: Joël Maïzi <joel.maizi@collorg.org>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -23,7 +23,7 @@ Requires-Dist: GitPython
|
|
|
23
23
|
Requires-Dist: click
|
|
24
24
|
Requires-Dist: pydash
|
|
25
25
|
Requires-Dist: pytest
|
|
26
|
-
Requires-Dist: half_orm<0.18.0,>=0.17.
|
|
26
|
+
Requires-Dist: half_orm<0.18.0,>=0.17.3
|
|
27
27
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
28
28
|
Requires-Dist: tomli_w>=1.0.0
|
|
29
29
|
Dynamic: license-file
|
|
@@ -45,6 +45,7 @@ half_orm_dev/patches/0/1/0/00_half_orm_meta.database.sql
|
|
|
45
45
|
half_orm_dev/patches/0/1/0/01_alter_half_orm_meta.hop_release.sql
|
|
46
46
|
half_orm_dev/patches/0/1/0/02_half_orm_meta.view.hop_penultimate_release.sql
|
|
47
47
|
half_orm_dev/patches/sql/half_orm_meta.sql
|
|
48
|
+
half_orm_dev/scripts/repair-metadata.py
|
|
48
49
|
half_orm_dev/templates/.gitignore
|
|
49
50
|
half_orm_dev/templates/MANIFEST.in
|
|
50
51
|
half_orm_dev/templates/Pipfile
|
|
@@ -16,7 +16,7 @@ from setuptools import setup
|
|
|
16
16
|
# Example: (0, 17, 3, 5, '0.17.1') means:
|
|
17
17
|
# for half_orm_dev 0.17.x where 3 <= x < 5, require half_orm >= 0.17.1
|
|
18
18
|
HALF_ORM_MIN_VERSIONS = [
|
|
19
|
-
(0, 17, 3, None, '0.17.
|
|
19
|
+
(0, 17, 3, None, '0.17.3'), # 0.17.3+ requires half_orm >= 0.17.3 (CustomGroup support)
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.17.3-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
|
{half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/half_orm_dev/migrations/0/17/1/00_move_to_hop.py
RENAMED
|
File without changes
|
{half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/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
|
{half_orm_dev-0.17.3a6 → half_orm_dev-0.17.3a8}/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
|