|
| 1 | +""" |
| 2 | +Schema upgrade utilities for the reindexer. |
| 3 | +
|
| 4 | +When a new FITS header field is added to the database schema, a new entry |
| 5 | +must be added to SCHEMA_VERSION_FIELDS mapping the new schema version to the |
| 6 | +list of DB column names that are populated from the FITS header. |
| 7 | +
|
| 8 | +The upgrade worker reads only the file header (no pixel data, no thumbnail |
| 9 | +regeneration), extracts the relevant fields, and returns them to the main |
| 10 | +process which performs the DB UPDATE. |
| 11 | +
|
| 12 | +To add a new schema version in the future: |
| 13 | + 1. Add the DB column via a Phinx migration. |
| 14 | + 2. Add the new version and its fields to SCHEMA_VERSION_FIELDS. |
| 15 | + 3. Add the corresponding header extraction inside schema_upgrade_worker. |
| 16 | + 4. Increment current_schema_version in reindex.py. |
| 17 | +""" |
| 18 | + |
| 19 | +import os |
| 20 | +import logging |
| 21 | + |
| 22 | +from astropy.io import fits |
| 23 | +from xisf import XISF |
| 24 | + |
| 25 | +from indexer_lib.file_utils import get_header_value, get_xisf_header_value |
| 26 | + |
| 27 | +logger = logging.getLogger('reindex') |
| 28 | + |
| 29 | +# Maps each schema version to the DB column names introduced in that step. |
| 30 | +# Keys are version numbers; values are lists of field names. |
| 31 | +# Only steps with version > old_version are applied to a given file. |
| 32 | +SCHEMA_VERSION_FIELDS = { |
| 33 | + 2: ['gain'], # v1 -> v2: GAIN FITS header (camera gain setting, ADU units) |
| 34 | +} |
| 35 | + |
| 36 | + |
| 37 | +def schema_upgrade_worker(task, fits_root): |
| 38 | + """Lightweight multiprocessing worker for schema version upgrades. |
| 39 | +
|
| 40 | + Reads only the FITS/XISF header — does NOT load pixel data or regenerate |
| 41 | + thumbnails. |
| 42 | +
|
| 43 | + Args: |
| 44 | + task: Tuple of (full_path: str, old_schema_version: int) |
| 45 | + fits_root: Root directory of the FITS file tree |
| 46 | +
|
| 47 | + Returns: |
| 48 | + dict with 'status', 'path', 'old_version', and one key per extracted |
| 49 | + field. On error: {'status': 'error', 'path': ..., 'reason': ...} |
| 50 | + """ |
| 51 | + full_path, old_version = task |
| 52 | + rel_path = os.path.relpath(full_path, fits_root) |
| 53 | + file_lower = full_path.lower() |
| 54 | + |
| 55 | + # Determine which fields need to be extracted for this upgrade path |
| 56 | + needed_fields = set() |
| 57 | + for step_v in sorted(SCHEMA_VERSION_FIELDS.keys()): |
| 58 | + if step_v > old_version: |
| 59 | + needed_fields.update(SCHEMA_VERSION_FIELDS[step_v]) |
| 60 | + |
| 61 | + try: |
| 62 | + result = {'status': 'success', 'path': rel_path, 'old_version': old_version} |
| 63 | + |
| 64 | + if needed_fields: |
| 65 | + header = None |
| 66 | + get_value = None |
| 67 | + if file_lower.endswith(('.fits', '.fit')): |
| 68 | + with fits.open(full_path, ignore_missing_end=True) as hdul: |
| 69 | + header = hdul[0].header |
| 70 | + get_value = get_header_value |
| 71 | + elif file_lower.endswith('.xisf'): |
| 72 | + xisf_file = XISF(full_path) |
| 73 | + images_meta = xisf_file.get_images_metadata() |
| 74 | + if images_meta: |
| 75 | + header = images_meta[0].get('FITSKeywords', {}) |
| 76 | + get_value = get_xisf_header_value |
| 77 | + |
| 78 | + if header is not None and get_value is not None: |
| 79 | + # v1 -> v2 |
| 80 | + if 'gain' in needed_fields: |
| 81 | + result['gain'] = get_value(header, 'GAIN', None, float) |
| 82 | + # v2 -> v3: add new fields here in the future |
| 83 | + |
| 84 | + return result |
| 85 | + except Exception as e: |
| 86 | + logger.error(f"Schema upgrade error for {rel_path}: {e}") |
| 87 | + return {'status': 'error', 'path': rel_path, 'reason': str(e)} |
0 commit comments