Skip to content

Add Database Client#20

Open
JBorrow wants to merge 9 commits intomainfrom
add-database-client
Open

Add Database Client#20
JBorrow wants to merge 9 commits intomainfrom
add-database-client

Conversation

@JBorrow
Copy link
Copy Markdown
Member

@JBorrow JBorrow commented Apr 24, 2026

Adds a client interface that connects directly to the database.

Also refactors the core 'sub-services' to be properties of the client itself, so they are accessible in all cases.

Depends on #19.

The main change here is in db.py where I've added a new interface that's similar to mock but instead uses the more 'usable' database format. We will want to think very soon about how we ingest ephemeris here because it is currently very slow doing this on a row-by-row basis.

This required taking some of the 'complex' databsae functionality and creating statements.py that lists shared statements that are beyond minor crud operations between the asynchonrous and synchronous interfaces. I am not yet fully convinced that this is the right final approach that we should keep but the alternative (re-using the asynchronous layer inside a synchronous wrapper) is also not idea.

In addition I also centralized a bunch of database setup operations including making a mirror interface of the async system with its synchronous counterpart.

The new db.py comes along with tests for this new functionality.

This PR used GPT-5.3-Codex through GitHub Copilot for some initial work. I will use the Copilot system to review the code now that it is complete.

@JBorrow JBorrow marked this pull request as draft April 24, 2026 20:01
@JBorrow JBorrow force-pushed the add-database-client branch 2 times, most recently from 947ec44 to 8f0879c Compare April 27, 2026 14:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a synchronous, DB-backed SOCat client (parallel to the existing mock client) and refactors “sub-services” (astroquery/SSO/ephem) to be accessible as properties on a composite client. It also centralizes shared SQLModel/SQLAlchemy statements and session factory setup, and updates ingestion and tests to use the new client shape.

Changes:

  • Add socat/client/db.py DB-backed composite client with .astroquery, .sso, .ephem sub-clients.
  • Introduce socat/database/statements.py and refactor core async update/query helpers to use shared SQL statements.
  • Centralize sync/async session factories and schema initialization in socat/database/session.py, updating API imports and adding DB client tests.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/test_sso_mock.py Refactors mock tests to use mock_client.sso / mock_client.ephem.
tests/test_sso_api.py Adjusts API test expectations for cascade deletion behavior.
tests/test_mock.py Updates mock tests to expect astroquery “no results” warnings and uses .astroquery sub-client.
tests/test_db_client.py Adds coverage for DB-backed client CRUD, cascade behavior, and not-found semantics.
tests/conftest.py Adds db_client fixture and refactors mock sub-client fixtures to derive from mock_client.
socat/ingest/jplparquet.py Switches ingestion API to accept a composite client and changes mock-db builder implementation.
socat/database/statements.py Adds shared SQL statement builders (create_name, box query, update helpers).
socat/database/session.py New module for shared sync/async session factories and schema initialization.
socat/core/services.py Refactors async update to use shared statements.update_service.
socat/core/moving_sources.py Refactors async update to use shared statements.update_ephem.
socat/core/fixed_sources.py Refactors box query and update to use shared statements.
socat/client/settings.py Adds "db" backend option to client settings.
socat/client/mock.py Adds .astroquery, .sso, .ephem properties to the mock composite client.
socat/client/db.py New DB-backed composite client and sub-clients.
socat/client/core.py Extends ClientBase with abstract .astroquery / .sso / .ephem properties.
socat/api/routers/*.py Updates router imports to use centralized SessionDependency.
socat/api/async_ses.py Removes duplicated async session dependency module.
socat/api/app.py Uses centralized async schema initialization during FastAPI lifespan.
socat/api/init.py Re-exports SessionDependency from the new centralized module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread socat/database/statements.py Outdated
Comment thread socat/database/statements.py Outdated
Comment thread socat/client/db.py
Comment on lines +100 to +102
) -> RegisteredFixedSource:
position, name, flux = statements.create_name(name, astroquery_service)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is supposed to raise a runtime error HTTPException if the returned source is None unless that error is caught higher up.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok this seems to be handled in routers.fixed_sources in a change that for some reason is not in this PR as I see it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sorry I confused myself reviewing this, this is for the mock database, so routers.fixed_sources is irrelevant. In that case I disagree with copilot, the database.statements.create_name function handles a no-result by raising a ValueError, not returning none. We can either accept the error handling as implemented in database.statements.create_name or use a try-except block here if we need to modify this instance of the error handling i.e. if we want this function to return None instead of raising the ValueError if there is a no-return of the querry.

Comment thread socat/ingest/jplparquet.py
Comment thread socat/database/statements.py Outdated
Comment thread tests/test_sso_mock.py
Comment on lines +22 to +41
def create_name(
name: str, astroquery_service: str
) -> tuple[ICRS, str, Quantity | None]:
""""""
service: BaseVOQuery = getattr(
import_module(f"astroquery.{astroquery_service.lower()}"),
astroquery_service,
)

requested_params = ["ra", "dec"]

result_table = service.query_object(name)
result_table["ra"].convert_unit_to("deg")
result_table["dec"].convert_unit_to("deg")
if "flux" in result_table.columns:
result_table["flux"].convert_unit_to("mJy") # pragma: no cover
result_dict = {param: None for param in requested_params}
if len(result_table) == 0:
return None
for param in requested_params:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot is confused here but there does seem to be a type annotation error since create_name only raises an error the type annotation should be tuple[ICRS, str, Quantity].

Comment thread socat/database/statements.py Outdated
@JBorrow JBorrow marked this pull request as ready for review April 27, 2026 15:12
@JBorrow JBorrow requested a review from Sulla2012 April 27, 2026 15:13
Comment thread socat/client/db.py
Comment on lines +100 to +102
) -> RegisteredFixedSource:
position, name, flux = statements.create_name(name, astroquery_service)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is supposed to raise a runtime error HTTPException if the returned source is None unless that error is caught higher up.

Comment thread socat/client/db.py
Comment on lines +100 to +102
) -> RegisteredFixedSource:
position, name, flux = statements.create_name(name, astroquery_service)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok this seems to be handled in routers.fixed_sources in a change that for some reason is not in this PR as I see it.

Comment thread socat/client/db.py
Comment on lines +100 to +102
) -> RegisteredFixedSource:
position, name, flux = statements.create_name(name, astroquery_service)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sorry I confused myself reviewing this, this is for the mock database, so routers.fixed_sources is irrelevant. In that case I disagree with copilot, the database.statements.create_name function handles a no-result by raising a ValueError, not returning none. We can either accept the error handling as implemented in database.statements.create_name or use a try-except block here if we need to modify this instance of the error handling i.e. if we want this function to return None instead of raising the ValueError if there is a no-return of the querry.

Comment thread socat/client/db.py
statements.get_box(lower_left=lower_left, upper_right=upper_right)
)

return [s.to_model() for s in sources.scalars().all()]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the above the no-result case seems not to be handled here or in database.statements. That seems to have been the behavior previous (which I wrote) so that's fine. We do seem to have lost the error handling on the box bounds unless I'm missing something.

Comment thread socat/client/db.py
def get_source(self, *, source_id: int) -> RegisteredFixedSource | None:
with self._get_session() as session:
source = session.get(RegisteredFixedSourceTable, source_id)
if source is None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small difference in functionality from core.fixed_sources.get_source which raises a ValueError instead of returning None.

Comment thread socat/client/db.py
with self._get_session() as session:
source = session.get(SolarSystemObjectTable, sso_id)

if source is None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None vs ValueError

Comment thread socat/client/db.py
)

source_list = [s.to_model() for s in sources.scalars().all()]
if len(source_list) == 0:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None vs ValueError

Comment thread socat/client/db.py
)

source_list = [s.to_model() for s in sources.scalars().all()]
if len(source_list) == 0:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None vs ValueError.

Comment thread socat/client/db.py
with self._get_session() as session:
ephem = session.get(RegisteredMovingSourceTable, ephem_id)

if ephem is None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None vs ValueError

Comment on lines +22 to +41
def create_name(
name: str, astroquery_service: str
) -> tuple[ICRS, str, Quantity | None]:
""""""
service: BaseVOQuery = getattr(
import_module(f"astroquery.{astroquery_service.lower()}"),
astroquery_service,
)

requested_params = ["ra", "dec"]

result_table = service.query_object(name)
result_table["ra"].convert_unit_to("deg")
result_table["dec"].convert_unit_to("deg")
if "flux" in result_table.columns:
result_table["flux"].convert_unit_to("mJy") # pragma: no cover
result_dict = {param: None for param in requested_params}
if len(result_table) == 0:
return None
for param in requested_params:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot is confused here but there does seem to be a type annotation error since create_name only raises an error the type annotation should be tuple[ICRS, str, Quantity].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants