Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Test Astro Web

on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
env:
ASTRO_WEB_DATABASE_URL: "sqlite:///SIMPLE.sqlite"
ASTRO_WEB_LOOKUP_TABLES: "Publications,Telescopes,Instruments,PhotometryFilters,Versions,RegimeList,SourceTypeList,ParameterList,AssociationList,CompanionList,Modes,Filters,Citations,References,Parameters,Regimes"
ASTRO_WEB_PRIMARY_TABLE: "Sources"
ASTRO_WEB_SOURCE_COLUMN: "source"
ASTRO_WEB_FOREIGN_KEY: "source"
ASTRO_WEB_RA_COLUMN: "ra"
ASTRO_WEB_DEC_COLUMN: "dec"
ASTRO_WEB_SPECTRA_URL_COLUMN: "access_url"
strategy:
matrix:
python-version: ["3.13"]

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
version: "latest"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: |
uv sync --all-extras --dev

- name: Download database
run: |
curl -L https://github.com/SIMPLE-AstroDB/SIMPLE-binary/raw/main/SIMPLE.sqlite -o SIMPLE.sqlite

- name: Run linter
run: |
uv run ruff check .

- name: Test with pytest
run: |
uv run pytest --cov --cov-branch --cov-report=xml
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AGENTS.md - Astro Web

## Project Overview
Astro Web is a FastAPI-based web application for exploring astronomical databases created with the AstroDB Toolkit. It features interactive Bokeh visualizations (scatter plots, spectra), source browsing with DataTables, and both text and cone search capabilities.

## Directory Structure
- `astro_web/`: Main application package.
- `main.py`: FastAPI entry point.
- `config.py`: Configuration and environment variables.
- `database/`: Database interaction (astrodbkit, SQLite).
- `routes/`: Web page route definitions (web.py).
- `visualizations/`: Bokeh plot generation (scatter, spectra).
- `templates/`: Jinja2 HTML templates.
- `static/`: CSS and schema definitions.
- `CONFIG.md`: Configuration settings guide.
- `pyproject.toml`: Project metadata and dependencies (managed by uv).
- `.env.example`: Template for environment variables.
- `constitution.md`: Project constitution and development guidelines.

## Guidance
Prior to making any code changes, read the `constitution.md` file to understand the project's development guidelines and coding standards.
1 change: 1 addition & 0 deletions astro_web/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"Versions",
"Parameters",
"Regimes",
"RegimeList",
"ParameterList",
"AssociationList",
"CompanionList",
Expand Down
38 changes: 37 additions & 1 deletion astro_web/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
"""Database module for Astrodbkit database interactions."""

__all__ = ["sources", "query"]
from contextlib import contextmanager
from astrodbkit.astrodb import Database
from astro_web.config import (
CONNECTION_STRING,
FOREIGN_KEY,
LOOKUP_TABLES,
PRIMARY_TABLE,
SCHEMA,
SOURCE_COLUMN,
)


@contextmanager
def get_db():
"""
Context manager for astrodbkit Database connections.
Ensures the database session is closed and engine is disposed.
"""
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)
try:
yield db
finally:
# Close the session and dispose the engine to release the database file
if hasattr(db, "session") and db.session:
db.session.close()
if hasattr(db, "engine") and db.engine:
db.engine.dispose()


__all__ = ["sources", "query", "get_db"]
44 changes: 14 additions & 30 deletions astro_web/database/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,14 @@

import time

from astrodbkit.astrodb import Database
from astropy.coordinates import SkyCoord
from astropy.units import Quantity

from astro_web.config import (
CONNECTION_STRING,
DEC_COLUMN,
FOREIGN_KEY,
LOOKUP_TABLES,
PRIMARY_TABLE,
RA_COLUMN,
SCHEMA,
SOURCE_COLUMN,
)
from astro_web.database import get_db


def search_objects(query: str):
Expand All @@ -36,15 +30,8 @@ def search_objects(query: str):
and execution_time is the time taken in seconds
"""
start_time = time.time()
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)
results = db.search_object(query.strip(), resolve_simbad=True, format="pandas")
with get_db() as db:
results = db.search_object(query.strip(), resolve_simbad=True, fmt="pandas")
execution_time = time.time() - start_time

return results, execution_time
Expand All @@ -68,18 +55,20 @@ def parse_coordinates_string(coords_str):
"""
coords_str = coords_str.strip()

# Check if the string contains sexagesimal indicators (h, m, s, d, etc.)
has_sexagesimal = any(char in coords_str.lower() for char in ["h", "m", "s", "d", "°", "'", '"'])
# Check if the string contains sexagesimal indicators or has multiple parts
parts = coords_str.split()
has_sexagesimal = (
any(char in coords_str.lower() for char in ["h", "m", "s", "d", "°", "'", '"', ":"]) or len(parts) > 2
)

try:
if has_sexagesimal:
# For sexagesimal format, SkyCoord can auto-detect
skycoord = SkyCoord(coords_str, frame="icrs")
# For sexagesimal format, assume hourangle for RA if not explicit
skycoord = SkyCoord(coords_str, frame="icrs", unit=("hour", "deg"))
ra_decimal = skycoord.ra.deg
dec_decimal = skycoord.dec.deg
else:
# For decimal format, split and parse manually
parts = coords_str.split()
if len(parts) != 2:
raise ValueError("Expected two space-separated values for decimal coordinates (e.g., '209.30 14.48')")

Expand Down Expand Up @@ -153,17 +142,12 @@ def cone_search(ra, dec, radius_deg):
and execution_time is the time taken in seconds
"""
start_time = time.time()
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)
coords = SkyCoord(ra, dec, unit="deg")
radius = Quantity(radius_deg, "deg")
results = db.query_region(coords, radius=radius, fmt="pandas", ra_col=RA_COLUMN, dec_col=DEC_COLUMN)

with get_db() as db:
results = db.query_region(coords, radius=radius, fmt="pandas", ra_col=RA_COLUMN, dec_col=DEC_COLUMN)

execution_time = time.time() - start_time

# Apply 10,000 result cap if needed
Expand Down
47 changes: 8 additions & 39 deletions astro_web/database/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@

import logging

from astrodbkit.astrodb import Database
from specutils import Spectrum

from astro_web.config import (
CONNECTION_STRING,
FOREIGN_KEY,
LOOKUP_TABLES,
PRIMARY_DATATYPE,
PRIMARY_TABLE,
SCHEMA,
SOURCE_COLUMN,
SPECTRA_URL_COLUMN,
)
from astro_web.database import get_db


def get_all_sources():
Expand All @@ -25,15 +20,8 @@ def get_all_sources():
list: List of dictionaries representing all Sources rows, or None on error
"""
try:
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)
df = db.query(db.metadata.tables[PRIMARY_TABLE]).pandas()
with get_db() as db:
df = db.query(db.metadata.tables[PRIMARY_TABLE]).pandas()
return df.to_dict("records")
except Exception as e:
logging.error(f"Error getting all sources: {e}")
Expand All @@ -52,21 +40,11 @@ def get_source_inventory(source_name):
Only tables with data are returned. Empty tables are filtered out.
"""
try:
print(LOOKUP_TABLES)

# Connect to database
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)

# Get inventory (returns dict of table name -> list of dicts)
source_name = PRIMARY_DATATYPE(source_name)
inventory = db.inventory(source_name)

with get_db() as db:
inventory = db.inventory(source_name)

# Filter out empty tables - only return tables that have data
result = {}
Expand All @@ -93,20 +71,11 @@ def get_source_spectra(source_name, convert_to_spectrum=False):
instrument, etc.) or None on error
"""

# Connect to database
db = Database(
CONNECTION_STRING,
primary_table=PRIMARY_TABLE,
primary_table_key=SOURCE_COLUMN,
lookup_tables=LOOKUP_TABLES,
schema=SCHEMA,
foreign_key=FOREIGN_KEY,
)

try:
# Query spectra table for the source using astrodbkit's pandas method
source_name = PRIMARY_DATATYPE(source_name)
spectra_df = db.query(db.Spectra).filter(db.Spectra.c.source == source_name).pandas()
with get_db() as db:
spectra_df = db.query(db.Spectra).filter(db.Spectra.c.source == source_name).pandas()
except Exception:
return None

Expand Down
Loading
Loading