Python API

The imo_vmdb package provides a Python API that can be used independently of any web framework or HTTP connection. It covers importing, normalising, cleaning up, exporting, and querying meteor observation data.

Database connection

class imo_vmdb.DBAdapter(config: Mapping[str, Any])

Lightweight database connection adapter supporting SQLite, PostgreSQL, and MySQL.

A dedicated adapter is used instead of a heavier abstraction (e.g. SQLAlchemy) because the SQL in this project is straightforward and the only cross-backend difference is placeholder syntax. The adapter handles three concerns:

  • Dynamic driver selection — the DB-API 2.0 module (sqlite3, psycopg2, mysql.connector, …) is imported at runtime from config, so no specific driver is a hard dependency of the package.

  • SQLite initialisation — enables foreign-key enforcement via PRAGMA foreign_keys = ON, which SQLite disables by default.

  • Placeholder normalisation — internally, %(name)s-style named placeholders are rewritten to the active backend’s dialect.

The default backend is sqlite3; a different module can be selected via the module key in config.

Parameters:

config

Connection parameters. The optional module key names the DB-API 2.0 module to load (default: "sqlite3"); all remaining keys are forwarded verbatim to that module’s connect() call.

Typical keys per backend:

  • SQLitedatabase: path to the .db file.

  • PostgreSQL (module = psycopg2) — database, user, optionally host, password.

  • MySQL (module = pymysql) — database, user, optionally host, password, plus any driver-specific keys such as sql_mode or init_command.

These correspond directly to the [database] section of the imo-vmdb configuration file.

close() None

Close the database connection.

commit() None

Commit the current transaction.

cursor() Any

Return a new database cursor.

Returns:

A new cursor object from the underlying DB-API 2.0 connection.

ping() None

Verify that the database accepts a trivial query.

Issues a SELECT 1 and discards the result. Intended for liveness/readiness checks (e.g. /health endpoints).

Raises:

DBException – If the underlying driver raises any error.

rollback() None

Roll back the current transaction.

The keys of the config dict correspond directly to the [database] section of the configuration file (see Setup). Examples:

import imo_vmdb

# SQLite
db = imo_vmdb.DBAdapter({"database": "/path/to/vmdb.db"})

# PostgreSQL
db = imo_vmdb.DBAdapter({
    "module": "psycopg2",
    "database": "vmdb",
    "user": "vmdb",
    "host": "localhost",
})

# MySQL
db = imo_vmdb.DBAdapter({
    "module": "pymysql",
    "database": "vmdb",
    "user": "vmdb",
})
class imo_vmdb.DBException

Raised when a database operation fails.

Operations

imo_vmdb.cleanup(db_conn: DBAdapter, logger: Logger) int

Remove all previously imported data, if any, while preserving normalized data in the database.

This function takes an existing database connection and a logger object as parameters. It removes all previously imported data from the database, leaving normalized data intact.

Parameters:
  • db_conn – An open database connection implementing DB-API 2.0.

  • logger (logging.Logger) – A logger object used to log errors, warnings, and additional information.

Returns:

An integer indicating the result of the operation. 0 for success, other values for errors.

Return type:

int

imo_vmdb.export_db(src_db_conn: DBAdapter, dst_conn: Connection) None

Export normalized observations and reference data into a SQLite database.

Creates the full imo-vmdb schema on dst_conn (dropping any pre-existing tables) and copies all rows from the normalized and reference tables of src_db_conn into it. The raw imported_* tables are intentionally skipped — an exported database is a clean snapshot of normalized data that can be shared and used as the input of a fresh imo-vmdb install.

The destination connection is committed but not closed; its lifecycle is owned by the caller. This allows in-memory destinations (e.g. sqlite3.connect(":memory:")) to be inspected after export.

Parameters:
  • src_db_conn – Source database connection. May be SQLite, PostgreSQL, or MySQL.

  • dst_conn – Empty (or to-be-overwritten) SQLite destination connection. Must be a real sqlite3.Connection; other DB-API drivers are not supported as the destination.

imo_vmdb.export_table(db_conn: DBAdapter, table: str, reimport: bool = False) tuple[list[str], list[tuple]]

Export all rows from a database table.

When reimport is True and table is 'shower', the result uses column names and date formats that are compatible with CSVImporter, so the exported CSV can be imported again without modification. For all other tables reimport has no effect.

Parameters:
  • db_conn – An open database connection implementing DB-API 2.0.

  • table – Name of the table to export. Must be one of the exportable normalized tables: shower, radiant, obs_session, magnitude, rate, magnitude_detail. Raw imported_* tables are intentionally not exportable.

  • reimport – If True, export in re-import-compatible format where applicable.

Returns:

Tuple of (column_names, rows).

Raises:

ValueError – If table is not an exportable table name.

imo_vmdb.initdb(db_conn: DBAdapter, logger: Logger) int

Initialize an empty database, removing all data if the database already exists.

This function takes an existing database connection and a logger object as parameters. It initializes an empty database, removing all data if the database already exists.

Parameters:
  • db_conn – An open database connection implementing DB-API 2.0.

  • logger (logging.Logger) – A logger object used to log errors, warnings, and additional information.

Returns:

An integer indicating the result of the operation. 0 for success, 1 for errors.

Return type:

int

imo_vmdb.normalize(db_conn: DBAdapter, logger: Logger) int

Establish relationships between imported records and enrich observations with additional information.

This function takes an existing database connection and a logger object as parameters. It establishes relationships between the imported records in the database, enriching observations with additional information.

Parameters:
  • db_conn – An open database connection implementing DB-API 2.0.

  • logger (logging.Logger) – A logger object used to log errors, warnings, and additional information.

Returns:

An integer indicating the result of the operation. 0 for success, 1 for errors.

Return type:

int

Exporting a whole database

export_db() writes the normalized observations and reference data of a source database into an empty SQLite destination, using the same schema as a regular imo-vmdb database. The resulting file can be shared and re-used as the input of another imo-vmdb installation. The raw imported_* tables are intentionally excluded.

import sqlite3
import imo_vmdb

src = imo_vmdb.DBAdapter({"database": "/path/to/vmdb.db"})

# Export to a portable SQLite file:
dst = sqlite3.connect("/tmp/snapshot.sqlite")
imo_vmdb.export_db(src, dst)
dst.close()
class imo_vmdb.CSVImporter(db_conn: DBAdapter, logger: Logger, do_delete: bool = False, try_repair: bool = False, is_permissive: bool = False)

A class for importing CSV files of various types into a database.

The CSVImporter class allows to import CSV files into a database. You can specify whether to delete existing data, attempt data repair, or be permissive about non-critical data errors during the import.

Parameters:
  • db_conn – An existing database connection implementing DB-API 2.0.

  • logger (logging.Logger) – A logger object used to log errors, warnings, and additional information.

  • do_delete (bool) – If True, delete existing data before importing. Default is False.

  • try_repair (bool) – If True, attempt data repair during import. Default is False.

  • is_permissive (bool) – If True, be permissive about non-critical data errors. Default is False.

run(file_list: Sequence[str]) None

Import CSV files specified in the files_list into the database.

This method imports CSV files into the database, with options to delete existing data, attempt data repair, and be permissive about non-critical data errors. After running this method, you can check the has_errors, counter_read, and counter_write properties of this object to determine the import result.

Parameters:

file_list (list of str) – A list of file paths to CSV files for import.

Importing data

SessionImporter is a programmatic alternative to CSV files when you need to persist a complete observation session (with its rates and magnitudes) directly from Python. The caller controls the transaction:

from datetime import datetime
import imo_vmdb

db = imo_vmdb.DBAdapter({"database": "/path/to/vmdb.db"})

session = imo_vmdb.SessionImport(
    id=12345,
    latitude=52.0,
    longitude=13.4,
    country="DE",
    location_name="Berlin",
    rates=(
        imo_vmdb.RateImport(
            id=700001,
            session_id=12345,
            shower="PER",
            period_start=datetime(2025, 8, 12, 22, 0, 0),
            period_end=datetime(2025, 8, 12, 23, 0, 0),
            t_eff=1.0,
            f=1.0,
            lim_magn=6.2,
            method="C",
            freq=17,
        ),
    ),
)

cur = db.cursor()
imp = imo_vmdb.SessionImporter(cur)
try:
    imp.upload(session)  # raises DuplicateSessionError if session already exists
    db.commit()
except Exception:
    db.rollback()
    raise

Use upload(..., replace=True) to overwrite an existing session, or delete() to remove one. Call normalize() afterwards to make the data visible in the normalized tables (obs_session, rate, magnitude). Log verbosity can be adjusted via logging.getLogger("imo_vmdb").setLevel(...).

class imo_vmdb.SessionImporter(cur)

Issues DB statements for SessionImport-based operations.

The caller owns both the connection (transaction) and the cursor. This class neither commits, rolls back, nor opens a cursor of its own — all statements are issued on the cursor passed at construction time. Combine multiple operations into one transaction by issuing them on the same cursor between a single db.commit() / db.rollback() pair.

Parameters:

cur – An open DB-API 2.0 cursor.

delete(session_id: int) bool

Issue DELETE statements for session_id and all its rates and magnitudes.

Parameters:

session_id – ID of the session to remove.

Returns:

True if the session existed (statements were issued), False if no row with that id exists (no statement issued).

Raises:

DBException – On database error.

upload(session: SessionImport, replace: bool = False) None

Issue INSERT statements for session and its children.

Parameters:
  • session – Session to persist.

  • replace – When True and a session with the same id already exists, DELETE statements for that session and its rates and magnitudes are issued first. When False (the default), a pre-existing session raises DuplicateSessionError before any statement is issued.

Raises:
class imo_vmdb.SessionImport(id: int, latitude: float, longitude: float, country: str, location_name: str, observer_id: int | None = None, observer_name: str | None = None, elevation: float | None = None, rates: tuple[RateImport, ...] = (), magnitudes: tuple[MagnitudeImport, ...] = ())

An observation session plus its rates and magnitudes, ready to upload.

Pure container. The caller is responsible for providing sensible values.

class imo_vmdb.RateImport(id: int, session_id: int, period_start: datetime, period_end: datetime, t_eff: float, f: float, lim_magn: float, method: str, freq: int, observer_id: int | None = None, shower: str | None = None, ra: float | None = None, dec: float | None = None)

A single rate observation to be uploaded together with its parent session.

Pure container. The caller is responsible for providing sensible values.

class imo_vmdb.MagnitudeImport(id: int, session_id: int, period_start: datetime, period_end: datetime, magn: dict[int, float], observer_id: int | None = None, shower: str | None = None)

A single magnitude observation to be uploaded together with its parent session.

Pure container. The caller is responsible for providing sensible values.

exception imo_vmdb.DuplicateSessionError

Raised when an upload targets a session id that already exists.

Service classes

Each service class wraps a DBAdapter connection and exposes the queries available for one entity. The query method returns the matching list (with optional pagination/total), by_id / by_code return a single record (or None).

class imo_vmdb.RateService(db_conn: DBAdapter)

Service for rate observation queries.

Parameters:

db_conn – An open DBAdapter connection.

by_id(rate_id: int) Rate | None

Return a single rate observation by ID, or None if not found.

query(f: RateFilter) Rates

Return rate observations matching f.

Parameters:

f – A RateFilter specifying filter criteria and includes.

Returns:

A Rates instance. sessions, magnitudes and magnitude_details are only set when the corresponding flags on f are True. total is set when with_total is True or when pagination is in use.

class imo_vmdb.MagnitudeService(db_conn: DBAdapter)

Service for magnitude observation queries.

Parameters:

db_conn – An open DBAdapter connection.

by_id(magn_id: int) Magnitude | None

Return a single magnitude observation by ID, or None if not found.

query(f: MagnitudeFilter) Magnitudes

Return magnitude observations matching f.

Parameters:

f – A MagnitudeFilter specifying filter criteria and includes.

Returns:

A Magnitudes instance. sessions and magnitude_details are only set when the corresponding flags on f are True. total is set when with_total is True or when pagination is in use.

class imo_vmdb.SessionService(db_conn: DBAdapter)

Service for observation session queries.

Parameters:

db_conn – An open DBAdapter connection.

by_id(session_id: int) Session | None

Return a single session by ID, or None if not found.

query(f: SessionFilter) Sessions

Return sessions matching f.

class imo_vmdb.ShowerService(db_conn: DBAdapter)

Service for meteor shower reference data and radiants.

Parameters:

db_conn – An open DBAdapter connection.

active(on_date: date) list[Shower]

Return all showers whose activity period covers on_date.

Handles year-wrapping showers (e.g. start in December, end in January) by treating the period as inclusive on both ends.

Parameters:

on_date – Calendar date to test against each shower’s start_* / end_* fields. Year is ignored.

Returns:

List of matching Shower instances.

by_code(iau_code: str) Shower | None

Return the shower with the given IAU code, or None if unknown.

query() list[Shower]

Return all meteor showers from the catalogue, ordered by IAU code.

radiants(iau_code: str) list[Radiant]

Return all radiant entries for iau_code, ordered by (month, day).

class imo_vmdb.StatsService(db_conn: DBAdapter)

Aggregate statistics over the observation database.

All by_* methods accept an optional period_start/period_end filter (ISO date strings) that restricts the rate and magnitude tables before aggregation. Sessions are not period-filtered for meta and by_country to keep the semantics simple (“how many sessions are in the database”).

Parameters:

db_conn – An open DBAdapter connection.

by_country(period_start: datetime | None = None, period_end: datetime | None = None) list[CountryStat]

Return per-country counts of sessions, rates and magnitudes.

by_shower(period_start: datetime | None = None, period_end: datetime | None = None) list[ShowerStat]

Return per-shower counts of rates and magnitudes.

by_year(period_start: datetime | None = None, period_end: datetime | None = None) list[YearStat]

Return per-year counts of rates and magnitudes.

meta() StatsMeta

Return overall counts and the covered date range.

Tolerates missing tables: a database where initdb has not yet been run (or which is mid-rebuild) yields zeros and None date bounds instead of raising, so the Web UI can poll the endpoint at any time without crashing.

Filter types

class imo_vmdb.RateFilter(showers: list[str] = <factory>, period_start: ~datetime.datetime | None = None, period_end: ~datetime.datetime | None = None, sl_min: float | None = None, sl_max: float | None = None, lim_magn_min: float | None = None, lim_magn_max: float | None = None, sun_alt_max: float | None = None, moon_alt_max: float | None = None, session_ids: list[int] = <factory>, rate_ids: list[int] = <factory>, include_sessions: bool = False, include_magnitudes: bool = False, include_magnitude_details: bool = False, limit: int | None = None, offset: int | None = None, order_by: str | None = None, order: str | None = None, with_total: bool = False)

Filter criteria for rate observation queries.

Parameters:
  • showers – IAU shower codes to include; use 'SPO' for sporadics.

  • period_start – Include only observations starting at or after this datetime.datetime (UTC).

  • period_end – Include only observations ending at or before this datetime.datetime (UTC).

  • sl_min – Minimum solar longitude (start of period).

  • sl_max – Maximum solar longitude (end of period).

  • lim_magn_min – Minimum limiting magnitude.

  • lim_magn_max – Maximum limiting magnitude.

  • sun_alt_max – Maximum sun altitude in degrees.

  • moon_alt_max – Maximum moon altitude in degrees.

  • session_ids – Restrict to specific session IDs.

  • rate_ids – Restrict to specific rate record IDs.

  • include_sessions – If True, include a sessions list in the result.

  • include_magnitudes – If True, include a magnitudes list with the full Magnitude observations referenced by the returned rates via their magn_id.

  • include_magnitude_details – If True, include a magnitude_details list with the per-magnitude-class frequencies (from magnitude_detail) for the magnitude observations referenced by the returned rates.

class imo_vmdb.MagnitudeFilter(showers: list[str] = <factory>, period_start: ~datetime.datetime | None = None, period_end: ~datetime.datetime | None = None, sl_min: float | None = None, sl_max: float | None = None, lim_magn_min: float | None = None, lim_magn_max: float | None = None, session_ids: list[int] = <factory>, magn_ids: list[int] = <factory>, include_sessions: bool = False, include_magnitude_details: bool = False, limit: int | None = None, offset: int | None = None, order_by: str | None = None, order: str | None = None, with_total: bool = False)

Filter criteria for magnitude observation queries.

Parameters:
  • showers – IAU shower codes to include; use 'SPO' for sporadics.

  • period_start – Include only observations starting at or after this datetime.datetime (UTC).

  • period_end – Include only observations ending at or before this datetime.datetime (UTC).

  • sl_min – Minimum solar longitude (start of period).

  • sl_max – Maximum solar longitude (end of period).

  • lim_magn_min – Minimum limiting magnitude.

  • lim_magn_max – Maximum limiting magnitude.

  • session_ids – Restrict to specific session IDs.

  • magn_ids – Restrict to specific magnitude record IDs.

  • include_sessions – If True, include a sessions list in the result.

  • include_magnitude_details – If True, include a magnitude_details list with the per-magnitude-class frequencies (from magnitude_detail) for each magnitude observation.

class imo_vmdb.SessionFilter(observer_ids: list[int] = <factory>, showers: list[str] = <factory>, period_start: ~datetime.datetime | None = None, period_end: ~datetime.datetime | None = None, sl_min: float | None = None, sl_max: float | None = None, lim_magn_min: float | None = None, lim_magn_max: float | None = None, include_rates: bool = False, include_magnitudes: bool = False, limit: int | None = None, offset: int | None = None, order_by: str | None = None, order: str | None = None, with_total: bool = False)

Filter criteria for session queries.

The observation-level filters (showers, period_*, sl_*, lim_magn_*) match sessions that have at least one rate or magnitude observation satisfying the criteria. When an include is requested (include_rates / include_magnitudes), the returned observations are filtered by the same criteria, restricted to the session IDs in the result page.

Parameters:
  • observer_ids – Restrict to specific observer IDs.

  • showers – IAU shower codes to include; use 'SPO' for sporadics.

  • period_start – Include only sessions with at least one rate or magnitude observation starting at or after this datetime.datetime (UTC).

  • period_end – Include only sessions with at least one rate or magnitude observation ending at or before this datetime.datetime (UTC).

  • sl_min – Minimum solar longitude (start of period) across rate/magnitude.

  • sl_max – Maximum solar longitude (end of period) across rate/magnitude.

  • lim_magn_min – Minimum limiting magnitude across rate/magnitude.

  • lim_magn_max – Maximum limiting magnitude across rate/magnitude.

  • include_rates – If True, include a rates list of full Rate observations in the result.

  • include_magnitudes – If True, include a magnitudes list of full Magnitude observations in the result.

  • limit – Maximum number of sessions to return.

  • offset – Number of leading sessions to skip.

  • order_by – Sort column (whitelist: id, country, observer_id).

  • order"asc" or "desc".

  • with_total – If True, populate Sessions.total.

Result types

class imo_vmdb.Shower(iau_code: str, name: str, start_month: int, start_day: int, end_month: int, end_day: int, peak_month: int | None, peak_day: int | None, ra: float | None, dec: float | None, v: float | None, r: float | None, zhr: float | None)

Single meteor shower from the reference catalogue.

See Field Reference for field descriptions.

class imo_vmdb.Session(id: int, longitude: float, latitude: float, elevation: float, country: str, location_name: str, observer_id: int | None, observer_name: str | None)

Observation session linked to rate or magnitude observations.

See Field Reference for field descriptions.

class imo_vmdb.Rate(id: int, shower: str | None, period_start: datetime, period_end: datetime, sl_start: float, sl_end: float, session_id: int, freq: int, lim_magn: float, t_eff: float, f: float, sidereal_time: float, sun_alt: float, sun_az: float, moon_alt: float, moon_az: float, moon_illum: float, field_alt: float | None, field_az: float | None, rad_alt: float | None, rad_az: float | None, magn_id: int | None, magn_solo: bool | None)

Single normalised rate observation returned by RateService.query().

See Field Reference for field descriptions.

magn_id links to the associated Magnitude observation, or is None when no matching magnitude observation exists. magn_solo is True when this rate is the only contributor to its linked magnitude (their periods match exactly), False when the magnitude observation aggregates this rate together with others over a longer period, and None when magn_id is None.

class imo_vmdb.Magnitude(id: int, shower: str | None, period_start: datetime, period_end: datetime, sl_start: float, sl_end: float, session_id: int, freq: int, mean: float, lim_magn: float | None)

Single normalised magnitude observation returned by MagnitudeService.query().

See Field Reference for field descriptions.

class imo_vmdb.MagnitudeDetail(id: int, magn: int, freq: float)

Per-class frequency entry for a magnitude observation.

See Field Reference for field descriptions.

class imo_vmdb.Radiant(shower: str, month: int, day: int, ra: float, dec: float)

Radiant position of a meteor shower at a given calendar day.

class imo_vmdb.Rates(observations: list[Rate], sessions: list[Session] | None = None, magnitudes: list[Magnitude] | None = None, magnitude_details: list[MagnitudeDetail] | None = None, total: int | None = None)

Return value of RateService.query().

class imo_vmdb.Magnitudes(observations: list[Magnitude], sessions: list[Session] | None = None, magnitude_details: list[MagnitudeDetail] | None = None, total: int | None = None)

Return value of MagnitudeService.query().

class imo_vmdb.Sessions(sessions: list[Session], rates: list[Rate] | None = None, magnitudes: list[Magnitude] | None = None, total: int | None = None)

Return value of SessionService.query().

class imo_vmdb.StatsMeta(sessions: int, rates: int, magnitudes: int, period_start: datetime | None, period_end: datetime | None, rate_meteors: int = 0, magnitude_meteors: int = 0, rate_period_start: datetime | None = None, rate_period_end: datetime | None = None, magnitude_period_start: datetime | None = None, magnitude_period_end: datetime | None = None, imported_sessions: int = 0, imported_rates: int = 0, imported_magnitudes: int = 0, imported_rate_meteors: int = 0, imported_magnitude_meteors: int = 0)

Database scope summary returned by StatsService.meta().

sessions/rates/magnitudes and *_meteors cover the normalized tables; the imported_* fields cover the raw imported tables that feed normalization. *_meteors are the sum of the freq column on the rate/magnitude rows. period_start / period_end is the union of rate_period_* and magnitude_period_* (kept for backward compatibility).

class imo_vmdb.ShowerStat(shower: str | None, rates: int, magnitudes: int)

Per-shower aggregate counts. shower is None for sporadics.

class imo_vmdb.CountryStat(country: str, sessions: int, rates: int, magnitudes: int)

Per-country aggregate counts.

class imo_vmdb.YearStat(year: int, rates: int, magnitudes: int)

Per-year aggregate counts.

WSGI deployment

The web UI and REST API can be hosted under any WSGI server using the public app factory imo_vmdb.httpd.wsgi_app. Configuration is read from the IMO_VMDB_CONFIG environment variable (path to an INI file) or directly from IMO_VMDB_* variables (see Setup).

imo_vmdb.httpd.wsgi_app()

WSGI app factory for Gunicorn --factory mode.

Configuration is read from the IMO_VMDB_CONFIG environment variable (path to an INI file) or from IMO_VMDB_DATABASE_* etc. directly. Host and port are controlled by Gunicorn’s --bind flag.

The Web UI is opt-in: set IMO_VMDB_WEBSERVER_ENABLE_WEBUI=true to enable it. By default only the REST API is served.

Example:

gunicorn --workers 1 --threads 4 "imo_vmdb.httpd:wsgi_app()"

Warning

Always use --workers 1. The JobManager stores job state in-process; multiple workers would make jobs invisible across processes, breaking status polling and log streaming.

Example using Gunicorn (install with pip install "imo-vmdb[web]"):

# With a config file:
IMO_VMDB_CONFIG=config.ini \
    gunicorn --workers 1 --threads 4 \
    --bind 127.0.0.1:8000 "imo_vmdb.httpd:wsgi_app()"

# Without a config file:
IMO_VMDB_DATABASE_DATABASE=./vmdb.db \
    gunicorn --workers 1 --threads 4 \
    --bind 127.0.0.1:8000 "imo_vmdb.httpd:wsgi_app()"

Warning

Always use --workers 1. The job manager stores job state in-process; multiple workers would make jobs invisible across processes, breaking status polling and log streaming.