Skip to content

Commit 4e44717

Browse files
authored
Merge pull request #408 from LCOGT/revert-407-revert-same-block-cals
Revert "Reverting changes on how calibrations are chosen that were included for floyds"
2 parents baa75c7 + a50e3c8 commit 4e44717

18 files changed

+921
-268
lines changed

CHANGES.md

-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
1.22.0 (2025-03-17)
2-
-------------------
3-
- Reverted the addition of prefer-same-block-cals, check-public-cals, and prefer-same-proposal-cals
4-
banzai imaging does not use them so refactoring to inject them only in floyds
5-
61
1.21.0 (2025-02-12)
72
-------------------
83
- Added the ability to change the proposal of a frame before saving

alembic/alembic.ini

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
# Use forward slashes (/) also on windows to provide an os agnostic path
6+
script_location = migrations
7+
8+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
9+
# Uncomment the line below if you want the files to be prepended with date and time
10+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
11+
# for all available tokens
12+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
13+
14+
# sys.path path, will be prepended to sys.path if present.
15+
# defaults to the current working directory.
16+
prepend_sys_path = .
17+
18+
# timezone to use when rendering the date within the migration file
19+
# as well as the filename.
20+
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
21+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
22+
# string value is passed to ZoneInfo()
23+
# leave blank for localtime
24+
# timezone =
25+
26+
# max length of characters to apply to the "slug" field
27+
# truncate_slug_length = 40
28+
29+
# set to 'true' to run the environment during
30+
# the 'revision' command, regardless of autogenerate
31+
# revision_environment = false
32+
33+
# set to 'true' to allow .pyc and .pyo files without
34+
# a source .py file to be detected as revisions in the
35+
# versions/ directory
36+
# sourceless = false
37+
38+
# version location specification; This defaults
39+
# to migrations/versions. When using multiple version
40+
# directories, initial revisions must be specified with --version-path.
41+
# The path separator used here should be the separator specified by "version_path_separator" below.
42+
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
43+
44+
# version path separator; As mentioned above, this is the character used to split
45+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47+
# Valid values for version_path_separator are:
48+
#
49+
# version_path_separator = :
50+
# version_path_separator = ;
51+
# version_path_separator = space
52+
# version_path_separator = newline
53+
#
54+
# Use os.pathsep. Default configuration used for new projects.
55+
version_path_separator = os
56+
57+
# set to 'true' to search source files recursively
58+
# in each "version_locations" directory
59+
# new in Alembic version 1.10
60+
# recursive_version_locations = false
61+
62+
# the output encoding used when revision files
63+
# are written from script.py.mako
64+
# output_encoding = utf-8
65+
66+
67+
[post_write_hooks]
68+
# post_write_hooks defines scripts or Python functions that are run
69+
# on newly generated revision scripts. See the documentation for further
70+
# detail and examples
71+
72+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
73+
# hooks = black
74+
# black.type = console_scripts
75+
# black.entrypoint = black
76+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
77+
78+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
79+
# hooks = ruff
80+
# ruff.type = exec
81+
# ruff.executable = %(here)s/.venv/bin/ruff
82+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
83+
84+
# Logging configuration
85+
[loggers]
86+
keys = root,sqlalchemy,alembic
87+
88+
[handlers]
89+
keys = console
90+
91+
[formatters]
92+
keys = generic
93+
94+
[logger_root]
95+
level = WARNING
96+
handlers = console
97+
qualname =
98+
99+
[logger_sqlalchemy]
100+
level = WARNING
101+
handlers =
102+
qualname = sqlalchemy.engine
103+
104+
[logger_alembic]
105+
level = INFO
106+
handlers =
107+
qualname = alembic
108+
109+
[handler_console]
110+
class = StreamHandler
111+
args = (sys.stderr,)
112+
level = NOTSET
113+
formatter = generic
114+
115+
[formatter_generic]
116+
format = %(levelname)-5.5s [%(name)s] %(message)s
117+
datefmt = %H:%M:%S

alembic/db_migration.py

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# I have archived this file here even though it is not in alembic format beacuse
2+
# this is one of the early database migrations we did when we changed banzai to track
3+
# instruments and cameras rather than just telescopes. Future migrations of this type
4+
# will use alembic.
5+
6+
import argparse
7+
8+
from sqlalchemy import create_engine
9+
from sqlalchemy import Column, Integer, String, Date, ForeignKey, Boolean, CHAR
10+
from sqlalchemy.ext.declarative import declarative_base
11+
12+
from banzai import dbs, logs
13+
14+
logger = logs.get_logger()
15+
16+
Base = declarative_base()
17+
18+
19+
# The five base classes below are taken from Banzai version < 0.16.0
20+
class CalibrationImage(Base):
21+
__tablename__ = 'calimages'
22+
id = Column(Integer, primary_key=True, autoincrement=True)
23+
type = Column(String(30), index=True)
24+
filename = Column(String(50), unique=True)
25+
filepath = Column(String(100))
26+
dayobs = Column(Date, index=True)
27+
ccdsum = Column(String(20))
28+
filter_name = Column(String(32))
29+
telescope_id = Column(Integer, ForeignKey("telescopes.id"), index=True)
30+
31+
32+
class Telescope(Base):
33+
__tablename__ = 'telescopes'
34+
id = Column(Integer, primary_key=True, autoincrement=True)
35+
site = Column(String(10), ForeignKey('sites.id'), index=True)
36+
instrument = Column(String(20), index=True)
37+
camera_type = Column(String(20))
38+
schedulable = Column(Boolean, default=False)
39+
40+
41+
class Site(Base):
42+
__tablename__ = 'sites'
43+
id = Column(String(3), primary_key=True)
44+
timezone = Column(Integer)
45+
46+
47+
class BadPixelMask(Base):
48+
__tablename__ = 'bpms'
49+
id = Column(Integer, primary_key=True, autoincrement=True)
50+
telescope_id = Column(Integer, ForeignKey("telescopes.id"), index=True)
51+
filename = Column(String(50))
52+
filepath = Column(String(100))
53+
ccdsum = Column(String(20))
54+
creation_date = Column(Date)
55+
56+
57+
class PreviewImage(Base):
58+
__tablename__ = 'previewimages'
59+
id = Column(Integer, primary_key=True, autoincrement=True)
60+
filename = Column(String(50), index=True)
61+
checksum = Column(CHAR(32), index=True, default='0'*32)
62+
success = Column(Boolean, default=False)
63+
tries = Column(Integer, default=0)
64+
65+
66+
def create_new_db(db_address):
67+
engine = create_engine(db_address)
68+
dbs.Base.metadata.create_all(engine)
69+
70+
71+
def base_to_dict(base):
72+
return [{key: value for key, value in row.__dict__.items() if not key.startswith('_')} for row in base]
73+
74+
75+
def change_key_name(row_list, old_key, new_key):
76+
for row in row_list:
77+
row[new_key] = row.pop(old_key)
78+
79+
80+
def add_rows(db_session, base, row_list, max_chunk_size=100000):
81+
for i in range(0, len(row_list), max_chunk_size):
82+
logger.debug("Inserting rows {a} to {b}".format(a=i+1, b=min(i+max_chunk_size, len(row_list))))
83+
db_session.bulk_insert_mappings(base, row_list[i:i + max_chunk_size])
84+
db_session.commit()
85+
86+
87+
def migrate_db():
88+
89+
parser = argparse.ArgumentParser()
90+
parser.add_argument('old_db_address',
91+
help='Old database address to be migrated: Should be in SQLAlchemy form')
92+
parser.add_argument('new_db_address',
93+
help='New database address: Should be in SQLAlchemy form')
94+
parser.add_argument("--log-level", default='debug', choices=['debug', 'info', 'warning',
95+
'critical', 'fatal', 'error'])
96+
args = parser.parse_args()
97+
98+
logs.set_log_level(args.log_level)
99+
logger.info("Creating new DB {new_db_address} from old DB {old_db_address}".format(
100+
new_db_address=args.new_db_address, old_db_address=args.old_db_address))
101+
create_new_db(args.new_db_address)
102+
103+
with dbs.get_session(db_address=args.old_db_address) as old_db_session, dbs.get_session(db_address=args.new_db_address) as new_db_session:
104+
105+
# First copy sites table
106+
logger.info("Querying and organizing the old Site table")
107+
sites = base_to_dict(old_db_session.query(Site).all())
108+
logger.info("Adding {n} rows from the old Site table to the new Site table".format(n=len(sites)))
109+
add_rows(new_db_session, dbs.Site, sites)
110+
111+
# Move Telescope to Instrument with a couple of variable renames
112+
logger.info("Querying and organizing the old Telescope table")
113+
telescopes = base_to_dict(old_db_session.query(Telescope).all())
114+
change_key_name(telescopes, 'instrument', 'camera')
115+
change_key_name(telescopes, 'camera_type', 'type')
116+
logger.info("Adding {n} rows from the old Telescope table to the new Instrument table".format(
117+
n=len(telescopes)))
118+
add_rows(new_db_session, dbs.Instrument, telescopes)
119+
120+
# Move old BPMs to CalibrationImage
121+
logger.info("Querying and organizing the old BadPixelMask table")
122+
bpms = base_to_dict(old_db_session.query(BadPixelMask).all())
123+
for row in bpms:
124+
row['type'] = 'BPM'
125+
row['is_master'] = True
126+
row['attributes'] = {'ccdsum': row.pop('ccdsum')}
127+
del row['id']
128+
change_key_name(bpms, 'creation_date', 'dateobs')
129+
change_key_name(bpms, 'telescope_id', 'instrument_id')
130+
# BPMs have some duplicates, remove them
131+
already_seen = []
132+
bpms_pruned = []
133+
for row in bpms:
134+
if row['filename'] not in already_seen:
135+
bpms_pruned.append(row)
136+
already_seen.append(row['filename'])
137+
logger.info("Adding {n} rows from the old BadPixelMask table to the new CalibrationImage table".format(
138+
n=len(bpms_pruned)))
139+
add_rows(new_db_session, dbs.CalibrationImage, bpms_pruned)
140+
141+
# Convert old CalibrationImage to new type
142+
logger.info("Querying and organizing the old CalibrationsImage table")
143+
calibrations = base_to_dict(old_db_session.query(CalibrationImage).all())
144+
for row in calibrations:
145+
row['is_master'] = True
146+
row['attributes'] = {'filter': row.pop('filter_name'), 'ccdsum': row.pop('ccdsum')}
147+
del row['id']
148+
change_key_name(calibrations, 'dayobs', 'dateobs')
149+
change_key_name(calibrations, 'telescope_id', 'instrument_id')
150+
logger.info("Adding {n} rows from the old CalibrationImage table to the new CalibrationImage table".format(
151+
n=len(calibrations)))
152+
add_rows(new_db_session, dbs.CalibrationImage, calibrations)
153+
154+
# Copy the PreviewImage table to ProcssedImage (attributes are all the same)
155+
logger.info("Querying and organizing the old PreviewImage table")
156+
preview_images = base_to_dict(old_db_session.query(PreviewImage).all())
157+
logger.info("Adding {n} rows from the old PreviewImage table to the new ProcessedImage table".format(
158+
n=len(preview_images)))
159+
add_rows(new_db_session, dbs.ProcessedImage, preview_images)
160+
161+
logger.info("Finished")

alembic/migrations/env.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from logging.config import fileConfig
2+
3+
import os
4+
from sqlalchemy import pool, create_engine
5+
from alembic import context
6+
7+
# this is the Alembic Config object, which provides
8+
# access to the values within the .ini file in use.
9+
config = context.config
10+
11+
# Interpret the config file for Python logging.
12+
# This line sets up loggers basically.
13+
if config.config_file_name is not None:
14+
fileConfig(config.config_file_name)
15+
16+
# add your model's MetaData object here
17+
# for 'autogenerate' support
18+
# from myapp import mymodel
19+
# target_metadata = mymodel.Base.metadata
20+
target_metadata = None
21+
22+
# other values from the config, defined by the needs of env.py,
23+
# can be acquired:
24+
# my_important_option = config.get_main_option("my_important_option")
25+
# ... etc.
26+
27+
28+
def run_migrations() -> None:
29+
"""Run migrations in 'online' mode.
30+
31+
In this scenario we need to create an Engine
32+
and associate a connection with the context.
33+
34+
"""
35+
connection_url = os.getenv("DB_ADDRESS", "sqlite:///test.db")
36+
connectable = create_engine(connection_url, poolclass=pool.NullPool)
37+
38+
with connectable.connect() as connection:
39+
context.configure(
40+
connection=connection, target_metadata=target_metadata
41+
)
42+
43+
with context.begin_transaction():
44+
context.run_migrations()
45+
46+
47+
run_migrations()

alembic/migrations/script.py.mako

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
${imports if imports else ""}
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = ${repr(up_revision)}
16+
down_revision: Union[str, None] = ${repr(down_revision)}
17+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19+
20+
21+
def upgrade() -> None:
22+
${upgrades if upgrades else "pass"}
23+
24+
25+
def downgrade() -> None:
26+
${downgrades if downgrades else "pass"}

0 commit comments

Comments
 (0)